Skip to content

Example Macros

Cabintech edited this page Feb 20, 2025 · 51 revisions

TODO: Similar to MR Table example, add example invocations and show generated code for all examples

MR Table Generation With $_eval() Function

This example shows how the $_eval() function can be used to create a table in a series of memory registers (MRx) starting at an arbitrary base address. This can be useful when a macro is used in different programs that need to locate the table at different MR locations, or to create a series of tables in a single program at different MR locations.

This macro produces a table with 4 entries that are set to the values 3/X, 6/X, 9/X, and 12/X where X is the value passed as the 2nd arg. The first arg is the memory register of the first entry (e.g. the table base address).

$macro DIVIDER_TABLE(baseMRNum, value) ++
.mreg	mr$_eval(${baseMRNum}+0)	3/${value}
.mreg	mr$_eval(${baseMRNum}+1)	6/${value}
.mreg	mr$_eval(${baseMRNum}+2)	9/${value}
.mreg	mr$_eval(${baseMRNum}+3)	12/${value}
$endmacro

For example, the following use of the macro:

.equ	DIV32_TABLE_BASE		40	; MR of first entry of 32/x table
$DIVIDER_TABLE(DIV32_TABLE_BASE, 32)

.equ	DIV48_TABLE_BASE	50		; MR of first entry of 48/x table
$DIVIDER_TABLE(DIV48_TABLE_BASE, 48)

Would produce the following assembler code:

.equ	DIV32_TABLE_BASE		40	; MR of first entry of table
;--- BEGIN MACRO: DIVIDER_TABLE
.mreg	mr40	3/32
.mreg	mr41	6/32
.mreg	mr42	9/32
.mreg	mr43	12/32
;--- END MACRO: DIVIDER_TABLE


.equ	DIV48_TABLE_BASE	50		; MR of first entry of table
;--- BEGIN MACRO: DIVIDER_TABLE
.mreg	mr50	3/48
.mreg	mr51	6/48
.mreg	mr52	9/48
.mreg	mr53	12/48
;--- END MACRO: DIVIDER_TABLE

Normalize Memory Register Number

Extracts the numeric portion of a memory register name, or returns the input unchanged if it does not start with "mr". This can be used when the input may be a full MR name ("mr46") or just the MR number ("46").

; $MR_NUMBER(mr46) will substitute "46"
; $MR_NUMBER(46) will substitute "46"

$macro MR_NUMBER(mr) $_eval(IF (STR_STARTS_WITH("${mr}", "mr"), STR_SUBSTRING("${mr}", 2), "${mr}"))

Normalize Memory Register Name

Returns a memory register name from an MR number or full MR name.

; $MR_NAME(46) returns "mr46"
; $MR_NAME(mr46) returns "mr46"

$macro MR_NAME(mr) mr$MR_NUMBER(${mr})

Name and Initialize a Memory Register

Defines a memory register symbolic name and initial (load) value. The MR can either be a simple integer number 0-127, or "mr" followed by 0-127. E.g. these generate the same code:
$defMR(myreg, 110, 0)
$defMR(myreg, mr110, 0)

Note a math expression for the MR cannot be used, e.g. "100+10" will cause an error. If an expression is needed, evaluate it with $_eval() like $defMR(myreg, $_eval(100+10), 0);

$macro defMR(name, mr, initVal) ++
.rn     ${name}     $MR_NAME(${mr})
.mreg   ${name}     ${initVal}
$endmacro

Set MR From (2) 16 Bit Constants

; Set an MR to a 32 bit value from (2) 16 bit constants
; Uses ACC32
$macro COPY_CONST_TO_MR(mrTo, constHi, constLo) ++
wrdld	acc32, ${constHi} 
ori		acc32, ${constLo}
cpy_mc	${mrTo}, acc32
$endmacro

Branch on Switch State LOW

; Branch to a target label if the given (debounced) SWITCH is LOW
; Uses ACC32
$macro IF_SWITCH_LOW(switch, label) ++
cpy_cs		acc32, SWITCH 
andi		acc32, ${switch}
jz			acc32, ${label}
$endmacro

Branch on Switch State HIGH

; Branch to a target label if the given (debounced) SWITCH is HIGH.
; The first arg should be one of the SWxDB assembler constants.
; Uses ACC32
$macro IF_SWITCH_HIGH(switch, label) ++
cpy_cs		acc32, SWITCH 
andi		acc32, ${switch}
jnz			acc32, ${label}
$endmacro

Macro Synonym

This is an example of providing an alternate name (synonym) for a macro

; Short hand for IF_SWITCH_HIGH()
$macro IF_SWITCH(a,b) ++
$IF_SWITCH_HIGH(${a},${b})
$endmacro

Invert a Core Register (1.0-CR)

; Invert a positive value in "cr" and leave result in acc32.
; The invert is defined as 1 minus the original value for any
; value between 0 and max pos (0x7FFFFFFF).
; Uses ACC32
$macro INVERT(cr) ++
wrdld	acc32, 0x7FFF 	; Load max pos value 
ori		acc32, 0xFFFF	
subs	acc32, ${cr}	; Leaves result in acc32
$endmacro

Multiply Two 16 Bit Numbers

Multiplying two 16 bit integers in FXCore takes several instructions, this macro simplifies this common operation.

; ACC32 = Multiply the lower 16 bits of two core registers
$macro MULT_16(cr1, cr2, crTemp) ++
sl			${cr1}, 16			; Move arg1 to upper 32 bits 
cpy_cc		${crTemp}, acc32	; Save in temp 
sl			${cr2}, 15			; Move arg 2, not sure why 15 instead of 16 bits 
multrr		acc32, ${crTemp}	; acc32 = upper 32 bits of 64 bit result
$endmacro

Convert Msec to Number of Samples (At 48k)

Convert msec to number (integral) number of delay samples, assuming 48kHz sample rate.

$macro MS_TO_SAMPLES_48K(msec) ((${msec}/1000)/(1/48000))

Convert Msec to Number of Samples (At Current Sample Rate)

Convert (fixed constant) msec to samples based on current sampling rate. Unlike MS_TO_SAMPLES_48K this macro expands to executable code that accounts for the current sampling rate. The results are left in ACC32.

The msec arg must be a fixed constant (or constant expression) that evaluates to less than 2048.

$macro MS_TO_SAMPLES(msec) ++

r0.u	= $_eval(FLOOR((${msec})*12)) ; Samples assuming 12kHz

acc32	= BOOTSTAT		
acc32	= acc32 andi 3          ; Mask all but PLL (sampling rate) bits [1:0]
acc32	= acc32 add -2          ; If PLL=2 then rate is 32k 
if acc32 =0 goto _mts_k32       ; Special case, 32k is not a multiple of 12k

acc32	= acc32 add 3           ; Get original PLL value plus 1, now a multiplier for 12k rate
acc32	= acc32 sl 15           ; Do multiply in upper 16 bits
acc32	= acc32 mult r0
goto _mts_end

_mts_k32: 
acc32.u	= $_eval(FLOOR((${msec})*32)) ; Samples at 32k
acc32	= acc32 sr 16

_mts_end:
$endmacro

Encode 4 Bytes Into 32-Bit Word

; Encode 4 bytes into a 32-bit word
$macro WORD32_BYTES(msb, b2, b1, lsb) (${msb}<<24)|(${b2}<<16)|(${b1}<<8)|${lsb}

4-Way Switch Statement

; Multi-target branch based on value of a CR. A value of 0 will
; branch to label target0, a value of 1 will branch to target1,
; etc. Any value is >=3 will branch to the last (default) label.
; Uses ACC32.
$macro SWITCH4(crValue, target0, target1, target2, default) ++
jz			${crValue}, ${target0}

cpy_cc		acc32, ${crValue}
addi		acc32, -1			
jz			acc32, ${target1}

cpy_cc		acc32, ${crValue}		
addi		acc32, -2			
jz			acc32, ${target2}

jmp			${default}
$endmacro

3-Way Switch Statement

; Multi-target branch based on value of a CR. A value of 0 will
; branch to label target0, a value of 1 will branch to target1,
; etc. If the value is >=2 will branch to the last (default) label.
; Uses ACC32.
$macro SWITCH3(crValue, target0, target1, default) ++
jz			${crValue}, ${target0}
cpy_cc		acc32, ${crValue}		
addi		acc32, -1			
jz			acc32, ${target1}
jmp			${default}
$endmacro

MR Symbolic Name and Initial Value

This macro is shorthand for the common practice of defining a symbolic name for a MR location, and initializing that location to a specific value. This example demonstrates the use of string expressions in the _eval() predefined macro.

$macro defMR(name, mr, initVal) ++
.rn     ${name}     mr$_eval(IF (STR_STARTS_WITH("${mr}", "mr"), STR_SUBSTRING("${mr}", 2), "${mr}"))
.mreg   ${name}     ${initVal}
$endmacro

The "mr" argument can either be a simple integer number 0-127, or "mr" followed by 0-127. E.g. these generate the same code:

$defMR(myreg, 110, 0)
$defMR(myreg, mr110, 0)

The above macros would both produce the same 2 statements:

.rn     myreg     mr110
.mreg   myreg     0

Note a math expression cannot be used for the "mr" argument, e.g. "100+10" will cause an error. If an expression is needed, evaluate it with $_eval() e.g. $defMR(myreg, $_eval(100+10), 0)

Copy SFR to MR

; Copy a Special Function Register (SFR) to a Memory Register (MR)
; Uses ACC32
$macro COPY_SFR_TO_MR(mr, sfr) ++
cpy_cs	acc32, ${sfr} 
cpy_mc	${mr}, acc32 
$endmacro

Copy MR to SFR

; Copy a Memory Register (MR) to a Special Function Register (SFR)
; Uses ACC32
$macro COPY_MR_TO_SFR(sfr, mr) ++
cpy_cm	acc32, ${mr} 
cpy_sc	${sfr}, acc32 
$endmacro

Copy MR to MR (uses ACC32)

; Copy a MR to another MR (uses ACC32)
$macro COPY_MR_TO_MR(mrTo, mrFrom) ++
cpy_cm	acc32, ${mrFrom}
cpy_mc	${mrTo}, acc32 
$endmacro

Copy MR to MR

; Copy MR to MR with a temp register (does not use acc32)
$macro COPY_MR_TO_MR_TEMP(mrTarget, mrSource, crTemp) ++
cpy_cm		${crTemp}, ${mrSource} 	
cpy_mc		${mrTarget}, ${crTemp}
$endmacro

Clone this wiki locally