Every release rule in this topic exists because a chip once broke it. The two failures below are the ones this section must prevent: a CPU that fetches before its fabric answers, and a bridge reset on one side while a transaction is mid-flight. Both hang the chip at its most fragile moment, and both are cheap to check once stated. This lesson tells both as symptom, capture and fix. Its one job is the failure set; the correct order belongs to the first lesson.
First, the CPU out of reset before the fabric. The symptom is a hang on the first fetches: the CPU issues reads from the reset vector while the fabric, the bridges or the ROM wrapper are still held, so no response ever comes and the CPU waits at the worst possible time, with no software running to diagnose it. It is caught by reset-sequence simulation with a stopwatch per step and by emulation of the full boot. The fix is the staged order with its gap: R5 releases rst_sys_n; the crossbar and the system side of nano_axi_cdc_m take their own release inside R5's 18 cycles, and the FIFO's gray pointers then need a 2-flop synchroniser, about 2 clk_sys (20.8 ns), before the first request is safe. An 8-sys-cycle gap (83.3 ns) covers that four times over, and only then does R6 release the CPU. The 8 is a NanoSoC teaching choice, not a law: the invariant that matters is the order, rst_cpu_n rises after rst_sys_n, and an assertion enforces it. The CPU's own 2-flop sync (2 clk_cpu, 10.4 ns) is irrelevant to the gap: it is the reason the gap is needed, because on its own it is faster than the fabric.
Second, the partial reset across a bridge. A software reset asserts one side of the AXI or APB bridge while the other side sits mid-transaction: the live side waits for a completion the reset side will never send. It is caught by reset-domain analysis and by directed tests that reset each domain under traffic. The fix is drain and quiesce before reset where the initiator can cooperate, and reset-together with a flush where it cannot; the tree lesson's both-sides rule is the structural half, and the pre-reset quiesce handshake is the procedural half.
# R6 gap check against the FABRIC, not against the CPU's own sync.
T_SYS_NS, T_CPU_NS = 1e9 / 96e6, 1e9 / 192e6
FIFO_PTR_SYNC_NS = 2 * T_SYS_NS # 2-flop pointer sync into the sys side
CPU_SYNC_NS = 2 * T_CPU_NS # the CPU's own release sync
def first_fetch_safe(gap_sys_cycles):
# rst_sys_n is released at time 0 (inside R5); the fabric and bridge FIFO
# are ready FIFO_PTR_SYNC_NS later. The CPU leaves reset after the gap
# plus its own sync. Its first fetch must arrive after the fabric is ready.
cpu_release = gap_sys_cycles * T_SYS_NS + CPU_SYNC_NS
return cpu_release > FIFO_PTR_SYNC_NS
assert abs(8 * T_SYS_NS - 83.333) < 0.01
assert first_fetch_safe(8)
assert abs(8 * T_SYS_NS / FIFO_PTR_SYNC_NS - 4.0) < 1e-9 # 4x margin
# The planted bug: no gap. The CPU sync (10.4 ns) beats the fabric (20.8 ns).
assert not first_fetch_safe(0)
print("PASS: 8-cycle gap (83.3 ns) is 4x the FIFO pointer sync; gap 0 fails")The R6 gap stated as a settling budget against the fabric and bridge readiness, with the arithmetic above, plus the ordering assertion that rst_cpu_n never rises before rst_sys_n.
A directed test per reset source under traffic, with hang forbidden and the quiesce handshake observed.
The CPU-early scenario named in the verification plan with the exact first-fetch addresses that expose it.
Shorten each gap in simulation until the boot fails, then confirm the taped-out gap sits at least twice past the failure point: margin proven, not assumed. On silicon, a hang at the first fetch with clocks confirmed points here before it points at the ROM contents; read back the release sequence state or its equivalent status before suspecting the image.
Stop programming new jobs, wait for STATUS BUSY to read 0, let the last APB access complete, and only then write SWRST. The reset then finds no transaction mid-flight on either bridge.