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.
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.
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.
`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:
`timescale 1ns/1ps — a compiler directive fixing what one simulated time unit means
(here, 1 nanosecond, with 1 picosecond precision). Every # delay is measured in this unit.@(posedge clk) used inside an initial block — "pause this script here until
the clock next rises." This is the standard way to synchronize stimulus to the clock instead of
guessing delay values.repeat (6) begin ... end — a loop, exactly like software. Totally normal in a
testbench; loops describing hardware structure (Lesson 7) are a different, synthesizable idea using
generate.$display — print once, when this line executes, printf-style (%b binary,
%d decimal, %h hex, %0t current simulation time).$dumpfile / $dumpvars — record every signal change to a
VCD file (Value Change Dump), which a waveform viewer like GTKWave
can render as the timing diagrams you're used to seeing in a digital logic textbook.$finish — end the simulation. Without it, a simulator with no other stopping condition
may run forever (particularly dangerous with a free-running clock always block).$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
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.
# delays and top-to-bottom sequential logic in an initial block, when Lesson 1 established that Verilog generally isn't executed top-to-bottom?New terms — testbench, DUT, VCD file, timescale — are in the glossary.