Verilog Crash Course Lesson 6 / 8
Lesson 6 · Verification

Testbenches & Simulation

Learning objective Write a self-contained testbench that generates a clock, drives a design's inputs over simulated time, and reports results — and understand clearly why testbench code is allowed to break rules (like using blocking assignment freely, or delays) that would never be acceptable in the design itself.

A testbench is simulation-only, on purpose

Everything in Lessons 2–5 had to be synthesizable — it had to correspond to real gates and flip-flops. A testbench has no such constraint, because it never becomes hardware. Its only job is to exist inside the simulator: instantiate your design (the DUT, device-under-test), apply stimulus to its inputs over a simulated timeline, and check or display the results. Because it's simulation-only, a testbench is allowed to use constructs that have no physical meaning — most importantly, explicit time delays.

The initial block

You've only seen always blocks so far, which run forever, re-triggering on their sensitivity. An initial block runs exactly once, starting at simulated time 0, and — unlike everything else in this course — executes top-to-bottom like ordinary code, because it's a simulation script, not a hardware description:

initial begin
    rst = 1;
    #10 rst = 0;      // wait 10 time units, then deassert reset
    #10 tick = 1;
    #10 tick = 0;
end

#10 means "advance simulated time by 10 units before doing the next thing." This delay control has no synthesizable meaning at all — there's no such thing as an instruction that tells real hardware to "wait 10 nanoseconds before continuing." It only makes sense to a simulator's event engine. That's the line: if a construct only makes sense as a script being played back over simulated time, it belongs in a testbench, never in your design.

Generating a clock

An always block with a delay is the standard idiom for a free-running simulated clock:

reg clk = 0;
always #5 clk = ~clk;   // toggles every 5 time units -> 10-unit period

This looks like it violates Lesson 4's rule (blocking assignment on a signal being toggled repeatedly) — but it's testbench code with no clock driving it, so the sequencing concerns from Lesson 4 don't apply. It's a common, accepted idiom specifically for clock generation.

Full example: testing the traffic light FSM

`timescale 1ns/1ps

module traffic_light_tb;
    reg clk = 0;
    reg rst;
    reg tick;
    wire red, yellow, green;

    always #5 clk = ~clk;   // 10ns period

    traffic_light_fsm dut (
        .clk(clk), .rst(rst), .tick(tick),
        .red(red), .yellow(yellow), .green(green)
    );

    initial begin
        $dumpfile("traffic_light.vcd");
        $dumpvars(0, traffic_light_tb);

        rst = 1; tick = 0;
        @(posedge clk);
        rst = 0;

        repeat (6) begin
            @(posedge clk);
            tick = 1;
            @(posedge clk);
            tick = 0;
            $display("t=%0t  red=%b yellow=%b green=%b", $time, red, yellow, green);
        end

        $finish;
    end
endmodule

Full source: code/traffic_light_tb.v.

New constructs here, none of them synthesizable:

$display vs. $monitor $display prints once, at the moment its statement executes — you place it explicitly where you want a snapshot. $monitor is set up once and then automatically reprints its argument list every time any listed variable changes value, for the rest of the simulation. $monitor is convenient for "show me everything that happens"; $display is better for "show me the result at this specific checkpoint," as used above.

Sample console output from the testbench above (state advances by exactly one step per loop iteration):

t=25  red=0 yellow=0 green=1
t=45  red=0 yellow=1 green=0
t=65  red=1 yellow=0 green=0
t=85  red=0 yellow=0 green=1
t=105 red=0 yellow=1 green=0
t=125 red=1 yellow=0 green=0
$finish called at time : 135
Try it, for real If you have Icarus Verilog installed (free, open-source), run the FSM and its testbench:
iverilog -o sim traffic_light_fsm.v traffic_light_tb.v
vvp sim
gtkwave traffic_light.vcd
The first line compiles both files together; the second runs the simulation and prints the $display output; the third (needs GTKWave, also free) opens the recorded waveform so you can see clk, state, and the three light outputs as real timing diagrams.
Q: Why is it fine for a testbench to use # delays and top-to-bottom sequential logic in an initial block, when Lesson 1 established that Verilog generally isn't executed top-to-bottom?
It's not actually fine — testbenches secretly have the same concurrency rules as RTL. Because initial blocks are specifically optimized by simulators to run faster than always blocks. Because a testbench never gets synthesized into real hardware — it only has to make sense as a script the simulator plays back, so constructs with no physical meaning (like #10) are fine there even though they'd be meaningless in synthesizable RTL.

New terms — testbench, DUT, VCD file, timescale — are in the glossary.

Previous← Finite State Machines Contents⌂ All lessons