Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
    • SoC Verification Strategy
    • Integration and Connectivity Tests
    • Firmware Co-Simulation
    • Performance Verification
    • Gate-Level and Power-Aware Simulation
    • Emulation and FPGA Prototyping
    • Bring-Up Readiness
      • The Boot Timeline the Lab Will Expect
      • Debug Access and Observability
      • Test Plan, Lab Setup and the Readiness Checklist
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Bring-Up Readiness
  1. Topics
  2. SoC Design
  3. Top-Level Verification and Bring-up
  4. Bring-Up Readiness
  5. The Boot Timeline the Lab Will Expect
On this page

    Part 10 of 14, lesson 19 of 21Part 10 of 14/Top-Level Verification and Bring-up/Lesson 19 of 21All levels

    The Boot Timeline the Lab Will Expect

    The pin-level timeline of a good NanoSoC boot, derived from the boot flow, and the UART tolerance check against the host.

    Why it matters

    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 core idea

    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.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    # 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")

    What the review looks for

    • 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.

    Verify and debug

    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.

    A timeline that nobody wrote down: the bring-up engineer sees the banner at 0.07 ms on the scope, a hundred times earlier than the lab notebook assumed, and loses a morning deciding whether the chip is fast or the probe is wrong. The table above removes the question: first start bit 65.9 us after reset release, 1.387 ms long.

    If asked how you know the chip is booting, answer with the pin timeline: UART banner 1.387 ms long starting about 66 us after reset, SPI chip-select low for 10.9 ms, one jump at 12.58 ms. Each is a stage, not just a time.

    Check yourself The scope shows the banner decoding correctly but lasting 11.1 ms instead of 1.387 ms. What is the first suspect, and what else should be eight times slower?

    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.

    Related topics

    • Next: Debug Access and Observability

    • Memories and Boot: The NanoSoC Boot Flow in Time

    • Package and Pads: Pad Function, Drive and Reset State

    • Bring-up: First-Silicon Power-on Checklist and Day-0 Triage

    • Bring-up: Boot ROM Execution and Firmware Stage Handoff

    • Verification First Principles: Pre- and Post-Silicon

    Previous

    214 / 289

    Next: Debug Access and Observability

    Up next

    Up nextDebug Access and Observability

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Test Plan, Lab Setup and the Readiness Checklist