Visual walkthrough — Testbenches and simulation
Step 0 — The three words every snippet below uses
Before the very first line of code, let us name the pieces you will see over and over. Nothing on this page uses a symbol we have not first spelled out here.
With those four words defined, every snippet ahead is readable line by line.
Step 1 — A clock is just a square wave in simulated time
WHAT. Before anything ticks, we need a heartbeat. A clock is a single wire whose value flips between 0 and 1 over and over, forever.
WHY. Our device under test — the DUT — is a sequential circuit (see Clocking and Sequential Logic). It only does work on a rising edge: the instant the clock goes from 0 up to 1. No clock, no edges, no ticking. So the very first job of the testbench is to manufacture this heartbeat.
PICTURE. Look at the figure. Time runs left to right along the horizontal axis, measured not in real seconds but in simulated time units. The clock (magenta) sits low for a while, then jumps high, then low again. Each up-jump is a rising edge, marked with an orange arrow.

The code that makes this shape is two lines — the initial runs once to set the starting level, the always loops forever to keep flipping (both defined in Step 0):
initial clk = 0; // runs once: start it low so the first edge rises
always #5 clk = ~clk; // loops forever: flip every 5 unitsStep 2 — Period and frequency fall straight out of the picture
WHAT. From the toggle delay we read off two numbers everyone asks for: the period (how long one full up-and-down cycle lasts) and the frequency (how many cycles per second).
WHY. We toggle every units, but one complete cycle is a low half plus a high half. So the natural period is not — it is two of them. Getting this factor of 2 right is the difference between a "100 MHz" claim and a "50 MHz" reality.
PICTURE. The figure brackets the low half () and the high half () and sums them into one period . Below it, frequency is shown as "how many of these brackets fit in one second."

Why the reciprocal and not some other operation? Because "period" and "frequency" are literally the two sides of the same trade: seconds per event vs events per second. Flip one over and you get the other — nothing deeper.
Step 3 — Reset first, or the counter starts as a question mark
WHAT. Our DUT is a 4-bit counter. Before we trust any output, we hold its reset line high for one clock cycle, then let go. All of this lives inside one initial block (Step 0) — the one-time setup that runs before the checking loop.
WHY. A flip-flop that has never been reset holds the value x — Verilog's "unknown." And x + 1 is still x. If we skip reset, the counter is unknown forever and every check fails for a silly reason. Reset is how we force a known starting point of 0.
PICTURE. The figure shows the counter output q as a fog of x (grey hatching) while reset (violet) is high. The moment reset drops on a clean edge, the fog clears and q becomes a solid 0.

initial begin
rst = 1; // one-time setup: hold reset high
@(posedge clk); #1; // wait one rising edge, +1 unit so it commits
rst = 0; // release -> counter now starts counting from 0
// ... the checking loop of Step 5 continues here ...
endStep 4 — The event queue: why the simulator jumps instead of crawls
WHAT. The simulator does not check the circuit every nanosecond. It keeps a sorted list — the event queue — of "things that will happen and when," and hops from one to the next.
WHY. Most wires sit perfectly still most of the time. Re-evaluating a silent gate a billion times a second is pure waste. So the engine only wakes up when a signal actually changes (an event), does the minimum work, schedules any knock-on changes, then leaps to the next scheduled moment. This is what "event-driven" means.
PICTURE. The figure draws the timeline as a row of stepping-stones at . Between the stones there is nothing to compute, so the simulator's foot (orange arrow) jumps clean over the gaps and lands only where an event lives.

Step 5 — The #1 trick: sampling after the edge, not on it
WHAT. After each rising edge we wait one extra time unit (#1) before reading q and comparing it. The comparison runs inside a repeat loop — a Verilog construct that simply repeats its body a fixed number of times.
WHY. Exactly at the edge, two versions of q momentarily coexist across delta cycles: the old value the flip-flop is leaving and the new value it is taking. If we peek at that instant we might grab the stale one — a race condition. Nudging one unit forward guarantees the new value has committed and every delta cycle has drained.
PICTURE. Zoomed in on one edge: at exactly, q is drawn as a blurred overlap of 3 and 4. One tick later, at , it is a crisp 4. The green "SAFE to sample" marker sits on the crisp side.

repeat (20) begin // repeat: run this body 20 times
@(posedge clk); #1; // land safely after the edge
expected = expected + 1; // our own running "correct answer"
if (q !== expected[3:0]) // compare hardware vs golden
$error("t=\%0t q=\%0d exp=\%0d", $time, q, expected[3:0]);
endWhy compare against a separately computed expected instead of trusting the DUT? Because a checker that re-uses the DUT's own logic would rubber-stamp its bugs. Independence is the whole point of verification (deepened in Functional Coverage and Verification).
Step 6 — Edge case: the counter wraps at 15 → 0
WHAT. A 4-bit counter can only hold 0 to 15. After 15 the very next tick rolls it back to 0, not 16.
WHY. Four bits represent distinct values. There is no bit to hold "16," so the count wraps around — like a car odometer rolling 9999 → 0000. If our golden expected didn't wrap the same way, we'd get a false failure precisely at this corner.
PICTURE. The figure shows q climbing 13, 14, 15 and then dropping sharply to 0 on the next edge — the classic sawtooth. The wrap point is circled and both the DUT and the expected[3:0] are shown wrapping together.

Step 7 — Degenerate case: no $finish, and the sim never ends
WHAT. The clock generator always #5 clk = ~clk; has no stopping condition. Something must explicitly kill the run.
WHY. An always block loops forever by design — that is what a real clock does. So even after our repeat (20) checking loop (Step 5) finishes, the event queue is never empty: there is always another clock toggle scheduled 5 units ahead. Without $finish the simulator happily runs until you force-quit it.
PICTURE. The figure contrasts two timelines. Top (magenta, "runaway"): the event queue keeps refilling itself, arrows marching off the right edge forever. Bottom (green, "clean"): $finish at empties the queue and the timeline stops with a solid end-cap.

$display("TEST DONE");
$finish; // drain the queue on purpose -> simulation haltsThe one-picture summary
Everything above, compressed into a single frame: the magenta clock ticking, the violet reset clearing the x-fog, the counter sawtooth climbing and wrapping, the green #1 sample points, and the $finish end-cap draining the queue.

Recall Feynman retelling — say it back in plain words
We started with an empty screen and built a heartbeat: a wire that flips 0/1 forever, one flip every units, so a full cycle is long and the frequency is one-over-that. That heartbeat has rising edges, and our counter only wakes up on those edges — but a fresh flip-flop is an unknown x, so first we shove reset high for one cycle to nail it to 0. The simulator underneath isn't crawling nanosecond by nanosecond; it keeps a to-do list sorted by time and hops from event to event, and inside a single instant it quietly re-orders things across "delta cycles" — which is why a <= swap works but a = swap doesn't. Because of that ordering, right on a clock edge the old and new counter values briefly blur together, so we wait one extra tick — the #1 trick — and only then read q and compare it, with !== (which also catches x/z garbage), against a running number we increment ourselves (the golden reference). We made sure our golden number wraps at 16 exactly like the 4-bit hardware does, so the 15 → 0 corner stays green. And because the clock loops forever, we finish with $finish on purpose, otherwise the event queue never empties and the run goes on all night.
Recall Quick self-quiz
Why must q be a wire but clk a reg in the testbench? ::: The DUT continuously drives q (so it needs a wire that reflects its driver), while the testbench itself assigns clk in an always block (so it needs a reg that holds the value we last wrote).
If you toggle the clock every 8 ns, what is the frequency? ::: ns, so MHz.
Why sample with #1 after the edge instead of exactly on it? ::: On the edge, old and new values coexist across delta cycles (a race); one tick later the new value has committed and is safe to read.
What forces the counter to a known value before checking begins? ::: Holding rst high for one cycle clears the x fog and sets q = 0.
What is the difference between x and z? ::: x is unknown (could be 0 or 1), z is high-impedance (floating, driven by nobody).