The 3 Self-Checking Patterns Every Verilog Testbench Needs (With Code)
If you're still verifying RTL by staring at waveforms, you're doing it wrong. Here are three self-checking patterns that let your testbench tell you whether the design is correct — automatically.
Pattern 1: The Inline Comparator
The simplest pattern. Compare DUT output to expected value immediately after each transaction.
task check_adder;
input [7:0] a, b;
reg [8:0] expected, actual;
begin
// Drive stimulus
@(posedge clk);
in_a <= a;
in_b <= b;
// Wait for result
@(posedge clk);
actual = sum_out;
expected = a + b;
// Self-check
if (actual !== expected) begin
$display("FAIL: %0d + %0d = %0d (expected %0d)",
a, b, actual, expected);
errors++;
end
end
endtaskWhen to use: Simple combinational logic, quick sanity checks.
Limitation: Breaks on pipelined designs with variable latency.
Pattern 2: The Transaction Scoreboard
Decouples stimulus from checking using queues. The golden model pushes expected results, the DUT monitor pushes actual results, and the scoreboard matches them up.
reg [15:0] expected_q [$];
reg [15:0] actual_q [$];
// Golden model side
task push_expected(input [15:0] val);
expected_q.push_back(val);
endtask
// DUT monitor side — triggers comparison
task push_actual(input [15:0] val);
reg [15:0] exp;
begin
actual_q.push_back(val);
if (expected_q.size() > 0) begin
exp = expected_q.pop_front();
if (val !== exp)
$error("Scoreboard mismatch: exp=%h act=%h", exp, val);
end
end
endtaskWhen to use: Pipelined designs, FIFOs, anything with latency between input and output.
Key advantage: Tolerates variable-latency responses.
Pattern 3: The Assertion Monitor
Continuous background checks that fire the instant a property is violated — no waiting for a transaction to complete.
// Protocol: data must be stable while valid is asserted
assert property (@(posedge clk) disable iff (rst)
(valid && !ready) |=> $stable(data)
) else $error("Data changed while valid was held");
// Safety: FIFO count must never exceed depth
assert property (@(posedge clk) disable iff (rst)
count <= DEPTH
) else $fatal("FIFO overflow detected: count=%0d", count);When to use: Protocol compliance, safety invariants, interface contracts.
Key advantage: Catches violations on the exact clock edge — no delay.
Putting It All Together
A production testbench uses all three:
Assertions for protocol and safety invariants (always running)
Scoreboards for data-path verification (per-transaction)
Inline comparators for quick directed tests during debug
The goal: run your entire regression suite overnight and wake up to a clean PASS/FAIL report. No waveforms needed.
I teach this complete methodology — from basic comparators to automated regression with coverage closure — in the course here at AutoBench Academy. If you're building FPGA verification environments and want to stop wasting time on manual debug, this is the system.
