Testbenches often need to talk to files: read input vectors prepared by a script, write results for later comparison, or save a log that is separate from the simulator's own output. Verilog provides system tasks and functions to open a file, write values into it, read values back into variables, and close it again.

12 min read | Beginner Level

What You'll Learn

  • How to open and close files with $fopen and $fclose, and which modes exist
  • How to write to files with $fdisplay, $fwrite, $fstrobe and $fmonitor
  • How to read a file line by line with $fgets, $fscanf and $feof
  • How to format values into a string with $sformat, and the common file I/O mistakes

Opening and closing files

$fopen opens a file and returns an integer file descriptor, a number that identifies the file in all later calls. $fclose closes the file and writes out anything still waiting in the buffer.


module tb;
  // Declare a variable to store the file handler
  integer fd;

  initial begin
    // Open a new file by the name "my_file.txt"
    // with "write" permissions, and store the file
    // handler pointer in variable "fd"
    fd = $fopen("my_file.txt", "w");

    // Close the file handle pointed to by "fd"
    $fclose(fd);
  end
endmodule

If the file cannot be opened, for example because it does not exist in read mode or the directory is not writable, $fopen returns 0. Always check for this before using the descriptor:


fd = $fopen("input_vectors.txt", "r");
if (fd == 0) begin
  $display ("ERROR: could not open input_vectors.txt");
  $finish;
end

Opening file modes

The second argument of $fopen selects the mode. The b variants exist for compatibility and behave the same as the plain modes on most systems.

ArgumentDescription
"r" or "rb"Open for reading
"w" or "wb"Create a new file for writing. If the file exists, truncate it to zero length and overwrite it
"a" or "ab"If file exists, append (open for writing at EOF), else create a new file
"r+", "r+b" or "rb+"Open for both reading and writing
"w+", "w+b" or "wb+"Truncate or create for update
"a+", "a+b", or "ab+"Append, or create new file for update at EOF

Called with only a file name, as in mcd = $fopen("log.txt");, $fopen opens the file for writing and returns a multichannel descriptor, in which each bit stands for one open file. Descriptors can then be combined with | to write the same line to several files at once. The mode form shown above is the more common one in modern code.

How to write files

Each display task has a file version with an f in its name. It takes the file descriptor as its first argument and otherwise works exactly like the display task it is based on.

FunctionDescription
$fdisplaySimilar to $display, writes out to file instead
$fwriteSimilar to $write, writes out to file instead
$fstrobeSimilar to $strobe, writes out to file instead
$fmonitorSimilar to $monitor, writes out to file instead

Each of these also has variants that print values without a format specifier in a different radix:

FunctionDescription
$fdisplay()Prints in decimal by default
$fdisplayb()Prints in binary
$fdisplayo()Prints in octal
$fdisplayh()Prints in hexadecimal

module tb;
  integer   fd;
  integer   i;
  reg [7:0] my_var;

  initial begin
    // Create a new file
    fd = $fopen("my_file.txt", "w");
    my_var = 0;

    $fdisplay(fd, "Value displayed with $fdisplay");
    #10 my_var = 8'h1A;
    $fdisplay(fd, my_var);      // Displays in decimal
    $fdisplayb(fd, my_var);     // Displays in binary
    $fdisplayo(fd, my_var);     // Displays in octal
    $fdisplayh(fd, my_var);     // Displays in hex

    // $fwrite does not print the newline char '\n' automatically at
    // the end of each line; So we can predict all the values printed
    // below to appear on the same line
    $fdisplay(fd, "Value displayed with $fwrite");
    #10 my_var = 8'h2B;
    $fwrite(fd, my_var);
    $fwriteb(fd, my_var);
    $fwriteo(fd, my_var);
    $fwriteh(fd, my_var);

    // Jump to new line with '\n', and print with strobe which takes
    // the final value of the variable after non-blocking assignments
    // are done
    $fdisplay(fd, "\nValue displayed with $fstrobe");
    #10 my_var <= 8'h3C;
    $fstrobe(fd, my_var);
    $fstrobeb(fd, my_var);
    $fstrobeo(fd, my_var);
    $fstrobeh(fd, my_var);

    #10 $fdisplay(fd, "Value displayed with $fmonitor");
    $fmonitor(fd, my_var);

    for(i = 0; i < 5; i= i+1) begin
      #5 my_var <= i;
    end

    #10 $fclose(fd);
  end
endmodule

Contents of my_file.txt after the simulation:

Value displayed with $fdisplay
 26
00011010
032
1a
Value displayed with $fwrite
 43001010110532b
Value displayed with $fstrobe
 60
00111100
074
3c
Value displayed with $fmonitor
 60
  0
  1
  2
  3
  4

Several details in this file are worth reading closely:

  • Without a format specifier, a decimal value is padded to the widest value the variable could hold. An 8-bit value can be up to 255, so 26 is written as 26 and 0 as 0.
  • The four $fwrite calls produce one line: 43, then 00101011, 053 and 2b, all for 8'h2B, with nothing in between.
  • The \n at the start of the next string ends that line, otherwise "Value displayed with $fstrobe" would be appended to it.
  • $fstrobe shows 8'h3C (60), the value after the nonblocking assignment in the same time step.
  • $fmonitor writes the current value once when it starts, then a line every time my_var changes.

Synthesis result: None. File I/O tasks are simulation-only and are ignored by synthesis. They belong in testbenches and behavioral models.

Quick Check

What would the first $fmonitor line contain if $fmonitor(fd, "%0d", my_var); were used instead?

Show Answer

60 without the leading space, and the following lines would be 0 to 4 without padding. The %0d specifier prints only the digits that are needed.

How to read files

Reading works the other way round: a function reads characters from the file descriptor into variables, and returns a status value that tells you whether it succeeded.

Reading a line

$fgets reads characters from the file into a string variable until the variable is full, a newline character is read, or the end of the file is reached. It returns the number of characters read, or 0 if an error occurred or nothing could be read. The newline itself is stored in the string too.


reg [8*80:1] line;     // Room for 80 characters
integer      code;

code = $fgets(line, fd);

To read values instead of text, $fscanf reads with a format, like $display in reverse. It returns the number of values it could read:


integer a, b, n;

n = $fscanf(fd, "%h %h\n", a, b);    // Reads two hex numbers from one line

Detecting EOF

$feof returns a non-zero value once the end of the file has been reached for the given file descriptor. The example below reads a file line by line until the end:


module tb;
  reg [8*45:1] str;
  integer      fd;
  integer      code;

  initial begin
    fd = $fopen("my_file.txt", "r");

    // Keep reading lines until EOF is found
    while (! $feof(fd)) begin

      // Get current line into the variable 'str'
      code = $fgets(str, fd);

      // Display contents of the variable, unless nothing was read
      if (code != 0)
        $write("%0s", str);
    end
    $fclose(fd);
  end
endmodule

Two details matter here. $feof only becomes true after a read has tried to go past the end, so the last call to $fgets reads nothing and returns 0; checking code prevents printing the last line twice. And because $fgets keeps the newline in str, the line is printed with $write, which adds no second newline.

Test Vectors from a Reference Model

Algorithms such as filters or image-processing blocks are usually developed first in a software model. The model writes its inputs and expected outputs to a text file, one vector per line. The testbench reads each line with $fscanf, drives the inputs into the RTL, and compares the outputs with the expected values from the same line. This way, the RTL is checked against thousands of known-good results without writing them by hand.

Multiple arguments to fdisplay

When several variables are passed to $fdisplay without a format string, they are written one after the other with no spaces in between:


module tb;
  reg [3:0] a, b, c, d;
  integer fd;

  initial begin
    a = 4'ha;
    b = 4'hb;
    c = 4'hc;
    d = 4'hd;

    fd = $fopen("my_file.txt", "w");
    $fdisplay(fd, a, b, c, d);
    $fclose(fd);
  end
endmodule
10111213

The four values 10, 11, 12 and 13 run together, which makes the file hard to read back. A format string such as $fdisplay(fd, "%0d %0d %0d %0d", a, b, c, d); separates them with spaces, so the line can later be read with $fscanf.

Quick Check

Why does each value take exactly two characters in 10111213?

Show Answer

The variables are 4 bits wide, so the largest possible value is 15, which has two digits. Without a format, each value is padded to that width. Here every value already has two digits, so no spaces appear.

Formatting data to a string

$sformat works like $display, but instead of printing the result, it stores it in a variable. The first argument is the destination variable, the second is the format string, and the remaining arguments are the values to format.


module tb;
  reg [8*19:0] str;
  reg [3:0] a, b;

  initial begin
    a = 4'hA;
    b = 4'hB;

    // Format 'a' and 'b' into a string given
    // by the format, and store into 'str' variable
    $sformat(str, "a=%0d b=0x%0h", a, b);
    $display("%0s", str);
  end
endmodule
Output
xcelium> run
a=10 b=0xb
xmsim: *W,RNQUIE: Simulation is complete.

Building a string first is useful when the same text is needed in several places, for example to print it to the console and to a file, or to build a file name such as "result_seed_42.txt" before calling $fopen. The variable must be wide enough: each character takes 8 bits, and a string that is too long is cut off from the left.

One Output File per Test

When many tests run in parallel, each must write to its own file. Testbenches build the file name with $sformat from the test name and seed, open it with $fopen, and write all results into it. A script then compares each file with a golden reference file, and any difference marks the test as failed.

Common Beginner Mistakes

Mistake #1: Not checking the result of $fopen

Wrong: fd = $fopen("vectors.txt", "r"); followed directly by reads
Right: Check if (fd == 0) and stop the simulation with an error message

Why? A wrong path or missing file makes $fopen return 0. All following reads fail quietly, and the test runs with no stimulus, which can even look like a pass.

Mistake #2: Reading one line too many at the end of the file

Wrong: Using the variable after $fgets or $fscanf without checking the return value
Right: Only use the data when the function returned a non-zero value

Why? $feof becomes true only after a read has failed at the end of the file. Without the check, the last line is processed twice, because the variable still holds it from the previous read.

Mistake #3: Forgetting $fclose

Wrong: Ending the simulation with $finish while files are still open
Right: Call $fclose(fd) before $finish

Why? Writes are buffered. Most simulators flush open files at the end, but if the simulation crashes or is killed, the last part of the file can be missing, often exactly the part that shows the failure.

Mistake #4: Calling a file function as a statement

Wrong: $fgets(str, fd);
Right: code = $fgets(str, fd);

Why? $fgets, $fscanf, $feof and $fopen are functions that return a value. In Verilog, a function call must be part of an expression, and the return value is exactly what tells you whether the read worked.

Practice Exercise

Challenge: Copy Vectors and Add a Result

A file in.txt contains pairs of hex numbers, one pair per line, such as 0a 05. Write a testbench that reads every pair, and writes each pair together with its sum to out.txt, in the format 0a + 05 = 00f.

Hint

Use $fscanf(fd_in, "%h %h\n", a, b) in a loop and stop when it returns something other than 2. Declare a and b as 8 bits and the sum as 9 bits, so the carry is not lost.

Solution

module tb;
  integer   fd_in, fd_out, n;
  reg [7:0] a, b;
  reg [8:0] sum;

  initial begin
    fd_in  = $fopen("in.txt",  "r");
    fd_out = $fopen("out.txt", "w");
    if (fd_in == 0 || fd_out == 0) begin
      $display ("ERROR: could not open files");
      $finish;
    end

    n = $fscanf(fd_in, "%h %h\n", a, b);
    while (n == 2) begin
      sum = a + b;
      $fdisplay(fd_out, "%h + %h = %h", a, b, sum);
      n = $fscanf(fd_in, "%h %h\n", a, b);
    end

    $fclose(fd_in);
    $fclose(fd_out);
    $finish;
  end
endmodule

For an input line ff 01, the output is ff + 01 = 100, because the 9-bit sum keeps the carry. The loop reads before it checks, so it stops cleanly at the end of the file or at the first line that does not contain two hex numbers.

Summary

In this tutorial, you learned:

  • $fopen(name, mode) returns a file descriptor, or 0 on failure, and $fclose closes the file
  • $fdisplay, $fwrite, $fstrobe and $fmonitor work like their display counterparts, but write to a file
  • $fgets reads a line, $fscanf reads formatted values, and $feof reports the end of the file
  • Always check the return values of read functions, because $feof only becomes true after a failed read
  • $sformat formats values into a string variable instead of printing them
  • File I/O is simulation-only and ignored by synthesis
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5