Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
    • DPI-C: Calling C from SystemVerilog
    • bind: Attaching Verification to RTL
    • Macros, Defines & Compilation
    • Plusargs, Files & Runtime Configuration
      • $test$plusargs & $value$plusargs
      • File I/O
      • Config Architecture: Files vs Plusargs vs Objects
      • Wave Dumping & Runtime Control
    • Simulation Performance & TB Memory
    • RTL/TB Boundaries, Races & Timing
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Plusargs, Files & Runtime Configuration
  1. Topics
  2. SystemVerilog
  3. Advanced Language & Integration
  4. Plusargs, Files & Runtime Configuration
  5. File I/O

Part 7 · Advanced & Integration · Intermediate

File I/O

Open, log, parse and preload: file logging, line parsing and memory image flows.

On this page

    Open, log and close

    $fopen returns an integer descriptor, and 0 means the open failed. Mode w truncates, a appends and r reads, so check the descriptor every time.

    $fdisplay appends a newline to each line. $fwrite writes exactly what the format produces, so it suits partial lines.

    Close every file before the run ends. Unclosed files can lose their last buffered lines when the simulator exits.

    Call

    Newline

    Use for

    $fdisplay

    Yes, one per call

    CSV rows and log lines

    $fwrite

    No

    Partial lines and prompts

    One write can reach the file and the console together, but only through a multichannel descriptor (MCD). $fopen(name) with no mode returns an MCD: bit 31 clear, one bit per channel, and bit 0 is the console. MCDs OR together.

    A descriptor from $fopen(name, "w") has bit 31 set and cannot be OR-ed. ORing 1 into it makes a different descriptor, so the line goes to the wrong place or nowhere. To tee such a file, write it twice.

    design.sv
    1
    2
    3
    4
    5
    // snippet
    // Tee one write to the log file and the console together.
    integer log_mcd;
    log_mcd = $fopen("run.log");          // no mode: a multichannel descriptor
    $fdisplay(log_mcd | 1, "checkpoint: %0d txns done", done);   // bit 0 = console

    Parse text without losing sync

    $fgets reads one line and $sscanf parses it. A malformed line fails alone, and the next line still parses.

    $fscanf reads straight from the file instead. One bad token desynchronizes everything after it, so production parsers prefer the per-line form.

    Skip comments and short reads explicitly. The testbench below drops any line whose conversion count is wrong.

    Preload and dump memories

    $readmemh loads a whole hex array in one call, and $readmemb does the same in binary. Values are whitespace-separated, comments are allowed, and @address markers set the next load index.

    File item

    Meaning

    Hex or binary words

    Values for consecutive indices

    @ followed by hex digits

    Next load index, as an array index

    Start and end arguments

    Only that index range loads

    Array indices, not byte addresses, follow the @ marker. A marker of @0040 loads index 64, and the optional range arguments bound the load the same way.

    Large memories preload through the hierarchy instead of the bus. The fragment below names DUT internals, so it is not run in this course's checks.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // not-run: needs a testbench harness with a matching DUT hierarchy
    string mem_image;
    
    initial begin
      if ($value$plusargs("MEM_IMAGE=%s", mem_image)) begin
        // Hierarchical reference into the DUT memory array
        $readmemh(mem_image, tb_top.dut.u_sram.mem_array);
        $display("preloaded %s into dut.u_sram", mem_image);
      end
      // else: memory powers up uninitialized, which is also a valid test
    end
    
    // Run: simv +MEM_IMAGE=images/boot_rom.hex
    // Keep all such paths in one file: an RTL rename breaks them all at once.

    Hand-written images stop scaling early. The generator below builds the image from the scoreboard model. It is a class with no top module, so it is not run here either.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // not-run: class-only listing with no top module to run (Verilator 5.048 compiles it)
    class mem_model;
      bit [31:0] mem [bit [31:0]];    // sparse associative model
    
      function void randomize_region(bit [31:0] base, int words);
        for (int i = 0; i < words; i++)
          mem[base + i] = $urandom();
      endfunction
    
      // Write a $readmemh-compatible image (sparse via @address)
      function void write_image(string fname);
        int fd = $fopen(fname, "w");
        bit [31:0] addr;
        if (fd == 0) $fatal(1, "cannot write %s", fname);
        if (mem.first(addr)) begin
          do begin
            $fdisplay(fd, "@%0h", addr);     // index marker
            $fdisplay(fd, "%h", mem[addr]);
          end while (mem.next(addr));
        end
        $fclose(fd);
      endfunction
    endclass

    The run closes with a dump and a compare. $writememh writes the array out, and an in-testbench loop with !== reports addresses that differ without X masking the result.

    File-driven stimulus replays recorded vectors the same way. That driver needs a bus_if interface and a DUT that are not shown, so it stays a documented pattern rather than a checked block.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // not-run: needs the bus_if interface and a DUT, which are not shown
    // 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
    endclass

    Common mistakes

    Common pitfall

    Three file traps: forgetting $fclose loses the last buffered lines, @ markers are array indices rather than byte addresses, and a != mismatch check (if (got != exp) errors++) is X-blind, because an X condition is false. Use !== for every memory check.

    Interview

    Likely questions: $fdisplay against $fwrite, why $fgets plus $sscanf beats $fscanf for robust parsing, the $readmemh file format including @address, and how to boot a memory without simulating the load.

    Check yourself A readmem file holds @0100 followed by CAFEF00D. Which array index gets the value?

    Index 256. The marker is hex, so 0x100 is 256, and it counts array indices rather than bytes.

    Check yourself The end-of-test compare is if (back[i] != mem[i]) errors++; and passes with X left in memory. What hid the corruption?

    A != with an X operand yields X, and an X condition is false, so the error is never counted. The !== form distinguishes X from every other value.

    Check yourself The log loses its last lines whenever simulation exits. What was skipped?

    The $fclose. Buffered output flushes on close, so every file closes before the run ends.

    Testbench

    One block proves four file flows: a CSV log round-trip over 8 lines, a sparse image preload with @ markers, and a dump plus reload compare over 8 words.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    module tb_fileio;
      localparam int N = 8;
      reg [31:0] exp_a [0:7];
      reg [31:0] exp_d [0:7];
      reg [31:0] mem [0:7];
      reg [31:0] back [0:7];
      integer fd;
      integer status;
      integer errors = 0;
      integer i;
      string line;
      reg [31:0] addr;
      reg [31:0] data;
    
      initial begin
        for (i = 0; i < N; i++) begin
          exp_a[i] = 32'h00001000 + i * 4;
          exp_d[i] = 32'h0000A000 + i;
        end
    
        // Phase 1: log N vectors as hex pairs, one per line.
        fd = $fopen("txn_log.csv", "w");
        if (fd == 0) errors++;
        else begin
          for (i = 0; i < N; i++)
            $fdisplay(fd, "%h %h", exp_a[i], exp_d[i]);
          $fclose(fd);
        end
    
        // Phase 2: read the lines back and compare each field.
        fd = $fopen("txn_log.csv", "r");
        if (fd == 0) errors++;
        else begin
          for (i = 0; i < N; i++) begin
            if ($fgets(line, fd) == 0) errors++;
            else begin
              status = $sscanf(line, "%h %h", addr, data);
              if (status != 2) errors++;
              else if (addr !== exp_a[i] || data !== exp_d[i]) errors++;
            end
          end
          $fclose(fd);
        end
    
        // Phase 3: write a sparse hex image, then preload it.
        for (i = 0; i < N; i++) mem[i] = 32'h0;
        fd = $fopen("img.hex", "w");
        if (fd == 0) errors++;
        else begin
          $fdisplay(fd, "%h", 32'h11111111);   // index 0
          $fdisplay(fd, "%h", 32'h22222222);   // index 1
          $fdisplay(fd, "@0004");              // skip to index 4
          $fdisplay(fd, "%h", 32'h44444444);   // index 4
          $fdisplay(fd, "%h", 32'h55555555);   // index 5
          $fclose(fd);
        end
        $readmemh("img.hex", mem);
        if (mem[0] !== 32'h11111111) errors++;
        if (mem[1] !== 32'h22222222) errors++;
        if (mem[2] !== 32'h0) errors++;
        if (mem[3] !== 32'h0) errors++;
        if (mem[4] !== 32'h44444444) errors++;
        if (mem[5] !== 32'h55555555) errors++;
        if (mem[6] !== 32'h0) errors++;
        if (mem[7] !== 32'h0) errors++;
    
        // Phase 4: dump the array and reload it elsewhere.
        $writememh("dump.hex", mem);
        $readmemh("dump.hex", back);
        for (i = 0; i < N; i++)
          if (back[i] !== mem[i]) errors++;
    
        if (errors == 0) $display("PASS: csv round-trip on 8 lines and image round-trip on 8 words");
        else $fatal(1, "FAIL: %0d file checks did not hold", errors);
        $finish;
      end
    endmodule

    Related topics

    • Logging strategy

    Previous

    200 / 259

    Next: Config Architecture: Files vs Plusargs vs Objects

    Up next

    Up nextConfig Architecture: Files vs Plusargs vs Objects

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Wave Dumping & Runtime Control