Reading Data from a File in Verilog and SystemVerilog

In our previous tutorials, we've already covered the basics of file operations and how to write to files in SystemVerilog. Today, let's dive into reading data from a file, a crucial task for processing or analyzing external data within your Verilog code.

To get started, you need to know that SystemVerilog provides a variety of tasks for reading data, including $fgetc, $ungetc, $fgets, $fscanf, $sscanf, and $fread. Each of these has its own use case and characteristics. Let's go through them one by one.

Opening a File

First things first, you need to open a file for reading, which is done using the $fopen system task with the r or r+ flags.

Reading a Single Character with $fgetc

The $fgetc task reads a single character from the file, which is 8 bits wide. If there is an error during reading, $fgetc returns EOF (which is -1). For example, a lexer would be a client of this function because the basic element for lexing tokens is a character.

Example

integer file, c;
file = $fopen("example.txt", "r");
if (file) begin
  c = $fgetc(file);
  // Process the character 'c'
  $fclose(file);
end

Note: It's good practice to ensure the variable used for reading is more than 8 bits to differentiate between -1 and actual data 0xFF.

Putting a Character Back with $ungetc

The $ungetc task puts the character specified by c back into the buffer associated with the file descriptor fd. This means the next time you call $fgetc, you'll get the character c again. This only changes the buffer, not the file.

Example

integer file, c;
file = $fopen("example.txt", "r");
if (file) begin
  c = $fgetc(file);
  $ungetc(c, file);
  c = $fgetc(file); // 'c' will be the same character as before
  $fclose(file);
end

Reading a Line with $fgets

The $fgets task reads a line from the file until a newline character, the end of the string buffer, or EOF is reached. The return value indicates the number of characters read or 0 if an error occurs. This method is commonly used to process files line by line.

Example

integer file;
string line;
file = $fopen("example.txt", "r");
if (file) begin
  line = $fgets(file);
  // Process the line
  $fclose(file);
end

Reading Formatted Data with $fscanf and $sscanf

These tasks read formatted data from a file or string, respectively, and are similar to the C fscanf function. You specify a format string, and the scanned values are stored in the provided arguments. This method is one approach to handling formatted data. It reads back the formatted string and processes it accordingly. This is particularly useful for structured text files, such as CSVs, where data is organized in a consistent manner.

Example with CSV File

Suppose you have a CSV file data.csv with the following content:

1,John,25
2,Jane,30

We want to read each line of this CSV file and extract the values into appropriate variables.

integer file, id, age, code;
string name;
file = $fopen("data.csv", "r");
if (file) begin
  while (!$feof(file)) begin
    code = $fscanf(file, "%d,%s,%d\n", id, name, age);
    if (code == 3) begin
      $display("ID: %d, Name: %s, Age: %d", id, name, age);
    end else if (code == -1) begin
      // Error occurred
      break;
    end
  end
  $fclose(file);
end

In this example, we open the data.csv file for reading. We then use a while loop to read each line of the CSV file until the end of the file is reached. The $fscanf task parses each line according to the format specified: %d, %s, %d\n. This format specifies that each line contains an integer, a string (name), and another integer, separated by commas. The variable code stores the number of successfully matched and assigned input items. If code equals 3, we have successfully read the ID, name, and age from the line, and we display them using $display. If code equals -1, an error occurred while scanning the data, and we break out of the loop.

Reading Binary Data with $fread

The $fread task reads binary data from a file into memory, byte by byte, in big-endian format. This function is particularly useful when you need to handle raw binary data directly, offering better performance compared to reading data as text. It is often used in situations where efficiency and speed are critical, such as processing large data files or handling complex data structures.

Usage

You can use $fread to read data into memory, specifying optional start and count parameters:

  • start: The start index of the memory.
  • count: The maximum indices of memory to read.

Example

Consider a binary file data.bin where each byte is 0xFF. The following example demonstrates reading from this file into a 32-bit memory array:

`timescale 1ns/1ns

module MonitorTB;
  int fd, code;
  logic [31:0] mem [0:255]; // Define memory array to store the data
  int start = 2; // Start index
  int count = 4; // Maximum memory index to read

  initial begin
    fd = $fopen("data.bin", "r");
    if (fd == 0) begin
      $display("Error: Could not open file.");
      $finish;
    end

    // Read data from the file into memory array
    code = $fread(mem, fd, start, count);
    if (code == 0) begin
      $display("Error: Could not read data.");
    end else begin
      $display("Read %0d bytes of data.", code);
    end
    
    // Display the contents of the first few memory locations
    for (int i = 0; i < 8; i++) begin
      $display("mem[%0d] = %h", i, mem[i]);
    end
    
    $fclose(fd);
  end
endmodule

The file data.bin is opened for reading. $fread(mem, file, start, count) reads data starting from memory index 2 and fills up to 4 memory locations. Since each memory location is 32 bits, a total of 16 bytes are read. The data is read in big-endian format, so each 32-bit memory location gets 0xFFFFFFFF (all bits set to 1).

Given that the file contains all 0xFF bytes and the memory starts as X, the expected output after reading is:

Read 16 bytes of data.
mem[0] = xxxxxxxx
mem[1] = xxxxxxxx
mem[2] = ffffffff
mem[3] = ffffffff
mem[4] = ffffffff
mem[5] = ffffffff
mem[6] = xxxxxxxx
mem[7] = xxxxxxxx

This output shows that data loading starts at memory index 2 and continues for 4 memory locations, each receiving 0xFFFFFFFF, while the other locations remain unchanged.


Reading data from a file in SystemVerilog can be straightforward once you understand the various tasks available. Each task has its specific use case: $fgetc for single characters, $ungetc for putting characters back into the buffer, $fgets for reading lines, $fscanf and $sscanf for formatted data, and $fread for binary data. Understanding these tools and how to use them effectively can significantly enhance your ability to handle external data within your SystemVerilog projects.