VERILOG SERIES · MODULE 17

System Tasks, Functions & File I/O — VLSI Trainers
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 ✅ Yes Immediately (active region) Standard debug print — most common
$write ❌ No Immediately (active region) Build a line in parts across multiple calls
$writeln ✅ Yes Immediately (active region) Alias of $display (SystemVerilog)
$strobe ✅ Yes End of time step (after NBA) Print stable post-NBA values — avoids mid-cycle x
$monitor ✅ Yes End of time step, whenever any listed signal changes Continuous automatic logging
$monitoron Immediately Re-enable monitoring after $monitoroff
$monitoroff Immediately Pause 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
%bBinary 8'hAB → 10101011Exact bit width, leading zeros shown
%oOctal 8'hAB → 253 Groups of 3 bits
%dDecimal 8'hAB → 171 Unsigned decimal; most readable for counters
%hHexadecimal 8'hAB → ab Lowercase hex; use for addresses and data
%HHex uppercase 8'hAB → AB Same as %h but uppercase
%eScientific 3.14 → 3.140000e+00Floating point exponential notation
%fFloat 3.14 → 3.140000 Fixed point decimal; default 6 decimal places
%gAuto float 3.14 → 3.14 Shorter of %e or %f
%sString "abc" → abc ASCII string from packed reg
%cCharacter 65 → A Single ASCII character (7:0 of value)
%tTime $time → 100 Formatted per $timeformat settings
%mModule path tb.dut.alu Hierarchical instance name — no argument needed
%0dNo padding 5 → 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
$finish Ends simulation immediately — exits the simulator Normal 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
$stop Pauses simulation — enters interactive mode Breakpoint debugging — simulation can be resumed
$stop(n) Pauses with detail level n Debugging with timing statistics
$fatal Terminates with error message and non-zero exit code Assertion failures — signal test failure to CI systems
$error Prints error message, increments error count Non-fatal test failures — lets simulation continue
$warning Prints warning, increments warning count Non-critical anomalies
$info Prints informational message Progress 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
$time Current simulation time 64-bit integer In units of the current `timescale time unit. Rounds to time unit.
$realtime Current simulation time real (floating point) Includes fractional time unit — matches precision setting.
$stime Current simulation time 32-bit integer 32-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 √x Returns 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 10 Returns 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 b Returns same type
$max(a,b) Maximum of a and b Returns 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 representation Transfers real values across module ports (reals cannot cross ports)
$bitstoreal(v) 64-bit integer → real Reverse of $realtobits — reconstructs real from bit pattern
$signed(expr) Interprets expression as signed Changes sign extension behaviour — does not change bit values
$unsigned(expr) Interprets expression as unsigned Removes 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
$random 32-bit signed random integer Range: −2³¹ to +2³¹−1. Same seed produces identical sequence.
$random(seed) 32-bit signed random integer Seeded call — provide an integer variable; it gets updated each call.
$urandom 32-bit unsigned random integer SystemVerilog. 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) distribution Returns integer approximation of normal distribution.
$dist_exponential(seed,mean) Exponential distribution Models 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 filename Must be called before any $dumpvars
$dumpvars(levels, scope) Specify which signals to dump levels=0 dumps all levels; scope=module dumps that hierarchy
$dumpall Force a dump of all current values right now Used to create a reference point in the waveform
$dumpon Resume VCD dumping after $dumpoff Re-enables recording
$dumpoff Suspend VCD dumping Saves file size by excluding uninteresting time windows
$dumplimit(size) Limit VCD file size in bytes Stops 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 array Space or newline separated hex values; @addr sets start address
$readmemb(file, mem) Load binary values from file into memory array Same as $readmemh but values are in binary (0/1)
$readmemh(file, mem, start) Load starting at specific address start = first array index to write
$readmemh(file, mem, start, end) Load into address range start..end Limits the region of the array populated
$writememh(file, mem) Save memory array to hex file Writes hex values, one per line
$writememb(file, mem) Save memory array to binary file Writes 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 ❌ No Immediately
$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 file Character value as integer; -1 (EOF) at end
$fgets(str, fd) Read one line into string variable 0 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 string Number of items successfully matched
$fread(mem, fd) Read binary data into memory array Number of bytes read
$feof(fd) Check if end-of-file reached Non-zero if at EOF, 0 otherwise
$rewind(fd) Seek back to beginning of file 0 on success
$fseek(fd, offset, origin) Seek to position in file 0 on success; origin: 0=start, 1=current, 2=end
$ftell(fd) Return current file position Byte 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
$display ConsoleActive region (immediate)
$write ConsoleActive region (immediate)
$strobe ConsoleObserved region (post-NBA)
$monitor ConsoleObserved region, auto on change
$fdisplay File Active region (immediate)
$fwrite File Active region (immediate)
$fstrobe File Observed region (post-NBA)
$fmonitor File Observed region, auto on change

File I/O Quick Reference

Task / FunctionOperationReturns
$fopen(name, mode) Open file File descriptor (0=fail)
$fclose(fd) Close file (flush)
$feof(fd) Check end-of-file Non-zero at EOF
$fgetc(fd) Read one character Integer (-1=EOF)
$fgets(str, fd) Read one line 0=ok, -1=EOF
$fscanf(fd, fmt, …) Read formatted values Items matched
$sscanf(str, fmt, …) Parse string Items matched
$fread(mem, fd) Read binary data Bytes read
$fseek(fd, off, org) Seek in file 0=success
$ftell(fd) Current position Byte offset
$rewind(fd) Seek to start 0=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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top