Skip to content

Verilog · Chapter 8 · System Tasks & Functions

System Tasks & Functions in Verilog — Chapter 8 Overview

System tasks and functions are Verilog's simulation-side utility layer, the names beginning with a dollar sign that give a testbench everything it needs to drive stimulus, observe behaviour, log results, and control the simulator. They are the verification side of the language. They do not become silicon and are not synthesizable, but they are essential to the build, test, and debug loop that produces working RTL. This overview surveys the whole family, which splits into eight groups covering display, randomization, time, file input and output, simulation control, formatting, assertions, and value dumping. It also explains the scheduling-region distinction that makes some print tasks show different values than others for the same signal at the same moment. From here, five deeper lessons drill into each major group in turn.

Foundation18 min readVerilogSystem TasksVerificationTestbenchSimulation

Chapter 8 · System Tasks & Functions (Overview)

1. The Engineering Problem

A junior engineer writes a testbench for a 4-bit adder. The adder works. The testbench fails — but the engineer can't tell why. The simulation runs to completion, no errors are reported, but the result isn't what was expected.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module adder_tb;
    reg [3:0] a, b;
    wire [4:0] sum;
    adder DUT (.a(a), .b(b), .sum(sum));
 
    initial begin
        a = 4'h3; b = 4'h5;  #10;
        a = 4'hF; b = 4'h1;  #10;
        // ... silence. No output. Did the test pass? Did it run at all?
    end
endmodule

The simulator ran the testbench, evaluated the adder, and finished — but produced no observable output. The engineer can't see whether sum was correct because nothing printed it. There's no log to scroll through, no assertion that fired, no exit code that signalled success or failure.

The fix is to add system tasks — the $-prefixed simulation utilities that print, observe, randomize, time-track, log to files, and control the simulator itself. The same testbench with $display becomes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module adder_tb;
    reg [3:0] a, b;
    wire [4:0] sum;
    adder DUT (.a(a), .b(b), .sum(sum));
 
    initial begin
        a = 4'h3; b = 4'h5;  #10;
        $display("[t=%0t] a=%h b=%h sum=%h (expected %h)", $time, a, b, sum, a+b);
 
        a = 4'hF; b = 4'h1;  #10;
        $display("[t=%0t] a=%h b=%h sum=%h (expected %h)", $time, a, b, sum, a+b);
 
        $finish;
    end
endmodule

Now every test point prints with timestamp, inputs, output, and expected value. $finish ends the simulation cleanly. The engineer can see what happened and judge correctness.

System tasks are not synthesisable — none of $display, $random, $time, $finish appear in the netlist. They live entirely in the simulator. But every working Verilog project relies on them — for verification, debug, file I/O, randomization, and simulator control. This chapter is the survey: the eight functional families, the active-vs-postponed scheduling region distinction, the synthesis discipline, and the five deep-dive sub-pages that follow.

2. What is a System Task?

A system task (or system function) is a built-in simulator utility whose name starts with $. The $ prefix distinguishes them from user-defined identifiers and signals the simulator to dispatch to its internal implementation.

2.1 Syntax

system-task-syntax.v — the two forms
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Task form — called as a statement, no return value
$display("hello %h", value);
$finish;
 
// Function form — called in an expression, returns a value
reg [31:0] r;
r = $random;
reg [63:0] t;
t = $time;

System tasks ($display, $finish, $readmemh) are invoked as statements — they perform an action and don't return a value. System functions ($random, $time, $realtime) are invoked in expressions — they return a value.

2.2 The $ prefix is reserved

Only the simulator can define $-prefixed names. A user-defined task or function cannot have a $ in its name. This is what makes the family safely globally visible — no naming conflict with project code.

3. Mental Model

4. The Eight Functional Families

The system-task family covers eight functional groups. Each is the subject of one or more sub-pages; this overview names them.

#FamilyExamplesWhat it does
1Display & Print$display, $write, $monitor, $strobeFormat and print values to stdout / log
2Randomization$random, $urandom, $urandom_rangeGenerate random values for stimulus
3Time Queries$time, $realtime, $stimeRead the current simulation time
4File I/O$fopen, $fclose, $fwrite, $fdisplay, $readmemh, $readmemb, $writememhRead / write external files
5Simulation Control$finish, $stop, $exitEnd / pause the simulation
6Formatting$sformat, $sformatf, $sscanfString formatting without printing
7Assertions / Severity$assert, $assertkill, $error, $warning, $fatal, $infoRuntime checks and severity reporting
8Value-Change Dump$dumpfile, $dumpvars, $dumpon, $dumpoff, $dumpallGenerate VCD files for waveform tools

The five sub-pages drill into the most-used families:

  • 8.1 $display vs $monitor vs $strobe vs $write — the display family in depth, including the active-vs-postponed region distinction.
  • 8.2 $random — Random Number Generation — the randomization family, seed handling, the $urandom_range variants, and the reproducibility discipline.
  • 8.3 Time Functions$time, $realtime, $stime, and how each interacts with the \timescale` precision.
  • 8.4 Conditional Simulation$assert, $error, $warning, $fatal, $info and the severity-level discipline.
  • 8.5 Simulation Control$finish, $stop, $exit, and the exit-code conventions every testbench should honour.

The remaining families (file I/O, formatting, value-change dump) appear in passing across the sub-pages and the practical examples in Chapter 9.

5. Visual Explanation

Three figures cover the system-task model.

5.1 Visual A — system-task family tree

The eight functional families at a glance.

System task family tree

data flow
System task family tree$ system taskssimulator-only utilities$display / $write / $monitor / $strobe$display / $write/ $monitor /…1. display family$random / $urandom / $urandom_range$random /$urandom /…2. randomization$time / $realtime/ $stime3. time queries$fopen / $fwrite/ $readmemh / …4. file I/O$finish / $stop /$exit5. simulation control$sformat / $sformatf / $sscanf$sformat /$sformatf /…6. formatting$assert / $error / $warning / $fatal$assert / $error/ $warning /…7. assertions$dumpfile / $dumpvars / $dumpon$dumpfile /$dumpvars /…8. VCD dump
Eight functional families covered by IEEE 1364-2005 §17. The display + randomization + time + sim-control families cover ~80% of testbench code; the rest live in the long tail.

5.2 Visual B — active vs postponed scheduling regions

Why $display and $strobe show different values for the same signal at the same time.

Verilog event-queue scheduling regionsActive regionblocking assigns, $display,$writeNBA regionnon-blocking updates applyMonitor region$monitor checks for changesPostponed region$strobe, $monitor finalprint12
Verilog's event-queue regions at one time-step. Blocking assignments resolve in the Active region; non-blocking right-hand-sides also evaluate in Active; non-blocking updates happen in NBA; $strobe runs in Postponed, AFTER all assignments resolve. $display in Active sees pre-NBA values; $strobe in Postponed sees post-NBA values. This is why `q <= d; $display(q)` shows OLD q and `q <= d; $strobe(q)` shows NEW q.

5.3 Visual C — synthesis-vs-simulation boundary

What survives synthesis and what doesn't.

Synthesis vs simulation domain boundaryRTL sourcesingle fileSynthesis toolreads only synthesisableSimulatorexecutes everythingNetlist (no $ tasks)production siliconSimulation log ($tasks run)verification output12
The synthesis-vs-simulation boundary. Synthesisable RTL (always blocks, assigns, instantiations) passes through to the netlist. System tasks ($display, $random, $time, $finish) are stripped — either silently dropped, with a warning, or by deleting the containing block. Wrap every system task in `ifdef SIMULATION (or `ifndef SYNTHESIS) to make the boundary explicit.

6. The Display Family in One Page

The four display tasks are the most-used members of the family. Here is the one-line characterisation; the 8.1 page drills each in detail.

display-family-glance.v — one-line essence of each
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("text");      // prints once, immediately (Active region), with newline
$write("text");        // prints once, immediately (Active region), no newline
$monitor("text");      // prints every time any argument changes (Monitor region)
$strobe("text");       // prints once, at END of timestep (Postponed region)
 
// Format specifiers — same as C's printf:
//   %d  decimal       %h  hex          %b  binary
//   %o  octal         %c  char         %s  string
//   %t  time          %m  hierarchical name
//   %0d %0h %0b       (no padding)    %05d (zero-padded width 5)

The choice rule:

  • $display for fire-and-forget event-time prints (cycle banners, state-change confirmations, debug snapshots).
  • $write for building one line from multiple calls (progress bars, table rows, custom-formatted output).
  • $monitor for tracking a small set of signals through every change in the simulation. Only one active per simulation.
  • $strobe for capturing post-NBA values — e.g., the value of a flop's q after the clock edge has applied the update. The $display shows pre-NBA q; the $strobe shows post-NBA q.

7. The Randomization Family in One Page

Random stimulus is the cornerstone of constrained-random verification. The 8.2 page drills the family; here is the essence.

random-family-glance.v — one-line essence
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [31:0] r;
 
// Pseudorandom 32-bit signed integer
r = $random;             // signed, can be negative
r = $random % 16;        // restrict to 0..15 ... but distribution non-uniform near 0
 
// Better: $urandom / $urandom_range
r = $urandom;                       // unsigned 32-bit
r = $urandom_range(255, 0);         // uniform 0..255
r = $urandom_range(100, 50);        // uniform 50..100
 
// Seeded — reproducible runs
integer seed = 42;
r = $random(seed);       // seed updated in place; subsequent calls advance the stream

The discipline:

  • Prefer $urandom_range(max, min) over $random % N — modulo of a signed value can bias the distribution toward 0.
  • Always seed. A test that uses random stimulus without a seed produces different results every run, breaking reproducibility. Use a $urandom(seed) form or set the seed via simulator command line.
  • The simulator's random stream is per-context (per initial/always/task invocation) in SystemVerilog; in Verilog-2005, the stream is global. Be explicit about which contexts share a stream.

8. The Time-Query Family in One Page

Three system functions return the current simulation time. The 8.3 page drills the differences; here is the contrast.

FunctionReturnsUnitUse case
$time64-bit integerrounded to time_unitGeneral-purpose timestamps in logs
$realtime64-bit realexact, in time_unitSub-precision timing measurements
$stime32-bit integerrounded to time_unitCompact timestamps (legacy, rarely used)

The choice rule:

  • $time for almost everything. Pair with %0t format specifier to print without unit suffix.
  • $realtime when measuring sub-unit precision (jitter, propagation delays smaller than time_unit).
  • $stime only when the testbench needs a 32-bit time value for compatibility with old code; otherwise prefer $time.

All three honour the \timescale` directive — the unit-and-precision contract covered in the 7.3 timescale page.

9. The Simulation-Control Family in One Page

Three tasks end or pause the simulation. The 8.5 page drills the discipline; here is the essence.

sim-control-glance.v — one-line essence
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$finish;       // ends simulation; exit code 0 (success)
$finish(1);    // ends simulation; exit code 1 (printed; behaviour vendor-specific)
$stop;         // pauses simulation, drops to interactive prompt
$exit;         // immediate termination (SystemVerilog, used in scopes)

The discipline:

  • Every testbench's top-level initial block ends with $finish (not nothing, not $stop). Without it, the simulator runs until the last scheduled event drains and then exits — usually fine, but produces an indeterminate exit code that CI pipelines can't check.
  • $stop is for interactive debugging — it drops to the simulator's TUI prompt. Never leave a $stop in checked-in code; it stalls automated regressions.
  • The exit code convention: 0 for pass, non-zero for fail. CI pipelines and test infrastructure rely on this to detect failed tests.

10. Synthesis Discipline — Wrap Every System Task

System tasks are not synthesisable. Synthesis tools handle them three ways:

  1. Silent strip — the task is removed; if it was inside an \always block`, the block may also be removed (the hazard covered in the 7.4 conditional-compilation page).
  2. Warning + strip — most modern synth tools emit a "non-synthesisable system task" warning and drop the task.
  3. Block deletion — some older tools delete the entire \always block` containing the task, taking real RTL with it.

The universal fix: wrap every system task in a \ifdef SIMULATION(or`ifndef SYNTHESIS`) guard, so the synthesiser never sees them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef SIMULATION
    always @(posedge clk) begin
        $display("[t=%0t] state=%h", $time, state);
    end
`endif

The 7.4 conditional-compilation page covers the discipline in depth; this is the canonical pattern for every system-task block.

11. Industry Use Cases

Five patterns where system tasks show up in production verification.

11.1 Per-cycle debug instrumentation

Every non-trivial RTL module has $display lines that catch regressions — register transitions, state-machine transitions, bus-handshake completion. Wrapped in \ifdef SIMULATION` so they don't bleed into synthesis.

11.2 Constrained-random stimulus

Verification suites generate millions of random transactions to stress corner cases. $urandom_range drives the input streams; seeded for reproducibility.

11.3 Self-checking testbench infrastructure

A testbench compares expected and actual values at every clock edge. $error / $fatal fire on mismatch; $finish ends the run with a non-zero exit code that CI catches.

11.4 File-based reference and stimulus

Golden-reference data is loaded via $readmemh / $readmemb; testbench output written via $fwrite / $writememh. Enables comparison against tools (Matlab, C reference) outside the simulator.

11.5 Waveform dump for post-run analysis

Every CI-run simulation dumps signals via $dumpvars for offline analysis in GTKWave, Synopsys Verdi, or Cadence SimVision. Failures are reproduced from the dump file without re-running the simulation.

12. Common Mistakes

Six pitfalls that catch every working verification engineer at least once.

12.1 $display vs $strobe on non-blocking results

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q <= d;                          // non-blocking
    $display("q after assign = %h", q);  // prints OLD q (pre-NBA)
end

$display runs in the Active region — before the non-blocking update applies. The print shows the old value. For the new value, use $strobe (Postponed region) or move the print into a separate \always block`.

12.2 $monitor overwritten

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$monitor("first: %h", a);
// later...
$monitor("second: %h", b);
// First monitor is silently disabled. Only the second is active.

Only one $monitor instance can be active per simulation. Calling $monitor again replaces the previous monitor. To track multiple signals, combine them in one $monitor call or use $display in a sensitivity-list-driven \always` block.

12.3 $random % N distribution bias

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [3:0] r;
r = $random % 16;   // r can be 0..15, BUT distribution is non-uniform
                    // because $random returns a SIGNED 32-bit integer
                    // and the modulo of a negative is implementation-defined.

Use $urandom_range(15, 0) instead. Gives a uniform unsigned distribution by construction.

12.4 Missing $finish in testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    // ... stimulus ...
    // forgot $finish
end
// Simulation runs until all scheduled events drain, then exits with
// indeterminate exit code. CI thinks the test passed even if it didn't.

Every testbench's top-level initial block ends with $finish (with explicit pass/fail exit code if the testbench knows the verdict).

12.5 Synthesis warnings about non-synthesisable tasks dismissed

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    $display("debug");        // synth warns; engineer dismisses
    important_signal <= a;    // synth deletes entire block including this!
end

Some synthesis tools delete the entire block containing a non-synthesisable task. The warning isn't a "don't worry" — it's a potential RTL deletion. Always gate system tasks behind \ifdef SIMULATION`.

12.6 Format-specifier mismatch

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [31:0] a;
$display("%d", a);            // prints as 32-bit signed — large positives become negatives
$display("%0d", a);           // same value, but unsigned interpretation in some tools
$display("%h", a);            // hex — preferred for register values

The format specifiers follow C's printf family, but with quirks (signedness handling varies between tools; %t requires a \timescaledirective in scope;%mshows the hierarchical name of the calling scope). When in doubt, use%hfor register values and%0d` for unsigned decimals.

13. Debugging Lab

Three system-task debug post-mortems

14. Coding Guidelines

Six rules that teams converge on.

  1. **Wrap every system task in \ifdef SIMULATION** (or the project's equivalent). Never leave a $display` exposed to synthesis.
  2. Use $strobe for post-NBA observations. When you need to print the value of a flop AFTER its non-blocking update has applied, $strobe is the right tool — not $display.
  3. Use $urandom_range(max, min) for bounded random values. Avoid $random % N — modulo on a signed value introduces subtle distribution bias.
  4. Every testbench's top-level initial ends with $finish. With an exit code if the testbench knows the verdict. Otherwise CI can't tell pass from fail.
  5. Use %h for register values, %0d for unsigned decimals, %0t for time. Avoid the signed-decimal %d for vector values — large positives print as negatives.
  6. One $monitor per simulation. If you need to track multiple groups of signals, combine into one $monitor call or use separate $display-in-always blocks.

15. Interview Q&A

16. Exercises

Three exercises that turn system tasks into reflex.

Exercise 1 — Predict the output

For each snippet, predict what $display / $monitor / $strobe prints.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Snippet A
reg [3:0] a, b;
initial begin
    a = 4'h3;
    b = 4'h5;
    $display("a + b = %h", a + b);
    $finish;
end
// Predicted output: ?
 
// Snippet B
reg clk, d, q;
always @(posedge clk) begin
    q <= d;
    $display("display: q = %b", q);
    $strobe ("strobe : q = %b", q);
end
initial begin
    clk = 0; d = 1; q = 0;
    #5 clk = 1;
    #5 $finish;
end
// Predicted output (at t=5): ?
 
// Snippet C
reg [7:0] x;
initial begin
    $monitor("x changed to %h", x);
    x = 8'h00;
    #5 x = 8'hAA;
    #5 x = 8'hFF;
    #5 $finish;
end
// Predicted output: ?

Hints. Apply the scheduling-region rules from §5.2 to Snippet B. Apply the $monitor "prints on any change, once per timestep" rule to Snippet C.

Exercise 2 — Pick the right system task

For each requirement, recommend the best system task.

#RequirementRecommendation
1Print a one-line debug message at a specific point in the testbench?
2Continuously print whenever a state signal changes?
3Print the value of a flop's q AFTER the clock edge applies its update?
4Generate a uniform random value in [0, 99]?
5Read a hex memory image from a file into a reg [7:0] mem [0:1023] array?
6End the simulation with exit code 0?
7End the simulation with exit code 1 (failure)?
8Dump all signals in the testbench scope to a VCD file?
9Get the current simulation time as an integer?
10Get the current simulation time as a real (sub-unit precision)?

What to produce. Name the task / function for each requirement, with a one-line argument-list pattern.

Exercise 3 — Fix the testbench

This testbench has four bugs related to system-task usage. Identify and fix.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module buggy_tb;
    reg clk, d, q;
    reg [3:0] r;
    integer i;
 
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end
 
    always @(posedge clk) begin
        q <= d;
        $display("q after clock = %b", q);   // Bug 1?
    end
 
    initial begin
        // Generate 10 random values in [0, 15]
        for (i = 0; i < 10; i = i + 1) begin
            r = $random % 16;                 // Bug 2?
            $display("r[%0d] = %h", i, r);
        end
 
        $monitor("first monitor: q = %b", q);
        // ... later
        $monitor("second monitor: d = %b", d); // Bug 3?
 
        d = 1;
        #50;
        // Bug 4 — missing what?
    end
endmodule

Hints. Bug 1 is a scheduling-region issue. Bug 2 is a distribution issue. Bug 3 is a $monitor-uniqueness issue. Bug 4 is the missing simulation-end discipline.

17. Summary

System tasks and functions are Verilog's simulation-side utility layer — the $-prefixed names that provide display, randomization, time queries, file I/O, simulation control, formatting, assertions, and value-change dump. They live entirely in the simulator; they are not synthesisable.

The eight functional families:

  • Display & Print$display, $write, $monitor, $strobe.
  • Randomization$random, $urandom, $urandom_range.
  • Time Queries$time, $realtime, $stime.
  • File I/O$fopen, $fwrite, $readmemh, $writememh.
  • Simulation Control$finish, $stop, $exit.
  • Formatting$sformat, $sformatf, $sscanf.
  • Assertions / Severity$assert, $error, $warning, $fatal, $info.
  • Value-Change Dump$dumpfile, $dumpvars, $dumpon, $dumpoff.

The five sub-pages drill into the most-used families:

  • 8.1 $display vs $monitor vs $strobe vs $write — the display family in depth.
  • 8.2 $random — Random Number Generation — randomization, seeding, distribution.
  • 8.3 Time Functions$time / $realtime / $stime and the \timescale` interaction.
  • 8.4 Conditional Simulation$assert / $error / $warning / $fatal / $info severity layer.
  • 8.5 Simulation Control$finish / $stop / $exit and exit-code discipline.

The mental model:

  • Active vs Postponed regions$display runs in Active (pre-NBA, sees OLD values); $strobe runs in Postponed (post-NBA, sees NEW values).
  • Not synthesisable — wrap every system task in \ifdef SIMULATION` so the synthesiser never sees it.
  • Vendor-extensible — the IEEE 1364-2005 §17 family is portable; vendor-proprietary tasks need portability checks.

The day-to-day discipline:

  • Gate every system task on \ifdef SIMULATION`. No exceptions.
  • Use $strobe for post-NBA observations. $display shows pre-NBA values, which surprises engineers who expect to see the new flop output.
  • Use $urandom_range over $random %. Uniform distribution by construction.
  • Every testbench ends with $finish. With an exit code if the verdict is known.
  • %h for register values, %0d for unsigned decimals, %0t for time. Predictable formatting.

Chapter 8 closes the verification-utilities arc. Chapter 9 picks up with the module / testbench assembly pattern — module structure, instantiation, port mapping, and the practical recipe for assembling a clean DUT-plus-testbench pair.

  • Conditional Compilation — Chapter 7.4; the `ifdef SIMULATION discipline that gates every system task.
  • [timescale](/verilog/timescale-directive) — Chapter 7.3; the simulator time-unit / precision contract that $time/$realtime` ride on.
  • Compiler Directives — Chapter 7; the parent directive family.
  • Constant Variables — Chapter 6; the elaboration-time-constant family that pairs with the system-task runtime utilities.