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.
// 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.
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
endEnd-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.
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)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 messagesKey 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.