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
      • Checking Bandwidth Against Budgets
      • Checking Latency Against Budgets
      • Counters, Monitors and Reading a Miss
    • Gate-Level and Power-Aware Simulation
    • Emulation and FPGA Prototyping
    • Bring-Up Readiness
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Performance Verification
  1. Topics
  2. SoC Design
  3. Top-Level Verification and Bring-up
  4. Performance Verification
  5. Checking Latency Against Budgets
On this page

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

    Checking Latency Against Budgets

    Measuring the blocking latencies and the interrupt path against the latency table, stage by stage.

    Why it matters

    Bandwidth decides whether the chip keeps up; latency decides whether software meets its deadlines. The UART holds one byte, and at 115,384.6 baud a new byte arrives every 86.7 us (10 bits at 115,384.6 baud), so a handler that starts 182.3 ns after the interrupt rises has about 475 times that margin to spare: hardware interrupt latency is not what loses a byte, software latency is. By contrast, a line fill is 24 clk_cpu cycles for four 4-byte instructions, so straight-line code pays 6 clk_cpu cycles per instruction on average before anything is wrong. Its one job is the latency method: the blocking measurement discipline, the stage table it checks, and the interrupt path with its best and worst cases.

    The core idea

    Latency is measured blocking — one access in flight, nothing queued — because that is what the latency table quotes and what the boot flow assumes. The CPU single-word read from SRAM costs 18 CPU cycles, 93.75 ns: issue, three system clocks of crossing, fabric address, two memory cycles, fabric data, three CPU cycles back. The 4-beat line fill adds three beats at one per system clock, 24 CPU cycles or 125 ns. A fast-APB register read costs 24 CPU cycles or 125 ns; a read of a clk_per peripheral register costs 9 system clocks plus 4 peripheral clocks plus 4 CPU cycles, 281.25 ns. The script below recomputes each from its stage list so the measurement has an expected value before the run.

    The interrupt path gets the same treatment with a range, not a number: 135.4 ns best case, 182.3 worst, from the two-flop synchroniser into the slow domain, the controller register, and the two-flop synchroniser into the CPU, plus alignment on each end. The test forces irq_dma and timestamps irq_dma rising and irq_cpu arriving at the core input across many trials, with the phase between the two clocks swept in fixed steps; the spread must sit inside the 26-to-35 CPU-cycle window. The handler entry is later still, because it adds the core's own pipeline and vector time, so it is a software-latency number and not part of this table; the DMA STATUS write is the clear, not the start. A pulse shorter than two peripheral clocks, 83.3 ns, may be missed entirely — that is not a miss, that is the synchroniser contract, and the DMA level interrupt holds until cleared precisely so it never depends on a pulse.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # Latency budget check, NanoSoC teaching values: sum the stage delays in a common
    # unit, bound the interrupt spread, and show that a stage fault is caught.
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    sram_read = 7 * T_SYS + 4 * T_CPU # 7 system clocks plus issue and response on the CPU clock
    assert abs(sram_read - 93.75) < 0.01, sram_read
    line_fill = sram_read + 3 * T_SYS # three more beats, one system clock each
    assert abs(line_fill - 125.0) < 0.01, line_fill
    per_read = 9 * T_SYS + 4 * T_PER + 4 * T_CPU # clk_per APB segment register
    assert abs(per_read - 281.25) < 0.01, per_read
    best = 2 * T_PER + 1 * T_PER + 2 * T_CPU # sync, controller, sync out
    worst = best + T_PER + T_CPU # one cycle of alignment per crossing
    assert abs(best - 135.4) < 0.1 and abs(worst - 182.3) < 0.1
    # a fill with one extra system clock per later beat measures 30 CPU cycles, not 24
    assert round((line_fill + 3 * T_SYS) / T_CPU) == 30 and round(line_fill / T_CPU) == 24
    # the measured spread over a phase sweep must sit in the window; an outlier is a failure
    def in_window(ns):
        return best - 0.1 <= ns <= worst + 0.1
    sweep = [best + k * (worst - best) / 4 for k in range(5)]
    assert all(in_window(x) for x in sweep) and not in_window(190.0) and not in_window(120.0)
    print(f"sram {sram_read:.2f} ns, fill {line_fill:.1f} ns, per {per_read:.2f} ns")
    print(f"irq {best:.1f}..{worst:.1f} ns = {best / T_CPU:.0f}..{worst / T_CPU:.0f} cpu")
    print("PASS: blocking latencies and the 26..35-cycle irq window hold")

    What the review looks for

    • Every latency test states blocking or queued before quoting a number; queued results never compare against this table.

    • The interrupt test timestamps irq_dma and irq_cpu, not the handler entry, and reports the spread over phase sweeps, not a single timestamp.

    • The pulse-width contract — levels held two slow clocks minimum — is asserted wherever a source could be short.

    Verify and debug

    A latency overrun is localised by stage: timestamp the crossing entry and exit, the fabric address and data, and the response. An extra slow clock at the crossing means the FIFO was full — the test was not blocking and the result belongs to the queueing analysis, not this table. An interrupt that never arrives after a force is wiring; one that arrives outside the window is a synchroniser or phase assumption to revisit.

    Lost interrupt across clock domains: a one-cycle pulse from a faster domain that the 24 MHz controller never sees. NanoSoC answers it structurally — level interrupts held until cleared, synchronised at the controller — and the phase-sweep test proves the window rather than hoping the pulse lands.

    If asked for the interrupt latency, answer the range with the stages: 135.4 best, 182.3 worst, two synchronisers plus the controller register plus alignment. A single number is the answer that fails.

    Check yourself The line fill measures 30 CPU cycles instead of 24. Which stage is the first suspect?

    The beats after the first: 30 minus 24 is 6 CPU cycles or 3 system clocks, exactly one extra system-clock bubble on each of the three later beats. Check arbitration contention or a bridge inserting a wait — the measurement stopped being blocking.

    Related topics

    • Checking Bandwidth Against Budgets

    • Next: Counters, Monitors and Reading a Miss

    • Interrupts and DMA: Interrupt Routing and Latency

    • Bus Fabrics: Checking Latency Link by Link

    • UVM: Layered Virtual Sequence

    Previous

    203 / 289

    Next: Counters, Monitors and Reading a Miss

    Up next

    Up nextCounters, Monitors and Reading a Miss

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error