P42 worked but had a wart: configUSE_IDLE_HOOK = 1 plus a
one-instruction nop hook to keep gcc from emitting j . at
prvIdleTask’s epilogue. The actual problem was that the chip’s
halt sentinel since P09 (jal x0, 0) collides with gcc’s noreturn
safety-net idiom, and the right fix is in the RTL.
P43 makes that fix. Three small edits in src/top.sv:
- MMIO platform: new address
MMIO_HALT = 0x10001ff8. On a word write, capturewdataintohalt_codeand pulsehalt_strobefor one cycle. - CPU core: new
external_haltinput port; latch intoexternal_halt_q; check at the S_WB → S_FETCH transition. Oldis_halt_loopdecoder removed. - Top wiring: connect
mmio_halt_strobeto the core; exposehalt_codeas a top-level output.
After the RTL change the watcher task in app/main.c does:
*MMIO_HALT = HALT_PASS; // 1 = pass, 31 = fail
for (;;) { }
instead of the old __asm__ volatile ("li x5, 1; .word 0x0000006f")
trick. The trailing for(;;) is a defensive spin in case the chip
doesn’t honour the halt write - and unlike P42, gcc’s j .
epilogue in that loop is fine, because our halt isn’t bound to that
encoding anymore.
configUSE_IDLE_HOOK goes back to 0. The nop hook in
p43_port.c is gone.
Result
PASS: P43 loaded 20852 bytes via UART
UART tx[0..9] = 0x53 0x61 ... 0x68 0x44 ('S a b c d e f g h D')
HALT: P43 cleanly halted at 5100751 clocks via MMIO_HALT (halt_code=1)
PASS: P43 FreeRTOS multi-task demo complete.
5,100,751 clocks - 25 cycles longer than P42 because the new halt
write replaces the old jal x0, 0 with an MMIO store-then-spin
that takes a few extra cycles to reach the chip-halt edge. The
image is 40 bytes smaller than P42 (the idle hook function got
dropped).
What I learned
A halt sentinel needs to be an instruction encoding the toolchain
never generates by accident. jal x0, 0 does not have that
property. Every gcc target with -O2 and noreturn analysis emits
that pattern eventually.
The standard answers for RISC-V sim halt:
tohost(HTIF) - polled by Spike/Sail- MMIO halt port - QEMU virt’s
sifive_test, every commercial simulator ebreakfrom M-mode - awkward when the trap path is owned by an RTOSwfimasked - real-silicon idle, doesn’t fit explicit termination
We picked the MMIO halt port. ~10 lines of RTL, the toolchain
never writes to 0x10001ff8 accidentally, the testbench reads the
written 32-bit value as the exit code (so we can distinguish PASS
from FAIL without inspecting registers).
The chip just hardened, in parallel with the directed sim. Once the run lands I’ll fill in the metrics. P43 should be the first rung on the ladder where “FreeRTOS on this chip” is a hardware claim, not a simulation claim.
Aside: this is the first time real software found an RTL bug
The earlier rungs all bumped against compiler issues
(-fno-delete-null-pointer-checks, missing volatile, gcc
optimization choices) but the chip itself hadn’t needed to change
because of a software discovery. P42 surfaced a real RTL flaw, and
P43 is the rung that fixes it.
That feels like a real ladder milestone - the chip getting better because the software exposed a problem with it, not the other way around.