3.5.5 · D5HDL & Digital Design Flow
Question bank — Testbenches and simulation
Recall What every trap here really tests
Almost every mistake in verification comes from forgetting one of three facts: (1) simulation time is virtual and event-driven, (2) at a clock edge old and new values momentarily coexist across delta cycles, (3) the testbench is a program that runs, not hardware that gets built. Keep those three in view.
True or false — justify
A testbench module should declare ports so the simulator can connect stimulus to it.
False. The TB is the top of the hierarchy — nothing sits above it to wire ports to, so it has no ports (
module tb;). Stimulus is generated inside it, not passed in from outside.A testbench must be synthesizable so the tools can check it.
False. The TB is deliberately non-synthesizable; constructs like
initial, #delay, $display, and $finish have no hardware meaning. It exists only to run in the simulator and drive the synthesizable DUT.Signals the TB drives into the DUT must be declared as wire.
False. Driven signals need
reg/logic because a procedural block (initial/always) assigns and holds them. wire is for signals the DUT drives, which the TB only observes.#10 advances the real clock by 10 nanoseconds.
False.
#10 advances simulation time by 10 time units, which are virtual; the mapping to nanoseconds depends on the `timescale directive, and it has nothing to do with wall-clock time.An event-driven simulator re-evaluates every gate once every time unit.
False. It only recomputes a signal when an input event occurs, then jumps to the next scheduled event. Most signals sit idle, so stepping every unit would be pure waste.
Because the DUT's flip-flops use <=, the testbench stimulus should also use <=.
False. In TB stimulus you want inputs to change immediately at a given time, so use blocking
=. Non-blocking <= schedules the change for the end of the delta and, combined with same-edge sampling, causes off-by-one confusion. See Blocking vs Non-blocking Assignments.!= and !== behave identically when comparing a DUT output to an expected value.
False.
!== also detects x (unknown) and z (high-Z), while != returns x (ambiguous) when either operand has them — so != can silently miss an uninitialised output.Waveform-staring is the most valuable part of verification.
False. About 90% of bug-catching value comes from good stimulus plus automatic self-checking; waveforms are the slow diagnostic tool you reach for after a self-check fires.
A for loop that drives all combinations of a 2-input gate is a complete functional test.
True (for that gate). With only 4 input combinations, exhaustive testing is cheap and total. But this stops being feasible as inputs grow — 32 inputs means vectors, which is why we move to constrained-random and coverage.
Delta cycles cause simulation time to advance.
False. Delta cycles are zero-time ordering steps that let combinational logic ripple; time does not advance across deltas — only event ordering does within the same instant.
A picture of delta cycles

Spot the error
always #5 clk = ~clk; with no initial clk = 0; — what breaks?
clk starts at x, so ~x = x forever — the clock never produces a valid posedge, and every sequential DUT stays stuck at x.The stimulus loop ends but the sim never terminates. Which line is missing?
$finish;. The free-running clock generator (always toggling every 5 units) runs forever, so without an explicit $finish the simulator keeps scheduling clock events after your test is logically done.@(posedge clk); if (q !== expected) $error(...); — the check sometimes fails spuriously. Why?
You sampled exactly at the edge, where the flip-flop's old and new values coexist in different delta cycles — a race. Add
#1 (or sample on @(negedge clk)) to read the committed value. See Clocking and Sequential Logic.counter dut(clk, rst, q); connected positionally, then someone reorders the module ports. What happens?
The signals silently mis-wire (e.g.
rst connects to clk) with no error. Named connection .clk(clk) is safe against port reordering.Reset is applied but never de-asserted: rst = 1; and the loop begins. What do you observe?
q stays at 0 every cycle because reset overrides the increment on every edge — the counter never counts, and the self-check fails from cycle one.{a, b} = i; where i is a 32-bit integer and you expect only 2 bits. Safe?
Only the low 2 bits land in
{a,b} here, so it happens to work — but relying on implicit truncation is fragile. Write {a,b} = i[1:0]; to make the width explicit.initial begin rst=1; #1; rst=0; end with no @(posedge clk) between — reset for a synchronous DUT. Problem?
Reset may be de-asserted before any clock edge samples it, so a synchronous reset never actually takes effect. Hold reset across at least one
@(posedge clk).Why questions
Why must the TB have no ports?
Because it is the top-level module of the simulation hierarchy — there is nothing above it to connect ports to. All stimulus is generated internally.
Why do we use blocking = for stimulus but the DUT uses non-blocking <=?
Stimulus should change values now, deterministically, so blocking
= is correct. Sequential DUT logic uses <= so all flip-flops read old values and update together at the end of the delta, enabling clean simultaneous updates and swaps.Why sample the DUT output slightly after the clock edge (#1) rather than on it?
At the exact edge the flip-flop's new value hasn't settled in the same delta step; sampling a hair later reads the stable, committed value and dodges the edge race condition.
Why prefer !== over != in a self-check?
!== distinguishes x/z explicitly, so an uninitialised or floating output is caught; != would return x and can let the bug slip through as "not definitely unequal."Why event-driven simulation instead of a fixed time step?
Because most signals are idle most of the time; only recomputing on actual events, and jumping straight to the next scheduled event, is dramatically faster than re-evaluating everything every nanosecond.
Why is a self-checking TB worth more than a directed one that just dumps waveforms?
Self-checking compares against a golden reference automatically and tells you pass/fail, so bugs surface instantly and regressions are re-runnable without a human re-reading waveforms.
Why do we still need reset even in a purely combinational DUT test?
We don't — combinational logic has no state, so no reset is needed; it just needs enough settle delay (
#10) before checking. Reset only matters for sequential/stateful DUTs. See HDL Basics — Verilog and VHDL.Why does $dumpvars(0, tb) use 0?
The
0 means dump all hierarchy levels below tb, so every signal in the design and TB is recorded to the VCD — full visibility when a self-check fails.Edge cases
What does q + 1 produce if q is still x (no reset applied)?
x. Any arithmetic with an unknown propagates unknown, so q stays x for the entire run and the check never sees a valid count.For a DUT with a synchronous reset, does asserting rst for zero clock edges reset it?
No. A synchronous reset only acts on a clock edge, so you must hold
rst across at least one @(posedge clk) for it to register.If two stimulus statements are scheduled at the same simulation time with <=, in what order do their effects apply?
Their updates are all deferred to the end of the current delta, so they apply together using the old right-hand-side values — order between them can't be relied on for read-then-write semantics.
Is #0 a real time delay?
No —
#0 advances zero time but forces the statement into a later delta cycle at the same instant, useful (and dangerous) for ordering. It does not wait any simulated time.A constrained-random TB hits a bug your directed tests missed. Was the directed test wrong?
Not wrong — just incomplete. Directed tests cover cases you thought of; constrained-random plus coverage reaches the ones you didn't, which is why both styles coexist.
What is the smallest legal exhaustive test for a 3-input combinational gate, and when does exhaustive testing stop being practical?
vectors — still trivial. Exhaustive testing becomes impractical roughly past ~20 inputs ( grows explosively), where assertions and random stimulus take over.
If you forget $dumpfile/$dumpvars, does the self-check still work?
Yes — self-checking is independent of waveform dumping. You simply lose the VCD you'd use to visually diagnose a failure after it fires.
Back to parent: Testbenches and simulation.