Skip to content

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 conditionWhat it meansTypical cause
Event queue emptiesNo future events remain to processAll stimulus done and no free-running clock
A control task fires$finish or $stop executesExplicit 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-syntax.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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 statistics

The 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-syntax.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$stop;          // suspend; hand control to the interactive prompt
$stop(1);       // same, with the default time/location diagnostic

Semantics:

  • 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/continue to resume — or quit to exit.
  • Requires a human (or a -do script) 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
ActionTerminates the simulator processSuspends into the interactive prompt
After it firesControl returns to the shellControl waits for a human / -do script
Design stateGone (process exits)Preserved and inspectable
Use whenThe run is genuinely completeYou want to debug at this cycle
Batch / regression✅ The correct choice❌ Hangs the job (no console to answer)
Interactive debugEnds 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 flow
Simulation lifecycle — control owns the final phaseStartelaborate · zero the queueStimulusdrive inputs (8.2)Observe$display / $monitor (8.1)Verifyscoreboard · errors$finishdeterministic end + verdict
A healthy run flows Start -> Stimulus -> Observe -> Verify -> $finish. Simulation control owns only the last node — but without it the chain never closes and the run does not end.

7.2 Visual B — simulation without termination (the §1 hang)

Simulation that never terminatesStartrun beginsTest completesverdict printed at 200nsNo $finishnothing ends the runalways #5 clk=~clkqueue never emptiesRun foreverCPU burns · DUT idle12
The failure mode. The test completes and prints its verdict, but with no $finish and a free-running clock, the event queue never empties — so the simulator loops in 'advance time forever', consuming CPU with the DUT idle until the farm's wall-clock limit kills it.

7.3 Visual C — the watchdog race

Watchdog timeout race between test and timerforkspawn both threadstest threadrun_test() -> $finishtimeout thread#TIMEOUT -> fail + endfirst to finish winsrace-to-completion12
Two threads race. The test thread runs the real sequence and calls $finish on success. The timeout thread waits a fixed budget and forces termination on expiry. Whichever finishes FIRST ends the run: success ends it early and cleanly; a hang lets the timeout win and fail loudly. Without the timeout thread, a hung test thread runs forever.

8. Worked Examples

8.1 Example 1 — basic $finish

basic-finish.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    #100;
    $finish;
end

Exactly 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:

watchdog-verilog.v — pure Verilog-1364
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
end

Two 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:

watchdog-sv.v — SystemVerilog idiom (note: 1800, not 1364)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 thread

join_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

pass-fail-finish.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    run_test();
    if (errors == 0)
        $display("PASS");
    else
        $display("FAIL (%0d errors)", errors);
    $finish;
end

The 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

stop-debug.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
end

At 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.

watchdog execution — success vs hang
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 $finish on completion or a watchdog $finish on 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/FAIL token; some flows additionally use $fatal/tool flags to set a non-zero OS exit code. (Standard $finish does 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 clock

Fix: 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

  1. Every testbench ends with $finish. The top-level sequence's last act is an unconditional $finish after the verdict. A live clock means the run never self-terminates.
  2. 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.
  3. $finish for scripts, $stop for humans. Use $finish in anything a regression or CI farm runs; reserve $stop for interactive sessions with a console attached.
  4. Never conditionalise the $finish itself. Branch the verdict message, not the termination — both pass and fail paths must reach $finish.
  5. Print a parseable verdict before $finish. Exactly one canonical PASS / FAIL token so the harness can score by log parsing (the portable signal; OS exit codes are simulator-specific).
  6. Lint $stop out of regression builds. A stray $stop is 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;                     // unguarded

Exercise 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    apply_reset();
    start_transfer();
    wait (dut.done);
    $display("PASS");
    $finish;
end

Exercise 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk)
    if (dut.q !== expected_q) begin
        $display("MISMATCH at t=%0t", $time);
        $stop;
    end

State 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 optional 0/1/2 argument 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.