Part 7 · Advanced & Integration · Intermediate

Memory Preload & Dump

Hierarchical readmem into DUT memories, generating preload images from a class model, end-of-test dumps, and golden-file compare flows.

Preloading DUT memories hierarchically

Booting a CPU or filling a large memory through bus transactions costs millions of simulation cycles. The shortcut: call $readmemh with a hierarchical path straight into the DUT's memory array, loading it instantly at time zero. This is white-box — the path names an internal signal — so it belongs in one well-marked place in the testbench, selected by plusarg.

systemverilog
// tb_top.sv — preload block, image chosen at run time
string mem_image;

initial begin
  if ($value$plusargs("MEM_IMAGE=%s", mem_image)) begin
    // Hierarchical reference into the DUT's RAM 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 X / uninitialized — also a valid test!
end

// simv +MEM_IMAGE=images/boot_rom.hex

// CAUTION: the path tracks RTL internals. If the designer renames
// u_sram or retimes the memory into banks, this line breaks —
// keep all such paths in ONE file so the fix is one edit.

Generating images from a class model

Hand-written hex files do not scale past toy examples. Generate the image from the same class model the scoreboard uses: randomize or construct the contents, write the file, preload the DUT, and keep the model as the expected state for the end-of-test compare.

systemverilog
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

initial begin
  mem_model model = new();
  model.randomize_region(32'h0000_0000, 256);
  model.randomize_region(32'h0001_0000, 64);
  model.write_image("preload.hex");
  $readmemh("preload.hex", tb_top.dut.u_sram.mem_array);
  // model now doubles as expected-state for the final compare
end

End-of-test dump and golden compare

Mirror of preload: at end of test, $writememh dumps the DUT memory to a file, which is compared against a golden image — either by diffing files outside the simulator or by an in-testbench loop against the class model. The in-TB compare gives precise per-address failure messages; the file diff integrates with scripted flows and signs off binary-identical images.

systemverilog
final begin
  // 1. Dump actual DUT memory to a file
  $writememh("mem_actual.hex", tb_top.dut.u_sram.mem_array);
end

// 2a. In-TB compare against the class model — precise diagnostics
task automatic check_mem(mem_model exp);
  bit [31:0] addr;
  int errors = 0;
  if (exp.mem.first(addr))
    do begin
      if (tb_top.dut.u_sram.mem_array[addr] !== exp.mem[addr]) begin
        $error("mem[%h]: dut=%h exp=%h", addr,
               tb_top.dut.u_sram.mem_array[addr], exp.mem[addr]);
        errors++;
      end
    end while (exp.mem.next(addr));
  if (errors == 0) $display("MEM COMPARE PASS");
endtask

// 2b. Or script-level golden diff after the sim exits:
//   $system("diff mem_actual.hex golden/mem_expected.hex");
//   (or in the regression script, keeping the TB tool-agnostic)
diagram
PRELOAD / DUMP / COMPARE FLOW

  mem_model (class)
      │ write_image()
      ▼
  preload.hex ──► $readmemh ──► DUT memory
                                   │  ... test runs, DUT modifies mem ...
      ┌────────────────────────────┘
      ▼ $writememh (final block)
  mem_actual.hex
      │                       mem_model (updated by scoreboard)
      ├── file diff ◄── golden.hex            │
      └── OR in-TB compare ◄──────────────────┘
              │
              ▼
      PASS / per-address $error messages

Key takeaways

  • Hierarchical $readmemh skips millions of boot cycles — preload at time 0, select the image by plusarg.

  • Generate images from the same class model the scoreboard uses — one source of truth for expected state.

  • $writememh at end of test plus a compare (file diff or in-TB loop) closes the memory-verification loop.

  • Keep all hierarchical memory paths in one file — they break on every RTL rename otherwise.

Common pitfalls

  • Use !== (4-state compare) in the in-TB check — == treats X as unknown-match and hides corruption.

  • Preloading after reset releases — reset logic may clear the memory you just loaded; order matters.

  • Golden file diff failing on X digits or formatting differences between tools — normalize before diffing.

  • Hierarchical paths into banked or generate-wrapped memories — one RTL refactor silently breaks the preload of half the banks.

Interview angle

Expect: how do you boot a CPU testbench without simulating the ROM load (hierarchical readmem); what is the trade-off (white-box path coupling vs cycles saved); and how do you verify final memory state — strong answers contrast file-diff vs in-TB compare and mention !== for X-safety.