Skip to content

UVM RAL · Chapter 8 · Memories

Memory Maps

A memory has to live somewhere in the address space, and you place it into a register map at a base offset with the add mem call, the memory sibling of add reg. Endianness, byte enables, and bytes per bus access apply just as they do for registers. But there is one decisive difference: a register occupies a single address, while a memory occupies a whole range from its base up to base plus size times its byte width. So placing a memory is allocating a footprint, and the entire span must be free. This changes how overlaps happen. If you place the next register too close, forgetting the memory extends across its whole span, the memory's tail overlaps its neighbour. Low indices pass, and only high indices, reached late, land on the neighbouring register and corrupt both. This page covers the add mem call, how a span is computed, and that overlap bug.

Foundation12 min readUVM RALadd_memaddress rangememory footprintoverlap

Chapter 8 · Section 8.2 · Memories

1. Why Should I Learn This?

Every memory you model has to be placed in the address space without colliding with anything else, and memories are the one thing in a map that occupy a range rather than a point — so getting their placement right (and their overlaps caught) is a distinct skill from register placement. Miss the fact that a memory spans size * n_bytes bytes and you get the nastiest kind of addressing bug: one that passes every ordinary test and only bites at high indices, corrupting two things at once.

Learning how add_mem places a memory, how its footprint is computed, and how its range can overlap a neighbour is what keeps your memory placements collision-free. It builds directly on register address maps (3.1–3.5) and on uvm_mem (8.1), and it sets up burst access (8.3) and backdoor (8.4), which both address into this range.

2. Industry Story — the memory that ate the register next door

A team places a 1K-by-32 buffer memory at base 0x2000 and, right after it, a control register at 0x2400. The reasoning at placement time was loose: '0x2000 for the buffer, and 0x2400 is a nice round address a bit later for the control register.' Everything passes — configuration writes the control register, tests write and read the low end of the buffer — and the map is signed off.

But a 1K-by-32 memory spans 1024 * 4 = 0x1000 bytes, so it occupies 0x2000 through 0x2FFF — and the control register at 0x2400 sits inside that range. The memory's own location index 0x100 also resolves to 0x2400. Low buffer indices (below 0x100) and the control register coexisted fine in every early test, which is why it passed. Then a stress test wrote the whole buffer, reached index 0x100, and that write landed on 0x2400 — corrupting the control register and failing to store the buffer data. The block was mis-configured mid-test by a memory write, producing a bizarre, intermittent functional failure that took days to trace back to an address-map overlap nobody saw because the memory's span was never accounted for. The post-mortem lesson: a memory occupies a range, not an address — base through base + size * n_bytes — so placing a memory means allocating that entire footprint; sizing the gap to the next register as if the memory were a single address lets the memory's tail overlap its neighbour, and only high indices, reached late, expose the collision by corrupting both.

3. Concept — a memory is a footprint, add_mem allocates the span

You add a memory to a map with add_mem, giving it a base offset — but what it claims is a whole range determined by its shape (8.1):

  • Footprint = size * n_bytes. A memory of size locations, each n_bytes wide (its n_bits rounded to the bus's byte granularity), spans from its base B to B + size * n_bytes. Location index i resolves to address B + i * n_bytes (adjusted for endianness and bus width, 3.3).
  • add_mem allocates that footprint. Placing the memory reserves the entire span; every other register and memory in the map must stay outside [B, B + size * n_bytes).
  • Overlap is a range problem, not a point problem. Unlike a register (one address), a memory can overlap a neighbour along any part of its span — most dangerously its tail, if the next item was placed too close to the base without accounting for the memory's length.
  • Endianness and byte layout still apply. How each location's bytes map onto bus lanes follows the map's endianness and n_bytes exactly as for registers (3.3).

Here is the address-space layout that shows the hazard — a memory whose span, if under-budgeted, runs into the next register:

Address map: buffer memory spans 0x2000-0x2FFF; a register wrongly placed at 0x2400 falls inside the span; safe next base is 0x3000extends for size x n_bytesextends forsize x…tail overlaps neighbourtailoverlaps…place next item HEREbuffer baseadd_mem @ 0x2000buffer SPAN0x2000 .. 0x2FFF =size(1024) x n_bytes(4)ctrl reg @ 0x2400INSIDE the span — collideswith mem index 0x100safe next base0x3000 = base + size xn_bytes12
Figure 1 — a memory occupies a RANGE in the map, not a single address. The buffer added at base 0x2000 with 1024 locations x 4 bytes spans 0x2000..0x2FFF (footprint = size x n_bytes). A control register placed at 0x2400 falls INSIDE that span — memory index 0x100 also resolves to 0x2400 — so high buffer indices collide with the register. The next item must sit at or beyond base + size x n_bytes (here 0x3000). Placing a memory means allocating its whole footprint, not just its base.

4. Mental Model — a memory is a plot of land, not a house number

5. Working Example — placing a memory by its footprint

Add a memory to a map by computing its footprint and placing the next item beyond it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A 1K x 32 buffer: size = 1024, n_bits = 32 -> n_bytes = 4. FOOTPRINT = 1024 * 4 = 0x1000 bytes.
class my_reg_block extends uvm_reg_block;
  rand uvm_mem buffer;
  rand ctrl_reg ctrl;
  virtual function void build();
    default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
 
    buffer = uvm_mem::type_id::create("buffer");
    buffer.configure(this, 1024, 32, "RW");        // size=1024, n_bits=32
    default_map.add_mem(buffer, 'h2000);           // occupies 0x2000 .. 0x2FFF (base + size*n_bytes)
 
    ctrl = ctrl_reg::type_id::create("ctrl");
    ctrl.configure(this); ctrl.build();
    // Place the NEXT item at or beyond base + footprint = 0x2000 + 0x1000 = 0x3000. NOT 0x2400.
    default_map.add_reg(ctrl, 'h3000);             // safely outside the memory's span
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The footprint is size * n_bytes — always place neighbours beyond it. A quick self-check:
// base = 0x2000, size = 1024, n_bytes = 4  ->  end = 0x2000 + 1024*4 = 0x3000 (exclusive)
// Any register/memory with an address in [0x2000, 0x3000) OVERLAPS the buffer. ctrl at 0x3000 is safe.

Placing the register at 0x3000 (base plus the full 0x1000 footprint) leaves the memory's entire span clear. Placing it at 0x2400 — a 'round number a bit later' — would put it inside the span, the bug of the next section.

6. Debugging Session — a register placed inside a memory's span

1

A memory occupies base + size*n_bytes; placing the next register inside that span makes high memory indices corrupt the register and vice-versa

MEMORY SPANS A RANGE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The buffer's FOOTPRINT (1024 * 4 = 0x1000) was ignored; the register was placed as if the
// memory occupied roughly one address, at a 'round number a bit later':
default_map.add_mem(buffer, 'h2000);   // buffer spans 0x2000 .. 0x2FFF
default_map.add_reg(ctrl,   'h2400);   // BUG: 0x2400 is INSIDE the buffer's span
// Buffer index 0x100 also resolves to 0x2400 -> writing that index corrupts ctrl, and vice-versa.
Symptom

Everything passes for a long time: configuration writes ctrl at 0x2400, and buffer tests write and read low indices (well below 0x100), which do not reach 0x2400. Then a stress test that writes the entire buffer reaches index 0x100, and that write lands on 0x2400 — the control register — silently corrupting it. The block is suddenly mis-configured mid-test, producing an intermittent, bizarre functional failure with no obvious link to memory: the memory access 'succeeded,' the register was never explicitly written, and only high buffer indices trigger it, so it looks random and takes days to trace to an address collision.

Root Cause

A memory occupies a range, not a single address: buffer at base 0x2000 with size = 1024 and n_bytes = 4 spans 0x2000 through 0x2FFF (footprint size * n_bytes = 0x1000). The control register was placed at 0x2400, which falls inside that span — and buffer location index 0x100 resolves to the same address 0x2000 + 0x100 * 4 = 0x2400. So the memory's tail and the register alias the same physical address: a write to buffer index 0x100 and a write to ctrl hit the same location. The map author sized the gap between the memory and the register as if the memory occupied only its base address (a register's mental model), forgetting that the memory extends for its full footprint. Early tests passed because they only touched low indices, far from the collision at index 0x100; the overlap was latent until a high index was reached. It is a range-versus-neighbour overlap, invisible to any test that never walks to the memory's tail.

Fix

Place neighbouring items beyond the memory's full footprint: compute base + size * n_bytes = 0x2000 + 0x1000 = 0x3000 and put ctrl at 0x3000 (or anywhere outside [0x2000, 0x3000)), so the memory's entire span is clear. Then audit the whole map for any register or memory whose address falls inside another memory's span. The rule the bug teaches: a memory occupies base through base + size * n_bytes, so placing it means allocating that entire footprint — always position neighbours at or beyond the memory's end, never budget the gap as if the memory were a single address. The tell for this class of bug in the wild is a memory that works at low indices and corrupts a register (or another memory) only at high indices: that is a span overlap, and the fix is in the map placement, not the memory or register themselves.

7. Common Mistakes

  • Placing a memory's neighbour as if the memory occupied one address. The memory spans size * n_bytes; a 'round number a bit later' can land inside the span — place neighbours at or beyond base + size * n_bytes.
  • Forgetting to convert n_bits to n_bytes. The footprint is in bytes; a 32-bit location is 4 bytes, so size * 4, not size — under-counting the span invites overlap.
  • Testing only low memory indices. Low indices miss a tail overlap entirely; a span collision only shows at high indices, often only under stress (8.5).
  • Treating a high-index corruption as a data bug. Works-low, fails-high with a neighbour corrupted is a range overlap in the map, not a memory data error.
  • Ignoring endianness/n_bytes for the location layout. Byte order and bus width still govern how each location maps to lanes (3.3).

8. Industry Best Practices

  • Place memories by footprint, not base. Reserve [base, base + size * n_bytes) and put every neighbour outside it.
  • Compute the memory's end address explicitly at placement. base + size * n_bytes; put the next item there or beyond — do not eyeball the gap.
  • Audit maps for memory-span overlaps. Check that no register or memory address falls inside any memory's span, not just for point collisions (3.5).
  • Stress high memory indices. Walk the whole memory, including its tail, so a span overlap surfaces in test, not silicon (8.5).
  • Diagnose works-low-fails-high as a range collision. A memory that corrupts a neighbour only at high indices is overlapping along its span — fix the placement.

9. Interview / Review Questions

10. Key Takeaways

  • A memory is placed in a map with add_mem at a base offset, but — unlike a register (one address) — a memory occupies a range: base through base + size * n_bytes (the footprint), with location i at base + i * n_bytes.
  • Placing a memory means allocating its entire footprint — every other register and memory must sit outside [base, base + size * n_bytes).
  • The signature bug is budgeting the gap as if the memory were a single address, so the next item lands inside the span and a high memory index aliases the neighbour, corrupting both.
  • Such overlaps escape ordinary tests (which touch only low indices) and surface only at high indices under stress — 'works low, fails high, neighbour corrupted' is a range collision in the map, not a data bug.
  • Defend by computing the end address (base + size * n_bytes) and placing neighbours beyond it, auditing for addresses inside any memory's span, and walking the whole memory including its tail (8.5).