Testbenches
Driving inputs and checking outputs
A Verilog testbench is a module with no ports that instantiates the design under test, drives its inputs over simulated time inside an initial block, and observes the outputs with $display or $monitor (ending on $finish), so a simulator can verify the design's logic before it is built.
Build it in the lab →You can write combinational and sequential Verilog. How do you know it is right? You simulate it: build a testbench, a module whose only job is to feed the design inputs and watch its outputs. It is the same idea as sweeping a truth table or driving a waveform on the canvas, written as code so it runs automatically.
A module that tests another module
A testbench is a module with no ports (nothing connects to the outside, it is the top of the simulation). Inside, it declares signals to drive the design under test (the DUT), instantiates the DUT wiring those signals to its ports, then uses an **
initial block** to apply a sequence of inputs over time. Here is a testbench for the XOR gate you wrote:module xor_gate_tb;
reg A, B;
wire F;
xor_gate dut(.A(A), .B(B), .F(F));
initial begin
A = 0; B = 0; #10;
A = 0; B = 1; #10;
A = 1; B = 0; #10;
A = 1; B = 1; #10;
$finish;
end
initial
$monitor("A=%b B=%b F=%b", A, B, F);
endmoduleRead it top to bottom.
reg A, B; are the inputs the testbench drives (assigned procedurally, so they must be reg); wire F; is the output the DUT drives. The line xor_gate dut(.A(A), .B(B), .F(F)); creates one copy of the gate named dut and connects each of its ports to a testbench signal by name. The initial block runs once at time zero: set the inputs, wait #10 time units, change them, and so on, then $finish stops the simulator.Seeing the results
$monitor is a system task (the $ marks a simulator command, not hardware) that reprints its line every time one of the listed signals changes, so you get a running log: A=0 B=0 F=0, A=0 B=1 F=1, and so on, exactly the XOR truth table. $display prints once when reached; $finish ends the run. To check automatically instead of eyeballing, you add an assertion: if (F !== expected) $display("FAIL ...");. A testbench that self-checks is a regression test for your hardware.Format specifiers: printing values your way
$display and $monitor take a format string first, then one argument for each % code in it, exactly like C's printf. The string is plain text with % codes punched into it, and each code says how to render the next value. A typical line reads $display("t=%0t a=%b sum=%d", $time, a, sum);, which pairs %0t with $time, %b with a, and %d with sum, left to right.%bprints the value in binary, every bit shown (a five-bitsumof 8 prints as01000).%dprints it in decimal (that samesumprints as8);%dright-justifies to the value's width, and%0dtrims the leading pad spaces.%hprints it in hexadecimal (sumof 8 prints as08, two hex digits for a five-bit bus).%0tprints the current simulation time from the$timesystem function; the0asks for minimum width, so you see10and not a padded10.- Any text that is not a
%code is printed literally (thet=, thea=, and the spaces);%sprints a string argument and\nstarts a new line.
$time returns the current simulated time as an integer (its unit is set by the design's timescale), which is why testbench logs usually lead with %0t: you can see exactly *when* each line printed. Here is a compact testbench for a four-bit adder add4 (inputs a, b, output sum) that drives three input pairs and prints each result:module add4_tb;
reg [3:0] a, b;
wire [4:0] sum;
add4 dut(.a(a), .b(b), .sum(sum));
initial begin
a = 4'd3; b = 4'd5; #10; // 3 + 5
a = 4'd9; b = 4'd7; #10; // 9 + 7
a = 4'hF; b = 4'h2; #10; // 15 + 2
$finish;
end
initial
$monitor("t=%0t a=%0d b=%0d sum=%0d (bin %b, hex %h)",
$time, a, b, sum, sum, sum);
endmoduleBecause
$monitor reprints every time one of the listed signals changes, that single statement logs the entire run for you:t=0 a=3 b=5 sum=8 (bin 01000, hex 08) t=10 a=9 b=7 sum=16 (bin 10000, hex 10) t=20 a=15 b=2 sum=17 (bin 10001, hex 11)
Contrast that with
$display, which prints its line once, wherever execution reaches it. So you reach for $display for one-shot messages (a header row, a final PASS/FAIL verdict) and for $monitor when you want a continuous trace that follows the signals on its own. Only one $monitor is active at a time, and it watches only the signals named in its argument list.initial blocks and # delays are simulation only, they are not synthesisable and never become gates. That is correct here: a testbench is not hardware, it is the scaffolding that exercises hardware. Never put #10 delays or initial stimulus in a module you intend to synthesise; keep testbench code and design code in separate files.Why this matters: in real HDL work you write as much testbench as design. Every block, a gate, an adder, a register, the whole CPU, is verified by a testbench that drives known inputs and checks the outputs before a chip is ever made, because fixing a bug in simulation costs seconds and fixing it in silicon costs months. Simulation is the safety net under everything you have built.
Try it
In the testbench, why is
A declared reg but F declared wire? What would go wrong if you swapped them?Answer
A is a reg because the testbench drives it procedurally inside the initial block, and only regs can be assigned in a procedural block. F is a wire because the DUT drives it (through its output port); the testbench only observes it. Swapping them breaks both: you cannot assign a wire (A) inside initial, and a module output port cannot connect to a reg externally, F must be a wire (a net) for the DUT to drive it. Drivers connect to wires; procedurally-assigned stimulus is reg.Frequently asked
What is a testbench in Verilog?
A testbench is a module with no ports that instantiates the design under test, drives its inputs over simulated time in an initial block, and observes the outputs with $display or $monitor. It verifies a design in simulation before it becomes hardware, the coded equivalent of sweeping a truth table by hand.
What is the difference between $display and $monitor?
$display prints its line once, when execution reaches it. $monitor prints its line every time one of the listed signals changes, giving a running log of the simulation. Both are system tasks (marked with $) that only run in simulation, not in synthesised hardware.
Why are the DUT inputs reg and the outputs wire in a testbench?
The testbench drives the inputs procedurally inside an initial block, and only a reg can be assigned there. The design under test drives its own outputs through its ports, so those must be wires that the testbench observes. Drivers connect to wires; procedurally-assigned stimulus is reg.
Are initial blocks and # delays synthesisable in Verilog?
No. initial blocks and # time delays are simulation-only constructs used to write testbenches; they never become gates. That is fine because a testbench is not hardware. Design code you intend to synthesise must avoid them and is kept in separate files from the testbench.
What do %b, %d, and %h mean in a Verilog $display?
They are format specifiers that pick the base for the next value: %b prints binary (every bit), %d prints decimal, and %h prints hexadecimal. A five-bit value of 8 prints as 01000 with %b, as 8 with %d, and as 08 with %h. Text and spaces in the format string print literally, and %0d removes the leading pad spaces from a decimal field.
What does $time print in a Verilog testbench?
$time is a system function that returns the current simulation time as an integer, so a line like $display("t=%0t ...", $time, ...) stamps each print with when it happened. The %0t code formats a time value at minimum width, and the actual unit (nanoseconds, picoseconds) is set by the design's timescale directive.
You've got the theory. Now build it from scratch and watch it work.
Build it in the lab →