module Z80::Program::Macros
Z80
Macros
¶ ↑
A few handy macros.
Public Instance Methods
Compares a pair of registers th
|tl
with a value
as unsigned 16-bit integers.
Provide value
as an integer or a label.
CF, ZF = (th|tl - value) CF = 1 if th|tl < value CF = 0 if th|tl >= value ZF = 0 if th|tl <> value ZF = 1 if th|tl == value
Options:
-
jr_msb_c
: provide a label if you want to jump immediately afterth
< (value >> 8) or:ret
symbol to return from a subroutine. -
jr_msb_nz
: provide a label if you want to jump immediately afterth
<> (value >> 8) or:ret
symbol to return from a subroutine. By default jumps toeoc
of thecp16n
code, so flags can be examined later.
Example:
cp16n h,l, foo, jr_msb_c: less_than_foo jr C, less_than_foo
Modifies: af
.
# File lib/z80/macros.rb, line 205 def cp16n(th, tl, value, jr_msb_c: nil, jr_msb_nz: :eoc) cp16r(th, tl, value>>8, value, jr_msb_c:jr_msb_c, jr_msb_nz:jr_msb_nz) end
Compares a pair of registers th
|tl
with another pair sh
|sl
as unsigned 16-bit integers.
CF, ZF = (th|tl - +sh+|+sl+) CF = 1 if th|tl < sh|sl CF = 0 if th|tl >= sh|sl ZF = 0 if th|tl <> sh|sl ZF = 1 if th|tl == sh|sl
Options:
-
jr_msb_c
: provide a label if you want to jump immediately afterth
<sh
or:ret
symbol to return from a subroutine. -
jr_msb_nz
: provide a label if you want to jump immediately afterth
<>sh
or:ret
symbol to return from a subroutine. By default jumps toeoc
of thecp16r
code, so flags can be examined later.
Example:
cp16r h,l, d,e, jr_msb_nz: not_equal jr NZ, not_equal
Modifies: af
.
# File lib/z80/macros.rb, line 244 def cp16r(th, tl, sh, sl, jr_msb_c: nil, jr_msb_nz: :eoc) raise ArgumentError, "only th can be the accumulator" if [tl, sh, sl].include?(a) isolate do |eoc| jr_msb_nz = eoc if jr_msb_nz == :eoc ld a, th unless th == a if sh == 0 ora a else cp sh case jr_msb_c when :ret ret C else jr C, jr_msb_c end unless jr_msb_c.nil? end case jr_msb_nz when :ret ret NZ else jr NZ, jr_msb_nz end ld a, tl cp sl end end
Compares a pair of 16-bit registers tt
with ss
as unsigned integers.
A sugar for:
cp16r(th,tl, sh,sl, ...)
See: Macros#cp16r
.
# File lib/z80/macros.rb, line 215 def cp16rr(tt, ss, jr_msb_c: nil, jr_msb_nz: :eoc) raise ArgumentError, "tt must be a 16-bit register" unless !pointer?(tt) && register?(tt) && !tt.bit8? raise ArgumentError, "ss must be a 16-bit register" unless !pointer?(ss) && register?(ss) && !ss.bit8? th, tl = tt.split sh, sl = ss.split cp16r(th, tl, sh, sl, jr_msb_c:jr_msb_c, jr_msb_nz:jr_msb_nz) end
Loads a content of the 16-bit register bb
into the 16-bit register aa
.
A sugar for two 8-bit ld
instructions.
Example:
ld16 bc, hl
# File lib/z80/macros.rb, line 169 def ld16(aa, bb) unless [bc, de, hl, ix, iy].include?(aa) and [bc, de, hl, ix, iy].include?(bb) raise ArgumentError, "Use one of: bc de hl ix iy registers in ld16" end raise ArgumentError, "Registers must be different" if aa == bb ah, al = aa.split bh, bl = bb.split isolate do ld al, bl ld ah, bh end end
A convenient method to create local macros.
Give a name
(Symbol
) to your macro, an optional list of registers
to push before and pop after code and a block of code. See Macros.with_saved
for explanation of registers
arguments. The block will receive eoc
label (see Program.ns
) and any argument you pass when calling a macro.
If you want your macros being exportable, do not use this method. Instead create module `Macros' inside your program class and define methods there. Remember to wrap the code in Program.ns
, or better yet: Program.isolate
.
Macros and labels share the same namespace, however labels can be nested, but macros can't.
Unlike labels, macros must be defined before being used.
- Note
-
Be carefull with
ret
instruction if you usedregisters
. You can useret: true
option instead, see:Macros.with_saved
.
Example:
macro :cp16 do |eoc, aa, bb| ah, al = aa.split bh, bl = bb.split ld a, ah cp bh jr NZ, eoc ld a, al cp bl end cp16 bc, hl jr C, bc_less_than_hl
# File lib/z80/macros.rb, line 38 def macro(name, *registers, **nsopts, &mblock) name = name.to_sym if name.is_a?(String) raise Syntax, "A macro must have a name" unless name.is_a?(Symbol) raise Syntax, "Macros may only be defined in the program context." if @contexts.size > 1 raise Syntax, "A label with the same name: \"#{name}\" exists." if @labels.has_key?(label.to_s) m = lambda do |*args, &block| if args.first.is_a?(Symbol) n = args.shift end if registers.empty? ns(n, **nsopts) do |eoc| mblock.call eoc, *args, &block end else lbl = with_saved(*registers, **nsopts) do |eoc| mblock.call eoc, *args, &block end lbl = self.define_label n, lbl if n lbl end end define_singleton_method(name.to_sym, &m) end
Adds a code that pushes specified registers on a machine stack, code from block
within a namespace and code that pops registers in reverse order. Returns a label pointing to the beginning of the pushes. You may optionally pass a name
for the returned label to be named.
registers
should be one of:
-
a 16bit register to push on the stack and pop after code from
block
, exceptsp
obviously, -
:exx
symbol to evaluateexx
instruction, -
:ex_af
symbol to evaluateex af, af
instruction, -
:all
symbol evaluates to:af
,bc
,de
,hl
,ix
,iy
-
:all_but_ixiy
symbol evaluates to:af
,bc
,de
,hl
-
:ixiy
symbol evaluates to:ix
,iy
opts
may be one of:
-
:ret
if theret
instruction should be added after the registers have been restored. In this instance jumping toeoc
will effectively return from the subroutine. If:ret => :after_ei
theei
instruction will be added beforeret
.
Other accepted values for ret:
option are:
-
:reti
: thereti
instruction will be added instead ofret
. -
:retn
: theretn
instruction will be added instead ofret
. -
:after_ei
: theei
instruction will be added beforeret
. -
:ei_reti
: theei
instruction will be added beforereti
.
Evaluates your block
with Program.ns
. All other opts
are being passed along to Program.ns
.
Examples:
with_saved af, de do |eoc| # ... do something with af and de end with_saved af, hl, :exx, hl, :exx, ret: true do |eoc| # ... saves and restores af, hl, hl' and swaps shadow registers back before # adds `ret' instruction after restoring all the registers end with_saved :bar, :all, :exx, :ex_af, :all_but_ixiy, inherit: foo, isolate: true do |eoc| # saves and restores: af, bc, de, hl, ix, iy, af', bc', de', hl' # creates an isolated namespace with inherited `foo' label # returns "bar" namespace label addressing beginning of the pushes end
Be carefull with ret
instruction in a provided block. Remember to pop registers first or use :ret
option instead.
# File lib/z80/macros.rb, line 107 def with_saved(*registers, **opts, &block) name = nil with_return = opts.delete :ret registers.map!.with_index do |rr, i| case rr when :all [af, bc, de, hl, ix, iy] when :no_ixy, :no_ixiy, :all_but_ixy, :all_but_ixiy, :afbcdehl [af, bc, de, hl] when :ixy, :ixiy [ix, iy] when :ex_af, :ex_af_af, :exx rr when Symbol, String if i == 0 name = rr [] else rr end else rr end end.flatten! saver = proc do |rr, instr| case rr when af, bc, de, hl, ix, iy self.send instr, rr when :exx exx when :ex_af, :ex_af_af ex af, af else raise ArgumentError, "arguments must be one of: af, bc, de, hl, ix, iy, :exx or :ex_af, got: #{rr.inspect}" end end ns(name, **opts) do registers.each{|rr| saver[rr, :push]} ns(**opts, merge: true, &block) registers.reverse_each{|rr| saver[rr, :pop]} if with_return ei if [:after_ei, :ei, :ei_ret, :ei_reti].include?(with_return) case with_return when :reti, :ei_reti reti when :retn retn when true, :after_ei, :ei, :ei_ret, :ret ret else raise ArgumentError, "unrecognized value of :ret option: #{with_return.inspect}" end end end end