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.
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.
// 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# 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")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.
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.
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.