This lesson has one job: guarantee the CPU fetches its first instruction from somewhere meaningful. The reset vector is address 0x0000_0000, the first word of the 16 KB boot ROM, and the CPU's first 4-word line fill costs 24 CPU clocks (clk_cpu, 192 MHz), which is 125 ns. The second job is the cold-versus-wake decision: if the ROM misreads why it was reset, it either reloads an image that was sitting retained in SRAM0 or jumps into SRAM0 on a chip that holds nothing there.
The ROM window is exactly the ROM size with no mirror, so there is no alias to remove and no remap bit to forget: fetch from zero and the ROM answers. Boot-source select on NanoSoC is degenerate by design: there are no boot straps and no GPIO is read before the load. There is one source, SPI, plus the retained-image shortcut at wake, and the only input to the decision is RST_CAUSE. On a chip that does have boot straps the discipline is different (see the pitfall below), but NanoSoC's ledger has no strap step. RST_CAUSE names power-on (bit 0), pad (bit 1), software (bit 2) and wake (bit 3) resets. It is reset only by power-on reset; every other reset sets its own bit on top of whatever is already there. So the ROM reads it first (one 281.25 ns APB access), branches on the bits, and then writes 1s to clear the bits it has handled. Without that clear, the first wake after a cold boot would show POR and WAKE together, and the test for cold versus wake would be ambiguous. The clear write is one more register access that the 12.581 ms ledger does not count: it is 281.25 ns if the ROM does it, or it is done by the first software stage after the handoff.
The script checks the three addresses against the memory map and then plays two reset sequences, a cold boot followed by a wake, against two ROMs: one that clears RST_CAUSE after reading it and one that does not. The decision function can fail: the non-clearing ROM must be caught as ambiguous.
bases = {"BOOTROM": 0x0000_0000, "SRAM0": 0x2000_0000, "SRAM1": 0x2001_0000}
ends = {"BOOTROM": 0x0000_3FFF, "SRAM0": 0x2000_FFFF, "SRAM1": 0x2001_FFFF}
vector = 0x0000_0000
stack_top = 0x2001_0000
image_base = 0x2000_0000
assert ends["BOOTROM"] - bases["BOOTROM"] + 1 == 16 * 1024
assert ends["SRAM0"] - bases["SRAM0"] + 1 == 64 * 1024
assert bases["BOOTROM"] <= vector <= ends["BOOTROM"], "vector must be in ROM"
assert bases["SRAM0"] <= image_base <= ends["SRAM0"], "image must be in SRAM0"
assert stack_top == ends["SRAM0"] + 1 == bases["SRAM1"], "stack top is SRAM0 end + 1"
print(f"vector {vector:#010x} in ROM, image {image_base:#010x}, stack {stack_top:#010x}")
POR, PAD, SW, WAKE = 1, 2, 4, 8
def path(cause):
if cause == WAKE:
return "wake"
if cause != 0 and not cause & WAKE:
return "cold"
return "ambiguous"
def run(clears):
cause, seen = 0, []
for event in (POR, WAKE):
cause |= event
seen.append(path(cause))
if clears:
cause &= ~cause
return seen
assert run(True) == ["cold", "wake"], run(True)
assert run(False) == ["cold", "ambiguous"], run(False)
print("clearing ROM:", run(True), "| non-clearing ROM:", run(False))
print("PASS: addresses fit the map; only a ROM that clears RST_CAUSE tells cold from wake")The reset vector row names the ROM window, the first fetch width and the cold-cache cost of 125 ns.
The ROM reads RST_CAUSE before any other state-changing step, branches on the bits, and clears the bits it has handled.
RST_CAUSE handling is documented per cause: cold boot loads, wake jumps, software reset re-enters safely.
If the chip has boot straps (NanoSoC has none), every strap has a defined pull, a settle time before reset release, and a read-back register.
Force each reset source in simulation from a clean power-on and read back RST_CAUSE: exactly one bit per cause, with older bits still present until software clears them, and only power-on reset clears the register by itself. Then run cold boot followed by wake and confirm the ROM takes the wake path only because it cleared the POR bit. On a chip with straps, also float each strap in a scratch build and confirm the pull defines the intended source; a floating strap that changes the boot path is a board-level bug wearing a silicon mask. If the first fetch hangs, check the ROM select equation before anything else: addr bits 31 down to 14 equal zero, or nothing answers.
The boot alias that is never removed: with no mirror there is no remap bit and no stale alias for vectors to fetch from after software remaps. Fixed maps cost one decision and remove a whole class of boot bugs.