Building a Self-Checking Testbench
Generate a clock, apply cases, calculate expected results, and stop simulation on a real failure.
A test that decides on its own
A self-checking testbench does not require manual inspection of every waveform. It applies an input, calculates or retrieves the expected value, and automatically compares the DUT output.
Waveforms remain useful for diagnosing a failure, but not for deciding by hand whether each case passed.
module add_sat_tb;
timeunit 1ns;
timeprecision 1ps;
logic clk = 1'b0;
logic [7:0] a, b;
logic [7:0] result;
int unsigned checks;
always #5ns clk = ~clk;
add_sat dut (
.i_a(a),
.i_b(b),
.o_result(result)
);
function automatic logic [7:0] reference_add(
input logic [7:0] lhs,
input logic [7:0] rhs
);
logic [8:0] wide;
wide = {1'b0, lhs} + {1'b0, rhs};
return wide[8] ? 8'hFF : wide[7:0];
endfunction
task automatic check_case(
input logic [7:0] lhs,
input logic [7:0] rhs
);
logic [7:0] expected;
a = lhs;
b = rhs;
#1ns;
expected = reference_add(lhs, rhs);
assert (result === expected)
else $fatal(1, "a=%0d b=%0d got=%0d expected=%0d",
lhs, rhs, result, expected);
checks++;
endtask
initial begin
check_case(0, 0);
check_case(10, 20);
check_case(200, 100);
check_case(255, 255);
$display("PASS: %0d checks", checks);
$finish;
end
endmoduleCompare with the right operator
=== also compares X and Z. In a result check, it prevents an unknown from producing an unknown comparison result that a poorly written condition might miss.
A useful policy rejects any unexpected X on checked outputs. If the protocol allows an unknown, handle it explicitly.
Sample at the right time
The small #1ns delay is suitable here for a purely combinational output in a testbench. For a synchronous DUT, apply inputs before the intended edge and observe outputs in a simulation region that avoids races.
A clocking block can define drive and sample skews. Without one, the environment needs a strict convention for drive and observation edges.
Useful messages and a final summary
A failure should report the scenario, actual value, and expected value. A check counter and final message prevent an empty simulation from being mistaken for success.
In a larger campaign, collecting several failures before stopping can be useful. In that case, count errors and finish with a nonzero status when the count is not zero.
Key points
- The testbench calculates and checks results without manual waveform review.
===exposes unknown values clearly.- Drive and sample timing must be defined.
- Error messages should make the failing case reproducible.
- A final summary separates real success from a simulation that tested nothing.
📝 Test your knowledge - Chapter quiz