System Tasks, Functions & File I/O — VLSI Trainers
VLSI Trainers Verilog Series · 15 / 18
Verilog Series · Module 17

System Tasks, Functions & File I/O

Complete reference for Verilog’s built-in system tasks and functions — display, simulation control, math, conversion, random — and the full set of file-based I/O tasks for reading and writing files during simulation.

📖 Introduction

System tasks and system functions are built-in Verilog constructs whose names begin with a dollar sign ($). They provide essential simulation services — printing output, controlling simulation time, generating random numbers, performing math, and reading/writing files. They operate entirely at simulation time and are never synthesized.

📺 Display ⚙️ Simulation Control 🧮 Math & Conversion 🎲 Random 🔄 Type Conversion 📁 File I/O
$
Dollar Prefix
All system tasks and functions start with $. Tasks are called as statements; functions return a value and appear in expressions.
🔬
Simulation Only
System tasks are completely invisible to synthesis tools. A synthesizer ignores every $ statement in the design.
📋
Task vs Function
Tasks ($display, $finish) perform actions with no return value. Functions ($time, $random) return a value for use in expressions.
🌐
Global Scope
System tasks can be called from any initial or always block, at any level of hierarchy. They share a single simulator-managed output stream.

📺 Display Tasks

Display tasks print formatted text to the simulation console. They are the primary debugging tool in Verilog testbenches — equivalent to printf in C. Each variant controls when it prints and whether it appends a newline.

📺 Display / Write / Write-line
TaskNewline?When printsUse case
$display✅ YesImmediately (active region)Standard debug print — most common
$write❌ NoImmediately (active region)Build a line in parts across multiple calls
$writeln✅ YesImmediately (active region)Alias of $display (SystemVerilog)
$strobe✅ YesEnd of time step (after NBA)Print stable post-NBA values — avoids mid-cycle x
$monitor✅ YesEnd of time step, whenever any listed signal changesContinuous automatic logging
$monitoronImmediatelyRe-enable monitoring after $monitoroff
$monitoroffImmediatelyPause monitoring during setup/reset phases
Fig 1 — Display family: syntax and all format specifiers in use
// ── Basic $display ────────────────────────────────────────────
$display("Hello Verilog!");              // prints with newline
$display("a = %b, b = %d", a, b);        // formatted output

// ── $write — no automatic newline ─────────────────────────────
$write("Part1 ");
$write("Part2 ");
$display("Part3");   // output: "Part1 Part2 Part3\n"

// ── $strobe — always shows final stable values ────────────────
always @(posedge clk) begin
  q <= d;
  $display("$display: q=%b", q);   // may show OLD q (pre-NBA)
  $strobe ("$strobe:  q=%b", q);   // always shows NEW q ✅
end

// ── $monitor — fires automatically on any signal change ───────
initial $monitor("t=%0t a=%b b=%b out=%b", $time, a, b, out);
// Prints every time a, b, or out changes — no explicit call needed

// ── Suppress monitor during initialization ────────────────────
initial begin
  $monitoroff;         // pause — suppress noise during reset
  rst_n = 0; #20;
  rst_n = 1; #5;
  $monitoron;          // resume — start logging from here
end

🔤 Format Specifiers

Format strings use %-prefixed specifiers to control how values are printed. The same specifiers apply to $display, $write, $strobe, $monitor, and all file I/O variants ($fdisplay, $fwrite, etc.).

SpecNameExample outputNotes
%bBinary8'hAB → 10101011Exact bit width, leading zeros shown
%oOctal8'hAB → 253Groups of 3 bits
%dDecimal8'hAB → 171Unsigned decimal; most readable for counters
%hHexadecimal8'hAB → abLowercase hex; use for addresses and data
%HHex uppercase8'hAB → ABSame as %h but uppercase
%eScientific3.14 → 3.140000e+00Floating point exponential notation
%fFloat3.14 → 3.140000Fixed point decimal; default 6 decimal places
%gAuto float3.14 → 3.14Shorter of %e or %f
%sString"abc" → abcASCII string from packed reg
%cCharacter65 → ASingle ASCII character (7:0 of value)
%tTime$time → 100Formatted per $timeformat settings
%mModule pathtb.dut.aluHierarchical instance name — no argument needed
%0dNo padding5 → 5 not 5%0 prefix removes leading spaces
%%Literal %%Escape to print a percent sign
\nNewline(newline)Explicit newline inside format string
\tTab(tab)Horizontal tab character
Fig 2 — Format specifiers: practical examples
reg [7:0] data = 8'hAB;
integer    cnt  = 42;

// All numeric formats for the same value
$display("Binary  : %b",  data); // Binary  : 10101011
$display("Octal   : %o",  data); // Octal   : 253
$display("Decimal : %d",  data); // Decimal :         171 (width-padded)
$display("Decimal : %0d", data); // Decimal : 171     (no padding)
$display("Hex     : %h",  data); // Hex     : ab
$display("Hex UC  : %H",  data); // Hex UC  : AB

// Time and module path
$display("Time=%0t  Module=%m", $time); // Time=100  Module=tb.u_dut

// Multi-signal formatted line
$display("[%0t] data=0x%h cnt=%0d", $time, data, cnt);
// [100] data=0xab cnt=42

// $timeformat: sets how %t appears globally
// $timeformat(units, precision, suffix, min_width);
$timeformat(-9, 2, " ns", 10); // -9=ns, 2 decimal places
$display("Time = %t", $time);    // Time =  100.00 ns

👁 $monitor and $strobe — Stable Value Printing

Both $strobe and $monitor wait until the end of the current time step (after all non-blocking assignment updates) before printing. This guarantees that post-NBA final values are shown — not intermediate simulation states.

🔵 $strobe — Once per call

// Prints once at end of timestep
// when the statement is encountered
always @(posedge clk)
  $strobe("q=%b", q);
// Fires every posedge clk
// Prints stable q (post-NBA)

🟢 $monitor — Auto on any change

// Set once, fires automatically
// whenever ANY listed signal changes
initial
  $monitor("%t q=%b", $time, q);
// Fires any time q changes value
// Prints at end of that time step
Only one $monitor active at a time. Calling $monitor again replaces the previous one — only the most recently declared $monitor is active. Use $monitoroff / $monitoron to selectively pause logging during initialization or when applying stimulus.

⚙️ Simulation Control Tasks

These tasks control the execution of the simulation itself — stopping it, exiting with a status code, or pausing for interactive debugging.

⚙️ Simulation Control
TaskEffectWhen to use
$finishEnds simulation immediately — exits the simulatorNormal end of testbench — always the last call
$finish(n)Ends simulation with detail level n (0=quiet, 1=stats, 2=full)When you need simulation performance statistics
$stopPauses simulation — enters interactive modeBreakpoint debugging — simulation can be resumed
$stop(n)Pauses with detail level nDebugging with timing statistics
$fatalTerminates with error message and non-zero exit codeAssertion failures — signal test failure to CI systems
$errorPrints error message, increments error countNon-fatal test failures — lets simulation continue
$warningPrints warning, increments warning countNon-critical anomalies
$infoPrints informational messageProgress reporting, pass messages
Fig 3 — Simulation control tasks in a self-checking testbench
integer pass_count=0, fail_count=0;

task check_result;
  input [7:0] expected, actual;
  input [63:0] test_id;
  begin
    if (actual === expected) begin
      $display("PASS [%0d] expected=%h got=%h", test_id, expected, actual);
      pass_count = pass_count + 1;
    end else begin
      $error("FAIL [%0d] expected=%h got=%h", test_id, expected, actual);
      fail_count = fail_count + 1;
    end
  end
endtask

initial begin
  // ... run all tests ...
  #1000;

  // Final summary
  $display("\n===== RESULTS: %0d PASS / %0d FAIL =====",
           pass_count, fail_count);

  if (fail_count > 0)
    $fatal(1, "%0d test(s) failed", fail_count); // non-zero exit
  else
    $finish;   // success — exit normally
end

Time Functions

Time functions return the current simulation time — essential for timestamping events, measuring intervals, and generating time-relative stimulus.

FunctionReturnsTypeNotes
$timeCurrent simulation time64-bit integerIn units of the current `timescale time unit. Rounds to time unit.
$realtimeCurrent simulation timereal (floating point)Includes fractional time unit — matches precision setting.
$stimeCurrent simulation time32-bit integer32-bit truncated version of $time. Wraps at 4.29 billion time units.
Fig 4 — Time functions and $timeformat
`timescale 1ns/1ps

// ── Reading current time ──────────────────────────────────────
initial begin
  #10.5;
  $display("$time     = %0t",    $time);     // 10  (rounded to 1ns)
  $display("$realtime = %0.3f",   $realtime); // 10.500 (ps precision)
end

// ── $timeformat configuration ─────────────────────────────────
// $timeformat(time_unit, decimal_places, suffix, min_field_width)
$timeformat(-9, 3, " ns", 12);
// -9 = nanoseconds, 3 decimal places, suffix " ns", min width 12

$display("Current time = %t", $time);   // "   10.000 ns"

// ── Measuring interval between events ────────────────────────
integer t_start, t_end;
initial begin
  t_start = $time;
  @(posedge done);
  t_end   = $time;
  $display("Operation took %0d ns", t_end - t_start);
end

🧮 Math Functions

Verilog provides a set of built-in math system functions for both integer and floating-point computations. These are useful in parameterised designs (e.g., $clog2 for deriving address widths) and in testbenches for stimulus generation.

🧮 Integer Math
FunctionReturnsSynthesizable?Example
$clog2(n)⌈log₂(n)⌉ — ceiling log base 2✅ Yes (Verilog-2005+)$clog2(256) = 8
$bits(expr)Bit width of an expression or type✅ Yes$bits(data) = 32 for 32-bit data
🧮 Real-Number Math (simulation only)
FunctionReturnsNotes
$abs(x)Absolute value |x|Works on integer and real
$sqrt(x)Square root √xReturns real; x must be ≥ 0
$pow(x, y)x raised to power y (xʸ)Returns real
$log(x)Natural logarithm ln(x)Returns real
$log10(x)Log base 10Returns real
$exp(x)e raised to power x (eˣ)Returns real
$sin(x)Sine of x (radians)Returns real
$cos(x)Cosine of x (radians)Returns real
$floor(x)Floor ⌊x⌋Returns real
$ceil(x)Ceiling ⌈x⌉Returns real
$min(a,b)Minimum of a and bReturns same type
$max(a,b)Maximum of a and bReturns same type
Fig 5 — Math functions: $clog2 in RTL and real math in testbenches
// ── $clog2 — synthesizable, used in parameter expressions ─────
parameter DEPTH  = 256;
localparam ADDR_W = $clog2(DEPTH);     // = 8
localparam CNT_W  = $clog2(DEPTH+1);   // = 9 (count 0..256)
reg [ADDR_W-1:0] ptr;                    // [7:0] — auto-sized

// ── Real math in testbench ────────────────────────────────────
real freq_hz, period_ns, amplitude;
freq_hz   = 1e9;                           // 1 GHz
period_ns = 1e9 / freq_hz;               // 1.0 ns

// Sine wave stimulus generation
real pi = 3.14159265;
integer i;
initial
  for (i=0; i<360; i=i+1) begin
    amplitude = $sin(i * pi / 180.0);
    $display("%0d deg: sin = %.4f", i, amplitude);
  end

🔄 Conversion Functions

Conversion functions translate between Verilog’s different data representations — integers, real numbers, and bit vectors.

FunctionConvertsNotes
$rtoi(x)real → integer (truncates toward zero)$rtoi(3.9) = 3, $rtoi(-3.9) = -3
$itor(n)integer → real$itor(7) = 7.0 — for use in real expressions
$realtobits(x)real → 64-bit IEEE 754 representationTransfers real values across module ports (reals cannot cross ports)
$bitstoreal(v)64-bit integer → realReverse of $realtobits — reconstructs real from bit pattern
$signed(expr)Interprets expression as signedChanges sign extension behaviour — does not change bit values
$unsigned(expr)Interprets expression as unsignedRemoves signed interpretation — result is always non-negative
Fig 6 — Conversion functions in practice
// ── $rtoi / $itor ─────────────────────────────────────────────
real    r  = 3.75;
integer n  = $rtoi(r);       // n = 3 (truncated)
real    r2 = $itor(n * 2);   // r2 = 6.0

// ── $realtobits / $bitstoreal — transfer reals across ports ───
module top;
  real   r_val = 3.14;
  wire [63:0] wire_bits;
  real   r_recv;

  assign wire_bits = $realtobits(r_val);  // pack real as 64-bit
  assign r_recv    = $bitstoreal(wire_bits); // unpack on other side
endmodule

// ── $signed / $unsigned — arithmetic interpretation ───────────
reg [7:0] a = 8'hFF;       // bit pattern: 11111111

$display("unsigned: %0d", a);               // 255
$display("signed:   %0d", $signed(a));     // -1

// Signed arithmetic comparison
if ($signed(a) < 0)
  $display("a is negative when treated as signed");

🎲 Random Functions

Random functions generate pseudo-random numbers for constrained-random testbenches, stimulus generation, and fault injection.

Function / TaskReturnsNotes
$random32-bit signed random integerRange: −2³¹ to +2³¹−1. Same seed produces identical sequence.
$random(seed)32-bit signed random integerSeeded call — provide an integer variable; it gets updated each call.
$urandom32-bit unsigned random integerSystemVerilog. Always non-negative. Range: 0 to 2³²−1.
$urandom_range(max, min)Unsigned random in [min, max]SystemVerilog. Easier than computing range manually.
$dist_uniform(seed,lo,hi)Uniform distribution in [lo, hi]Verilog PLI — uses Mersenne Twister algorithm.
$dist_normal(seed,mean,sd)Gaussian (normal) distributionReturns integer approximation of normal distribution.
$dist_exponential(seed,mean)Exponential distributionModels inter-arrival times in queuing systems.
Fig 7 — Random functions: generating bounded and distributed values
// ── $random: basic usage ─────────────────────────────────────
reg [7:0] rand_byte;
rand_byte = $random;         // lower 8 bits of 32-bit random
rand_byte = $random % 256;  // same — forces 0..255

// ── Bounded random — common patterns ─────────────────────────
integer r;
r = {$random} % 16;        // {} makes it unsigned → 0..15
r = ($random % 10) + 1;   // 1..10

// ── With seed for reproducible sequences ─────────────────────
integer seed = 42;
r = $random(seed);          // seed is modified each call

// ── Generate random test packet ───────────────────────────────
reg [7:0] pkt [0:63];
integer pkt_len, i;
pkt_len = ({$random} % 60) + 4;  // 4..63 bytes
for (i=0; i<pkt_len; i=i+1)
  pkt[i] = $random;

// ── $dist_uniform — clean bounded generation (PLI) ───────────
integer seed2 = 1234;
r = $dist_uniform(seed2, 0, 255);   // uniform 0..255

📈 VCD and Waveform Dump Tasks

VCD (Value Change Dump) tasks record signal value changes to a file that can be opened in waveform viewers (GTKWave, Verdi, DVE). Every change to a dumped signal is captured with a timestamp.

TaskPurposeNotes
$dumpfile(“file.vcd”)Set the VCD output filenameMust be called before any $dumpvars
$dumpvars(levels, scope)Specify which signals to dumplevels=0 dumps all levels; scope=module dumps that hierarchy
$dumpallForce a dump of all current values right nowUsed to create a reference point in the waveform
$dumponResume VCD dumping after $dumpoffRe-enables recording
$dumpoffSuspend VCD dumpingSaves file size by excluding uninteresting time windows
$dumplimit(size)Limit VCD file size in bytesStops dumping when file reaches this size
Fig 8 — Complete VCD setup in a testbench
initial begin
  // ── Configure waveform dump ───────────────────────────────
  $dumpfile("sim_output.vcd");    // VCD output file name
  $dumpvars(0, tb);               // dump ALL signals in tb hierarchy
  $dumpvars(1, tb.dut);           // dump only top-level of dut
  $dumpvars(0, tb.dut.alu);       // dump all of alu sub-module

  // Apply reset stimulus...
  $dumpoff;                        // stop recording during reset
  rst_n = 0; #20; rst_n = 1;
  $dumpon;                         // resume recording

  // Run tests...
  #5000;
  $finish;
end

// ── Memory load tasks (often used with waveform) ──────────────
reg [7:0] rom [0:255];
initial begin
  $readmemh("program.hex", rom);         // load hex file into array
  $readmemh("data.hex", rom, 10, 20);   // load into addresses 10..20
  $readmemb("data.bin", rom);            // load binary format
  $writememh("out.hex", rom);           // write memory to file
end

💾 Memory Load and Save Tasks

TaskPurposeFormat
$readmemh(file, mem)Load hex values from file into memory arraySpace or newline separated hex values; @addr sets start address
$readmemb(file, mem)Load binary values from file into memory arraySame as $readmemh but values are in binary (0/1)
$readmemh(file, mem, start)Load starting at specific addressstart = first array index to write
$readmemh(file, mem, start, end)Load into address range start..endLimits the region of the array populated
$writememh(file, mem)Save memory array to hex fileWrites hex values, one per line
$writememb(file, mem)Save memory array to binary fileWrites binary values, one per line

📁 File-Based Tasks and Functions — Introduction

Verilog provides a complete set of file I/O system tasks for reading data from and writing data to files during simulation. This enables testbenches to load stimulus from external files, compare results against golden reference files, and log detailed simulation output for post-processing.

📂
File Handle
Files are identified by an integer file descriptor (handle) returned by $fopen. Pass this handle to all subsequent read/write operations.
📝
Multi-Channel
Each bit of the 32-bit descriptor selects a channel. Multiple files can be written simultaneously by ORing their descriptors.
🔄
Same Format
File versions mirror display tasks: $fdisplay, $fwrite, $fstrobe, $fmonitor — all take a file descriptor as first argument.
🔒
Always Close
Always call $fclose before $finish to flush buffers and release file handles. Unclosed files may lose the last writes.

🔑 $fopen and $fclose

$fopen opens a file for reading or writing and returns an integer file descriptor. $fclose closes it, flushing all buffered writes.

“r”
Read
Open existing file for reading. Fails if not found.
“w”
Write
Create or truncate file for writing.
“a”
Append
Open file and append at end. Creates if not found.
“rb”
Read Binary
Open binary file for byte-level reading.
“wb”
Write Binary
Create or truncate binary file for writing.
Fig 9 — $fopen / $fclose: opening, checking, and closing files
integer log_fd, ref_fd;

initial begin
  // ── Open files ────────────────────────────────────────────
  log_fd = $fopen("sim_log.txt",  "w");   // write new log
  ref_fd = $fopen("reference.hex", "r");  // read reference

  // ── Always check for open failure (fd=0 means failure) ───
  if (log_fd == 0) begin
    $display("ERROR: Cannot open sim_log.txt");
    $finish;
  end

  // ── Use files... ──────────────────────────────────────────
  $fdisplay(log_fd, "Simulation started at t=%0t", $time);

  // ── Always close before $finish ───────────────────────────
  $fclose(log_fd);
  $fclose(ref_fd);
  $finish;
end

// ── Multi-channel write — OR descriptors together ─────────────
integer f1, f2, both;
f1   = $fopen("file1.txt", "w");
f2   = $fopen("file2.txt", "w");
both = f1 | f2;          // write to BOTH at once
$fdisplay(both, "This goes to both files!");

✍️ Writing to Files

All display tasks have file equivalents — add f prefix and a file descriptor as the first argument. The format string and remaining arguments are identical to the console versions.

File TaskConsole equivalentNewline?When writes
$fdisplay(fd, fmt, …)$display✅ YesImmediately
$fwrite(fd, fmt, …)$write❌ NoImmediately
$fstrobe(fd, fmt, …)$strobe✅ YesEnd of time step
$fmonitor(fd, fmt, …)$monitor✅ YesEnd of step, on change
Fig 10 — Writing formatted data and binary data to files
integer fd;
reg [7:0] data;

initial begin
  fd = $fopen("output.csv", "w");

  // Write CSV header
  $fdisplay(fd, "time_ns,data_hex,data_dec,data_bin");

  // Write data rows as simulation runs
  repeat(16) begin
    @(posedge clk);
    $fdisplay(fd, "%0t,%h,%0d,%b", $time, data, data, data);
  end

  // Write without newlines then add one manually
  $fwrite(fd, "Summary: ");
  $fwrite(fd, "16 samples written");
  $fwrite(fd, "\n");          // manual newline

  // Write to console AND file simultaneously
  $fdisplay(fd | 1, "Done!"); // fd|1: fd=file, 1=stdout

  $fclose(fd);
end

📖 Reading from Files

File reading tasks parse text files and populate Verilog variables with the read values. They are essential for loading test vectors, golden reference data, and configuration from external files.

Task / FunctionPurposeReturns
$fgetc(fd)Read one character (byte) from fileCharacter value as integer; -1 (EOF) at end
$fgets(str, fd)Read one line into string variable0 on success, -1 at EOF
$fscanf(fd, fmt, vars…)Read formatted values from file (like C scanf)Number of items successfully matched
$sscanf(str, fmt, vars…)Parse formatted values from a stringNumber of items successfully matched
$fread(mem, fd)Read binary data into memory arrayNumber of bytes read
$feof(fd)Check if end-of-file reachedNon-zero if at EOF, 0 otherwise
$rewind(fd)Seek back to beginning of file0 on success
$fseek(fd, offset, origin)Seek to position in file0 on success; origin: 0=start, 1=current, 2=end
$ftell(fd)Return current file positionByte offset from start of file

🔍 $fscanf and $sscanf

$fscanf reads formatted values from a file — exactly like C’s fscanf. $sscanf parses a string instead of a file. Both are essential for reading structured test vector files.

Fig 11 — $fscanf and $sscanf: parsing structured input
// ── $fscanf: read test vectors from CSV ───────────────────────
integer    fd, n;
reg [7:0] in_a, in_b, expected;
string     line;

initial begin
  fd = $fopen("vectors.txt", "r");
  if (fd == 0) begin $fatal(1, "Cannot open vectors.txt"); end

  // File format example (one test per line):
  //   0xAB 0xCD 0x01
  //   0xFF 0x00 0xFF

  while (!$feof(fd)) begin
    n = $fscanf(fd, "%h %h %h\n", in_a, in_b, expected);
    if (n == 3) begin            // 3 items matched
      @(posedge clk);
      a = in_a; b = in_b;
      @(posedge clk);
      if (result !== expected)
        $error("FAIL: a=%h b=%h exp=%h got=%h", in_a, in_b, expected, result);
    end
  end
  $fclose(fd);
end

// ── $sscanf: parse a string in memory ────────────────────────
reg [255:0] str_buf;
integer     val1, val2;

str_buf = "0xAB 0xCD";
n = $sscanf(str_buf, "%h %h", val1, val2);
// n=2, val1=8'hAB, val2=8'hCD
Fig 12 — $fgetc and $fgets: character and line reading
// ── $fgetc: read character by character ──────────────────────
integer fd, ch;
fd = $fopen("input.txt", "r");

while (1) begin
  ch = $fgetc(fd);
  if (ch == -1) break;          // -1 = EOF
  if (ch == "\n") $display(""); // newline
  else            $write("%c", ch);
end
$fclose(fd);

// ── $fgets: read a whole line ─────────────────────────────────
reg [1023:0] line_buf;    // wide enough for 128 chars
integer fd2, ret;
fd2 = $fopen("log.txt", "r");

while (!$feof(fd2)) begin
  ret = $fgets(line_buf, fd2);  // ret=0 success, -1=EOF
  if (ret != -1)
    $display("Line: %s", line_buf);
end
$fclose(fd2);

💡 Complete File I/O Examples

Fig 13 — Golden Reference Comparison Testbench

Load stimulus from file, compare DUT output against golden reference
module golden_tb;
  reg  [7:0] a, b;
  wire [7:0] result;
  reg        clk = 0;

  alu_dut dut(.a(a), .b(b), .out(result));
  always #5 clk = ~clk;

  integer stim_fd, gold_fd, log_fd;
  integer n, pass=0, fail=0;
  reg [7:0] exp;

  initial begin
    stim_fd = $fopen("stimulus.txt", "r");
    gold_fd = $fopen("golden.txt",   "r");
    log_fd  = $fopen("results.log",  "w");

    $fdisplay(log_fd, "%-8s %-8s %-8s %-8s %-5s",
              "time", "a", "b", "result", "PASS?");

    while (!$feof(stim_fd)) begin
      n = $fscanf(stim_fd, "%h %h\n", a, b);   // read a, b
      n = $fscanf(gold_fd, "%h\n",    exp);     // read expected
      @(posedge clk); #1;

      if (result === exp) begin
        $fdisplay(log_fd, "%t %h %h %h PASS", $time, a, b, result);
        pass++;
      end else begin
        $fdisplay(log_fd, "%t %h %h %h FAIL(exp=%h)",
                  $time, a, b, result, exp);
        fail++;
      end
    end

    $fdisplay(log_fd, "\nTotal: %0d pass, %0d fail", pass, fail);
    $fclose(stim_fd); $fclose(gold_fd); $fclose(log_fd);
    if (fail) $fatal(1, "%0d failures", fail);
    else      $finish;
  end
endmodule

Fig 14 — Waveform + Log File simultaneously

Write to console, log file, and VCD all at once
integer log_fd;

initial begin
  // VCD waveform
  $dumpfile("waves.vcd");
  $dumpvars(0, tb);

  // Text log
  log_fd = $fopen("sim.log", "w");
  $timeformat(-9, 2, " ns", 10);

  // fd|1 writes to both log file AND console (fd=1 is stdout)
  $fmonitor(log_fd | 1,
    "%t clk=%b d=%b q=%b", $time, clk, d, q);

  #200;
  $fclose(log_fd);
  $finish;
end

📋 Summary Reference

Display and Monitor Tasks

TaskOutputNewlineTiming
$displayConsoleActive region (immediate)
$writeConsoleActive region (immediate)
$strobeConsoleObserved region (post-NBA)
$monitorConsoleObserved region, auto on change
$fdisplayFileActive region (immediate)
$fwriteFileActive region (immediate)
$fstrobeFileObserved region (post-NBA)
$fmonitorFileObserved region, auto on change

File I/O Quick Reference

Task / FunctionOperationReturns
$fopen(name, mode)Open fileFile descriptor (0=fail)
$fclose(fd)Close file (flush)
$feof(fd)Check end-of-fileNon-zero at EOF
$fgetc(fd)Read one characterInteger (-1=EOF)
$fgets(str, fd)Read one line0=ok, -1=EOF
$fscanf(fd, fmt, …)Read formatted valuesItems matched
$sscanf(str, fmt, …)Parse stringItems matched
$fread(mem, fd)Read binary dataBytes read
$fseek(fd, off, org)Seek in file0=success
$ftell(fd)Current positionByte offset
$rewind(fd)Seek to start0=success
$readmemh(file, mem)Load hex file → array
$readmemb(file, mem)Load binary file → array
$writememh(file, mem)Save array → hex file
Professional testbench pattern: Always combine $dumpfile+$dumpvars (waveform) with $fopen+$fdisplay (text log) and self-checking assertions that call $fatal on failure. This gives you waveform debug capability, a machine-readable log for CI, and automatic pass/fail detection — all three essential elements of a production-quality testbench.
Switch-Level Delays, Strengths & Trireg☰ Verilog Series IndexSystem Tasks, Parameters & Path Delays
Scroll to Top