On the first day nobody can read a register. The only evidence that the chip is alive is what it does on its pins and when. A lab that does not know the expected times cannot tell a slow boot from a dead one, and cannot tell which stage stopped. Its one job is the timeline: the instants at which the pins should change, computed once from the boot flow, so that the first scope capture is a comparison and not a guess.
The boot flow is a list of steps with durations, and the pin-visible events are a subset of it. The oscillator and reset steps are internal; the first thing that appears outside is the UART banner, then the chip-select of the SPI load, then, when the load ends, nothing on a pin until the loaded image speaks.
The reference point matters, because por_n is an internal analog signal that the lab cannot probe. The table below is measured from the instant por_n is released, which the lab approximates by the moment the vdd_core supply crosses the power-on-reset threshold, plus the POR cell's own release delay (an offset the model does not include; the lab measures it once and subtracts it). A scripted RESET_N edge is the second reference, but the table is the power-on timeline only: after a pad reset the oscillator-ready counter may not restart, so the first events come earlier and the pad-reset timeline is measured separately. A real crystal adds its own start-up time, also measured separately.
Pin event | Pad | Time from the reference | How long | What it proves |
|---|---|---|---|---|
Hardware reset sequence done, CPU out of reset | none | 53.948 us | Internal | Not visible; inferred from the next event |
UART TX first start bit | P0 uart_tx | 65.865 us | Banner: 1,386.667 us | Clocks, reset order, ROM fetch, stack clear and UART init all worked |
Banner complete (16 characters) | P0 uart_tx | 1,452.531 us | The UART divisor and the 24 MHz crystal are right | |
SPI chip-select falls | P5 spi_cs_n (high until now) | 1,460.917 us, plus the 0.84 us of SPI set-up writes | Load: 10,928 us | The DMA self-test passed: reset values, a job, the interrupt, the read-back |
SPI clock and data start | P2 spi_sck, P3 spi_mosi, P4 spi_miso | just after chip-select | 6 Mb/s | The flash answers the 4-byte read command |
SPI chip-select rises | P5 spi_cs_n | 12,388.917 us | Checksum 192 us | The image was read: 8,192 bytes plus the 4-byte command at 6 Mb/s |
Jump to the loaded image | none | 12,580.917 us | The checksum matched; the image runs from 0x2000_0000 |
Tolerance. The model treats the banner as 160 continuous bits, but the ROM polls TXE (a 281.25 ns register read) between characters, so the real frames can finish a few microseconds late in total: at most 16 characters times two clk_per accesses, 9 us, if nothing overlaps. The chip-select instant also moves by the three SPI register writes, about 0.84 us. So the lab compares events within a few microseconds (this lesson uses an illustrative 10 us), not to the microsecond; a tolerance that tight would flag a good chip, while 10 us is still far below the 5.55 ms that extra printed characters would add.
Before the first event, the pins show the pad pulls, not boot activity. Every pad driver is held off by the pad cell until the core supply is valid, so with the rails up and nothing running the open-pin levels are those of the reset-state table: P0 uart_tx high (pull-up), P1 uart_rx high, P2 spi_sck low, P3 spi_mosi low, P4 spi_miso low, P5 spi_cs_n high, P6 to P15 low, RESET_N high. Two of the three pad planted bugs show here and nowhere in simulation. Pad bug P1 gives pad P5 the pull-down of the GPIO rows by mistake, so chip-select reads low from power-on and the flash sees the first clock edges in the middle of a command: the boot's checksum fails. (The pad bugs P1 to P3 are numbered separately from the pads Pn.) Pad bug P3 leaves RESET_N without its pull-up, so noise can hold it low for more than 666.7 ns and the chip reboots at random with RST_CAUSE bit 1 set and the banner printed again.
The table is the contract between the verification of the previous topics and the lab. The simulated boot must print the same events at the same times, within the tolerance of the model, and the prototype must print them stretched by its scale factor. Any event that appears at a different time on silicon has a stage to blame, named by the step before it.
# Rebuild the pin-event timeline from the boot flow steps (boot flow table)
# and check the UART against a host terminal. NanoSoC teaching values.
T_PER, T_SYS, T_CPU = 1e9 / 24e6, 1e9 / 96e6, 1e9 / 192e6 # ns
STEPS = [
("oscillator ready", 1024 * T_PER), ("rst_per_n released", 2 * T_PER),
("pll lock", 260 * T_PER), ("clock gates open", 2 * T_PER),
("rst_sys_n released", 18 * T_SYS), ("rst_cpu_n released", 8 * T_SYS + 2 * T_CPU),
("first fetch", 24 * T_CPU), ("rst_cause read", 281.25),
("stack clear", 1024 * T_SYS), ("uart init", 3 * 281.25),
("banner", 160 / (24e6 / (16 * 13)) * 1e9),
("dma self-test", 625 + 875 + 72 * T_SYS + 135.4 + 6000),
("spi load", 8196 * 8 / 6e6 * 1e9), ("checksum", 2048 * 18 * T_CPU),
]
t, at = 0.0, {}
for name, dur in STEPS:
at[name + " start"] = t
t += dur
at[name + " end"] = t
total_us = t / 1000
print(f"banner {at['banner start'] / 1000:.3f} to {at['banner end'] / 1000:.3f} us")
print(f"spi load {at['spi load start'] / 1000:.3f} to {at['spi load end'] / 1000:.3f} us; jump at {total_us:.3f} us")
assert abs(at["rst_cpu_n released end"] / 1000 - 53.948) < 0.001
assert abs(at["banner start"] / 1000 - 65.865) < 0.01
assert abs(at["banner end"] / 1000 - 1452.531) < 0.01
assert abs(at["spi load start"] / 1000 - 1460.917) < 0.01
assert abs(total_us - 12580.917) < 1.0
# The reference and tolerance: compare events within TOL_US, never to the microsecond.
TOL_US = 10.0 # ILLUSTRATIVE: TXE polling (at most 16 x 2 x 0.28125 = 9 us) plus 0.84 us of SPI set-up
assert 16 * 2 * 0.28125 + 3 * 0.28125 < TOL_US
EXPECT = {"first start bit": 65.865, "banner end": 1452.531, "cs falls": 1460.917,
"cs rises": 12388.917, "jump": 12580.917}
def late_events(measured):
return [k for k, v in EXPECT.items() if abs(measured[k] - v) > TOL_US]
real = {k: v + (0 if k == "first start bit" else 4.5) for k, v in EXPECT.items()}
assert late_events(real) == [] # a good chip, a few us late, passes
assert late_events({**real, "cs falls": 1460.917 + 5546.7, "cs rises": 12388.917 + 5546.7,
"jump": 12580.917 + 5546.7}) == ["cs falls", "cs rises", "jump"] # 64 extra prints
# the PLL reference: a 3 MHz crystal puts the VCO at 3 x 32 = 96 MHz, outside the 600 to 1200 MHz range
def pll_locks(xtal_hz):
return 600e6 <= xtal_hz * 32 <= 1200e6
assert pll_locks(24e6) and not pll_locks(3e6)
# BAUDDIV 104 instead of 13 stretches only the banner, by exactly 8: the first start bit does not move
banner_us = lambda div: 160 * 16 * div / 24e6 * 1e6
assert abs(banner_us(13) - 1386.667) < 0.001 and abs(banner_us(104) - 11093.333) < 0.001
assert abs(banner_us(104) / banner_us(13) - 8) < 1e-9
# pad pulls: the reset pull must equal the idle level of the function the pad takes (reset-state table)
PULL = {"P0 uart_tx": 1, "P1 uart_rx": 1, "P2 spi_sck": 0, "P3 spi_mosi": 0, "P5 spi_cs_n": 1, "RESET_N": 1}
IDLE = dict(PULL) # UART idles high, SPI mode 0 clock and data idle low, CS_N inactive high, RESET_N not in reset
def pull_violations(pull):
return [pad for pad, idle in IDLE.items() if pull[pad] != idle]
assert pull_violations(PULL) == []
assert pull_violations({**PULL, "P5 spi_cs_n": 0}) == ["P5 spi_cs_n"] # pad bug P1: CS low from t = 0
assert pull_violations({**PULL, "RESET_N": 0}) == ["RESET_N"] # an open RESET_N must read as run
# UART tolerance: a frame of 10 bits is sampled in the middle of the stop bit, 9.5
# bit times after the start edge, so the total rate mismatch must stay below half a
# bit over that span (ideal limit); as a general guideline keep it under 2 to 3 percent.
chip_baud = 24e6 / (16 * 13)
chip_err = chip_baud / 115_200 - 1
limit = 0.5 / 9.5
host_err = 0.015 # illustrative host adapter error
assert abs(chip_err * 100 - 0.16) < 0.01
assert abs(chip_err) + host_err < 0.02 and abs(chip_err) + host_err < limit
# the check can fail: a host adapter 5 percent off breaks the guideline
assert abs(chip_err) + 0.05 > 0.02
print("PASS: timeline matches the boot flow within 10 us; extra prints, BAUDDIV 104, a 3 MHz crystal and a CS pull-down are each caught")The timeline table is in the bring-up plan with the same numbers the simulated boot prints (bench monitor events, within the stated tolerance), and the scope capture length is chosen from it: about 14 ms covers the whole boot with margin.
The UART divisor error against the host terminal rate, not just against the nominal baud, is written down with the host adapter's own error. The ideal limit is about 5.3 percent for the transmit and receive errors together (half a bit over the 9.5 bit times to the middle of the stop bit); as a general guideline keep the sum under 2 to 3 percent.
Each timeline row names its pad and its fall-back probe: what to look at when the event is late, early or absent, and the open-pin level of each pad before the first event.
Compare the simulated, prototype and silicon timelines in that order. A silicon event that is late by a fixed ratio points at a clock or a divisor, and two realistic causes of a banner that takes eight times longer than 1.387 ms separate by the rest of the timeline. If BAUDDIV was programmed as 104 instead of 13, only the banner stretches (to 11.09 ms) and the first start bit still comes at 65.9 us. If the whole timeline is stretched, the first start bit included, the clock is wrong. A slow crystal is not the clean explanation it looks like: clk_per is the crystal itself (NanoSoC has no divider on it), so a 3 MHz crystal would stretch the peripheral timeline by eight, but it would also put the PLL reference at 3 MHz and the VCO at 3 x 32 = 96 MHz, below the teaching VCO range of 600 to 1200 MHz. A real PLL would not lock, and the symptom would be no banner at all; the model's 256-cycle lock counter counts cycles and would hide that. An event that is late by a fixed amount points at a sequence: a first start bit at 1,300 us and not 66 us is an oscillator that took 1.2 ms to start, which the model does not include. An event that never occurs is the next lesson.
Eight times too long decodes at 14,400 baud, which is the BAUDDIV of 104 instead of 13 (baud = 24 MHz / (16 x BAUDDIV)) or a clock eight times slow. Check the first start bit: still at 65.9 us means only the divisor is wrong and the chip-select follows 8.4 us after the banner ends, about 11.2 ms in all; a first start bit at about 0.53 ms means the whole chip runs slow, the SPI load takes 87.4 ms and the boot 100.6 ms, the stretch of the prototype at one eighth scale. A 3 MHz crystal is not the answer to offer: the PLL would not lock with a 96 MHz VCO, so there would be no banner at all.