Verilog · Chapter 9.6 · Design & Testbench Creation
Design + Testbench Practical Examples in Verilog — Complete Working Pairs
You have learned every piece of this chapter, including what a module is, how to instantiate it, wire its ports, make it a good design block, and build a testbench around it. This capstone assembles those pieces into complete, working units. You walk through two full pairs end to end, each combining a design under test with its testbench. A combinational example uses an ALU and a sequential example uses a counter, and each pair has a self-checking testbench that drives the design, checks every output against the specification, and prints a deterministic pass or fail. Earlier pages stayed at shell level on purpose; here you see the whole thing run. The lesson here is integration, meaning how the modules, the instance, the port mapping, the design discipline, and the testbench technique come together into one verifiable thing.
Foundation22 min readVerilogTestbenchDUTSelf-CheckingWorked ExampleVerification
Chapter 9 · Section 9.6 · Design & Testbench Creation
1. The Engineering Problem
You can now name every part — modules, ports, instances, port mapping, design quality, testbench technique. But you have only ever seen them as fragments: a port list here, a clock generator there, a check in isolation. A real piece of work is not fragments — it is a complete, running unit: a design module and a testbench, compiled together, producing one verdict.
The unit of real RTL work is not a module or a testbench — it is a verified DUT: a design block and the self-checking testbench that proves it, assembled and run together. Until you have built one end to end, the pieces are theory.
This page builds two of them, completely, and runs them: a combinational ALU and a sequential counter, each with a self-checking testbench. You will see every part you have learned in its place, watch the simulation print PASS, and — in the Debug Lab — watch a self-checking testbench catch a real bug. This is where Chapter 9 stops being a list of techniques and becomes a skill.
2. Mental Model — The Verified Unit
Visual A — the verified unit
3. Example 1 — A Combinational ALU, End to End
A complete combinational unit. First the DUT — a clean design module (9.4 conventions: parameterized, well-named, synthesizable):
module alu #(
parameter integer WIDTH = 8
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire [1:0] op,
output reg [WIDTH-1:0] y
);
// Combinational logic. (case / always @(*) are Chapter 14 — read it as:
// op selects one of four operations.)
always @(*) begin
case (op)
2'b00: y = a + b; // add
2'b01: y = a - b; // subtract
2'b10: y = a & b; // bitwise AND
2'b11: y = a | b; // bitwise OR
endcase
end
endmoduleNow the testbench — port-less, sim-only, self-checking (9.5):
`timescale 1ns/1ps
module alu_tb;
localparam integer WIDTH = 8;
reg [WIDTH-1:0] a, b; // TB drives DUT inputs → reg
reg [1:0] op;
wire [WIDTH-1:0] y; // DUT drives output → wire
integer errors = 0;
// instantiate the DUT (named port mapping — 9.3)
alu #(.WIDTH(WIDTH)) u_dut (
.a(a), .b(b), .op(op), .y(y)
);
// self-checking task: compare actual vs expected, count failures
task check(input [WIDTH-1:0] exp, input [127:0] name);
if (y !== exp) begin
$display("FAIL %0s: a=%0d b=%0d op=%b -> y=%0d (expected %0d)",
name, a, b, op, y, exp);
errors = errors + 1;
end
endtask
// directed stimulus — no clock; drive, settle (#10), check
initial begin
a = 8'd10; b = 8'd5; op = 2'b00; #10; check(8'd15, "add");
a = 8'd10; b = 8'd5; op = 2'b01; #10; check(8'd5, "sub");
a = 8'hF0; b = 8'h0F; op = 2'b10; #10; check(8'h00, "and");
a = 8'hF0; b = 8'h0F; op = 2'b11; #10; check(8'hFF, "or");
if (errors == 0) $display("TEST PASSED");
else $display("TEST FAILED (%0d errors)", errors);
$finish;
end
endmoduleWalk the assembly, piece by piece:
- No clock. A combinational DUT has no clock port; the testbench drives inputs and waits
#10for the output to settle before checking. Time here is just "apply, wait, compare." - reg/wire by drive direction (9.5).
a,b,oparereg(the testbench drives them);yiswire(the DUT drives it). - Named instantiation with a parameter override (9.2/9.3).
alu #(.WIDTH(WIDTH)) u_dut ( .a(a), ... )— the parameter sets the width, the named connections wire it correctly. - Self-checking (9.5). The
checktask encodes the spec — for each case the testbench knows the expectedyand compares with!==, counting failures. No human reads the log to judge correctness. - Deterministic end. A PASS/FAIL verdict, then
$finish. The directed sequence is finite and the DUT is combinational, so the run ends naturally at$finish— no free-running clock to keep it alive, so no watchdog is strictly required here (it becomes essential the moment a clock is involved — Example 2).
Trace — what it prints
TEST PASSEDEvery case matched, so no FAIL line appears and the verdict is TEST PASSED. That single line is the deliverable: the ALU is verified against its directed spec.
4. Example 2 — A Sequential Counter, End to End
The sequential case adds a clock and reset. First the DUT:
module counter #(
parameter integer WIDTH = 4
)(
input wire clk,
input wire rst_n,
input wire en,
output reg [WIDTH-1:0] count
);
// Sequential logic. (always @(posedge clk ...) is Chapter 14 — read it as:
// on each clock edge, reset to 0, else increment when enabled.)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count <= 0;
else if (en) count <= count + 1'b1;
end
endmoduleNow the testbench — note the new pieces the clock forces: a clock generator, a reset sequence, edge-synchronized stimulus, post-edge sampling, and a watchdog:
`timescale 1ns/1ps
module counter_tb;
localparam integer WIDTH = 4;
reg clk = 0; // TB-driven
reg rst_n, en; // TB drives DUT inputs → reg
wire [WIDTH-1:0] count; // DUT drives output → wire
integer errors = 0;
// 1. clock generation — the heartbeat (runs forever)
always #5 clk = ~clk;
// 2. DUT instance
counter #(.WIDTH(WIDTH)) u_dut (
.clk(clk), .rst_n(rst_n), .en(en), .count(count)
);
// 3. self-check (sampled after the edge settles)
task check(input [WIDTH-1:0] exp);
if (count !== exp) begin
$display("FAIL at t=%0t: count=%0d (expected %0d)", $time, count, exp);
errors = errors + 1;
end
endtask
// 4. reset + stimulus + checks
initial begin
rst_n = 0; en = 0; // assert reset, disabled
@(posedge clk); @(posedge clk);
rst_n = 1; // release reset
@(posedge clk); #1; check(4'd0); // still 0 (en low)
en = 1;
@(posedge clk); #1; check(4'd1); // counts...
@(posedge clk); #1; check(4'd2);
@(posedge clk); #1; check(4'd3);
en = 0;
@(posedge clk); #1; check(4'd3); // paused — holds 3
if (errors == 0) $display("TEST PASSED");
else $display("TEST FAILED (%0d errors)", errors);
$finish;
end
// 5. watchdog — essential once a free-running clock exists
initial begin
#1000;
$display("TEST FAILED — TIMEOUT");
$finish;
end
endmoduleWhat the clock adds over the combinational case:
- A clock generator (
always #5 clk = ~clk) — the heartbeat the sequential DUT needs, and the reason a$finishand watchdog are now mandatory (it runs forever). - A reset sequence — assert
rst_n = 0first, hold across two edges, then release, so the DUT starts from a knowncount = 0. - Edge-synchronized stimulus —
@(posedge clk)advances the testbench one clock at a time, in lockstep with the DUT. - Post-edge sampling (
#1after the edge). The check readscounta small delta after the clock edge, so the DUT's non-blocking update has settled before the testbench samples it — avoiding the read-before-update race (the Chapter 8.1 active-vs-NBA point, seen here in a testbench). Sampling exactly on the edge is the classic timing bug (DebugLab 2). - The same self-checking and verdict as the combinational case — only the timing of stimulus and sampling changed.
Trace — what it prints
TEST PASSEDThe counter held at 0 while disabled, counted 1→2→3 while enabled, and paused at 3 — every check matched, so TEST PASSED.
Visual B — combinational vs sequential testbench
5. The Anatomy of a Complete Pair
Stepping back, every verified unit — combinational or sequential — contains the same parts, drawn from across Chapter 9. This is the checklist for "is my pair complete?"
What a complete DUT + testbench pair contains
data flow6. Industry Perspective
- The verified pair is the unit of work. RTL engineers do not ship modules — they ship verified modules: a design block plus the self-checking testbench (and, at scale, the full verification environment) that proves it. The pair is what passes review and what gets reused.
- The two shapes generalize. Every real DUT is some composition of combinational and sequential logic, and every real testbench is the machinery above scaled up — more stimulus, constrained-random generation, coverage, scoreboards. The combinational/sequential pair you built here is the seed of a UVM environment.
- Hierarchical DUTs are verified the same way. A DUT built from sub-modules (the hierarchy of 9.2) is wrapped by a testbench identically — the testbench drives the top-level interface and checks the top-level outputs; the internal hierarchy is the DUT's business. The unit shape does not change with DUT complexity.
- Green means checked, not merely run. A passing self-checking testbench is trusted because the checks encode the spec. This is why §3–§4's
checktasks, not the$displays, are the heart of the pair — a point the Debug Lab drives home.
7. Common Mistakes
- Sampling a sequential output exactly on the clock edge — reads the value before the DUT's non-blocking update settles; sample after a small delta (DebugLab 2).
- Forgetting reset in the sequential pair — stimulus runs against an
xstarting state and checks fail with unknowns (DebugLab 3). - Trusting a
$display-only run — withoutchecktasks, a passing run proves nothing; self-check or it isn't verified (DebugLab 1). - No watchdog with a free-running clock — a hung DUT runs forever (Chapter 8.5 / §4).
- reg/wire mixed up — DUT outputs as
regor inputs aswirewon't compile (9.5).
8. Debugging Lab
Three end-to-end assembly debug post-mortems
Pitfall 1 — the self-check catches a real DUT bug (the loop working)
// DUT: the subtract case has an off-by-one bug.
module alu #(parameter integer WIDTH = 8)(
input wire [WIDTH-1:0] a, b, input wire [1:0] op,
output reg [WIDTH-1:0] y
);
always @(*) begin
case (op)
2'b00: y = a + b;
2'b01: y = a - b - 1'b1; // <-- BUG: spurious -1
2'b10: y = a & b;
2'b11: y = a | b;
endcase
end
endmodule
// The self-checking testbench from §3 is run against it.The ALU testbench reports a failure on the subtract case and a non-zero error count: FAIL sub: a=10 b=5 op=01 -> y=4 (expected 5) TEST FAILED (1 errors) The 'add', 'and', and 'or' cases pass. The unit does not ship.
This is the verified-unit working exactly as designed. The DUT has a real bug — the subtract case computes a - b - 1 instead of a - b — and the self-checking testbench CAUGHT it automatically: it knew the expected result (10 - 5 = 5), compared it against the DUT's output (4), and failed loudly with a non-zero error count and a clear diagnostic.
Contrast with an observation-only testbench (9.5, DebugLab 3), which would have printed 'y=4' and still said nothing — the bug would have shipped. The 'fix' here is in the DUT, not the testbench: the testbench did its job.
The fix is to correct the DUT's subtract logic; the testbench then passes.
module alu #(parameter integer WIDTH = 8)(
input wire [WIDTH-1:0] a, b, input wire [1:0] op,
output reg [WIDTH-1:0] y
);
always @(*) begin
case (op)
2'b00: y = a + b;
2'b01: y = a - b; // corrected
2'b10: y = a & b;
2'b11: y = a | b;
endcase
end
endmodule
// Re-run: the self-check now reports TEST PASSED. The loop — write, test,
// read verdict, FIX, re-test — is the whole point of the verified pair.Pitfall 2 — sampling on the clock edge instead of after it
// Sequential testbench that checks count RIGHT ON the posedge,
// before the DUT's non-blocking update has settled.
initial begin
rst_n = 0; en = 0;
@(posedge clk); @(posedge clk); rst_n = 1;
en = 1;
@(posedge clk); check(4'd1); // <-- no settle delay; reads OLD count
@(posedge clk); check(4'd2);
// ...
end
// Intermittent failures like "FAIL at t=35: count=0 (expected 1)" — the
// counter is correct, but the check reads count before the edge's update
// applies.A counter testbench reports failures where the observed count is exactly one step behind expected (count=0 when 1 was expected, count=1 when 2 was expected). The counter DUT is correct and passes when inspected on a waveform. The failures are timing-sensitive and feel 'flaky'.
The check runs in the SAME time-step as the clock edge, racing the DUT's non-blocking update. At the posedge, the counter schedules 'count <= count + 1' for the NBA region, but the testbench's check reads 'count' in the active region — BEFORE that update applies. So it sees the OLD value, one step behind. This is the active-vs-NBA region issue from Chapter 8.1, appearing inside a testbench: read-before-update.
The DUT is correct; the testbench samples at the wrong instant.
The fix is to sample AFTER the edge settles — add a small delay (#1) after @(posedge clk) before checking, so the non-blocking update has applied.
initial begin
rst_n = 0; en = 0;
@(posedge clk); @(posedge clk); rst_n = 1;
@(posedge clk); #1; check(4'd0);
en = 1;
@(posedge clk); #1; check(4'd1); // settle, THEN sample
@(posedge clk); #1; check(4'd2);
// ...
end
// Sampling 1 time unit after each edge reads the post-update value. The
// flaky one-step-behind failures disappear; the counter verifies cleanly.Pitfall 3 — forgot reset in the sequential pair
// Sequential testbench with NO reset sequence.
initial begin
en = 1; // jump straight to stimulus
@(posedge clk); #1; check(4'd1);
@(posedge clk); #1; check(4'd2);
// ...
end
// Failures full of X: "FAIL at t=6: count=x (expected 1)". The counter
// never started from a known state.Every check fails, and the reported count is 'x' (unknown) rather than a number: "FAIL at t=6: count=x (expected 1)". The counter design is correct and verifies fine in another testbench. Here it produces only unknowns.
A sequential DUT powers up in an UNKNOWN state — its count register is 'x' until reset puts it into a known value. This testbench never asserts reset, so the counter starts at x, and 'x + 1' is still x: every cycle the count stays unknown, and every check fails because x !== any number.
The DUT is fine; the testbench skipped the reset job (9.5). A sequential unit must apply reset BEFORE meaningful stimulus, so the DUT begins each run from a defined state.
The fix is to add a reset sequence at the start: assert rst_n, hold it across a couple of edges, release it, THEN drive stimulus.
initial begin
rst_n = 0; en = 0; // assert reset first
@(posedge clk); @(posedge clk);
rst_n = 1; // release into a known state (count=0)
@(posedge clk); #1; check(4'd0);
en = 1;
@(posedge clk); #1; check(4'd1); // now counts from a defined start
@(posedge clk); #1; check(4'd2);
// ...
end
// With reset applied, count starts at 0 and the checks see real values.9. Interview Q&A
10. Exercises
Exercise 1 — Read the verdict
The ALU testbench from §3 prints FAIL or: a=240 b=15 op=11 -> y=240 (expected 255) then TEST FAILED (1 errors). What is wrong, and is the bug most likely in the DUT or the testbench? Justify from the diagnostic.
Exercise 2 — Complete a combinational pair
Given a design module comparator (input [7:0] a, b, output gt, eq, lt), write a self-checking testbench: declare the correct reg/wire signals, instantiate the DUT by name, drive at least three directed cases (a > b, a == b, a < b), check gt/eq/lt against expected, and print PASS/FAIL before $finish. (No clock needed — explain why.)
Exercise 3 — Complete a sequential pair
Given shift_reg (input clk, rst_n, in_bit, output [3:0] q) that shifts in_bit into q every clock, write a self-checking testbench with: a clock generator, a reset sequence, edge-synchronized stimulus shifting in a known bit pattern, post-edge sampling of q, a PASS/FAIL verdict, and a watchdog. State why the watchdog is required here but not in Exercise 2.
Exercise 4 — Diagnose three failing runs
For each verdict, name the most likely cause (DUT bug, edge-sampling race, or missing reset) and the fix: (a) count=x (expected 1); (b) count=0 (expected 1), intermittently; (c) y=4 (expected 5) on subtract, every run.
11. Summary
This capstone assembled the pieces of Chapter 9 into complete, verified units — a combinational ALU and a sequential counter, each a clean DUT wrapped by a self-checking testbench that proved it with a deterministic PASS/FAIL.
The core ideas:
- The unit of work is the verified pair — a design module plus the testbench that proves it, built and run together. The verdict, not the design alone, is the deliverable.
- Two shapes recur. Combinational units (no clock — drive, settle, check) and sequential units (clock, reset, edge-synchronized stimulus, post-edge sampling, mandatory watchdog). The self-checking and verdict are the same; only the timing machinery differs.
- Every Chapter 9 lesson appears in its place — the clean DUT (9.4), the named instance with reg/wire signals (9.2/9.3/9.5), clock-reset-stimulus (9.5), self-checking (9.5), and a deterministic verdict (8.5).
- Self-checking is what makes "passed" mean "correct" — the
checktasks, not the$displays, are the heart of the pair; they caught the ALU's off-by-one automatically.
The discipline this page instils:
- Ship the pair, not the module — a design without a passing self-checking testbench is unverified.
- Sample sequential outputs after the edge settles (
#1), reset before stimulus, and always end with$finishplus a watchdog when a clock runs. - Read the verdict, then loop — write, test, fix, re-test until the checks pass.
Chapter 9 — and the foundation arc — complete
With 9.6 you have finished Chapter 9 and, with it, the foundation arc of the Verilog track. Across Chapters 1–9 you learned to read Verilog, understand its data types and directives, drive a simulation with system tasks, and — in this chapter — organize hardware into modules, instantiate and wire them into hierarchies, write quality design blocks, and verify them with self-checking testbenches. You can now build and prove a small digital system end to end.
What you have not yet done is fill those modules with rich hardware logic — the operators, dataflow, and procedural constructs that describe real RTL. That is the RTL core, and it begins now:
Chapter 10 Operators → Dataflow (
assign) →always→ Blocking vs Non-Blocking → Combinational Logic → Sequential Logic.
Everything up to here taught you to read Verilog, run simulations, and assemble and verify modules. What follows teaches you to design the hardware inside them — and every block you build from Chapter 10 onward, you will verify with exactly the kind of self-checking testbench you assembled here.
Related Tutorials
- Testbench Creation Techniques — Chapter 9.5; the testbench techniques this page assembles into complete pairs.
- Design Module Fundamentals — Chapter 9.4; the clean DUTs used as the designs under test here.
- Simulation Control — Chapter 8.5; the
$finishand watchdog that close every run. - Design & Testbench Creation — Chapter 9 overview; the design-and-verify loop this capstone realizes end to end.
Standards & specifications
- Governing standard
- IEEE Std 1364 (Verilog)(opens IEEE in a new tab)
Defines the Verilog language and its simulation semantics, including the event scheduling model. Synthesis support is defined by tools, not by this standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Verilog HDL curriculum.
