Logo
All chapters
Volume II: Digital Logic  ›  Combinational Logic

Writing a Simple Testbench

Code that drives inputs into a design and checks the outputs — how we verify logic.

PrevBehavioral Modeling
NextLogic Simulation

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

PieceRole
DUT instancethe design
reg inputsstimulus
wire outputsobserve
initial/# delaystiming
$display/$finishreport/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
endmodule

Self-checking testbench skeleton for a combinational adder.

Real-world applications

RTL verificationRegression testingCI for hardware

The 5 Whys

  1. 1

    Why a testbench? Verify behavior before fabrication.

  2. 2

    Why no ports? It is the top of the simulation, not a component.

  3. 3

    Why stimulus over time? To exercise input sequences.

  4. 4

    Why self-checking? Automatic pass/fail at scale.

  5. 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.
PrevBehavioral Modeling
NextLogic Simulation