UVM RAL · Chapter 8 · Memories
uvm_mem
The RAL memory model represents a memory, a large array of locations, as one lightweight object instead of the thousands of registers it would take to model each location. Modelling a memory as an array of registers would explode build time and simulator memory for no benefit, because memory locations are homogeneous storage rather than individually specified registers. So it represents the whole array by its shape, its number of locations, its width, and an access policy, and lets you read and write by index. The defining surprise, and the thing that trips up everyone coming from registers, is that the model keeps no per-location mirror, so a memory read moves data but does not auto-check it. Verifying memory contents is a scoreboard or backdoor job. This lesson ends with the bug where an engineer expects a memory read to catch corruption through a mirror that does not exist.
Foundation12 min readUVM RALuvm_memmemoryno mirrorscoreboard
Chapter 8 · Section 8.1 · Memories
1. Why Should I Learn This?
Real designs are full of memories — packet buffers, lookup tables, descriptor rings, scratchpad RAMs — and a register model that only knows registers cannot reach them. uvm_mem is how RAL covers that storage, and understanding its two defining properties — it is one lightweight object for the whole array, and it keeps no mirror — is what lets you model memories efficiently and, critically, check them correctly rather than assuming an auto-compare that is not there.
Learning uvm_mem — why memories are not modelled as register arrays, and why a memory read does not self-check — is the foundation for everything else in this chapter (maps, bursts, backdoor, large-array strategy). It builds on the mirror concept from 0.1, and it reframes checking as a scoreboard/backdoor responsibility that later sections make concrete.
2. Industry Story — the memory test that checked nothing
A team adds a packet-buffer RAM to their environment, models it with uvm_mem, and writes a test that writes a pattern across the memory and reads it back. They rely on the same instinct that served them for registers: 'the read compares against the model, so if a location is corrupt, the read will catch it.' The test passes, and memory integrity is marked verified.
But uvm_mem keeps no mirror, so mem.read() did not compare anything — it moved the data from the DUT into a variable and returned, checking nothing against any expected value. The 'pattern write then read' exercised the datapath but verified nothing, because nothing in the flow held an expected copy to compare against. A real corruption — a stuck bit in one RAM location — sailed through: the read returned the corrupt value, no mirror existed to disagree with it, and the test reported success. The gap was found much later when the corrupt buffer caused a functional failure downstream. The post-mortem lesson: uvm_mem keeps no per-location mirror, so a memory read moves data but does not auto-check it — verifying memory contents requires a scoreboard or a backdoor compare that holds the expected data, and assuming a memory read self-checks like a register read verifies nothing.
3. Concept — one lightweight object, sized by shape, with no mirror
uvm_mem models an entire memory as a single object. You declare it once, configure it into its block, and give it three defining attributes:
size— the number of addressable locations (the depth of the array).n_bits— the width of each location in bits.- access —
RWorROfor the whole memory (memories are homogeneous; you do not give each location its own fields and policy the way you do a register, 1.5).
It is added to an address map (8.2) at a base offset, and you access it by index into the array — mem.write(status, index, data), mem.read(status, index, data), plus peek/poke for backdoor (8.4) and burst operations for contiguous ranges (8.3). Here is the shape it represents:
The two things that make uvm_mem what it is: it is one lightweight object (not a register per location — that is the whole point of a separate class), and it holds no mirror (so accesses move data but the model does not shadow or auto-check the contents).
4. Mental Model — a labelled warehouse, not a wall of mailboxes, and no inventory list
5. Working Example — declaring, sizing, and accessing a memory
Declare a uvm_mem, size it by shape, add it to the block, and access it by index — then check with a scoreboard because the read does not:
// Model a 4K x 32 packet-buffer RAM as ONE uvm_mem — not 4096 uvm_reg.
class my_reg_block extends uvm_reg_block;
rand uvm_mem pkt_buf; // the whole memory, one object
virtual function void build();
// size = 4096 locations, n_bits = 32, access = "RW".
pkt_buf = uvm_mem::type_id::create("pkt_buf");
pkt_buf.configure(this, 4096, 32, "RW"); // shape: depth x width x access
// add to the address map at a base offset (8.2):
default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
default_map.add_mem(pkt_buf, 'h1000); // base offset in the map
endfunction
endclass// Access BY INDEX. read()/write() move data but do NOT auto-check (no mirror).
uvm_status_e s;
uvm_reg_data_t rd;
pkt_buf.write(s, 12'h010, 32'hCAFE_F00D); // write location 0x010
pkt_buf.read(s, 12'h010, rd); // read it back INTO rd — NO comparison happens here
// 'rd' now holds whatever the DUT returned. Nothing has been checked yet.// Because there is no mirror, YOU compare — e.g. against a scoreboard/reference copy you kept:
if (rd !== expected_mem[12'h010]) // your own expected data, not a model mirror
`uvm_error("MEM", $sformatf("corruption at 0x010: got %h exp %h", rd, expected_mem[12'h010]))
// The check exists only because you wrote it. A register's mirror would auto-do this; uvm_mem does not.One object models the whole RAM, accessed by index — and the correctness check is explicit, because uvm_mem moves data without checking it. The next section is what happens when you forget that.
6. Debugging Session — expecting a mirror that memories do not have
A memory read does not auto-check because uvm_mem keeps no mirror — relying on 'the read will catch corruption' verifies nothing
uvm_mem HAS NO MIRROR// Test writes a pattern and reads it back, expecting the read to CATCH corruption 'via the mirror':
foreach (pattern[i]) pkt_buf.write(s, i, pattern[i]);
foreach (pattern[i]) pkt_buf.read(s, i, rd); // BUG: rd is fetched, but NOTHING compares it
// Engineer's assumption: 'like a register, read() auto-checks against the model.' It does NOT for
// a memory -- uvm_mem keeps no mirror, so this loop verifies nothing. Corruption passes silently.The memory test passes, and memory integrity is recorded as verified — but a real defect (a stuck bit in one RAM location, an address-decode error) is not caught. The read returns the corrupt value, the test does nothing with it, and reports success. Later, the corrupt buffer causes a functional failure somewhere downstream, and the memory 'that was tested' turns out never to have been checked at all. There is no error message, no failure — the tell is only visible if you notice that the read loop fetches values but never compares them to anything.
The test relied on the register mental model, where a uvm_reg's read auto-compares the value hardware returns against the register's mirror — its shadow copy of the expected value (0.1) — and raises an error on a mismatch. uvm_mem deliberately keeps no such mirror: shadowing every location of a large array is exactly the cost that motivated modelling a memory as one lightweight object rather than a register per location, so the class simply does not maintain per-location expected values. Consequently mem.read() moves data — it fetches the DUT's value into your variable — but has nothing to compare against and performs no check. The 'write a pattern, read it back' loop exercises the access path but verifies nothing, because at no point does anything hold the expected data and assert equality. The corruption passes not because the read failed, but because the read was never a check to begin with.
Provide the checking yourself, since the model does not. Two standard approaches: keep the expected contents in a scoreboard or reference array (what you wrote) and explicitly compare each read against it; or read the location backdoor and compare the frontdoor and backdoor values (8.4), which cross-checks the access path against the storage. Either way the comparison is code you write — if (rd !== expected[i]) uvm_error(...). The rule the bug teaches: uvm_mem keeps no mirror, so a memory read does not auto-check the way a register read does — verifying memory contents is a scoreboard or backdoor-compare responsibility, and a read-back loop with no explicit comparison verifies nothing. The register instinct 'the read will catch it' does not transfer to memories, because there is no mirror to catch anything.
7. Common Mistakes
- Modelling a memory as an array of
uvm_reg. One register per location explodes build time and simulator memory for no benefit — use oneuvm_memsized by shape. - Assuming a memory read auto-checks.
uvm_memkeeps no mirror;readmoves data but compares nothing — you must check via scoreboard or backdoor (8.4). - Recording 'memory verified' from a read-back loop with no comparison. Fetching values is not checking them; without an explicit compare, nothing was verified.
- Expecting per-location fields and access policies. Memories are homogeneous storage — one access policy for the whole array, not per-location fields (1.5).
- Confusing the register mirror with memory behaviour. The mirror-based auto-compare of
uvm_reg(0.1) simply does not exist foruvm_mem.
8. Industry Best Practices
- Model every memory with
uvm_mem. One lightweight object per array, sized bysizexn_bitswith an access policy — never a register per location. - Check memory contents with a scoreboard or backdoor compare. Hold expected data yourself and compare explicitly; the model will not (8.4).
- Treat a read-back as data movement, not verification. Add the comparison; a loop that reads without comparing verifies nothing.
- Use backdoor peek for independent cross-checks. Compare frontdoor reads against backdoor reads of the same location to catch access-path bugs (8.4).
- Size and place memories in the map deliberately.
size,n_bits, endianness, and base offset define the memory's footprint (8.2).
9. Interview / Review Questions
10. Key Takeaways
uvm_memmodels an entire memory as one lightweight object defined by its shape —size(locations) xn_bits(width) x one access policy — accessed by index, added to a map at a base offset (8.2).- A memory is not modelled as an array of
uvm_reg: a register per location would explode build time and simulator memory for homogeneous storage that needs none of the per-register machinery. - The defining surprise:
uvm_memkeeps no per-location mirror, so a memoryreadmoves data but does not auto-check it — unlike a register read (0.1). - Therefore checking memory contents is your job — hold expected data in a scoreboard or compare via backdoor (8.4); a read-back loop with no explicit comparison verifies nothing.
- The register instinct 'the read will catch corruption via the mirror' does not transfer to memories — there is no mirror to catch anything.