P39 added Zicntr counters - real ISA work, but small. P40 is a software-side rung: same RTL, but the runtime’s trap path gets overhauled so FreeRTOS can plug into it at P41 without surprise.
The smoking-gun motivation: the P37/P39 trap glue saved exactly two
registers (t0, t1) and didn’t recognise ecall at all. Calling
ecall from FreeRTOS-style code on that runtime would have fallen
through mcause matching, hit the p37_fail label, and halted the
chip with x5 = 31 - silently, no diagnostic. Worst kind of bug for
software bring-up.
Three things landed:
- A real trap frame.
runtime/start.Snow saves all 30 GPRs +mepc+mcauseinto a fixed 128-byte stack frame. Same layout for every cause - no special cases. The C-side mirror inruntime/trap.hhas a_Static_assertthat catches drift between the assembly offsets and the struct. - A C-side handler.
c_trap_handler()inruntime/main.creceives the frame pointer, advancesmepcby 4 for ecall, and can mutate any GPR slot in the frame. The directed test sendsecallfrom C, observesa0come back as0xC0DEC0DE(a magic cookie the handler wrote), and confirms the ecall counter incremented. Two back-to-back ecalls verify re-entrance. - Real IRQ primitives.
irq_save()clearsmstatus.MIEatomically (viacsrrci) and returns the previous bit;irq_restore()re-arms it iff the saved bit was set. The probe walks all four corner cases (was-clear/was-set × restore-clear/ restore-set). All pass.
The directed test now halts at 5808 clocks (P39 was 4002); the
extra 1806 are the new ecall and IRQ probes plus the larger trap
frame on each interrupt.
The point isn’t that P40 does anything observable from outside the chip. It does not. It’s that P41 (FreeRTOS port) inherits a runtime that has already proven the shapes work, instead of debugging them under a scheduler.
ISA hasn’t changed; the arch-test sweep isn’t re-run here but the target is wired up in case someone wants the regression smoke.