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
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.