CPU.i

Summary
CPU.i
RW* (register width macros)Instead of using ca65’s .a* and .i* directives or the “smart” mode to set current register widths, libSFX uses these macros to track CPU state.
procDefine procedure with separate RW state
endprocEnd procedure and restore RW state
RW_initDefine RW state variables in current scope
RWSet accumulator/index register widths
RW_assumeAssume known accumulator/index register widths without emitting any instructions
RW_forcedForce set accumulator/index register widths (ie.
RW_assertAssert (at assemble time) that the specified register widths match with the state of the register tracking logic.
RW_pushPush current register widths state to the RW stack, and optionally set new state
RW_pullPull register widths state from the RW stack
RW_pull_forcedPull register widths state from the RW stack, always emitting rep/sep instructions
RW_printPrint (at assemble time) the current register widths state
RW_a_size()Get the current accumlator register width
RW_i_size()Get the current index register width
CPU register macros
pushPush CPU state to stack
pullPull CPU state from stack
dbankSet data bank register (DB)
dpageSet direct page register (D)
dpo()Get address minus current direct page offset
CPU meta instructions
bgtBranch if greater than
bsrRelative subroutine call
bslRelative long subroutine call
addAdd (without carry)
subSubtract (without carry)
asrArithmetic shift right
negNegate (signed integer)
breakBreak debugger

RW* (register width macros)

Instead of using ca65’s .a* and .i* directives or the “smart” mode to set current register widths, libSFX uses these macros to track CPU state.

The main advantage of this approach is that rep/sep instructions will be emitted only when necessary.  When relying a lot on “function inlining” via macros those tend to add up quickly.  A bonus is that the state can be queried with the RW_a_size() and RW_i_size() macros, allowing for conditial assembly depending on register widths.

This tracking can only work within the same assembly unit, of course.  When calling function over unit barriers there’s an RW stack and a couple of helper macros to keep those pesky register sizes in sync.

Example

.macro call_external
      RW_push set:a16             ;Push current state and set accumlator
                                  ;width to 16 bits if necessary
                                  ;Only if accumulator is 8 bits wide a
                                  ;rep #$20 instruction will be emitted

      jsl     external            ;Call external subroutine that assumes
                                  ;16-bit accumulator

      RW_pull                     ;Register widths are restored as needed
.endmac

The 'external' subroutine looks like this:

proc external, a16                ;16 bit accumulator is assumed (no instruction emitted)
      lda     #$f00d              ;So this will assemble nicely
      rtl
endproc

proc

Define procedure with separate RW state

This is equivalent to .proc directive, but ensures that using the RW* macros inside of the procedure does not affect tracking of CPU state anywhere outside of the procedure, and vice-versa.

It is recommended to use the ‘proc’ and ‘endproc’ macros instead of .proc and .endproc for any code using libSFX macros.

By default, code inside the procedure assumes 8-bit A and 16-bit X/Y (M=1, X=0).  Optionally, you may specify other incoming register sizes as a parameter.

(See also: ‘RW_init’, ‘RW_assume’)

Parameter

:in:    name      Procedure name
:in?:   widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8

endproc

End procedure and restore RW state

This is the equivalent to .endproc corresponding with the ‘proc’ macro.  This ensures that the outer scope continues to track the correct register sizes regardless of any RW* macros which were used in the inner scope.

RW_init

Define RW state variables in current scope

Used by all register width macros to ensure that register state can be tracked in any scope.  This also means that code inside of a .proc or .scope will not affect tracked register widths for code in the global scope, and vice-versa.  (Thus, the register widths inside a nested scope are always initially a8i16.)

RW

Set accumulator/index register widths

No-op if current state == intended state.

Parameter

:in:    widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8

RW_assume

Assume known accumulator/index register widths without emitting any instructions

Parameter

:in:    widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8

RW_forced

Force set accumulator/index register widths (ie. always emit rep/sep instructions)

Parameter

:in:    widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8

RW_assert

Assert (at assemble time) that the specified register widths match with the state of the register tracking logic.

Parameters

:in:    widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8
:in:    message   Error message           string

RW_push

Push current register widths state to the RW stack, and optionally set new state

No-op if current state == intended state.

Parameter

:in?:   widths    Register widths         a8/a16/i8/i16/a8i8/a16i16/a8i16/a16i8

RW_pull

Pull register widths state from the RW stack

No-op if current state == intended state.

RW_pull_forced

Pull register widths state from the RW stack, always emitting rep/sep instructions

Might come in handy when calling a subroutine that returns with the register widths in an unknown state.

RW_print

Print (at assemble time) the current register widths state

RW_a_size()

Get the current accumlator register width

Returns

0 = 16 bits
1 = 8 bits

RW_i_size()

Get the current index register width

Returns

0 = 16 bits
1 = 8 bits

CPU register macros

push

Push CPU state to stack

pull

Pull CPU state from stack

dbank

Set data bank register (DB)

Parameter

:in:    bank      Bank (uint8)            a/x/y       Requires RW a8 or i8
                                          constant    Using a

dpage

Set direct page register (D)

Parameter

:in:    offs      Offset (uint16)         a
                                          constant    Using a

dpo()

Get address minus current direct page offset

Calculates a byte offset using the latest value set by the ‘dpage’ macro.

Parameter

:in:    addr      Address (uint16)        constant

Example

dpage   INIDISP
stz     z:dpo(OBJSEL)           ;Reset OAM regs
stz     z:dpo(OAMADDL)
stz     z:dpo(OAMADDH)

CPU meta instructions

bgt

Branch if greater than

Parameter

:in:    addr      Address

bsr

Relative subroutine call

Parameter

:in:    addr      Address

bsl

Relative long subroutine call

Parameter

:in:    addr      Address

add

Add (without carry)

Parameters

:in:    op        Operand
:in?:   ix        Index

sub

Subtract (without carry)

Parameters

:in:    op        Operand
:in?:   ix        Index

asr

Arithmetic shift right

neg

Negate (signed integer)

break

Break debugger

If assembled with debug=1 the break macro emits a “wdm $00” instruction, which can be set to trigger a break in the bsnes+ debugger.