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. Debug Access and Observability
On this page

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

    Debug Access and Observability

    What the lab can see on NanoSoC, how a silent pin maps to a boot stage, and the gaps a readiness review must raise.

    Why it matters

    When the timeline is not met, the lab needs a way to find out why. NanoSoC v1 has no debug port and no trace block: its block list has a UART, an SPI master, 16 GPIO pins, a reset pin and a crystal. If the CPU is stuck, no register can be read from outside. Its one job is the honest inventory of what can be seen, the triage that turns a silent pin into a stage and a short list of suspects, and the gaps worth raising while the design can still change.

    The core idea

    Observability on this chip comes from three places. Pins: the UART transmit line (pad P0) carries the banner and nothing else, because the shipped ROM prints only the banner; the SPI lines (pads P2 to P5) show the load; the GPIO pads P6 to P15 could show a status code, but the boot flow never writes the GPIO block, so on v1 they stay low; and the reset pin and crystal are inputs the lab controls. Registers read by software: RST_CAUSE, PLL_STATUS, PWR_STATUS, the INTC RAW, PENDING and TOP registers, and the DMA and SBR STATUS and COUNT registers, all readable only by code that is running. Timing: the timeline of the previous lesson, because a stage that stops leaves its successors silent.

    That inventory has gaps a review should state plainly. The boot flow table lists only success steps; it does not say what the ROM does when the self-test or the checksum fails. Without a defined failure path (print a code, drive a GPIO pattern, stop in a known loop), a failed self-test and a hang look the same on the pins. And with no debug access, a CPU stalled at 0x0000_0000 (planted bug 2) can be told apart from other early failures only by the absence of the banner. These are review findings, not claims about the shipped chip: they must be closed in the ROM content, which is final at tape-out, or accepted knowingly. Any added hardware access (a debug port with security gating, a status register that mirrors stage numbers) is a design proposal for a later version, not part of NanoSoC v1; how such a port would be designed, and what it would cost in pads, is the subject of the debug-infrastructure and test-pin lessons linked below.

    A per-step code in the ROM is the cheap in-ROM hook, and it has a price that has to be written next to the proposal. A UART character is 10 bits at 115,384.6 baud, 86.7 us each, so a code printed at each of five self-test steps adds 433 us and moves every later event of the timeline. A GPIO status write is one clk_per register access, 281.25 ns. The pads P6 to P15 are GPIO-only, so no peripheral shares them, but GPIO.DIR resets to 0 (all inputs), so the ROM must write DIR first: six writes in all, about 1.7 us. Either is a ROM content change, final at tape-out, and the timeline of the previous lesson then has to include it.

    Pin observation

    Stage that stopped

    First suspects (planted bugs where they apply)

    Next probe

    No UART activity at all

    Before UART init: reset, clocks, first fetch, stack clear

    Crystal or reset pin; PLL lock; CPU released early, stalled at PC 0 (bug 2)

    Reset pin and crystal on the scope; compare with the reset-release timeline

    Chip-select (pad P5) low from power-on, banner timing normal

    A pad fault, not a boot stage

    Pad bug P1: pad P5 given a pull-down; the flash was selected before the first clock

    Open-pin level of P5 with the chip in reset; compare with the reset-state table

    Banner printed, SPI chip-select never falls (or falls only sometimes)

    DMA self-test

    Interrupt on the wrong input (3), byte-offset wiring (4), NS default (7), LEN mismatch (8), overlap (1); an intermittent banner-then-silence also fits the unsynchronised crossing (5)

    Only a per-step code or GPIO pattern, if the ROM had one (a proposal), would separate them; otherwise repeat the run to tell the intermittent bug 5 from the rest

    Chip-select falls and never rises

    SPI load

    Flash not responding, SPI wiring or clock divisor, wrong image size

    SPI clock and data lines against the 6 Mb/s timeline

    Chip-select rises, then silence from the image

    Checksum or jump

    Wrong image, corrupted load, image does not print

    Compare the stored checksum; image's own first output

    The banner appears again at random, long after boot

    Reset, not a boot stage

    Pad bug P3: RESET_N without a pull-up, so noise holds it low for more than 666.7 ns (RST_CAUSE bit 1 set, SRAM0 cleared)

    RESET_N on the scope; add the board pull-up and capacitor

    UART framing errors only when software drives all 16 GPIO pads

    Pads and supply, not a boot stage

    Pad bug P2: both vdd_io pairs placed side by side, 13 outputs on one pair against a limit of 8

    Scope vdd_io while GPIO DATA_OUT toggles 0xFFFF; check the pair per half of the ring

    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
    # Pin-observation triage: the stage each planted bug stops the boot at comes from
    # running it in simulation (the table of stages below); the pin pattern comes from the
    # stage, and suspects are derived from both. NanoSoC teaching values; codes are ILLUSTRATIVE.
    # Pin pattern after each stage: (banner, cs_fell, cs_rose, image_output).
    PATTERN = {"before-uart": (0, 0, 0, 0), "dma-self-test": (1, 0, 0, 0),
               "spi-load": (1, 1, 0, 0), "checksum-or-jump": (1, 1, 1, 0), "booted": (1, 1, 1, 1)}
    STAGE_OF = {2: "before-uart", 1: "dma-self-test", 3: "dma-self-test", 4: "dma-self-test",
                5: "dma-self-test", 7: "dma-self-test", 8: "dma-self-test"} # 5: intermittent
    def triage(banner, cs_fell, cs_rose, image_output):
        stage = next((s for s, p in PATTERN.items() if p == (banner, cs_fell, cs_rose, image_output)), None)
        return stage, {b for b, st in STAGE_OF.items() if st == stage}
    for stage, pat in PATTERN.items():
        assert triage(*pat)[0] == stage
    assert triage(0, 0, 0, 0)[1] == {2}
    assert triage(1, 0, 0, 0)[1] == {1, 3, 4, 5, 7, 8} # six suspects behind one pin pattern
    # separability is computed, not asserted: group the bugs by what the pins (and codes) show
    def groups(key):
        out = {}
        for b in STAGE_OF:
            out.setdefault(key(b), set()).add(b)
        return sorted(out.values(), key=lambda g: (-len(g), sorted(g)))
    pins_only = groups(lambda b: PATTERN[STAGE_OF[b]])
    assert pins_only[0] == {1, 3, 4, 5, 7, 8} # not separable by pins alone
    # a code per self-test step (what the bug was waiting for); bug 5 waits for the interrupt, like 3 and 7
    STEP_OF = {4: "reg-values", 3: "irq-wait", 5: "irq-wait", 7: "irq-wait", 1: "readback", 8: "count-check"}
    by_step = groups(lambda b: (PATTERN[STAGE_OF[b]], STEP_OF.get(b)))
    assert by_step[0] == {3, 5, 7} and len(by_step) == 5 # splits to one group of three
    # plus the DMA STATUS bits at the timeout: DONE for 3 and 5, BUSY with COUNT 0 for 7
    STATUS_OF = {4: "pslverr", 3: "done", 5: "done", 7: "busy", 1: "done", 8: "done"}
    by_status = groups(lambda b: (PATTERN[STAGE_OF[b]], STEP_OF.get(b), STATUS_OF.get(b)))
    assert {3, 5} in by_status and all(len(g) <= 2 for g in by_status) # 3 and 5 only differ by repeating the run
    # the price of the hook: a UART character is 10 bits at 115,384.6 baud, a GPIO write one clk_per access
    char_us = 10 / 115384.6 * 1e6
    steps = 5
    uart_cost_us = steps * char_us
    gpio_cost_ns = (1 + steps) * 281.25 # GPIO.DIR resets to 0, so one DIR write first
    assert abs(char_us - 86.67) < 0.01 and abs(uart_cost_us - 433.3) < 0.1 and abs(gpio_cost_ns - 1687.5) < 1e-9
    print(f"six suspects behind one pin pattern; a step code leaves {len(by_step)} groups; UART code {uart_cost_us:.0f} us, GPIO code {gpio_cost_ns / 1000:.2f} us")
    print("PASS: pin patterns map to stages; one pattern hides six suspects until a code is added, and the code has a price")

    What the review looks for

    • A written inventory of every observable (pins and registers) with the instrument that reads it, and a statement of what cannot be seen at all.

    • A defined failure behaviour for every boot stage: what the ROM prints or drives on the pins, and where it stops, so no failed stage looks like a hang.

    • For each gap, an owner and a decision: close it in the ROM content before tape-out, add hardware in a later version, or accept it with the consequence written down.

    Verify and debug

    Prove the observability in simulation, where everything is visible: run each planted bug in the boot test and write down what the pins show for it, with the pad pulls modelled, since the RTL has none. If two bugs give the same pin pattern, the readiness review has found a place where silicon will be harder to debug than simulation, and a cheap hook (a GPIO pattern per step, 281.25 ns a write, rather than a printed character at 86.7 us) is worth adding to the ROM now, because the ROM cannot be changed afterwards. For the debug method itself, follow the Hardware Debug links.

    No way in when the CPU hangs: the self-test waits for an interrupt that never arrives (planted bug 3, the DMA interrupt on the spare input), the banner is printed and then nothing, and polling the DMA STATUS would have worked. Six suspects share one pin pattern. A code per self-test step would split the list, but it is not free: one printed character is 86.7 us, so five steps add 433 us, while a GPIO status write is 281.25 ns (after setting GPIO.DIR, which resets to input). Either has to be in the ROM before tape-out, because after it the ROM cannot be changed.

    If asked what you do when first silicon hangs and there is no debug port, answer with the pins and the timeline: which event is missing names the stage, the stage names the short suspect list, and what is printed per step splits the list. Then say that this is why the ROM's failure path is designed before tape-out.

    Check yourself The banner prints and the chip then goes quiet for good. Name two planted bugs that fit and one test that separates them.

    The interrupt on spare input 6 (bug 3) and the SBR's non-secure attribute against secure-only SRAM1 (bug 7) both leave the self-test waiting. In simulation the bench can read STATUS and separate them: with bug 7 the DMA stays BUSY with COUNT 0 and SBR STATUS.ERR is set; with bug 3 the DMA reads DONE = 1 and only the interrupt never reaches the controller. On silicon only a ROM variant that drives that status onto the GPIO pads would do the same, which is a proposal, not v1.

    Related topics

    • The Boot Timeline the Lab Will Expect

    • Next: Test Plan, Lab Setup and the Readiness Checklist

    • IP Integration: Debug Infrastructure

    • Package and Pads: Test and Debug Pins

    • Hardware Debug: The First Failing Test on Silicon

    • Hardware Debug: The Chip Hangs, What Do You Do?

    • Hardware Debug: Observability by Design

    • Bring-up: Debug Interfaces and Observability

    Previous

    215 / 289

    Next: Test Plan, Lab Setup and the Readiness Checklist

    Up next

    Up nextTest Plan, Lab Setup and the Readiness Checklist

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error