I'm trying to create a template that emits a single line of ARM assembly with two register args and two immediate args. The asm statement's triple-quotes was causing a problem when the template expanded its arguments, so I put the asm statement in a proc and call that proc from the template. Here's my best attempt so far:
{.push stackTrace:off.}
proc setField[T](
regVal: T, bitOffset: static int, bitWidth: static int, fieldVal: RegisterVal
): T {.asmNoStackFrame.} =
asm """
BFI `regVal`, `fieldVal`, #bitOffset, #bitWidth
"""
{.pop.}
# chained field modify
template GPIOAEN*(regVal: RCC_AHB1ENR_Val, fieldVal: uint32): RCC_AHB1ENR_Val =
setField[RCC_AHB1ENR_Val](regVal, 0, 1, fieldVal)
This results in the following error:
/var/folders/_8/x78bn0wj3nz4gwnbtm47qsqw0000gn/T//ccBPqMCO.s: Assembler messages:
/var/folders/_8/x78bn0wj3nz4gwnbtm47qsqw0000gn/T//ccBPqMCO.s:93: Error: ARM register expected -- `bfi regVal_p0,fieldVal_p3,#bitOffset,#bitWidth'
Error: execution of an external compiler program 'arm-none-eabi-gcc -c -w -fmax-errors=4 -mthumb -march=armv7 -mtune=cortex-m4 -I/opt/homebrew/Cellar/nim/2.0.0_1/nim/lib -I/Users/hidepath/code/nim/arm/src -o /Users/hidepath/code/nim/arm/build/nimcache/@mmain_7.nim.c.o /Users/dwhall/GoogleDrive/code/nim/arm/build/nimcache/@mmain_7.nim.c' failed with exit code: 1
Does anyone know a way to make this work? Even if I get past the argument-to-register error (above), I don't see how I'll ever get the argument-to-immediate-value conversion. I have read about, but never coded macros; do they hold any hope of resolving this?
If you want to play with it, see main_7.nim in: https://github.com/dwhall/arm_sandbox and use nimble cross to cross-compile with your local arm-none-eabi-gcc toolchain. The project may have MacOS and VSCode assumptions.
I'm not good at asm, but I think it should be something like
asm """
MOV %0 %1
BFI %0, %2, #`bitOffset`, #`bitWidth`
: "=r" (`regVal`)
: "r" (`regVal`), "r" (`fieldVal`)
"""
This ugly construction has come very close to what I want:
# chained field modify
template GPIOAEN*(regVal: RCC_AHB1ENR_Val, fieldVal: uint32): RCC_AHB1ENR_Val =
{.emit: ["asm (\"BFI %0, %1, #0, #1\"\n\t: \"=r\" (", regVal, ")\n\t: \"r\" (", fieldVal, "));\n"].}
regVal
but it causes an error in the C that Nim generates, error: redeclaration of 'resX60gensym1_' with no linkage:
N_LIB_PRIVATE N_NIMCALL(void, main__main9556_u45)(void) {
NU32 resX60gensym1_;
NU32 resX60gensym1_;
resX60gensym1_ = (NU32)0;
resX60gensym1_ = (*(NU32 volatile*)((NU32*)1073887280));
asm ("BFI %0, %1, #0, #1"
: "=r" (resX60gensym1_)
: "r" (((NU32)1)));
The manual makes strong recommendations against the emit pragma, so I'll keep looking for other solutions.
No luck. It had the same error message, redeclaration of the variable. Here's the whole code for context. The other templates may be affecting the outcome of the one I quoted previously in this discussion. Solving this isn't critical. I have a working implementation in Nim. I am trying for an optimization in assembly in part to make my library efficient and also in part to learn how and when to best use asm in conjunction with Nim.
# main_8.nim
#
# Try to allow this chained rmw construction:
# RCC.AHB1ENR.GPIOAEN(1).write()
import std/bitops
import std/volatile
type
RegisterAddr = uint32
RegisterVal = uint32
# RCC peripheral
type RCC_Base = distinct RegisterAddr
const RCC* = RCC_Base(0x40023800)
# RCC.AHB1ENR register
type RCC_AHB1ENR_Val* = distinct RegisterVal
type RCC_AHB1ENR_Ptr = ptr RCC_AHB1ENR_Val
const RCC_AHB1ENR_Offset = 0x30'u32
const RCC_AHB1ENR = cast[RCC_AHB1ENR_Ptr](RCC.uint32 + RCC_AHB1ENR_Offset)
# reg read
template AHB1ENR*(base: static RCC_Base): RCC_AHB1ENR_Val =
volatileLoad(RCC_AHB1ENR)
# chained field modify
template GPIOAEN*(regVal: RCC_AHB1ENR_Val, fieldVal: uint32): RCC_AHB1ENR_Val {.dirty.} =
{.emit: ["asm (\"BFI %0, %1, #0, #1\"\n\t: \"=r\" (", regVal, ")\n\t: \"r\" (", fieldVal, "));\n"].}
regVal
# write the modified fields to the reg
template write*(regVal: RCC_AHB1ENR_Val) =
volatileStore(RCC_AHB1ENR, regVal)
proc main() =
RCC.AHB1ENR # reg read
.GPIOAEN(1'u32) # field modify
.write() # reg write
when isMainModule:
main()
Remember that templates are code substitution. I think what's going on here is that AHB1ENR returns volatileLoad(RCC_AHB1ENR), this is a statement which resolves to the type RCC_AHB1ENR_Val. Now in GPIOAEN this is used once in the emit statement which causes a read, then it is passed on to the write. Then in the write it is essentially expanded as volatileStore(RCC_AHB1ENR, volatileLoad(RCC_AHB1ENR)) causing your second incorrect read. If you change AHB1ENR to:
proc AHB1ENR*(base: static RCC_Base): RCC_AHB1ENR_Val {.inline.} =
volatileLoad(RCC_AHB1ENR)
Then I think this issue should be gone as what you're passing along now is the actual value and not the statement which resolves to a value.
When I turned all the templates into inline procs, then it no longer read from the register a second time. I also read the gcc's docs about the asm statement in greater detail and fixed one register modifier and discovered how to convert Nim static args to an assembly immediate operand. With these two details in place, I finally got this working as intended. The near-final code will look like this:
proc setField[T](regVal: T, fieldVal: RegisterVal, bitOffset: static int, bitWidth: static int): T {.inline.} =
{.emit: ["asm (\"BFI %0, %1, %2, %3\"\n\t: \"+r\" (", regVal, ")\n\t: \"r\" (", fieldVal, "), \"n\" (", bitOffset, "), \"n\" (", bitWidth, "));\n"].}
regVal
# reg read
proc AHB1ENR*(base: static RCC_Base): RCC_AHB1ENR_Val {.inline.} =
volatileLoad(RCC_AHB1ENR)
# chained field modify
proc GPIOAEN*(regVal: RCC_AHB1ENR_Val, fieldVal: uint32): RCC_AHB1ENR_Val {.inline.} =
setField[RCC_AHB1ENR_Val](regVal, fieldVal, 0, 1)
# write the modified fields to the reg
proc write*(regVal: RCC_AHB1ENR_Val) {.inline.} =
volatileStore(RCC_AHB1ENR, regVal)
proc main() =
RCC.AHB1ENR # reg read
.GPIOAEN(1'u32) # field modify
.write() # reg write
I verified that the procs are inlined when the --passC="-Ox" is greater than 1. This will soon be integrated into https://github.com/dwhall/minisvd2nim