Compiler intrinsics¶
A handful of @-prefixed names are intrinsics: they are recognised by the
compiler’s code generator and emit specific AVR instructions inline, rather than
being ordinary function calls. They use call syntax but have no function body and
no call overhead.
These are the only built-in functions. Everything else that looks like
@name(...) is either one of your own functions or a standard-library routine
(which is itself written in ik).
- @nop()¶
Emits a single
NOP(no-operation) instruction — one wasted CPU cycle. Takes no arguments and returns nothing. Useful for precise one-cycle padding and as a placeholder where the hardware needs a settling cycle.
- @sei()¶
Emits
SEI— set the global interrupt enable bit (the I bit in SREG). After this, unmasked interrupt sources can fire. Takes no arguments, returns nothing. See Interrupts.
- @cli()¶
Emits
CLI— clear the global interrupt enable bit, disabling all interrupts globally. Takes no arguments, returns nothing. Thestd/atomicmodule builds critical sections on this.
- @wdr()¶
Emits
WDR— reset the watchdog timer (“kick the dog”). Takes no arguments, returns nothing. This is what@wdt_resetinstd/wdtis built on.
- @sleep()¶
Emits
SLEEP— enter the sleep mode previously selected/enabled in the device’s sleep-control register. Takes no arguments, returns nothing. Thestd/sleepmodule configures the mode and then calls this.
- @break()¶
Emits
BREAK— the on-chip debug breakpoint instruction. Takes no arguments, returns nothing. It acts as aNOPwhen no debugger is attached.
- @burn($cycles)¶
Emits a calibrated busy-wait that consumes
$cyclesCPU cycles. Returns nothing.When
$cyclesis a compile-time constant, the compiler emits an unrolled/looped delay sized exactly for that count (a zero count emits nothing).When
$cyclesis a runtime value (au8oru16), the compiler emits a counted loop that decrements the value to zero, preserving the registers it borrows.
@burnis the primitive the std/delay — Delays library is built on; prefer@delay_ms/@delay_usfor time-based waits, and use@burndirectly only when you want an exact cycle count.
- @swap($reg)¶
Emits
SWAPon a literal register number, exchanging the high and low nibbles of that register. The argument must be a literal register index in the range 0–31. Returns nothing. This is a low-level escape hatch for hand-tuned register manipulation.
- @movw($rd, $rr)¶
Emits a 16-bit register-pair move (
MOVWwhere available, or an equivalent pair of moves), copying the register pair$rrinto$rd. Both arguments must be literal register indices (0–31). Returns nothing.
- @mul($rd, $rr)¶
Emits the hardware
MULinstruction multiplying the two literal registers$rdand$rr(result in the R1:R0 pair, per the AVR convention). Both arguments must be literal register indices (0–31). Returns nothing.This intrinsic requires a hardware multiplier. On core families without one (such as the reduced-core
AVRrcparts), the compiler rejects@mulwith an error rather than emitting an unsupported instruction.
- @goto($word_addr)¶
Emits an unconditional
JMPto the absolute Flash word address$word_addr(a literal). The address is never adjusted by abootorigin, so it is the way a bootloader hands control to the application — typically@goto(0). Returns nothing.
- @spm($spmcsr, $cmd, $zaddr, $word)¶
Runs the timed Store-Program-Memory sequence used for flash self-programming: it waits for any previous SPM to finish, writes
$cmdto the SPM control/status register at data address$spmcsr, then issuesSPMwithZ = $zaddr(a byte address) andR1:R0 = $word. The exact command (page erase / fill buffer / page write / RWW re-enable) is the caller’s;$zaddrand$wordmust be 16-bit values.$spmcsris the SPMCSR address, which differs per device. Rather than pass a literal, accept the per-device std/boot register constant —@spmresolves a%register constant to its address — or just use the@boot_*wrappers, which do this for you. Requires a core with SPM (notAVRrc); interrupts should be disabled around the call. Returns nothing.
- @swtch($old_sp_ptr, $new_sp)¶
Switches execution from the current context to another — the primitive a cooperative or preemptive scheduler is built on. Both arguments are 16-bit values (not literal register numbers):
$old_sp_ptrpoints to au16that receives the outgoing context’s stack pointer, and$new_spis the stack pointer of the context to resume. Returns nothing.The sequence is:
push the callee-saved register file (
r2–r15) and a resume address onto the current stack;store the resulting stack pointer through
$old_sp_ptr;load the stack pointer from
$new_spandRETinto that context, which continues at its saved resume address.
When another
@swtchlater switches back, this call restoresr2–r15and execution continues right after it. A freshly created context is entered by hand-building a stack whose top holds the entry address (so the first switch’sRETlands there).@swtchmust run with interrupts disabled (inside a critical section or an ISR), because it writes the stack pointer in two non-atomic steps. It saves the classic/modern ABI callee-saved classr2–r15; the reducedAVRrccore lacks that register range, so the compiler rejects@swtchthere with an error.
Notes¶
The register-level intrinsics (
@swap,@movw,@mul) take literal register numbers, not variables — the value must be a constant the compiler can resolve to a register index 0–31. Passing a non-literal, or an out-of-range index, is a compile error.None of the intrinsics that “do” something return a value; using one in a value position is an error.
Because intrinsics expand inline, they are safe to use inside ISRs and tight loops where a real call’s overhead would matter.