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.