Part 7 · Advanced & Integration · Intermediate
File I/O
$fopen/$fclose, $fdisplay/$fwrite logging, $fscanf/$fgets parsing, $readmemh/$readmemb preload, and a stimulus-from-file driver.
Opening, writing, closing
$fopen returns an integer file descriptor — zero means the open failed, so always check it. Mode "w" truncates and writes, "a" appends, "r" reads. $fdisplay writes a line with a newline appended; $fwrite writes exactly what the format produces, so you control line breaks. Unclosed files may lose buffered output when simulation ends — close them in a final block.
int log_fd;
initial begin
log_fd = $fopen("txn_log.csv", "w");
if (log_fd == 0) $fatal(1, "cannot open txn_log.csv");
$fdisplay(log_fd, "time,kind,addr,data"); // CSV header
end
// Per-transaction logging from the monitor
function void log_txn(string kind, bit [31:0] addr, bit [31:0] data);
$fdisplay(log_fd, "%0t,%s,%h,%h", $time, kind, addr, data);
endfunction
final begin
$fclose(log_fd); // flush buffers — do not skip this
end
// MCD trick: fd from $fopen has bit 31 set (regular descriptor).
// Writing to (log_fd | 1) sends output to the file AND stdout,
// because descriptor 1 is the console channel.Reading: $fgets, $fscanf, and $readmemh
// Line-oriented parse: $fgets reads a line, $sscanf parses it
int fd, status;
string line;
bit [31:0] addr, data;
initial begin
fd = $fopen("stimulus.txt", "r");
if (fd == 0) $fatal(1, "stimulus.txt missing");
while ($fgets(line, fd)) begin
if (line.substr(0, 0) == "#") continue; // skip comments
status = $sscanf(line, "%h %h", addr, data);
if (status != 2) continue; // skip malformed lines
$display("stim: addr=%h data=%h", addr, data);
end
$fclose(fd);
end
// $fscanf reads directly from the file — convenient, but a malformed
// token desynchronizes everything after it. $fgets + $sscanf lets you
// recover per line, which is why production parsers prefer it.$readmemh / $readmemb format
For memory images, $readmemh (hex) and $readmemb (binary) load a whole array in one call. The file holds whitespace-separated values, optional comments, and optional @address markers that set where the next value lands — perfect for sparse images. Optional start/end arguments restrict which part of the array is loaded.
logic [31:0] mem [0:1023];
initial begin
$readmemh("boot.hex", mem); // fill from index 0
$readmemh("patch.hex", mem, 512, 767); // only load this range
end
// boot.hex:
// // comments allowed
// @0040 <- @address in HEX, sets next load index
// DEADBEEF 00000001 0000FFFF
// @0100
// CAFEF00DA stimulus-from-file driver
// File-driven stimulus: replays recorded or generated vectors.
// File format per line: <delay_cycles> <write> <addr> <data>
class file_driver;
virtual bus_if vif;
task run(string fname);
int fd, status, delay;
bit wr;
bit [31:0] addr, data;
string line;
fd = $fopen(fname, "r");
if (fd == 0) $fatal(1, "stim file %s missing", fname);
while ($fgets(line, fd)) begin
status = $sscanf(line, "%d %b %h %h", delay, wr, addr, data);
if (status != 4) continue;
repeat (delay) @(posedge vif.clk);
vif.valid <= 1; vif.write <= wr;
vif.addr <= addr; vif.wdata <= data;
@(posedge vif.clk iff vif.ready);
vif.valid <= 0;
end
$fclose(fd);
endtask
endclassFILE TRAFFIC IN A TESTBENCH
IN OUT
── ───
stimulus.txt ──► file_driver ──► DUT ──► monitor ──► txn_log.csv
boot.hex ──► $readmemh ──► dut.mem └─► waves (lesson 5)
│
▼ end of test
mem dump ──► compare vs golden (lesson 4)Key takeaways
Always check the $fopen descriptor and $fclose in a final block — unflushed buffers lose data.
$fdisplay appends a newline; $fwrite does not — pick per use, and CSV logs make post-processing trivial.
Prefer $fgets + $sscanf over raw $fscanf — per-line recovery from malformed input.
$readmemh loads whole arrays with @address sparse markers and optional range limits.
Common pitfalls
Forgetting $fclose — the last buffered lines of the log vanish when simulation exits.
$readmemh file values wider than the array element — high bits silently truncated by most tools.
Treating @address markers in readmem files as byte addresses — they are array indices.
Opening a file per transaction instead of once — file-system overhead dominates simulation time.
Interview angle
Common questions: $fdisplay vs $fwrite (newline handling); why $fgets+$sscanf beats $fscanf for robust parsing; the $readmemh file format including @address; and what the descriptor returned by $fopen means — including the (fd | 1) trick for tee-to-console logging.