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
    • Topologies and Bridges
    • AXI Integration Details
    • Ordering and Deadlock at the Top
    • QoS and Arbitration
    • NoC Basics
    • Coherent Interconnect View
    • Firewalls and Protection
    • Bandwidth and Latency Verification
      • The Budget Table as Arithmetic
      • Checking Latency Link by Link
      • Performance Counters and What a Miss Looks Like
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Bandwidth and Latency Verification
  1. Topics
  2. SoC Design
  3. Interconnect and Bus Fabrics
  4. Bandwidth and Latency Verification
  5. Checking Latency Link by Link
On this page

    Part 4 of 14, lesson 25 of 26Part 4 of 14/Interconnect and Bus Fabrics/Lesson 25 of 26All levels

    Checking Latency Link by Link

    Converting every access into cycles per domain: the NanoSoC latencies recomputed, plus the DMA job formula and the boot sum.

    Why it matters

    The boot flow's 12.581 ms total is a sum of sixteen steps, and each step is a latency from this table times a count. But not every step depends on every latency. If the 281.25 ns peripheral access were really 300 ns, only the four register accesses (one in the reset-cause read of step 9, three in the UART initialisation of step 11) would slip, by 75 ns in total: the banner and the SPI load are limited by their line rates. What does depend on the table is the checksum of step 15 (2048 single reads at 93.75 ns) and the DMA self-test of step 13. Link-by-link recomputation keeps the boot sum honest, and it is also the fastest way to find a slow path: convert each stage to nanoseconds in its own domain and the longest bar names itself.

    The core idea

    Every access is a sum of stage costs, each in the cycles of its own domain: issue cycles on the CPU side, crossing cycles on each bridge, fabric address and data cycles, slave latency, and response crossing back. One access in flight at a time keeps the arithmetic blocking and exact. The DMA job adds pipeline fill and drain: N beats plus 8 clocks for read address, SRAM latency, the output register and the write path, so a 64-word job takes 72 system clocks, 750 ns. The interrupt path is two synchronisers plus the controller register, 135.4 ns best and 182.3 worst with alignment.

    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
    # NanoSoC teaching values: every NanoSoC latency recomputed, then the boot sum.
    T_CPU = 1 / 192e6 * 1e9 # 5.2083 ns
    T_SYS = 1 / 96e6 * 1e9 # 10.4167 ns
    T_PER = 1 / 24e6 * 1e9 # 41.6667 ns
    
    cpu_sram = (3 + 1 + 2 + 1) * T_SYS + (1 + 3) * T_CPU # 93.75
    cpu_line = cpu_sram + 3 * 2 * T_CPU # 125.0, 3 more beats at 1 T_SYS each
    cpu_fast = 10 * T_SYS + 4 * T_CPU # 125.0
    cpu_slow = 9 * T_SYS + 4 * T_PER + 4 * T_CPU # 281.25
    cdc_bridge = 2 * T_PER + 2 * T_PER + 2 * T_SYS + 2 * T_SYS # 208.33
    dma_64 = (64 + 8) * T_SYS # 750.0 ns
    irq_best = 2 * T_PER + 1 * T_PER + 2 * T_CPU # 135.4
    irq_worst = irq_best + T_PER + T_CPU # 182.3
    
    for name, got, want in [("sram", cpu_sram, 93.75), ("line", cpu_line, 125.0),
        ("fast", cpu_fast, 125.0), ("slow", cpu_slow, 281.25),
        ("cdc", cdc_bridge, 208.33), ("dma64", dma_64, 750.0),
        ("irq_best", irq_best, 135.4), ("irq_worst", irq_worst, 182.3)]:
        assert abs(got - want) < 0.05, (name, got)
    
    def boot_us(per_access_ns=cpu_slow, sram_word_ns=cpu_sram):
        # the sixteen boot steps grouped; time in microseconds
        hardware = (1024 + 2 + 260 + 2) * T_PER + 18 * T_SYS + (8 * T_SYS + 2 * T_CPU) # steps 1 to 7
        steps = [
            hardware,
            24 * T_CPU, # 8: first fetch, a 4-word line fill
            per_access_ns, # 9: read RST_CAUSE, one clk_per access
            1024 * T_SYS, # 10: clear 4 KB of SRAM0, posted writes at 1 per clk_sys
            3 * per_access_ns, # 11: UART init, three clk_per accesses
            160 / (24e6 / (16 * 13)) * 1e9, # 12: banner at the UART line rate
            5 * cpu_fast + 7 * cpu_fast + dma_64 + irq_best + 64 * cpu_sram, # 13: DMA self-test
            8196 * 8 / 6e6 * 1e9, # 14: SPI load at 6 Mb/s
            2048 * sram_word_ns, # 15: checksum, single reads
        ]
        return sum(steps) / 1e3
    
    total = boot_us()
    assert abs(total - 12580.917) < 0.01 # 12.581 ms
    # a 300 ns peripheral access moves only the four register accesses: 4 x 18.75 ns
    assert abs((boot_us(per_access_ns=300.0) - total) * 1e3 - 75.0) < 0.01
    # a 100 ns SRAM word moves the checksum (and the self-test read-back is a fixed row here)
    assert abs(boot_us(sram_word_ns=100.0) - total - 12.8) < 0.01
    print("PASS: 8 latencies recomputed, boot sum 12,580.917 us, a 300 ns register access slips boot by 75 ns, a 100 ns SRAM word by 12.8 us")

    What the review looks for

    • Each latency row names every stage with its domain and cycle count, so a clock change invalidates exactly the rows it touches.

    • The DMA job formula states the 8-cycle fill and drain, not just N beats.

    • Interrupt latency carries best and worst, with the alignment term explicit; boot-time budgets say which steps depend on which latency.

    Verify and debug

    Timestamp one access per row in simulation with a single transaction in flight and compare against the script: the numbers must match to the cycle. If the slow-register read exceeds 281.25, count the cdc handshake cycles first; one possible cause of an extra peripheral cycle is an access that crossed a synchroniser twice. Then rebuild the boot sum from the checked rows: step 14, the SPI load at 10.928 ms, should still dominate at 86.9 percent.

    Forgetting the cold I-cache on the first fetch: the boot model charges a 4-beat line fill of 125 ns for the reset vector, not a single-word read. Budgets built from steady-state hit rates undercount boot by exactly the fills the cold cache must perform.

    If asked for an end-to-end latency, add stages aloud with units: cycles in each domain converted to nanoseconds, plus two to three destination cycles per crossing. The NanoSoC slow-register answer, 9 slow plus 4 peripheral plus 4 fast, is the template.

    Check yourself A 256-word DMA job is timed at 2.75 us. Does that match the model?

    Yes: 256 plus 8 is 264 system clocks, times 10.4167 ns gives 2.75 us exactly. A longer time means backpressure on m_tready or bank contention, not a slower clock.

    Related topics

    • The Budget Table as Arithmetic

    • Next: Performance Counters and What a Miss Looks Like

    • Protocol First Principles: Little's Law

    Previous

    95 / 289

    Next: Performance Counters and What a Miss Looks Like

    Up next

    Up nextPerformance Counters and What a Miss Looks Like

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error