02 · Performance architecture
Performance Verification Framework
Measure transaction lifecycle, latency distributions, tail behavior, bandwidth, stalls, and effective throughput without turning the simulator log into the bottleneck.
Lifecycle timestamps feed an in-memory histogram, a sliding bandwidth window, and separate average, p99, and maximum results.
Why it matters
- Functional correctness is only the baseline. A fabric must also meet throughput, stall, latency, and gigabyte-per-second architecture targets.
- Average latency alone can hide the 99th-percentile tail where architectural bottlenecks appear.
- Timestamped transactions and a reusable subscriber turn performance into a measurable verification result rather than an informal waveform inspection.
What is difficult
- Tracking every cycle of every transaction across 100 masters can produce enough data to slow simulation by 5x to 10x.
- Recording only 1 out of 100 transactions reduces overhead but can miss rare tail-latency spikes.
- Keeping all statistics in memory avoids log bloat, but a simulation crash can lose data that was never persisted.
- Bandwidth, backpressure response, skid depth, and effective throughput require explicit windows and boundary definitions.
Failure signatures
- The design passes functionally but misses sustained bandwidth or latency targets.
- Statistical sampling misses a rare 99th-percentile stall.
- Per-transaction logging dominates runtime and distorts the workload being measured.
- In-memory histograms disappear when simulation terminates unexpectedly.
- READY/VALID backpressure leaves extra data in flight beyond the intended skid depth.
- Protocol headers and idle gaps consume cycles, reducing useful effective throughput.
Two viable approaches—and their cost
Statistical sampling monitor
Record timing for 1 out of every 100 transactions.
- Very low collection overhead.
- Can estimate average latency when sampling is unbiased and sufficiently large.
- Can miss corner-case and 99th-percentile latency spikes.
- Sampling policy can bias results during bursty or priority-dependent traffic.
In-memory analysis subscriber
Process every transaction in memory and emit only a final summary table or machine-readable report.
- Maintains full transaction-count accuracy.
- Avoids per-event log-file bloat.
- Unflushed data is lost if simulation crashes.
- Histogram state can still consume substantial memory in a long, high-cardinality run.
Interview answer, built from the mechanism
- I build a performance framework that records start, accept, and done timestamps in each monitored transaction.
- A UVM subscriber computes latency and aggregates it into in-memory histograms rather than printing every event. That preserves simulation throughput while retaining the distribution.
- I report average latency and the 99th-percentile tail, then export CSV for comparison with the architectural golden model.
- I also define a sliding bandwidth window, separate peak from sustained bandwidth, measure backpressure/skid response, and report effective throughput as useful data beats divided by total clock cycles.
Component responsibility contract
| Component | Responsibility | Required change |
|---|---|---|
| Transaction | Timestamps | Add realtime fields for start, accept, and done. |
| Subscriber | Stats engine | Use an associative array to bin latencies into a histogram. |
| Final Report | CSV/SQL output | Dump data in a format that Python or Excel can parse. |
Implementation patterns
class perf_analyzer extends uvm_subscriber #(trans);
int latency_bins[int]; // 0-10ns, 10-20ns, ...
virtual function void write(trans t);
realtime lat = t.done_time - t.start_time;
int bin = lat / 10ns;
latency_bins[bin]++;
endfunction
virtual function void report_phase(uvm_phase phase);
foreach (latency_bins[i])
`uvm_info(
"PERF",
$sformatf(
"Bin %0dns-%0dns: %0d hits",
i * 10,
(i + 1) * 10,
latency_bins[i]
),
UVM_LOW
)
endfunction
endclassEach transaction contributes done_time minus start_time to one 10 ns bucket. The report prints every occupied interval and its hit count.
Source rendering:
BW=Window_TimeBits_Transferred
Intended dimensional form:
bandwidth = bits_transferred / window_time
effective_throughput =
useful_data_beats / total_clock_cycles
backpressure observation =
cycles from READY low to the last VALID beatThe source formula is preserved verbatim, then qualified with the dimensionally correct bandwidth relationship. Peak and sustained results must use named window boundaries.
Stress recipe
- Drive representative congestion across 100 masters and timestamp start, accept, and done for every observed packet.
- Run a full-capture subscriber and a 1-in-100 sampler on the same seeded workload.
- Compare average and 99th-percentile latency to quantify sampling error.
- Bin full-capture latency in 10 ns increments and export a final CSV or SQL-ready report.
- Measure bits transferred in named sliding windows and report peak and sustained bandwidth.
- Deassert READY under load, count cycles to the last VALID beat, and compare with the allowed skid depth.
- Calculate useful data beats per total clock cycle and attribute lost efficiency to headers, stalls, and idle gaps.
Follow-up questions
How do you measure bandwidth over time?
Use a sliding window, count transferred bits inside it, and divide by the window duration. Track peak separately from sustained bandwidth.
How do you verify backpressure efficiency?
Measure the cycles between READY going low and the last VALID beat. Relate that delay to the permitted skid or buffering depth.
What is effective throughput?
It is the ratio of useful data beats to total clock cycles. It exposes cycles consumed by protocol headers and idle gaps.
