Verilog · Chapter 8.5 · System Tasks & Functions
Simulation Control in Verilog — $finish, $stop, Watchdogs & Clean Termination
A Verilog simulator does not stop when the design goes idle. It keeps running until something tells it to stop, and controlling that decision is your job. This lesson covers the simulation-control tasks that end a run cleanly, pause it for interactive debug, and guard every regression with a watchdog timeout so a deadlocked design can never burn compute forever. It also shows how to report a deterministic pass or fail verdict that an automated farm can read, and how batch and interactive runs differ. None of this describes hardware. All of it is simulation engineering, the discipline of owning the terminate phase of a run. You will finish able to end simulations predictably and keep regression farms healthy and fast.
Foundation16 min readVerilog$finish$stopSimulation ControlWatchdogRegressionVerification
Chapter 8 · Section 8.5 · System Tasks & Functions
1. The Engineering Problem
A regression job has been running for six hours. The grid scheduler shows it pinned at 100% CPU. An engineer opens the log: the directed test finished at simulation time 200 ns, printed TEST PASSED, and then — nothing. No more output. The DUT is idle. The simulator is still running, six hours of wall-clock later, advancing nothing.
The test did not crash. It did not deadlock the DUT. It simply never told the simulator to stop. After the last stimulus event, the testbench had a free-running clock generator (always #5 clk = ~clk;) and no $finish. So the event queue was never empty — every 5 ns the clock posted another event — and a simulator runs until its event queue is empty or a control task ends it. Neither happened. The job ran until the farm's hard wall-clock limit killed it, six hours and one wasted licence-seat later.
This is the defining fact of simulation control:
A simulator only stops when something tells it to stop. "The test is done" is not a condition the simulator can detect. You know the test is done; the simulator only knows the event queue is non-empty. Ending the run is an explicit act, and owning it is what this page teaches.
Everything that follows — $finish, $stop, watchdog timeouts, pass/fail reporting — exists to make the terminate phase of a simulation a deliberate, deterministic event instead of an accident.
2. Mental Model — The Simulation Lifecycle
3. The Simulation View — What "Stop" Means to the Simulator
A simulator advances by draining a time-ordered event queue. It stops under exactly two conditions:
| Stop condition | What it means | Typical cause |
|---|---|---|
| Event queue empties | No future events remain to process | All stimulus done and no free-running clock |
| A control task fires | $finish or $stop executes | Explicit termination in the testbench |
In practice the first condition almost never occurs in a real testbench, because the clock generator (always #5 clk = ~clk;) posts a new event every period, forever. So $finish is effectively the only way a real simulation ends. Understanding this kills the most common beginner assumption — that a simulation "ends when the test finishes." It does not; it ends when the queue is empty (which a live clock prevents) or when you end it.
$finish and $stop are simulation-only system tasks — they have no hardware meaning and are stripped/ignored by synthesis. Like the rest of Chapter 8, they belong to the verification and run-control layer, never to the synthesizable design.
4. $finish — End the Simulation
$finish terminates the simulation and returns control to the operating system / shell. It is the normal, clean end of a run.
$finish; // end the run, default diagnostics (level 1)
$finish(0); // end, print nothing
$finish(1); // end, print simulation time and location (default)
$finish(2); // end, print time, location, and memory/CPU statisticsThe optional argument controls only the diagnostic message the simulator prints as it exits — it does not set the OS exit code (that is simulator-specific; see §10). Semantics:
- Fires once, immediately, when executed. The current time-step completes; no further events are processed.
- Exits the simulator process — control returns to the shell / regression script. The simulator binary terminates.
- Default
$finish(1)prints the final simulation time and the file/line that called it — useful for confirming where a run ended.
$finish is what you call when the test has reached its natural end: the last directed sequence ran, the scoreboard tallied, the verdict printed.
5. $stop — Pause Into the Debugger
$stop suspends the simulation and drops into the simulator's interactive prompt — it does not exit. It is a breakpoint.
$stop; // suspend; hand control to the interactive prompt
$stop(1); // same, with the default time/location diagnosticSemantics:
- Suspends, does not terminate. The event queue is frozen; the design state is intact and inspectable.
- Drops to the simulator console, where the engineer can dump signals, step time, set further breakpoints, and then
run/continueto resume — orquitto exit. - Requires a human (or a
-doscript) to do anything next. With no interactive console attached, the simulation simply hangs.
$stop is a debugging tool: stop the run at the interesting cycle, look around, continue. The "requires a human" property is exactly why it is dangerous in automation (§10, §12).
6. $finish vs $stop — The Decision
Same family, opposite purposes. Pick by asking who consumes the result.
$finish | $stop | |
|---|---|---|
| Action | Terminates the simulator process | Suspends into the interactive prompt |
| After it fires | Control returns to the shell | Control waits for a human / -do script |
| Design state | Gone (process exits) | Preserved and inspectable |
| Use when | The run is genuinely complete | You want to debug at this cycle |
| Batch / regression | ✅ The correct choice | ❌ Hangs the job (no console to answer) |
| Interactive debug | Ends too early to look around | ✅ The correct choice |
The rule: $finish for any run whose result is consumed by a script (every regression, every CI job, every farm run). $stop for any run whose result is consumed by a person sitting at the simulator console. Using $stop in a batch job is the canonical hang (§12.3).
7. Visual Explanation
7.1 Visual A — the simulation lifecycle (correct termination)
Simulation lifecycle — control owns the final phase
data flow7.2 Visual B — simulation without termination (the §1 hang)
7.3 Visual C — the watchdog race
8. Worked Examples
8.1 Example 1 — basic $finish
initial begin
#100;
$finish;
endExactly what happens: the initial block schedules itself to resume at simulation time 100. At t=100 it executes $finish, which completes the current time-step and then terminates the simulator process. Any free-running clock or other always block stops with it — $finish ends the whole simulation, not just this block. Control returns to the shell. This is the minimum viable clean termination: run for a fixed window, then end.
8.2 Example 2 — the timeout watchdog
Every serious testbench bounds its own runtime so a hung DUT cannot run forever. The portable, pure Verilog-1364 form uses an independent timeout block:
// Real test: ends the run on success.
initial begin
run_test(); // directed/random sequence
$display("TEST PASSED");
$finish; // success path — ends BEFORE the timeout
end
// Watchdog: an independent block that ends the run on expiry.
initial begin
#10000; // timeout budget in time units
$display("TEST FAILED — TIMEOUT at %0t", $time);
$finish; // failure path — fires only if run_test() hung
endTwo initial blocks race. If run_test() completes within 10000 time units, its $finish ends the run first and the watchdog never fires. If run_test() hangs, the watchdog's #10000 expires, prints a loud failure, and ends the run anyway. The simulation can no longer run forever — its worst case is bounded at 10000 units.
The modern SystemVerilog (IEEE 1800) testbench writes the same idea with fork ... join_any:
fork
run_test(); // thread A: the real test
begin // thread B: the watchdog
#10000;
$fatal(1, "Timeout"); // $fatal: SV — fail + non-zero exit
end
join_any // resume as soon as EITHER thread ends
disable fork; // kill the surviving threadjoin_any resumes the moment either thread finishes (the race), and disable fork stops the loser. join_any, disable fork, and $fatal are SystemVerilog extensions — not part of IEEE 1364 — but this is the form you will see in every UVM testbench, so recognise it. The Verilog-1364 two-initial-block version above is semantically equivalent and works in any simulator.
Why every serious testbench has timeout protection: without it, a single deadlocked DUT in a 10,000-test nightly regression doesn't fail one test — it hangs one job until the farm's wall-clock limit, holding a licence seat and a compute slot hostage for hours while reporting nothing. The watchdog converts an unbounded hang into a bounded, reported failure.
8.3 Example 3 — pass/fail termination
initial begin
run_test();
if (errors == 0)
$display("PASS");
else
$display("FAIL (%0d errors)", errors);
$finish;
endThe discipline: compute the verdict, print it in a machine-parseable form, then $finish. The $finish is unconditional and comes after the verdict so the run always ends cleanly with exactly one PASS or FAIL line in the log. A regression harness greps for that line to score the test (§10). Never branch the $finish itself — both the pass and the fail path must terminate, or a failing test could hang.
8.4 Example 4 — $stop for interactive debug
always @(posedge clk) begin
if (state == ERROR_STATE) begin
$display("Entered ERROR_STATE at t=%0t — pausing for inspection", $time);
$stop; // suspend INTO the interactive prompt
end
endAt the first entry into ERROR_STATE, $stop freezes the simulation and drops to the simulator console with full design state intact. The engineer dumps the offending registers, walks the upstream logic, maybe single-steps a few cycles, then types run to continue or quit to exit. This is a breakpoint: it assumes a human is watching.
That same code in a batch regression is a defect — there is no console to answer the prompt, so the job hangs at the first ERROR_STATE until killed (§12.3). The fix in automation is to report and finish, not pause: replace $stop with $display(...); errors = errors + 1; $finish;. Regressions prefer $finish precisely because it never waits for a human.
9. Trace — Execution Timeline of the Watchdog
The watchdog's value is clearest as a timeline of the two competing threads.
SUCCESS CASE HANG CASE
t=0 fork: spawn test + timeout t=0 fork: spawn test + timeout
t=0.. test thread runs sequence t=0.. test thread runs sequence
t=842 test: "TEST PASSED"; $finish <-- A t=???? test thread DEADLOCKS (no progress)
timeout thread (#10000) discarded t=10000 timeout: "FAILED — TIMEOUT"; $finish <-- B
RESULT run ends at t=842, verdict PASS RESULT run ends at t=10000, verdict FAIL
Without the timeout thread, the HANG CASE has no row B:
the test thread never progresses, the clock keeps the queue alive,
and the run continues to the farm's wall-clock kill — hours, no verdict.Two properties to read off the trace: success ends early (at 842, not 10000 — the budget is a ceiling, not a fixed runtime), and a hang ends bounded and reported (at exactly 10000, with a FAIL line) instead of unbounded and silent.
10. Industry Usage
The choice of control task is dictated by who runs the simulation.
10.1 Local debug — $stop
An engineer at their desk, simulator GUI open, hunting a bug, uses $stop (or interactive breakpoints) to freeze at the interesting cycle and inspect. The human-in-the-loop is present, so the "waits for a human" property is a feature.
10.2 Automated regression — $finish
A nightly regression of thousands of directed and random tests uses $finish exclusively. Each test must end deterministically, print a parseable verdict, and return the seat. A stray $stop anywhere in the compiled sources hangs that job (§12.3). Mature shops lint for $stop in regression builds and reject it.
10.3 CI / compute-farm execution
Farm runs add three hard requirements that simulation control must satisfy:
- Deterministic exits. Every run ends — via
$finishon completion or a watchdog$finishon timeout — so no job lingers. - Timeout guards. Every test carries a watchdog (§8.2). A hung DUT fails one bounded test instead of stalling a slot for hours.
- Pass/fail signalling. The harness scores by parsing the log for a canonical
PASS/FAILtoken; some flows additionally use$fatal/tool flags to set a non-zero OS exit code. (Standard$finishdoes not set the exit code itself — it is simulator-specific — so log-token parsing is the portable contract.)
The through-line: automation needs bounded, deterministic, self-reporting runs, and simulation control is the only layer that provides them.
11. Common Mistakes
11.1 No $finish — the run never ends
A free-running clock keeps the event queue non-empty forever, so a testbench with no $finish runs until the farm kills it (§1). Fix: every testbench's top sequence ends with $finish.
11.2 No watchdog — a hang has no upper bound
Without a timeout block, a deadlocked DUT runs to the wall-clock limit with no verdict. Fix: every testbench carries an independent watchdog that $finishes on a budget.
11.3 $stop in a batch job
$stop waits for an interactive console that a regression does not have, so the job hangs at the first $stop. Fix: use $finish in any script-consumed run; lint $stop out of regression builds.
11.4 Conditional $finish that a failing path skips
if (errors == 0) begin
$display("PASS");
$finish; // only the PASS path ends the run!
end
// FAIL path falls through with no $finish -> hangs on the live clockFix: make $finish unconditional after the verdict (Example 3) — both outcomes must terminate.
11.5 $finish inside a clocked always firing every cycle
Calling $finish from always @(posedge clk) without a guard ends the run on the first edge. Fix: gate it on the genuine end condition (if (test_done) $finish;), or drive termination from the initial sequence.
12. Debugging Lab
Three simulation-control debug post-mortems
13. Coding Guidelines
- Every testbench ends with
$finish. The top-level sequence's last act is an unconditional$finishafter the verdict. A live clock means the run never self-terminates. - Every testbench carries a watchdog. An independent timeout block (
#BUDGET; $display("...TIMEOUT..."); $finish;) bounds the worst case so a hung DUT fails loudly instead of running forever. $finishfor scripts,$stopfor humans. Use$finishin anything a regression or CI farm runs; reserve$stopfor interactive sessions with a console attached.- Never conditionalise the
$finishitself. Branch the verdict message, not the termination — both pass and fail paths must reach$finish. - Print a parseable verdict before
$finish. Exactly one canonicalPASS/FAILtoken so the harness can score by log parsing (the portable signal; OS exit codes are simulator-specific). - Lint
$stopout of regression builds. A stray$stopis a latent hang; treat it as an error in any non-interactive flow.
14. Interview Q&A
15. Exercises
Exercise 1 — Predict termination
For each snippet, state when (or whether) the simulation ends, and why.
// Snippet A
initial begin #100; $finish; end
always #5 clk = ~clk;
// Snippet B
always #5 clk = ~clk;
initial begin run_test(); $display("done"); end // no $finish
// Snippet C
always @(posedge clk) $finish; // unguardedExercise 2 — Add watchdog protection
This testbench can hang if the DUT never asserts done. Add a pure-Verilog watchdog that bounds the run at 50,000 time units and reports a timeout failure, without changing the success path.
initial begin
apply_reset();
start_transfer();
wait (dut.done);
$display("PASS");
$finish;
endExercise 3 — Fix the batch-hang
This block passes interactively but hangs the nightly regression whenever a mismatch occurs. Identify the cause and rewrite it to fail cleanly in batch.
always @(posedge clk)
if (dut.q !== expected_q) begin
$display("MISMATCH at t=%0t", $time);
$stop;
endState explicitly which control task belongs in interactive debug versus automation, and why.
16. Summary
Simulation control owns the terminate phase of the simulation lifecycle. Its founding fact: a simulator stops only when something tells it to — a live clock keeps the event queue alive forever, so ending a run is always an explicit act.
The two core tasks:
$finish— terminates the simulator process and returns to the shell. The clean, normal end of a run. The optional0/1/2argument controls only the exit diagnostic, not the OS exit code.$stop— suspends into the interactive prompt with state intact. A breakpoint for human-in-the-loop debug; it waits for a person.
The decision: $finish for script-consumed runs (every regression and CI job), $stop for human-consumed sessions (local debug). A $stop in a batch job is the canonical hang.
The protective pattern: every serious testbench carries a watchdog — an independent timeout that $finishes after a fixed budget so a deadlocked DUT fails loudly and bounded instead of running to the farm's wall-clock limit. The pure-Verilog form is two racing initial blocks; the SystemVerilog form is fork ... join_any with $fatal (IEEE 1800).
The automation contract: deterministic exits, timeout guards, and a parseable PASS/FAIL verdict printed before an unconditional $finish.
Chapter 8 is now complete. The five sections form the full simulation-control toolkit: 8.1 Observation ($display / $monitor / $strobe), 8.2 Random Stimulus ($random), 8.3 Time Functions ($time / $realtime), 8.4 Conditional Simulation (build control), and 8.5 Simulation Control (termination & lifecycle). Together they are everything you need to drive, observe, verify, and cleanly end a simulation — the verification scaffolding that every design is exercised inside.
With the foundation and simulation layers complete, the curriculum now turns to the RTL core — the constructs that describe hardware itself:
Chapter 10 Operators → Dataflow (
assign) →always→ Blocking vs Non-Blocking → Combinational Logic → Sequential Logic.
Everything up to here taught you to read Verilog and to run a simulation; what follows teaches you to design hardware. The simulation-control toolkit you just finished is what you will use to verify every block you build from Chapter 10 onward.
Related Tutorials
- System Tasks & Functions — Chapter 8; the parent overview of the system-task families this chapter completes.
- Conditional Simulation — Chapter 8.4; gating simulation-only code (including the watchdog) out of synthesis builds.
- $display vs $monitor vs $strobe vs $write — Chapter 8.1; the observation tasks that print the
PASS/FAILverdict before$finish. - Time Functions — Chapter 8.3;
$time/$realtime, used in timeout budgets and verdict timestamps.