
Writing a Simple Testbench
Code that drives inputs into a design and checks the outputs — how we verify logic.
Description
A testbench is non-synthesizable HDL that instantiates the design under test (DUT), applies stimulus over time, and observes/checks outputs. It has no ports, generates a clock if needed, drives inputs with delays, and reports pass/fail against expected results.
- Top module with no ports.
- Instantiate the DUT and wire its ports to regs/wires.
- Drive inputs as reg; read outputs as wire.
- Use initial blocks and # delays for stimulus.
- Generate a clock with always #half clk = ~clk (if sequential).
- Apply known input vectors and expected outputs.
- Compare with if (y !== expected) $display("FAIL …").
- $monitor/$dumpvars for waveform inspection.
- $finish ends the simulation.
- Self-checking testbenches report pass/fail automatically.
At a glance
What
An HDL module that stimulates and checks a design under test.
Why
Functional verification before synthesis catches bugs early.
How
Instantiate the DUT, drive inputs over time, compare outputs to expected.
Where
Every RTL block's verification flow.
When
After writing RTL, before synthesis.
Think of it like…
A testbench is a crash-test rig: it straps in the design, applies known forces (inputs), and records whether it behaves safely (correct outputs).
Testbench anatomy
- Top module with no ports.
- Instantiate the DUT and wire its ports to regs/wires.
- Drive inputs as reg; read outputs as wire.
- Use initial blocks and # delays for stimulus.
- Generate a clock with always #half clk = ~clk (if sequential).
Checking
- Apply known input vectors and expected outputs.
- Compare with if (y !== expected) $display("FAIL …").
- $monitor/$dumpvars for waveform inspection.
- $finish ends the simulation.
- Self-checking testbenches report pass/fail automatically.
Testbench pieces
| Piece | Role |
|---|---|
| DUT instance | the design |
| reg inputs | stimulus |
| wire outputs | observe |
| initial/# delays | timing |
| $display/$finish | report/stop |
HDL — Verilog · VHDL · SystemVerilog
module tb;
reg [3:0] a, b; reg cin;
wire [3:0] sum; wire cout;
adder dut(.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
initial begin
a=4'd3; b=4'd5; cin=0; #10;
if ({cout,sum} !== 5'd8) $display("FAIL: got %0d", {cout,sum});
else $display("PASS");
$finish;
end
endmoduleSelf-checking testbench skeleton for a combinational adder.
Real-world applications
The 5 Whys
- 1
Why a testbench? Verify behavior before fabrication.
- 2
Why no ports? It is the top of the simulation, not a component.
- 3
Why stimulus over time? To exercise input sequences.
- 4
Why self-checking? Automatic pass/fail at scale.
- 5
Root cause: applying stimulus and checking outputs is how logic is proven correct.
Cheat sheet
Working principle
- Instantiate the DUT, drive inputs over time, compare outputs to expected.
- An HDL module that stimulates and checks a design under test.
Formulas & Boolean expressions
- Generate a clock with always #half clk = ~clk (if sequential).
- Compare with if (y !== expected) $display("FAIL …").
- DUT instance = the design
- reg inputs = stimulus
- wire outputs = observe
- initial/# delays = timing
- $display/$finish = report/stop
Key facts
- Top module with no ports.
- Apply known input vectors and expected outputs.
Why it exists
- Root cause: applying stimulus and checking outputs is how logic is proven correct.