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.
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
endmoduleThe 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:
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
endmoduleNow 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
// 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.
| # | Family | Examples | What it does |
|---|---|---|---|
| 1 | Display & Print | $display, $write, $monitor, $strobe | Format and print values to stdout / log |
| 2 | Randomization | $random, $urandom, $urandom_range | Generate random values for stimulus |
| 3 | Time Queries | $time, $realtime, $stime | Read the current simulation time |
| 4 | File I/O | $fopen, $fclose, $fwrite, $fdisplay, $readmemh, $readmemb, $writememh | Read / write external files |
| 5 | Simulation Control | $finish, $stop, $exit | End / pause the simulation |
| 6 | Formatting | $sformat, $sformatf, $sscanf | String formatting without printing |
| 7 | Assertions / Severity | $assert, $assertkill, $error, $warning, $fatal, $info | Runtime checks and severity reporting |
| 8 | Value-Change Dump | $dumpfile, $dumpvars, $dumpon, $dumpoff, $dumpall | Generate VCD files for waveform tools |
The five sub-pages drill into the most-used families:
- 8.1
$displayvs$monitorvs$strobevs$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_rangevariants, 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,$infoand 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 flow5.2 Visual B — active vs postponed scheduling regions
Why $display and $strobe show different values for the same signal at the same time.
5.3 Visual C — synthesis-vs-simulation boundary
What survives synthesis and what doesn't.
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("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:
$displayfor fire-and-forget event-time prints (cycle banners, state-change confirmations, debug snapshots).$writefor building one line from multiple calls (progress bars, table rows, custom-formatted output).$monitorfor tracking a small set of signals through every change in the simulation. Only one active per simulation.$strobefor capturing post-NBA values — e.g., the value of a flop'sqafter the clock edge has applied the update. The$displayshows pre-NBAq; the$strobeshows post-NBAq.
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.
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 streamThe 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/taskinvocation) 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.
| Function | Returns | Unit | Use case |
|---|---|---|---|
$time | 64-bit integer | rounded to time_unit | General-purpose timestamps in logs |
$realtime | 64-bit real | exact, in time_unit | Sub-precision timing measurements |
$stime | 32-bit integer | rounded to time_unit | Compact timestamps (legacy, rarely used) |
The choice rule:
$timefor almost everything. Pair with%0tformat specifier to print without unit suffix.$realtimewhen measuring sub-unit precision (jitter, propagation delays smaller thantime_unit).$stimeonly 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.
$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
initialblock 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. $stopis for interactive debugging — it drops to the simulator's TUI prompt. Never leave a$stopin 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:
- 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). - Warning + strip — most modern synth tools emit a "non-synthesisable system task" warning and drop the task.
- 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.
`ifdef SIMULATION
always @(posedge clk) begin
$display("[t=%0t] state=%h", $time, state);
end
`endifThe 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
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
$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
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
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
always @(posedge clk) begin
$display("debug"); // synth warns; engineer dismisses
important_signal <= a; // synth deletes entire block including this!
endSome 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
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 valuesThe 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.
- **Wrap every system task in
\ifdef SIMULATION** (or the project's equivalent). Never leave a$display` exposed to synthesis. - Use
$strobefor post-NBA observations. When you need to print the value of a flop AFTER its non-blocking update has applied,$strobeis the right tool — not$display. - Use
$urandom_range(max, min)for bounded random values. Avoid$random % N— modulo on a signed value introduces subtle distribution bias. - Every testbench's top-level
initialends with$finish. With an exit code if the testbench knows the verdict. Otherwise CI can't tell pass from fail. - Use
%hfor register values,%0dfor unsigned decimals,%0tfor time. Avoid the signed-decimal%dfor vector values — large positives print as negatives. - One
$monitorper simulation. If you need to track multiple groups of signals, combine into one$monitorcall or use separate$display-in-alwaysblocks.
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.
// 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.
| # | Requirement | Recommendation |
|---|---|---|
| 1 | Print a one-line debug message at a specific point in the testbench | ? |
| 2 | Continuously print whenever a state signal changes | ? |
| 3 | Print the value of a flop's q AFTER the clock edge applies its update | ? |
| 4 | Generate a uniform random value in [0, 99] | ? |
| 5 | Read a hex memory image from a file into a reg [7:0] mem [0:1023] array | ? |
| 6 | End the simulation with exit code 0 | ? |
| 7 | End the simulation with exit code 1 (failure) | ? |
| 8 | Dump all signals in the testbench scope to a VCD file | ? |
| 9 | Get the current simulation time as an integer | ? |
| 10 | Get 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.
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
endmoduleHints. 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
$displayvs$monitorvs$strobevs$write— the display family in depth. - 8.2
$random— Random Number Generation — randomization, seeding, distribution. - 8.3 Time Functions —
$time/$realtime/$stimeand the\timescale` interaction. - 8.4 Conditional Simulation —
$assert/$error/$warning/$fatal/$infoseverity layer. - 8.5 Simulation Control —
$finish/$stop/$exitand exit-code discipline.
The mental model:
- Active vs Postponed regions —
$displayruns in Active (pre-NBA, sees OLD values);$stroberuns 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
$strobefor post-NBA observations.$displayshows pre-NBA values, which surprises engineers who expect to see the new flop output. - Use
$urandom_rangeover$random %. Uniform distribution by construction. - Every testbench ends with
$finish. With an exit code if the verdict is known. %hfor register values,%0dfor unsigned decimals,%0tfor 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.
Related Tutorials
- Conditional Compilation — Chapter 7.4; the
`ifdef SIMULATIONdiscipline 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.