Here is the industry's uncomfortable arithmetic: on a serious chip project, more than half of the total engineering effort goes into verification — often nearer two-thirds. More people, more compute, more calendar time than writing the design itself. The reason is brutal economics: a software bug ships a patch; a silicon bug ships a recall, with mask sets costing millions and respins costing months. So the industry built an entire discipline around one question — how do we know the RTL is right before we pay for it? — and this module is that discipline. It begins with its humblest, most important artifact: the testbench.
A testbench is a SystemVerilog module that wraps the design under test (DUT) — it
instantiates your design, wiggles its inputs, and watches its outputs. Two things make it unlike any
module you wrote in Module 1. It has no ports: nothing outside it exists; it is the
whole universe as far as the DUT can tell. And it is never synthesized — it lives
entirely in
initial, # delays, $error) is not
just allowed but the whole point. Here is a complete self-checking bench for Module 1's
full_adder:
Read the three verbs, because every testbench ever written has exactly these three jobs:
drive (apply stimulus to the DUT's inputs), wait (let virtual time
pass so the design reacts), and check (compare the DUT's outputs against what they
should be). The check compares against a + b + cin — an independent,
obviously-correct description of the intent. That little expression is doing enormous work, and it
gets a name in a moment.
A sequential DUT needs a heartbeat, and the bench supplies it with the most famous three lines in verification:
forever #5 clk = ~clk toggles the clock every 5 time units — a full period of 10, high
for 5 and low for 5, forever. The second initial block runs concurrently (the
bench may have many, all in parallel — this is the simulation dialect flexing), holding reset across
the first two edges so every register wakes into a known state before stimulus begins.
The picture to burn in: stimulus flows into two places — the DUT and the reference
model (also called the golden model): a separate, simpler description of
intent, like our a + b + cin. The checker's job is a single question asked forever:
do they agree? Every disagreement is a bug — in the DUT, or occasionally in the model, and
either way you learned something real.
Here is that architecture as running code: a gate-level DUT (with a planted bug), a golden model, a loop of stimulus, and pass/fail accounting. This shape — drive both, compare, count — is the skeleton of every serious verification environment on Earth:
The buggy carry term only matters when ai & carry would have fired — some inputs
sail through, which is exactly why counting a few passes proves nothing and regressions run
many inputs. Note the printed seed: rerunning with the same seed replays
the identical stimulus, so a failure found at 3 a.m. is reproducible at 9 a.m. — a theme the
What is the simulator actually doing? It is an event-driven program: it keeps an event queue ordered by virtual time, and only ever computes where something changed — a signal toggle schedules evaluations of just the blocks that read it, which may schedule further updates in the same instant (the zero-duration bookkeeping rounds are called delta cycles), until the moment quiesces and time jumps to the next event. That is why simulating a billion-gate chip is possible at all: at any instant, almost nothing is changing.
And benches are not run once. Teams accumulate them into a regression suite — hundreds or thousands of tests rerun automatically on every change to the RTL, exactly like a software CI pipeline but measured in compute-farm-nights. A change that breaks a test three subsystems away is caught before breakfast. The regression suite is the project's memory of every bug ever fixed.
#, @(posedge clk)), check outputs;$error on every mismatch;Here is a fact that surprises every student: at most chip companies, verification engineers outnumber designers — sometimes two to one. And it is not the junior job. A great verification engineer is part detective (what input sequence would expose this bug?), part adversary (the designer says it works; assume it doesn't), part software architect (a modern bench is a bigger program than the design it tests). The mindset flip is the fun of it: a designer's good day is "nothing failed"; a verification engineer's good day is "I broke it" — every failure found in simulation is a failure that will never brick a product in the field. The industry's grim koan: if you haven't found a bug this week, your testbench is broken.
The classic beginner bench drives some inputs, dumps waveforms, and prints "test done". The
engineer scrolls the waves, they look plausible, ship it. This is not a test — it is a
screensaver. Eyeball-checking does not scale past ten cycles, is not repeatable, and
silently rots: change the RTL next month and nobody re-stares at the waves. The iron rule:
if it isn't self-checking, it isn't a test. Every bench must know the correct
answer (a golden model, a stored expected trace, an invariant) and must fail loudly —
$error, non-zero exit, red in the regression dashboard — when reality disagrees.
Waveforms are for debugging a failure after a self-check catches it, never for deciding
that things pass. A thousand-test regression of self-checking benches is knowledge; a folder of
waveform dumps is décor.