This lesson has one job: hand control from unchangeable ROM to writable SRAM without dropping the baton. The handoff is six acts, read the cause, set the stack pointer, clear memory (cold boot only), load the image, verify it, jump, and each act has a failure that looks like the next act's bug. The wake shortcut skips the middle entirely, which only works because the cause is read before anything is written. And because ROM cannot be fixed, the lesson ends with the patching hooks that let a fixed ROM survive its own errata.
RST_CAUSE is read first, before any write to SRAM0. Power-on, pad and software resets are cold boots; a wake cause skips the clear and the load and jumps straight to 0x2000_0000 on the retained image, because the clear would otherwise wipe state that sleep was supposed to keep. On a cold boot the stack pointer is then set to 0x2001_0000 (the end of SRAM0) before any call or push, and the clear loop zeroes 4 KB of SRAM0 with posted writes. Which 4 KB is a NanoSoC teaching choice, since the boot table only says 4 KB of SRAM0: the top 4 KB, 0x2000_F000 to 0x2000_FFFF, where the ROM's own stack and variables live, so the image area from 0x2000_0000 is untouched until the load overwrites it. The clear initialises those 1024 words only. That is 1/16 of SRAM0 and none of SRAM1, so with SECDED the rest of SRAM0 and all of SRAM1 must be written before they are read: all 16,384 words of one bank at one write per system clock take about 170.7 microseconds. A software reset re-enters with its cause bit set so drivers reinitialise instead of assuming a clean slate. The load moves 8 KB plus a 4-byte command at 6 Mb/s, the checksum rereads 2,048 words at 18 CPU clocks each for 192 microseconds, and the jump lands on the first loaded word. The wake jump skips verification entirely: a product with secure boot must decide whether a retained image is trusted because retention protects it, or is re-verified (the 192 microsecond checksum, plus authentication) before the jump. Patching, a NanoSoC teaching value since the NanoSoC reference defines no patch block, would reserve a small table of address-and-redirect entries that diverts a matched ROM address to SRAM0 code. That table is mutable state redirecting the root of trust, so it needs rules: entries are loaded only after the patch image passes the same authentication as any other image, they are locked once written (or come from fuses), and the comparator on the ROM read path adds delay that the 24-clock line fill and 18-clock access figures do not include. An erratum then costs entries, not a respin, but never at the price of an unverified redirect.
# not-run: handoff step costs from the NanoSoC boot ledger, NanoSoC teaching values
Clear 4 KB (cold boot only): 1024 posted writes x 1 T_sys = 1024 x 10.4167 ns = 10.667 us.
Checksum: 2048 single reads x 18 T_cpu = 2048 x 93.75 ns = 192.000 us.
DMA self-test: 625 + 875 + 750 + 135.4 ns + 6000 ns = 8.385 us.
Wake shortcut: 14.073 us power-up, then the line fill (0.125 us) and the cause read (0.281 us): about 14.479 us to the jump (derived).t_sys = 1 / 96e6
t_cpu = 1 / 192e6
assert 4 * 1024 // 4 == 1024
assert 0x2000_FFFF - 0x2000_F000 + 1 == 4 * 1024
clear = 1024 * t_sys
checksum = 2048 * 18 * t_cpu
dma_test = 625e-9 + 875e-9 + 72 * t_sys + 135.4e-9 + 6000e-9
assert abs(clear * 1e6 - 10.667) < 0.002, clear
assert abs(checksum * 1e6 - 192.0) < 0.01, checksum
assert abs(dma_test * 1e6 - 8.385) < 0.01, dma_test
print(f"clear {clear * 1e6:.3f} us, checksum {checksum * 1e6:.3f} us, DMA test {dma_test * 1e6:.3f} us")
def boot(cause_wake):
t = 24 * t_cpu + 281.25e-9
if cause_wake:
return t
return t + clear + checksum + dma_test
assert boot(False) > boot(True)
assert abs((14.073e-6 + boot(True)) * 1e6 - 14.479) < 0.002
words_sram0 = 64 * 1024 // 4
ecc_init = words_sram0 * t_sys
assert abs(ecc_init * 1e6 - 170.667) < 0.001
assert 1024 / words_sram0 == 1 / 16
print(f"wake path to the jump: {(14.073e-6 + boot(True)) * 1e6:.3f} us; full-bank ECC init {ecc_init * 1e6:.1f} us")
print("PASS: clear 10.667 us, checksum 192.000 us, DMA test 8.385 us, wake skips all three")The handoff sequence is written as six acts with the actor and the cost of each, with the cause read first and the clear marked cold-boot-only.
The wake path is a named branch with its own test, not an afterthought: retained image, no SPI traffic, jump observed.
Patch entries, if provided, are loaded only from an authenticated image, locked once written, and costed on the ROM read path; each redirects one ROM address to SRAM0 code.
Boot each reset cause separately: cold loads and jumps, wake jumps without SPI activity and with the retained image bytes unchanged (compare the first 4 KB before and after), software reset re-enters cleanly. Corrupt one image byte in a scratch build and confirm the checksum refuses the jump; corrupt the stack setup and confirm the failure points at the clear loop, not the loader. Test every patch entry by triggering its address and observing the redirect.
Suspect the jump target first: a wrong entry address, or an image linked for another base, runs garbage from a perfectly checksummed load. The next suspect is a bad image that was checksummed correctly (the checksum proves the bytes arrived, not that the code is right). The load path is cleared, because the checksum matched. The clear loop is not cleared by the checksum at all; only a separate test of the cleared region does that.