;;; ============================================================ ;;; Generic Macros ;;; ============================================================ .define _is_immediate(arg) (.match (.mid (0, 1, {arg}), #)) .define _is_register(arg) (.match ({arg}, x) .or .match ({arg}, y)) .define _is_y_register(arg) (.match ({arg}, y)) .define _immediate_value(arg) (.right (.tcount ({arg})-1, {arg})) .macro _op_lo op, arg .if _is_immediate {arg} op #<_immediate_value {arg} .else op arg .endif .endmacro .macro _op_hi op, arg .if _is_immediate {arg} op #>_immediate_value {arg} .else op arg+1 .endif .endmacro ;;; ============================================================ ;;; Length-prefixed string ;;; ;;; Can include control chars by using: ;;; ;;; PASCAL_STRING {"abc",$0D,"def"} .macro PASCAL_STRING str,res .local data .local end .byte end - data data: .byte str end: .if .paramcount > 1 .res res - (end - data), 0 .endif .endmacro ;;; ============================================================ ;;; Pad with zeros to the given address .macro PAD_TO addr .if (addr - *) >= 0 .res addr - * .else .error .sprintf("Padding offset %d", addr - *) .endif .endmacro ;;; ============================================================ ;;; Common patterns .define AS_BYTE(arg) arg & $FF .define AS_WORD(arg) arg & $FFFF .macro return arg lda arg rts .endmacro .macro copy arg1, arg2, arg3, arg4 .if _is_register {arg2} && _is_register {arg4} ;; indexed load/indexed store lda arg1,arg2 sta arg3,arg4 .elseif _is_register {arg2} ;; indexed load variant (arg2 is x or y) lda arg1,arg2 sta arg3 .elseif _is_register {arg3} ;; indexed store variant (arg3 is x or y) lda arg1 sta arg2,arg3 .else lda arg1 sta arg2 .endif .endmacro ;;; ============================================================ ;;; Calls with one parameter (address in A,X) .macro addr_call target, addr lda #addr jsr target .endmacro .macro addr_call_indirect target, addr lda addr ldx addr+1 jsr target .endmacro .macro addr_jump target, addr lda #addr jmp target .endmacro ;;; ============================================================ ;;; Calls with two parameters (call # in y, address in A,X) .macro yax_call target, yparam, addr ldy #yparam lda #addr jsr target .endmacro .macro yax_jump target, yparam, addr ldy #yparam lda #addr jmp target .endmacro ;;; ============================================================ ;;; 16-bit pseudo-ops ;;; Load A,X ;;; ldax #$1234 ; immediate ;;; ldax $1234 ; absolute .macro ldax arg _op_lo lda, {arg} _op_hi ldx, {arg} .endmacro ;;; Load A,Y ;;; lday #$1234 ; immediate ;;; lday $1234 ; absolute .macro lday arg _op_lo lda, {arg} _op_hi ldy, {arg} .endmacro ;;; Load X,Y ;;; ldxy #$1234 ; immediate ;;; ldxy $1234 ; absolute .macro ldxy arg _op_lo ldx, {arg} _op_hi ldy, {arg} .endmacro ;;; Store A,X ;;; stax $1234 ; absolute .macro stax arg sta arg stx arg+1 .endmacro ;;; Store X,Y ;;; stxy $1234 ; absolute .macro stxy arg stx arg sty arg+1 .endmacro ;;; Core for add16/sub16 .macro _addsub16 op, opc, arg1, arg2, arg3, arg4, arg5, arg6 .if _is_register {arg2} && _is_register {arg4} && _is_register {arg6} ;; xxx16 $1111,x, $2222,x, $3333,x lda arg1,arg2 opc op arg3,arg4 sta arg5,arg6 lda arg1+1,arg2 op arg3+1,arg4 sta arg5+1,arg6 .elseif _is_register {arg2} && _is_register {arg4} ;; xxx16 $1111,x, $2222,x, $3333 lda arg1,arg2 opc op arg3,arg4 sta arg5 lda arg1+1,arg2 op arg3+1,arg4 sta arg5+1 .elseif _is_register {arg2} && _is_register {arg5} ;; xxx16 $1111,x, $2222, $3333,x ;; xxx16 $1111,x, #$2222, $3333,x lda arg1,arg2 opc _op_lo op, {arg3} sta arg4,arg5 lda arg1+1,arg2 _op_hi op, {arg3} sta arg4+1,arg5 .elseif _is_register {arg2} ;; xxx16 $1111,x, $2222, $3333 ;; xxx16 $1111,x, #$2222, $3333 lda arg1,arg2 opc _op_lo op, {arg3} sta arg4 lda arg1+1,arg2 _op_hi op, {arg3} sta arg4+1 .elseif _is_register {arg3} ;; xxx16 $1111, $2222,x $3333 ;; xxx16 #$1111, $2222,x $3333 _op_lo lda, {arg1} opc op arg2,arg3 sta arg4 _op_hi lda, {arg1} op arg2+1,arg3 sta arg4+1 .elseif _is_register {arg4} ;; xxx16 $1111, $2222, $3333,x ;; xxx16 #$1111, $2222, $3333,x ;; xxx16 $1111, #$2222, $3333,x ;; xxx16 #$1111, #$2222, $3333,x _op_lo lda, {arg1} opc _op_lo op, {arg2} sta arg3,arg4 _op_hi lda, {arg1} _op_hi op, {arg2} sta arg3+1,arg4 .else ;; xxx16 $1111, $2222, $3333 ;; xxx16 #$1111, $2222, $3333 ;; xxx16 $1111, #$2222, $3333 ;; xxx16 #$1111, #$2222, $3333 _op_lo lda, {arg1} opc _op_lo op, {arg2} sta arg3 _op_hi lda, {arg1} _op_hi op, {arg2} sta arg3+1 .endif .endmacro ;;; Core for add16/sub16, with leading carry operation .macro _addsub16lc op, opc, arg1, arg2, arg3, arg4, arg5, arg6 opc .if _is_register {arg2} && _is_register {arg4} && _is_register {arg6} ;; xxx16 $1111,x, $2222,x, $3333,x lda arg1,arg2 op arg3,arg4 sta arg5,arg6 lda arg1+1,arg2 op arg3+1,arg4 sta arg5+1,arg6 .elseif _is_register {arg2} && _is_register {arg4} ;; xxx16 $1111,x, $2222,x, $3333 lda arg1,arg2 op arg3,arg4 sta arg5 lda arg1+1,arg2 op arg3+1,arg4 sta arg5+1 .elseif _is_register {arg2} && _is_register {arg5} ;; xxx16 $1111,x, $2222, $3333,x ;; xxx16 $1111,x, #$2222, $3333,x lda arg1,arg2 _op_lo op, {arg3} sta arg4,arg5 lda arg1+1,arg2 _op_hi op, {arg3} sta arg4+1,arg5 .elseif _is_register {arg2} ;; xxx16 $1111,x, $2222, $3333 ;; xxx16 $1111,x, #$2222, $3333 lda arg1,arg2 _op_lo op, {arg3} sta arg4 lda arg1+1,arg2 _op_hi op, {arg3} sta arg4+1 .elseif _is_register {arg3} ;; xxx16 $1111, $2222,x $3333 ;; xxx16 #$1111, $2222,x $3333 _op_lo lda, {arg1} op arg2,arg3 sta arg4 _op_hi lda, {arg1} op arg2+1,arg3 sta arg4+1 .elseif _is_register {arg4} ;; xxx16 $1111, $2222, $3333,x ;; xxx16 #$1111, $2222, $3333,x ;; xxx16 $1111, #$2222, $3333,x ;; xxx16 #$1111, #$2222, $3333,x _op_lo lda, {arg1} _op_lo op, {arg2} sta arg3,arg4 _op_hi lda, {arg1} _op_hi op, {arg2} sta arg3+1,arg4 .else ;; xxx16 $1111, $2222, $3333 ;; xxx16 #$1111, $2222, $3333 ;; xxx16 $1111, #$2222, $3333 ;; xxx16 #$1111, #$2222, $3333 _op_lo lda, {arg1} _op_lo op, {arg2} sta arg3 _op_hi lda, {arg1} _op_hi op, {arg2} sta arg3+1 .endif .endmacro ;;; Core for add16in/sub16in .macro _addsub16in op, opc, arg1, arg2, arg3, arg4, arg5, arg6 .if _is_y_register {arg2} && _is_y_register {arg4} && _is_y_register {arg6} ;; xxx16in $1111,y, $2222,y, $3333,y lda (arg1),y opc op (arg3),y sta (arg5),y iny lda (arg1),y op (arg3),y sta (arg5),y .elseif _is_y_register {arg2} && _is_y_register {arg4} ;; xxx16in $1111,y, $2222,y, $3333 lda (arg1),y opc op (arg3),y sta arg5 iny lda (arg1),y op (arg3),y sta arg5+1 .elseif _is_y_register {arg2} && _is_y_register {arg5} ;; xxx16in $1111,y, $2222, $3333,y ;; xxx16in $1111,y, #$2222, $3333,y lda (arg1),y opc _op_lo op, {arg3} sta (arg4),y iny lda (arg1),y _op_hi op, {arg3} sta (arg4),y .elseif _is_y_register {arg2} ;; xxx16in $1111,x, $2222, $3333 ;; xxx16in $1111,x, #$2222, $3333 lda (arg1),y opc _op_lo op, {arg3} sta arg4 iny lda (arg1),y _op_hi op, {arg3} sta arg4+1 .elseif _is_y_register {arg3} ;; xxx16in $1111, $2222,y $3333 ;; xxx16in #$1111, $2222,y $3333 _op_lo lda, {arg1} opc op (arg2),y sta arg4 iny _op_hi lda, {arg1} op (arg2),y sta arg4+1 .elseif _is_y_register {arg4} ;; xxx16in $1111, $2222, $3333,y ;; xxx16in #$1111, $2222, $3333,y ;; xxx16in $1111, #$2222, $3333,y ;; xxx16in #$1111, #$2222, $3333,y _op_lo lda, {arg1} opc _op_lo op, {arg2} sta (arg3),y iny _op_hi lda, {arg1} _op_hi op, {arg2} sta (arg3),y .else .error "Indirect indexed required at least one use of y reg" .endif .endmacro ;;; Add arg1 to arg2, store to arg3 ;;; add16 $1111, $2222, $3333 ; absolute, absolute, absolute ;;; add16 $1111, #$2222, $3333 ; absolute, immediate, absolute ;;; add16 $1111,x, $2222, $3333 ; indexed, absolute, absolute ;;; add16 $1111, $2222,x, $3333 ; absolute, indexed, absolute ;;; add16 $1111, $2222, $3333,x ; absolute, absolute, indexed ;;; add16 $1111,x, $2222, $3333,x ; indexed, absolute, indexed ;;; add16 $1111,x, $2222,x, $3333,x ; indexed, indexed, indexed .macro add16 arg1, arg2, arg3, arg4, arg5, arg6 _addsub16 adc, clc, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; (as above, but clc precedes first lda) .macro add16lc arg1, arg2, arg3, arg4, arg5, arg6 _addsub16lc adc, clc, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; (as above, but indirect indexed, y register is incremented) .macro add16in arg1, arg2, arg3, arg4, arg5, arg6 _addsub16in adc, clc, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; Add arg1 (absolute) to arg2 (8-bit absolute), store to arg3 ;;; add16_8 $1111, #$22, $3333 ; absolute, immediate, absolute ;;; add16_8 $1111, $22, $3333 ; absolute, absolute, absolute .macro add16_8 arg1, arg2, arg3 _op_lo lda, {arg1} clc adc arg2 sta arg3 _op_hi lda, {arg1} adc #0 sta arg3+1 .endmacro ;;; Add A,Z to arg1 (immediate or absolute), store to arg2 ;;; addax #$1111, $3333 ; immediate, absolute ;;; addax $1111, $3333 ; absolute, absolute .macro addax arg1, arg2 clc _op_lo adc, {arg1} sta arg2 txa _op_hi adc, {arg1} sta arg2+1 .endmacro ;;; Subtract arg2 from arg1, store to arg3 ;;; sub16 #$1111, #$2222, $3333 ; immediate, immediate, absolute ;;; sub16 #$1111, $2222, $3333 ; immediate, absolute, absolute ;;; sub16 $1111, #$2222, $3333 ; absolute, immediate, absolute ;;; sub16 $1111, $2222, $3333 ; absolute, absolute, absolute ;;; sub16 $1111, $2222,x, $3333 ; absolute, indexed, absolute ;;; sub16 $1111, $2222, $3333,x ; absolute, absolute, indexed ;;; sub16 $1111,x, $2222,x, $3333 ; indexed, indexed, absolute ;;; sub16 $1111,x, $2222, $3333,x ; indexed, absolute, indexed ;;; sub16 $1111,x, $2222,x $3333,x ; indexed, indexed, indexed .macro sub16 arg1, arg2, arg3, arg4, arg5, arg6 _addsub16 sbc, sec, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; (as above, but sec precedes first lda) .macro sub16lc arg1, arg2, arg3, arg4, arg5, arg6 _addsub16lc sbc, sec, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; (as above, but indirect indexed, y register incremented) .macro sub16in arg1, arg2, arg3, arg4, arg5, arg6 _addsub16in sbc, sec, arg1, arg2, arg3, arg4, arg5, arg6 .endmacro ;;; Subtract arg2 from arg1, store to arg3 ;;; sub16_8 #$1111, #$22, $3333 ; immediate, immediate, absolute ;;; sub16_8 #$1111, $22, $3333 ; immediate, absolute, absolute ;;; sub16_8 $1111, #$22, $3333 ; absolute, immediate, absolute ;;; sub16_8 $1111, $22, $3333 ; absolute, absolute, absolute .macro sub16_8 arg1, arg2, arg3 _op_lo lda, {arg1} sec sbc arg2 sta arg3 _op_hi lda, {arg1} sbc #0 sta arg3+1 .endmacro ;;; Copy 16-bit value ;;; copy16 #$1111, $2222 ; immediate, absolute ;;; copy16 $1111, $2222 ; absolute, absolute ;;; copy16 $1111,x, $2222 ; indirect load, absolute store ;;; copy16 $1111, $2222,x ; absolute load, indirect store ;;; copy16 $1111,x $2222,x ; indirect load, indirect store ;;; copy16 #$1111, $2222,x ; immediate load, indirect store .macro copy16 arg1, arg2, arg3, arg4 .if _is_register {arg2} && _is_register {arg4} ;; indexed load/indexed store lda arg1,arg2 sta arg3,arg4 lda arg1+1,arg2 sta arg3+1,arg4 .elseif _is_register {arg2} ;; indexed load variant (arg2 is x or y) lda arg1,arg2 sta arg3 lda arg1+1,arg2 sta arg3+1 .elseif _is_register {arg3} ;; indexed store variant (arg3 is x or y) _op_lo lda, {arg1} sta arg2,arg3 _op_hi lda, {arg1} sta arg2+1,arg3 .else _op_lo lda, {arg1} sta arg2 _op_hi lda, {arg1} sta arg2+1 .endif .endmacro ;;; Copy 16-bit value, indexed indirect, y register incremented ;;; copy16in #$1111, ($2222),y ; immediate load, indexed indirect store ;;; copy16in $1111, ($2222),y ; absolute load, indexed indirect store ;;; copy16in ($1111),y, $2222 ; indexed indirect load, absolute store ;;; copy16in ($1111),y ($2222),y ; indexed indirect load, indexed indirect store .macro copy16in arg1, arg2, arg3, arg4 .if _is_y_register {arg2} && _is_y_register {arg4} ;; copy16in ($1111),y, ($2222),y lda (arg1),y sta (arg3),y iny lda (arg1),y sta (arg3),y .elseif _is_y_register {arg2} ;; copy16in ($1111),y, $2222 lda (arg1),y sta arg3 iny lda (arg1),y sta arg3+1 .elseif _is_y_register {arg3} ;; copy16in #$1111, ($2222),y ;; copy16in $1111, ($2222),y _op_lo lda, {arg1} sta (arg2),y iny _op_hi lda, {arg1} sta (arg2),y .else .error "Indirect indexed required at least one use of y reg" .endif .endmacro ;;; Compare 16-bit values ;;; cmp16 #$1111, #$2222 ; immediate, immediate (silly, but supported) ;;; cmp16 #$1111, $2222 ; immediate, absolute ;;; cmp16 $1111, #$2222 ; absolute, immediate ;;; cmp16 $1111, $2222 ; absolute, absolute ;;; cmp16 $1111,x, $2222 ; indirect, absolute ;;; cmp16 $1111, $2222,x ; absolute, indirect .macro cmp16 arg1, arg2, arg3 .if _is_register {arg2} ;; indexed variant (arg2 is x or y) lda arg1,arg2 cmp arg3 lda arg1+1,arg2 sbc arg3+1 .elseif _is_register {arg3} ;; indexed variant (arg3 is x or y) lda arg1 cmp arg2,arg3 lda arg1+1 sbc arg2+1,arg3 .else _op_lo lda, {arg1} _op_lo cmp, {arg2} _op_hi lda, {arg1} _op_hi sbc, {arg2} .endif .endmacro ;;; Shift 16-bit values ;;; lsr16 $1111 ; absolute .macro lsr16 arg1 lsr arg1+1 ror arg1 .endmacro ;;; asl16 $1111 ; absolute .macro asl16 arg1 asl arg1 rol arg1+1 .endmacro ;;; Increment 16-bit value .macro inc16 arg .local skip inc arg bne skip inc arg+1 skip: .endmacro ;;; Decrement 16-bit value .macro dec16 arg .local skip lda arg bne skip dec arg+1 skip: dec arg .endmacro ;;; ============================================================ ;;; Param Blocks ;;; ============================================================ ;;; Helper macros to set up a scoped block of parameters at a pre-determined ;;; address. ;;; ;;; Note: to use this macro, your cfg must have a BSS segment: ;;; (BSS: load = BSS, type = bss) ;;; ;;; Example: ;;; .proc my_function ;;; PARAM_BLOCK params, $82 ;;; arg1: .res 1 ;;; arg2: .res 2 ;;; arg3: .res 2 ;;; END_PARAM_BLOCK ;;; ;;; lda params::arg1 ; equiv. to lda $82 ;;; lda params::arg2 ; equiv. to lda $83 ;;; lda params::arg3 ; equiv. to lda $85 ;;; .macro PARAM_BLOCK name, addr name := addr .scope name saved_org := * .pushseg .bss .org addr start := * .endmacro .macro END_PARAM_BLOCK size := * - start .popseg .org saved_org .endscope .endmacro ;;; ============================================================ ;;; Structure Helpers ;;; ============================================================ .macro COPY_BYTES size, src, dst .scope ldx #size - 1 loop: lda src,x sta dst,x dex bpl loop .endscope .endmacro .macro COPY_STRUCT type, src, dst COPY_BYTES .sizeof(type), src, dst .endmacro .macro COPY_BLOCK block, dst COPY_BYTES .sizeof(block), block, dst .endmacro .macro COPY_STRING src, dst .scope ldx src loop: lda src,x sta dst,x dex bpl loop .endscope .endmacro ;;; ============================================================ ;;; Placed Procedures ;;; ============================================================ ;;; Usage: ;;; PROC_AT relocated_proc, $300 ;;; .assert * = $300, ... ;;; ... ;;; END_PROC_AT ;;; .assert * = back to normal .macro PROC_AT name, addr .proc name saved_org := * .org addr .proc __inner__ .endmacro .macro END_PROC_AT .endproc ; __inner__ .org saved_org + .sizeof(__inner__) .endproc .endmacro ;;; ============================================================ ;;; Temporary org change, for relocated routines ;;; ============================================================ __pushorg_depth__ .set 0 .macro pushorg addr ::__pushorg_depth__ .set ::__pushorg_depth__ + 1 .ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) := * .org addr .ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__)) := * .endmacro .macro poporg .org .ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) + (* - .ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__))) ::__pushorg_depth__ .set ::__pushorg_depth__ - 1 .endmacro ;;; ============================================================ ;;; Flow Control ;;; ============================================================ ;;; Usage: ;;; lda foo ;;; cmp bar ;;; IF_EQ ;;; ... ;;; ELSE ; optional ;;; ... ;;; END_IF ;;; ;;; Macros: ;;; IF_EQ aliases: IF_ZERO ;;; IF_NE aliases: IF_NOT_ZERO ;;; IF_CC aliases: IF_LT ;;; IF_CS aliases: IF_GE ;;; IF_PLUS ;;; IF_MINUS aliases: IF_NEG __depth__ .set 0 .macro IF_EQ ::__depth__ .set ::__depth__ + 1 .scope bne .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .macro IF_NE ::__depth__ .set ::__depth__ + 1 .scope beq .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .macro IF_CC ::__depth__ .set ::__depth__ + 1 .scope bcs .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .macro IF_CS ::__depth__ .set ::__depth__ + 1 .scope bcc .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .macro IF_PLUS ::__depth__ .set ::__depth__ + 1 .scope bmi .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .macro IF_MINUS ::__depth__ .set ::__depth__ + 1 .scope bpl .ident(.sprintf("__else__%d", ::__depth__)) .endmacro .define IF_ZERO IF_EQ .define IF_NOT_ZERO IF_NE .define IF_GE IF_CS .define IF_LT IF_CC .define IF_NEG IF_MINUS ;;; -------------------------------------------------- .macro ELSE jmp .ident(.sprintf("__endif__%d", ::__depth__)) .ident(.sprintf("__else__%d", ::__depth__)) := * .endmacro .macro ELSE_IF .error "ELSE_IF not supported" .endmacro .macro ELSEIF .error "ELSEIF not supported" .endmacro ;;; -------------------------------------------------- .macro END_IF .if .not(.defined(.ident(.sprintf("__else__%d", ::__depth__)))) .ident(.sprintf("__else__%d", ::__depth__)) := * .endif .ident(.sprintf("__endif__%d", ::__depth__)) := * .endscope ::__depth__ .set ::__depth__ - 1 .endmacro .macro ENDIF .error "Do you mean END_IF ?" .endmacro