Bring-up debugs start with a stopwatch: at 54 microseconds the CPU should fetch, and every step before that has a number. When the first fetch arrives late or never, the table below says which wait to blame: oscillator, lock, gates, system release or CPU release. Without it the team guesses between clock, reset and ROM. This lesson gives the table and recomputes its total from the NanoSoC specification. Its one job is the order and its timing; how each release is synchronised is next.
Cold boot runs seven steps. POR releases at time zero. The oscillator-ready counter waits 1024 clk_per cycles, 42.667 us, the largest single wait. The peripheral reset releases after a 2-flop synchroniser, 2 clk_per. The PLL enables and locks over 4 start-up plus 256 lock cycles, 10.833 us. The fast clock gates open 2 clk_per after lock with resets still asserted. The system reset releases after a 16-cycle hold plus a 2-flop sync, 18 clk_sys or 187.5 ns (the 16 and the 18 are the same number counted twice: 16 is also the width of a software reset assertion, 166.7 ns, and the 2-flop sync adds the last two). The CPU reset follows after an 8-cycle gap plus its own 2-flop sync in clk_cpu. Hardware boot totals 53.948 us, of which the oscillator and the PLL are nearly everything: the three releases together cost under half a microsecond.
Step | Event | Waits for | Cycles | Time |
|---|---|---|---|---|
R0 | por_n released | Supply | n/a | 0 |
R1 | xtal_ok | Oscillator counter | 1024 clk_per | 42.667 us |
R2 | rst_per_n released | xtal_ok, POR, 2-flop sync | 2 clk_per | 83.333 ns |
R3 | PLL locked | EN, start-up, lock counter | 260 clk_per | 10.833 us |
R4 | Fast gates open | pll_lock | 2 clk_per | 83.333 ns |
R5 | rst_sys_n released | R4, 16 hold, 2-flop sync | 18 clk_sys | 187.5 ns |
R6 | rst_cpu_n released | R5, 8 gap, 2-flop sync | 8 clk_sys + 2 clk_cpu | 93.75 ns |
T_PER_NS, T_SYS_NS, T_CPU_NS = 1e9 / 24e6, 1e9 / 96e6, 1e9 / 192e6
steps = {"R1": 1024 * T_PER_NS, "R2": 2 * T_PER_NS, "R3": 260 * T_PER_NS,
"R4": 2 * T_PER_NS, "R5": 18 * T_SYS_NS,
"R6": 8 * T_SYS_NS + 2 * T_CPU_NS}
total_us = sum(steps.values()) / 1000
for name, ns in steps.items():
print(f"{name}: {ns / 1000:.3f} us")
assert abs(steps["R1"] / 1000 - 42.6667) < 0.001, steps["R1"]
assert abs(steps["R3"] / 1000 - 10.8333) < 0.001, steps["R3"]
assert abs(steps["R5"] - 187.5) < 0.01, steps["R5"]
assert abs(steps["R6"] - 93.75) < 0.01, steps["R6"]
assert abs(total_us - 53.9479) < 0.001, total_us
print("PASS: hardware boot R1..R6 totals 53.948 us")The table above, current, with every wait traced to a counter or a synchroniser, never to a delay line.
The total recomputed by the script, not copied; the boot-time budget quotes the same number.
Software reset and wake reset reuse the tail of the table: R5 and R6 with their holds (16-cycle assertion for software, then the same 18 and 8 + 2 cycles), never a shortcut release.
Simulate cold boot with a stopwatch on each step: any step that overruns points at its wait, usually the lock counter or a gate that never opened. On silicon, measure time to first fetch against 53.948 us plus the line fill; a boot that is slow by exactly one wait has a counter problem, while a boot that never fetches has a release-order problem, which the third lesson covers.
The oscillator is running and the PLL is locked: neither wait has anything to re-qualify. The holds and synchronisers in R5 and R6 are still needed, because the flops need their per-domain synchronous release regardless of what came before.