Every testbench needs the same six parts. The testbench below shows them working together.
Clock generator, defined once.
Reset sequence with a clear assert and release.
DUT instance, connected by name.
Stimulus, driven at well-defined times relative to the clock.
Checks that compare against expected values and count errors.
Watchdog that ends the simulation if the test hangs, and a final verdict.
Read the chart top to bottom; the numbers match the list above and the numbered comments in the testbench below.
Three inputs, one DUT: the clock, the reset sequence and the stimulus are the only things that drive it.
Two ways out: the normal path goes through the checks to a PASS or FAIL verdict; the watchdog is the second exit, for a test that hangs.
The testbench has no ports. It is the top of the simulation, generating the clock and observing the DUT.
Use detailed ERROR diagnostics, then one final greppable PASS: or FAIL: verdict. Normal completion calls $finish; unexpected failure calls $fatal(1, ...) so standalone automation also gets a failing status. A watchdog is an alternative failure exit, not another success verdict.
Declare timeunit 1ns; timeprecision 1ps; in the testbench. For this 10 ns clock, #1 samples 1 ns after an edge and before the next edge; it is a teaching observation delay, not a physical gate delay.
Put checks in a task so every failure prints the same useful message (what, when, got, expected).
A testbench without a watchdog can run forever when the DUT never produces the event you wait for. Always add one.
Count errors instead of stopping at the first. A run that shows all failures is easier to debug.
#1 after @(posedge clk) before calling check?Its clocked assignments update in the NBA region of the edge's timestamp. Sampling 1 ns later observes the settled outputs in this zero-delay RTL example. The delay must remain shorter than the next relevant edge; it is not a universal race fix or a timing guarantee. Larger environments use explicit clocking/sampling contracts.
A counter testbench exercising reset, counting and hold.
module counter (
input logic clk, rst_n, en,
output logic [3:0] count
);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) count <= '0;
else if (en) count <= count + 1'b1;
endmodule