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. Counters, Monitors and Reading a Miss
On this page

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

    Counters, Monitors and Reading a Miss

    Placing fabric monitors, sizing queues with Little's law, and tracing a missed budget to its bottleneck.

    Why it matters

    A budget that misses without a monitor is an argument; with a monitor it is a measurement. NanoSoC has no performance-counter block in its block list, so this lesson shows where the verification bench puts its own (bench-side monitors, not chip modules): byte counts per port, bubble counts per burst, queue occupancy at the crossing. Its one job is the monitor plan plus the miss analysis: what to count, how deep the queues must be, and how a miss reads back to arbitration, banking or bursts.

    The core idea

    Three monitors cover the fabric. A byte counter per slave port proves the sustained rates of the first lesson: run the generator for a fixed window and divide. A bubble counter per master — idle cycles between bursts — separates arbitration effects from slave effects: bubbles that grow only under cross-traffic belong to the round-robin, bubbles present in isolation belong to the master. A queue-occupancy sampler at the CPU crossing proves the 4-deep FIFO never limits throughput: a FIFO sustains one beat per clock only if its depth covers the credit loop, the time from a pop to the writer seeing the freed slot, and Little's law turns that loop into a number of entries. Only the CPU uses that FIFO (the SBR drives the fabric directly and never crosses the clock-domain bridge), so the rate to use is the CPU's demand, not the 361.4 MB/s SBR link: during a line fill the response beats arrive at most one per clk_sys, and averaged over a whole blocking fill, four beats in 12 clk_sys.

    Reading a miss follows the monitors in order. Bytes low with bubbles flat means the generator under-drove: lengthen the bursts or close the gaps before suspecting the fabric. Bubbles up only under cross-traffic means arbitration or a shared bank: check bank placement first, because moving a buffer is cheaper than retuning QoS. Bubbles up in isolation means the master or its converter: short bursts, 4 KB splits, or bridge waits. The contention case — a latency-critical master slowed by bulk traffic at a shared slave — has an unmistakable signature: the bulk bytes hold while the critical master's bubbles grow.

    perf_mon.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // Bench-side fabric byte-and-bubble monitor (NanoSoC has no such block). Counts transfer
    // beats and cycles without an accepted beat (idle or stalled) per window; the
    // bench divides for the rate analysis.
    module perf_mon ( // bench-side monitor, not a NanoSoC block
      input  logic        clk,
      input  logic        rst_n,
      input  logic        valid,    // beat present this cycle
      input  logic        ready,    // beat accepted this cycle
      input  logic        window,   // measurement window, held high by the bench
      output logic [31:0] beats,    // accepted beats in the window
      output logic [31:0] bubbles   // cycles in the window with no accepted beat
    );
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          beats   <= 32'd0;
          bubbles <= 32'd0;
        end else if (window) begin
          if (valid && ready)
            beats <= beats + 32'd1;
          else
            bubbles <= bubbles + 32'd1;
        end
      end
    endmodule
    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # Queue sizing by Little's law: entries needed = rate x loop delay. The FIFO
    # depth (4) is from the bridge description; the credit-loop delay is ILLUSTRATIVE and is
    # replaced by the delay measured in the bench. The rate is the CPU's own demand.
    DEPTH = 4
    def entries_needed(rate_beats_per_sys_clk, loop_sys_clks):
        return rate_beats_per_sys_clk * loop_sys_clks
    LOOP = 3.0 # illustrative: pointer sync into the other domain plus the way back
    PEAK = 1.0 # a fill burst: later beats arrive one per clk_sys
    FILL_AVG = 4 / 12 # four beats over a whole blocking fill of 24 clk_cpu = 12 clk_sys
    SINGLE = 1 / 9 # blocking single reads: one per 18 clk_cpu = 9 clk_sys (42.67 MB/s)
    assert entries_needed(PEAK, LOOP) == 3.0 <= DEPTH # peak burst: 3 of 4 entries
    assert abs(entries_needed(FILL_AVG, LOOP) - 1.0) < 1e-9 # on average one entry
    # the loop delay at which the FIFO would start to limit each demand: depth / rate
    assert round(DEPTH / PEAK) == 4 and round(DEPTH / FILL_AVG) == 12 and round(DEPTH / SINGLE) == 36
    # the check can fail: a slower credit loop (illustrative 5 clocks) overruns 4 entries at the burst peak
    assert entries_needed(PEAK, 5.0) > DEPTH
    limited = min(1.0, DEPTH / 5.0)
    assert abs(limited - 0.8) < 1e-9 # the burst would run at 80 percent of its 384 MB/s
    print(f"burst needs {entries_needed(PEAK, LOOP):.0f} of {DEPTH} entries, fill average {entries_needed(FILL_AVG, LOOP):.0f}; a 5-clock loop would cap a burst at {limited:.0%}")
    print("PASS: 4-deep crossing FIFO holds for the CPU at an illustrative 3-clock loop; an overlong loop is caught")

    What the review looks for

    • Each budget line names its monitor: byte counter, bubble counter or occupancy sampler, with the window defined.

    • Queue depths quote the Little's-law sizing with the demand of the master that uses the queue, not a copied number and not another link's rate.

    • The miss flowchart — generator, arbitration and banks, master — is written down before the first miss arrives.

    Verify and debug

    Calibrate the monitors against a known-good run before trusting a miss: the byte counter must read exactly the injected bytes, and the bubble counter must read the programmed gaps. A monitor that disagrees with its stimulus is a bench bug, and bench bugs outnumber fabric bugs early in the project. Keep the monitor reset tied to the window so partial windows never dilute a rate.

    Bandwidth passes, latency does not: the SBR streams SRAM0 to SRAM1 at 361.4 MB/s, and the CPU line fills from SRAM0 wait behind SBR bursts. If grants are per burst (an assumption to confirm in the arbiter), a fill that arrives just after an SBR burst starts waits one 16-beat burst plus the idle cycle, 17 system clocks or 177 ns, on top of its 125 ns: a worst case of about 302 ns (the average wait is about half). It needs the SBR source and the CPU fill in the same bank, SRAM0; the SBR write stream to SRAM1 adds nothing. Every bandwidth test is green. The bubble counter on the CPU master under cross-traffic is the detector, and the fix is placement (CPU code and the SBR source in different banks) or shorter SBR bursts, proved by rerunning the cross-traffic test.

    If asked to size a queue on the spot, answer Little's law first — entries needed equal the rate times the loop delay — then compare with the depth. Quote the NanoSoC result with its label: with an illustrative three-clock loop the CPU's fill burst needs three entries against a 4-deep FIFO (about one on average), so one entry of margin; the number moves with the measured loop.

    Check yourself Bytes are low and bubbles are flat in isolation but grow under cross-traffic. Where is the bottleneck?

    Shared resource between masters: the round-robin or a shared SRAM bank. Flat in isolation clears the master; growth only under contention convicts the arbitration or the banking.

    Related topics

    • Checking Latency Against Budgets

    • Bus Fabrics: Performance Counters and What a Miss Looks Like

    • Verification First Principles: Performance and Power Verification

    • Verification First Principles: Reuse up the Hierarchy

    Previous

    204 / 289

    Next: Gate-Level and Power-Aware Simulation

    Up next

    Up nextGate-Level and Power-Aware Simulation

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Checking Bandwidth Against Budgets