Verilog · Chapter 9.5 · Design & Testbench Creation
Testbench Creation Techniques in Verilog — Clock, Stimulus, Checking, Termination
You have learned to build a clean design module, the device under test. This lesson teaches you to prove it works by building a testbench, the simulation-only harness that wraps that design, brings it to life, and judges its behaviour. A testbench has a fixed set of jobs. It generates a clock, applies reset, drives stimulus into the inputs, observes the outputs, checks them against what was expected, and then terminates cleanly. You will learn the technique for each job, plus the one rule beginners get wrong on the harness side, which signals should be a reg and which should be a wire. This is where earlier lessons converge, from modules and instances to port mapping and the system tasks that control a run, into the harness that turns I wrote it into I proved it.
Foundation22 min readVerilogTestbenchVerificationDUTSelf-CheckingSimulation
Chapter 9 · Section 9.5 · Design & Testbench Creation
1. The Engineering Problem
You followed 9.4 and built a clean alu design module — synthesizable, single-purpose, well-interfaced. Is it correct? You have no idea. A design module sitting in a file does nothing: it has no clock ticking, no values on its inputs, no one watching its outputs. It is a chip with no power, no signal generator, and no oscilloscope attached.
A junior's instinct is to "look at the waveform and see if it seems right." That does not scale and it does not hold: eyeballing a few cycles proves nothing about the hundreds of cases that matter, and "seems right" has shipped countless bugs to silicon. The real question is:
How do you bring a design module to life, exercise it across the cases that matter, and have the design itself report whether it passed or failed — automatically?
The answer is a testbench: a simulation-only module that surrounds the DUT, generates its clock, drives its inputs, watches its outputs, and checks them against expected values, ending with a clear PASS or FAIL. It is the test rig that turns a dormant design into a running, judged experiment. This page builds that rig piece by piece — clock, reset, stimulus, observation, checking, termination — using the toolkit Chapter 8 and 9.1–9.4 already gave you.
2. Mental Model — The Testbench Is a Virtual Test Bench
Visual A — the six jobs of a testbench
What every testbench does
data flow3. The Anatomy of a Testbench
Here is the standard testbench skeleton wrapped around a DUT, with each region labelled. (The alu is the DUT from 9.4; its internals are not shown — the testbench only touches its interface.)
`timescale 1ns/1ps
module alu_tb; // 1. module with NO ports
// 2. driver signals: reg for DUT inputs, wire for DUT outputs
reg [15:0] a, b;
reg [2:0] op;
wire [15:0] y;
wire zero;
// 3. instantiate the DUT (named port mapping — 9.3)
alu u_dut (
.a(a), .b(b), .op(op), .y(y), .zero(zero)
);
// 4. clock generation (combinational DUTs may omit this)
// ... see §4
// 5. stimulus + reset + checking, in an initial block
initial begin
// apply reset, drive inputs, check outputs, finish
// ... see §5–§8
end
// 6. watchdog timeout — bound the run (§8, Chapter 8.5)
endmoduleSix regions, every testbench: a port-less module, driver signals (the reg/wire split of §9), the DUT instance, clock generation, the stimulus-and-check body, and a watchdog. The shape is as invariant as the design-module skeleton in 9.4 — learn it once and every testbench is a variation on it.
4. Clock Generation
A synchronous DUT does nothing without a clock. The testbench generates one — a free-running square wave — with a single line:
reg clk = 0;
always #5 clk = ~clk; // toggle every 5 time units → 10-unit period → 100 MHz at 1nsThe always #5 clk = ~clk flips the clock every 5 time units, producing a 10-unit period. This is sim-only (the #5 delay has no hardware meaning) and lives only in the testbench. Two cautions:
- It runs forever. A free-running clock keeps the simulation's event queue alive indefinitely — which is exactly why a testbench must call
$finishand carry a watchdog (Chapter 8.5). The clock will not stop on its own. - Combinational DUTs need no clock. A purely combinational block (an adder, a mux) has no clock port; its testbench drives inputs and waits for the outputs to settle, no clock generator required.
5. Reset Application
A sequential DUT starts in an unknown state until reset puts it into a known one. The testbench asserts reset at the very start, holds it briefly, then releases it before stimulus begins:
reg rst_n;
initial begin
rst_n = 0; // assert reset (active-low) at t=0
#20; // hold across a couple of clock edges
rst_n = 1; // release — DUT is now in a known state
// ... stimulus follows ...
endReset is applied first, before any meaningful stimulus, so the DUT begins every test from a defined state. Forgetting it is a classic bug: stimulus applied to an unreset sequential DUT operates on x state and produces meaningless results (§11). The DUT's reset behaviour is its own design (Chapter 14); the testbench's job is simply to drive the reset signal at the right time.
6. Stimulus Application
Stimulus is the sequence of input values the testbench drives into the DUT to exercise it. In a directed testbench, the engineer writes out specific cases by hand:
initial begin
rst_n = 0; #20; rst_n = 1; // reset first
// directed cases — drive inputs, wait, let outputs settle
a = 16'd10; b = 16'd5; op = 3'b000; #10; // add
a = 16'd10; b = 16'd5; op = 3'b001; #10; // subtract
a = 16'd12; b = 16'd4; op = 3'b010; #10; // and
// ... more cases ...
$finish;
endEach case sets the inputs, waits (#10) for the DUT to respond, and moves on. Directed stimulus targets specific, known-interesting cases — boundary values, each operation, corner cases. (When you need thousands of varied cases, constrained-random stimulus uses $random/$urandom from Chapter 8.2 to generate them — that is a verification technique previewed here and drilled later; directed testing is where you start.)
7. Observation and Self-Checking
Driving stimulus is half the job; the testbench must also judge the result. There are two levels, and only one is acceptable for real work.
Observation prints what happened — useful, but it makes a human the checker:
$display("[t=%0t] a=%0d b=%0d op=%b -> y=%0d", $time, a, b, op, y);
// A person must read the log and decide if y is right. Doesn't scale.Self-checking makes the testbench the checker — it computes the expected value and compares automatically, counting failures:
integer errors = 0;
task check(input [15:0] exp);
if (y !== exp) begin
$display("[t=%0t] FAIL: a=%0d b=%0d op=%b -> y=%0d expected=%0d",
$time, a, b, op, y, exp);
errors = errors + 1;
end
endtask
// usage in stimulus:
a = 16'd10; b = 16'd5; op = 3'b000; #10; check(16'd15); // add → expect 15A self-checking testbench encodes the specification as executable comparisons — it fails loudly and automatically on any mismatch and needs no human to read the log. This is the standard for all real verification: a testbench that only prints (and relies on someone noticing) will pass a broken DUT the day no one is watching (§11, DebugLab 3). (The $display mechanics and the ===/!== X-aware comparison are Chapter 8.1 / Chapter 5; here the point is the testbench checks itself.)
Visual B — observation-only vs self-checking
8. Termination and the Watchdog
A testbench must end deterministically with a verdict — and protect itself against a hung DUT. Both come straight from Chapter 8.5.
// at the end of the stimulus sequence:
initial begin
// ... reset, stimulus, checks ...
if (errors == 0) $display("TEST PASSED");
else $display("TEST FAILED (%0d errors)", errors);
$finish; // deterministic end with a verdict
end
// independent watchdog — bound the run so a hung DUT can't run forever
initial begin
#100000;
$display("TEST FAILED — TIMEOUT");
$finish;
endThe stimulus block prints a parseable verdict and $finishes; an independent watchdog block bounds the worst case so a deadlocked DUT fails loudly instead of running to the farm's wall-clock limit. Every testbench carries both. (The full discipline — $finish vs $stop, the watchdog race, exit semantics — is Chapter 8.5; here it is the testbench's closing region.)
9. The reg/wire Rule — Which Side Drives
The one rule beginners get wrong on the testbench side, and the complement to 9.3's port-direction rule:
- Signals connected to DUT inputs must be
reg. The testbench drives them from procedural (initial/always) blocks, and only areg(a procedural variable) can be assigned there. - Signals connected to DUT outputs must be
wire. The DUT drives them through its instance, and an instance output must connect to a net (the 9.3 rule, seen from the testbench).
testbench DRIVES → DUT input ⇒ declare as reg
DUT DRIVES → testbench observes ⇒ declare as wireThe mnemonic: the testbench writes its regs and reads the DUT's wires. Get it backwards — an output declared reg, or an input declared wire — and the tool rejects it (DebugLab 1). The logic is the same direction rule as 9.3, now applied from inside the harness: whoever drives a signal determines its type, and for DUT inputs that driver is the testbench.
Visual C — testbench drives reg inputs, reads wire outputs
10. Worked Examples
Three testbench fragments — clock, a reset-plus-stimulus sequence, and a self-checking skeleton.
10.1 Example 1 — clock generation
module dut_tb;
reg clk = 0;
always #5 clk = ~clk; // 10-unit period, free-running
// ... DUT instance and stimulus use clk ...
endmoduleOne line makes the heartbeat. Remember it runs forever — the testbench must $finish and carry a watchdog, or the simulation never ends.
10.2 Example 2 — reset then directed stimulus
initial begin
// 1. reset into a known state
rst_n = 0; en = 0; d = 0;
#20; rst_n = 1;
// 2. directed stimulus, synchronized to the clock
@(posedge clk); en = 1; d = 8'hA5;
@(posedge clk); d = 8'h3C;
@(posedge clk); en = 0;
// 3. end
#20; $finish;
endReset first, then a short directed sequence driving the DUT's inputs across clock edges, then a clean $finish. This is the everyday shape of a directed stimulus block.
10.3 Example 3 — a self-checking skeleton
integer errors = 0;
task expect_eq(input [15:0] got, input [15:0] exp, input [127:0] name);
if (got !== exp) begin
$display("FAIL %0s: got=%0d expected=%0d at t=%0t", name, got, exp, $time);
errors = errors + 1;
end
endtask
initial begin
rst_n = 0; #20; rst_n = 1;
a = 16'd10; b = 16'd5; op = 3'b000; #10; expect_eq(y, 16'd15, "add");
a = 16'd10; b = 16'd5; op = 3'b001; #10; expect_eq(y, 16'd5, "sub");
if (errors == 0) $display("TEST PASSED");
else $display("TEST FAILED (%0d errors)", errors);
$finish;
endThe testbench encodes the spec as expect_eq checks, counts failures, and reports a deterministic verdict — no human in the loop. This is the standard structure all six worked together; 9.6 assembles a complete DUT-plus-testbench pair end to end.
11. Industry Perspective
- Self-checking is mandatory. No serious team accepts an observation-only ("eyeball the waveform") testbench. A testbench that does not encode the expected result and fail automatically provides no regression protection — it passes a broken DUT the moment no one reads the log.
- Testbenches run unattended, in bulk. Thousands of testbenches run nightly on compute farms with no human watching, which is exactly why deterministic
$finish, watchdog timeouts, and a parseable PASS/FAIL verdict (Chapter 8.5) are non-negotiable — the harness must report its own result. - Directed gives way to constrained-random + coverage. Directed testbenches (this page) are the foundation; at scale, verification moves to constrained-random stimulus (
$urandom) plus functional coverage to reach cases no human would enumerate. The directed skeleton here is the base those techniques build on. - The testbench is half the engineering. In modern projects, verification code often exceeds design code in volume and effort. The testbench is not an afterthought to the DUT — it is a co-equal deliverable, built in the same loop (the Chapter 9 overview's thesis).
12. Common Mistakes
- Wrong signal types — a DUT input declared
wire(the testbench can't drive it procedurally) or a DUT output declaredreg(an instance output must drive a net). The reg/wire rule of §9 (DebugLab 1). - No clock — a synchronous DUT with no clock generator never advances; the simulation runs but nothing happens (DebugLab 2).
- No
$finish— a free-running clock keeps the run alive forever (Chapter 8.5 / §8). - No self-checking — only
$display, relying on a human to notice wrong values; passes a broken DUT silently (DebugLab 3). - Forgetting reset — driving stimulus into an unreset sequential DUT, which operates on
xstate and yields meaningless results (§5).
13. Debugging Lab
Three testbench debug post-mortems
14. Interview Q&A
15. Exercises
Exercise 1 — Classify the testbench signals
A DUT has ports input clk, input rst_n, input [7:0] d, input en, output [7:0] q, output valid. In the testbench, state which signals must be reg and which must be wire, and explain the rule in one sentence.
Exercise 2 — Write the missing pieces
Given a testbench skeleton with a DUT instance already in place, write: (a) a free-running clock generator with a 10-unit period, (b) a reset sequence that asserts active-low reset at t=0, holds it 20 units, and releases it, and (c) a $finish after the stimulus. Code only these three fragments.
Exercise 3 — Make it self-checking
This fragment is observation-only:
a = 8'd7; b = 8'd9; #10;
$display("sum = %0d", sum);Convert it to self-checking: compute the expected value, compare against sum, increment an errors counter on mismatch, and (separately) print a final PASS/FAIL. Describe what the final verdict block looks like.
Exercise 4 — Find the testbench bugs
List every testbench bug below (reference §4–§9 / §12) and give a one-line fix for each.
module dut_tb;
reg clk;
reg [7:0] d;
reg [7:0] q; // q is a DUT output
dut u_dut ( .clk(clk), .d(d), .q(q) );
initial begin
clk = 0; // never toggled
d = 8'h10; #10;
$display("q=%0d", q);
// no $finish
end
endmodule16. Summary
A testbench is the simulation-only harness that proves a design module (the DUT) works — it brings the dormant DUT to life, exercises it, and judges its behaviour automatically.
The core ideas:
- Six jobs: generate the clock, apply reset, drive stimulus, observe outputs, check them against expected values, and terminate with a verdict (plus a watchdog). Every testbench arranges these six.
- A testbench is a port-less, simulation-only module that reaches down into the DUT and freely uses
initial,#delays,$display,$finish— everything a design module must not. - The reg/wire rule: DUT-input signals are
reg(the testbench drives them); DUT-output signals arewire(the DUT drives them). The testbench writes itsregs and reads the DUT'swires. - Self-checking, not observation-only: the testbench computes the expected result and compares automatically, failing loudly — printing alone passes broken DUTs silently.
- Clock generation +
$finish+ watchdog (Chapter 8.5) make the run advance, end deterministically, and survive a hung DUT.
The discipline this page instils:
- Always self-check — encode the spec as comparisons; a testbench that only prints is no testbench.
- Reset first, clock always, finish always — known state, a heartbeat, and a deterministic end.
- Type signals by who drives them —
regfor DUT inputs,wirefor DUT outputs.
You can now build the harness that turns a design module into a verified one — the full convergence of Chapters 8 and 9.1–9.4. The final step is to see it all assembled: Chapter 9.6 Practical Examples walks a complete, working DUT-plus-testbench pair end to end — design module and self-checking testbench together — closing Chapter 9 and the foundation arc before the curriculum turns to the RTL core (Chapter 10 Operators onward).
Related Tutorials
- Design Module Fundamentals — Chapter 9.4; the DUT this testbench wraps and the design-vs-testbench split it builds on.
- Simulation Control — Chapter 8.5; the
$finishand watchdog discipline that ends every testbench. - $display vs $monitor vs $strobe vs $write — Chapter 8.1; the observation tasks a testbench uses.
- Module Port Mapping — Chapter 9.3; the port-direction rule behind the testbench's reg/wire split.