Testbenches and simulation
WHY does this exist?
WHAT are the pieces?
HOW do you build one — derived from scratch
Let's build a TB step by step for a DUT, reasoning about why each line must exist.
Suppose our DUT is a 4-bit synchronous counter:
module counter(input clk, input rst, output reg [3:0] q);
always @(posedge clk)
if (rst) q <= 0; else q <= q + 1;
endmoduleStep 1 — a module with no ports.
module tb;Why? A TB is the top of the hierarchy; there is nothing above it to wire ports to.
Step 2 — declare TB-side signals.
reg clk, rst; // we DRIVE these → reg
wire [3:0] q; // DUT drives this → wireWhy? clk and rst are outputs of the TB into the DUT, so the TB must hold/assign them → reg. q is driven by the DUT → wire.
Step 3 — instantiate the DUT (connect the rig to the thing under test).
counter dut(.clk(clk), .rst(rst), .q(q));Why? Named port connection (.port(signal)) is safer than positional — order changes won't silently mis-wire.
Step 4 — generate a clock.
initial clk = 0;
always #5 clk = ~clk; // period = 10 time units, 100 MHz if 1 unit = 1 nsWhy? Sequential logic needs a posedge. Toggling every 5 units gives a 10-unit period. The always runs forever, which is what a clock must do.
Step 5 — stimulus + self-checking.
integer expected;
initial begin
rst = 1; expected = 0;
@(posedge clk); #1; // apply reset for one cycle
rst = 0;
repeat (20) begin
@(posedge clk); #1; // wait for the flip-flop to update, then check
expected = expected + 1;
if (q !== expected[3:0])
$error("t=\%0t: q=\%0d expected=\%0d",$time, q, expected[3:0]);
end
$display("TEST DONE");
$finish; // stop the (otherwise infinite) simulation
endWhy the #1 after @(posedge clk)? At the exact clock edge the flip-flop's new value hasn't settled in the same delta step — sampling a hair after the edge avoids a race condition and reads the stable value. Why !== not !=? !== also catches x/z (unknown/high-Z), which != treats as ambiguous.
Step 6 — waveform dump (Dual Coding for your eyes).
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb); // record everything under tb
endWhy? When the self-check fails, the VCD waveform lets you see the timing relationship that broke.

The event-driven simulation model (the real engine)
Blocking (=) vs non-blocking (<=) — a testbench trap
Types of testbenches (know these names)
Worked example 2 — combinational DUT (a 2-input AND gate), exhaustive TB
DUT: module and2(input a,b, output y); assign y = a & b; endmodule
module tb;
reg a, b; wire y;
and2 dut(.a(a), .b(b), .y(y));
integer i;
initial begin
for (i = 0; i < 4; i = i + 1) begin
{a, b} = i[1:0]; // Why? concatenation drives both inputs from a counter
#10; // Why? let the combinational output settle before checking
if (y !== (a & b))
$error("a=%b b=%b y=%b", a, b, y);
end
$display("AND2 OK"); $finish;
end
endmoduleWhy exhaustive? A 2-input gate has only input combinations — cheap to test all of them. In general an -input combinational block has cases; exhaustive testing is only feasible for small , which is why random/coverage methods exist for large designs.
Worked example 3 — using a golden reference model
To test an adder, compute the expected sum in the TB itself:
reg [7:0] a, b; wire [8:0] sum;
adder8 dut(.a(a), .b(b), .sum(sum));
initial begin
repeat (1000) begin
a = $random; b = $random; // Why? constrained-random-ish coverage
#1;
if (sum !== (a + b)) // Why? {a+b} in TB is the golden model
$error("%0d + %0d = %0d", a, b, sum);
end
$finish;
endWhy this works: the simulator's own integer arithmetic is our trusted reference; the DUT's gate-level logic is what we distrust. Comparing the two automatically finds mismatches over 1000 random cases.
Recall Feynman: explain to a 12-year-old
Imagine you built a toy vending machine. Before you sell candy with it, you make a test kit: a hand that pushes the buttons in order, and a checklist that says "if I push this, I should get that." The test kit is not part of the machine — it just pokes it and watches. That poking-and-checking done inside a computer (before building anything real) is simulation, and the test kit is the testbench. The clever part: instead of you watching every time, the checklist screams automatically when the machine gives the wrong candy.
Active recall
What is a testbench?
Why is a testbench non-synthesizable?
initial, #delay, $display, $random) that don't map to hardware; its job is to test, not to be built.In a Verilog TB, why must DUT-input signals be reg?
initial/always) which require a variable that holds its value; a wire can't be procedurally assigned.In a TB, why must DUT-output signals be wire?
wire reflects its driver's value.How do you generate a clock in a testbench?
initial clk=0; always #5 clk=~clk; — toggling every 5 units gives a period of 10 units.Given a toggle delay d, what is the clock period and frequency?
Why sample outputs a small time after @(posedge clk)?
Difference between != and !== in Verilog?
!== (case inequality) distinguishes x/z exactly; != returns x when operands contain x/z, so it can miss unknown-value bugs.What is an event-driven simulator?
What are delta cycles?
What is a self-checking testbench?
$error) instead of manual waveform inspection.What is a golden reference model?
a+b computed by the simulator) the TB compares the DUT against.Why use named port connections in DUT instantiation?
Why must a testbench call $finish?
always block runs forever, so without $finish the simulation never terminates.When is exhaustive testing feasible?
What is constrained-random verification?
What does $dumpvars(0, tb) do?
tb (level 0 = all hierarchy) into a VCD waveform file for viewing.Why prefer blocking = for TB stimulus?
Connections
- HDL Basics — Verilog and VHDL
- Blocking vs Non-blocking Assignments
- Synthesizable vs Non-synthesizable Constructs
- Digital Design Flow — Spec to Silicon
- Clocking and Sequential Logic
- SystemVerilog Assertions (SVA)
- Functional Coverage and Verification
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, testbench ek aisa HDL module hai jise hum hardware banane ke liye nahi likhte — iska kaam sirf tumhare design (jise DUT, Device Under Test bolte hain) ko test karna hai. Socho tumne ek circuit banaya; ab usko silicon pe daalne se pehle tum ek "test rig" banate ho jo DUT ko inputs deta hai, clock chalata hai, aur outputs ko check karta hai — sab kuch computer ke andar. Isi chalane ko simulation kehte hain. Faayda? Bugs pehle hi pakde jaate hain, warna FPGA/ASIC pe debug karna bahut mehenga aur slow hota hai.
TB module ke koi ports nahi hote kyunki ye sabse upar hota hai, iske upar kuch connect nahi hota. Jo signals tum DUT ko drive karte ho (jaise clk, rst) wo reg hote hain, aur jo DUT se aate hain (jaise q) wo wire. Clock banane ke liye always #5 clk = ~clk; — matlab har 5 unit pe toggle, toh period ban jaata hai units. Sabse important trick: output ko clock edge ke thoda baad check karo (@(posedge clk); #1;), warna edge pe purani aur nayi value dono confuse kar deti hain (race condition).
Ek self-checking TB banao — matlab code khud if (q !== expected) $error(...) kar ke bataye, taaki tumhe har baar waveform dekh ke aankhen kharaab na karni pade. !== use karo != ki jagah, kyunki !== x/z (unknown/high-Z) values ko bhi pakadta hai. Aur simulator event-driven hota hai — ye har nanosecond calculate nahi karta, sirf tab kaam karta hai jab koi signal change (event) hota hai, phir agle event pe time jump kar deta hai. Yahi reason hai ki simulation itni fast aur smart hoti hai.
Yaad rakhna 80/20 rule: asli value achhe stimulus + automatic checking se aati hai, waveform ghoorna sabse slow tareeka hai. Aur $finish lagana mat bhoolna, warna always clock hamesha chalta rahega aur sim kabhi rukega hi nahi!