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 Bandwidth Against Budgets
On this page

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

    Checking Bandwidth Against Budgets

    Proving each fabric link's sustained rate with traffic generators: peak, efficiency, banking and arbitration.

    Why it matters

    The DMA datasheet rate of one word per clock is true at its stream port and false everywhere else: the source SRAM reads at 361.4 MB/s sustained, both streams share one port when they land in one bank, and the APB segment moves 76.8 MB/s with its bridge and wait states. A test that only checks the stream port certifies a number the system can never use. Its one job is the bandwidth method: which rate is proved on which link, with what traffic, and which efficiency term explains each gap between peak and sustained.

    The core idea

    Every link has three numbers: peak from width times clock, sustained with the burst efficiency, and measured under traffic. Peak is 4 bytes times 96 MHz, 384.0 MB/s, on every 32-bit fabric link that runs at 96 MHz (the CPU side of the clock-domain bridge runs at 192 MHz and peaks at 768 MB/s). Sustained multiplies by the efficiency for the burst length: a 16-beat burst with one bubble cycle per burst runs at 16/17, giving 361.4 MB/s. The traffic generator proves it by issuing back-to-back 16-beat bursts from the SBR and counting bytes over a fixed window, first with source and destination in different banks, then in the same bank. The different-bank run needs the SBR to be secure (CTRL.NS = 0), because SRAM1 is secure-only and a non-secure SBR gets SLVERR at the first beat (planted bug 7). Different banks must show the full 361.4 each way; the same bank must show 180.7 each, the halving that the bandwidth table prices and the standing trap of putting a stream's source and destination in one bank.

    Three more links complete the picture. The APB path to the DMA and SBR proves 76.8 MB/s sustained — five system clocks per 4-byte access with the bridge and one wait state — while the slow segment behind the clock-domain bridge proves 19.2 MB/s. The UART line proves 0.0115 MB/s at 115,384.6 baud, which matters because the banner alone consumes 11 percent of boot. Arbitration gets two tests. With the CPU line-filling from the ROM while the SBR streams SRAM0 to SRAM1, the masters share no slave and each keeps its rate. With the CPU line-filling from SRAM0 while the SBR reads SRAM0, they share one port: the two rates must sum to no more than the 361.4 MB/s of that port (the figure that assumes one bubble per 16-beat burst), and the round-robin change-of-master bubble of one idle cycle is the only loss permitted beyond that sharing. For the alternating pattern of a 4-beat CPU fill and a 16-beat SBR burst there are two bubbles per 20 beats, so the bound is 384 x 20/22 = 349.1 MB/s (20 beats in 22 cycles), not 361.4.

    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
    # Bandwidth check: a single memory port serves bursts to the streams that want it,
    # round robin, one idle cycle per grant (the NanoSoC arbitration rule). The halving
    # of the same-bank run is MEASURED from the grants, not typed in. NanoSoC teaching values.
    W, F_MHZ = 4, 96
    def run(streams, ports, bubbles=1, cycles=7480):
        """streams: name -> burst length (beats); ports: list of lists of stream names.
        Returns MB/s per stream over the window, simulated cycle by cycle."""
        beats = {name: 0 for name in streams}
        for names in ports: # each port is independent
            grant, left, idle, nxt = None, 0, 0, 0
            for _ in range(cycles):
                if idle:
                    idle -= 1
                elif grant is None: # arbitrate: next stream in round-robin order
                    grant, left = names[nxt % len(names)], streams[names[nxt % len(names)]]
                    nxt += 1
                if grant is not None and not idle:
                    beats[grant] += 1
                    left -= 1
                    if left == 0:
                        grant, idle = None, bubbles
        return {n: b * W / (cycles / F_MHZ) for n, b in beats.items()}
    peak = W * F_MHZ
    budget = peak * 16 / 17
    assert abs(peak - 384.0) < 0.01 and abs(budget - 361.4) < 0.1
    # SBR read and write streams, each its own 16-beat bursts
    split = run({"rd": 16, "wr": 16}, [["rd"], ["wr"]]) # source and destination in different banks
    shared = run({"rd": 16, "wr": 16}, [["rd", "wr"]]) # same bank: one port serves both
    assert all(abs(v - budget) < 0.5 for v in split.values()), split
    assert all(abs(v - budget / 2) < 0.5 for v in shared.values()), shared # 180.7 each
    # arbitration: a 4-beat CPU fill alternating with a 16-beat SBR burst on one port
    mixed = run({"cpu": 4, "sbr": 16}, [["cpu", "sbr"]])
    total = sum(mixed.values())
    assert abs(total - peak * 20 / 22) < 0.5 and abs(total - 349.1) < 0.2, total
    assert total < budget # below the 361.4 single-stream figure
    # the check can fail: a second bubble per grant (an arbitration fault) is caught
    slow = run({"rd": 16, "wr": 16}, [["rd"], ["wr"]], bubbles=2)
    assert all(v < budget - 10 for v in slow.values())
    apb_fast = W * F_MHZ / 5 # bridge 2 clocks plus 3 APB clocks per access
    uart_mbs = 115384.6 / 10 / 1e6 # 10 line bits per byte
    assert abs(apb_fast - 76.8) < 0.1 and abs(uart_mbs - 0.01154) < 0.0001
    print(f"peak {peak:.1f}, different banks {split['rd']:.1f}, same bank {shared['rd']:.1f}, cpu+sbr {total:.1f}")
    print("PASS: fabric 361.4, same bank 180.7 each (measured), 349.1 for 4+16, two-bubble fault caught, APB 76.8 MB/s")

    What the review looks for

    • Each link names its traffic shape — burst length, bank placement, competing master — not just its number.

    • The same-bank run is a required line, with the 180.7 MB/s expectation written before the run.

    • Arbitration is proved under cross-traffic: the bound is 361.4 MB/s at most for one stream, and about 349 MB/s for the alternating 4-beat and 16-beat pattern, with the one-cycle bubble per grant as the only loss.

    Verify and debug

    A link that misses its sustained rate is debugged by efficiency term: count the bubble cycles per burst first — arbitration, address turnaround, bridge accept — then check bank placement, then burst length. A short burst at length 4 runs at 80 percent before anything is wrong; the table says so. If the rate varies run to run, the traffic is not back-to-back and the generator, not the fabric, is the suspect.

    Stack and stream in the same bank: the SBR reads and writes inside SRAM0, the single-port macro serves reads and writes together, and each stream falls from 361.4 to 180.7 MB/s with no error anywhere. Block tests cannot see it and a boot test still passes. The same-bank run with its expectation written first is the standing guard; the fix is placement (source and destination in different banks, with the SBR made secure for SRAM1), never a faster block.

    If asked to prove 361.4 MB/s, answer peak 384.0 from 4 bytes at 96 MHz times 16/17 for the bubble, then describe the generator: back-to-back 16-beat bursts, bytes counted over a window, split banks first.

    Check yourself An SBR copy from SRAM0 to SRAM0 measures 180.7 MB/s in each direction instead of 361.4. What is the bottleneck?

    The single SRAM port: reads and writes share one access per clock, so the two streams split the 361.4 MB/s of the port. Move the destination to SRAM1, which is secure-only, so the SBR must then be secure (CTRL.NS = 0).

    Related topics

    • Next: Checking Latency Against Budgets

    • Bus Fabrics: The Budget Table as Arithmetic

    • UVM: Env Wiring, From Block Scoreboard to End-to-End Checker

    • Verification First Principles: Performance and Power Verification

    Previous

    202 / 289

    Next: Checking Latency Against Budgets

    Up next

    Up nextChecking Latency Against Budgets

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Counters, Monitors and Reading a Miss