Verilog Testbenches

A testbench is a Verilog module written to simulate and verify a design. Unlike RTL modules, a testbench is not intended for synthesis — it exists purely in the simulation domain and provides the stimulus needed to validate the Design Under Test (DUT).

Testbenches are the primary tool for catching functional bugs before committing to costly FPGA bitstreams or silicon tape-outs.

What is a Testbench?

A testbench wraps the DUT and drives its inputs with controlled sequences of values while observing the outputs. The basic structure is:

  • Instantiate the DUT.

  • Declare reg signals for the DUT’s inputs (so the testbench can drive them).

  • Declare wire signals for the DUT’s outputs (so the testbench can observe them).

  • Drive inputs using an initial or always block.

  • Check outputs with $display, $monitor, or assertion-like conditionals.

module tb_and_gate;

    // Testbench signals
    reg  a;
    reg  b;
    wire y;

    // Instantiate the Design Under Test
    and_gate dut (
        .a(a),
        .b(b),
        .y(y)
    );

    // Apply stimulus
    initial begin
        $dumpfile("and_gate.vcd");
        $dumpvars(0, tb_and_gate);

        a = 0; b = 0; #10;
        a = 0; b = 1; #10;
        a = 1; b = 0; #10;
        a = 1; b = 1; #10;

        $finish;
    end

endmodule

Clock Generation

Sequential designs require a clock signal. A common technique is to toggle a reg every half-period in an always block:

module tb_counter;

    reg clk;
    reg reset;
    wire [3:0] count;

    // Instantiate DUT
    counter dut (
        .clk(clk),
        .reset(reset),
        .count(count)
    );

    // Generate a 10ns-period clock
    initial clk = 0;
    always #5 clk = ~clk;

    // Apply reset and run
    initial begin
        reset = 1;
        #20;
        reset = 0;
        #100;
        $finish;
    end

endmodule

The #5 delay means the clock toggles every 5 time units, giving a full period of 10 time units. Adjust the delay to match the DUT’s timing requirements.

Reset Initialization

Always initialize critical signals, especially reset, before releasing them. Failure to reset can leave flip-flops in unknown (X) state, causing simulations to produce meaningless results.

initial begin
    reset = 1;
    #20;            // Hold reset for 2 clock cycles (at 10ns period)
    reset = 0;
end

Checking Output Behavior

Use Verilog system tasks to observe and verify signals during simulation.

$display

Prints a message once when the line executes:

initial begin
    #30;
    $display("Time=%0t count=%b", $time, count);
end

$monitor

Automatically prints whenever any of its arguments change. Useful for continuous monitoring:

initial begin
    $monitor("Time=%0t a=%b b=%b y=%b", $time, a, b, y);
end

Assertion-style checking

Use if statements to assert expected values and report failures:

initial begin
    a = 1; b = 1; #10;
    if (y !== 1'b1)
        $display("FAIL: expected y=1 at time %0t", $time);
    else
        $display("PASS: y=1 correct");
end

Waveform Dumping

Most simulators can output a VCD (Value Change Dump) file that records signal transitions over time. This file can be loaded into a waveform viewer such as GTKWave to visualize the simulation:

initial begin
    $dumpfile("simulation.vcd");  // Output file name
    $dumpvars(0, tb_top);         // Dump all variables in tb_top hierarchy
end

The first argument to $dumpvars is the scope depth (0 means all levels). The second argument is the top-level module scope.

Complete Testbench Example

The following testbench tests a 4-bit counter with synchronous reset:

module tb_counter_full;

    reg        clk;
    reg        reset;
    wire [3:0] count;

    // Instantiate DUT
    counter dut (
        .clk(clk),
        .reset(reset),
        .count(count)
    );

    // Clock generation (10ns period)
    initial clk = 0;
    always #5 clk = ~clk;

    // Waveform dump
    initial begin
        $dumpfile("counter.vcd");
        $dumpvars(0, tb_counter_full);
    end

    // Monitor output
    initial $monitor("Time=%0t reset=%b count=%d", $time, reset, count);

    // Stimulus and checks
    initial begin
        // Apply reset
        reset = 1;
        @(posedge clk); #1;
        @(posedge clk); #1;
        reset = 0;

        // Count for several cycles
        repeat (20) @(posedge clk);

        // Apply reset again
        reset = 1;
        @(posedge clk); #1;
        if (count !== 4'd0)
            $display("FAIL: counter did not reset");
        else
            $display("PASS: counter reset correctly");

        reset = 0;
        repeat (5) @(posedge clk);

        $finish;
    end

endmodule

Running the Simulation

With Icarus Verilog (iverilog), compile and run your testbench as follows:

iverilog -o sim counter.v tb_counter_full.v
vvp sim

This produces console output from $display and $monitor, and generates a counter.vcd file if the dump commands are present. Open the VCD file in GTKWave to view waveforms:

gtkwave counter.vcd

See also