A reference model describes the expected result independently of the design. The same stimulus drives both, and a comparator reports each mismatch.
For a fixed-latency pipeline, a queue is the clearest model. Whatever enters must leave a fixed number of cycles later.
Read the chart top to bottom: everything below the stimulus runs in parallel on the same inputs.
The queue is the reference: it is written from the spec (whatever enters leaves two cycles later), not copied from the RTL.
The broken copy proves the checker works: the test passes only if the good design matches and the bad one does not.
One comparator with !==, so an x output counts as a mismatch.
Independent: written from the spec, in a different style. Copying the RTL into the testbench just duplicates its bugs.
Cycle-accurate where needed: for a fixed-latency pipeline, track the latency exactly; for a variable-latency block, compare transactions in order rather than cycle by cycle.
Simple: queues, associative arrays, plain functions. If the model is as complex as the design, it will have the same bugs.
Practice | Why |
|---|---|
Print time, input and both values on a mismatch | You can reproduce and understand the failure from the log alone. |
Count errors and stop only at the end (or after N errors) | One run reveals the pattern, not just the first symptom. |
Use | An |
Log the random seed | Any failing random test can be replayed exactly. |
Add assertions inside the design for impossible states | Failures point at the cause, not just the symptom. |
always_ff block. Why does the test pass but prove little?Both share the same logic, so any bug in the RTL is also in the model and the comparison always matches. The model must come from the specification, expressed independently (for example a queue instead of two registers).
A two-stage pipeline checked against a queue, with a broken copy proving the checker fails.
module pipe2 #(parameter int BUG = 0) (
input logic clk, rst_n,
input logic [7:0] d,
output logic [7:0] y
);
logic [7:0] s1, s2;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin s1 <= '0; s2 <= '0; end
else begin s1 <= d; s2 <= s1; end
assign y = (BUG == 1) ? s1 : s2; // BUG=1 drops one pipeline stage
endmodule
The verdict comes after 41 comparisons: the initial reset value and all 40 generated inputs, including the final input drained from the queue. The bad DUT must produce at least one mismatch; its exact error count depends on the random sequence because adjacent samples can coincide. An empty queue and the comparison count check prevent a final transaction from escaping verification.
Run with Icarus Verilog 13.0 (2026-10-16): the verdict comes after 41 comparisons, the initial reset value plus 40 generated inputs. The exact error count of the bad DUT depends on the random sequence.