UVM RAL · Chapter 12 · Advanced RAL
Virtual Registers
A virtual register has no dedicated hardware register. Instead it is a field layout, a register-like view, imposed on the locations of a memory. It is how you get register-style field access to structured data that physically lives in RAM, such as register files, DMA descriptor arrays, and lookup tables, where each entry has fields you want to read and write by name. Two intuitions carry the topic. First, a virtual register is a view over memory, not a real register, so its value lives in the underlying memory and every access reads or writes those locations. Second, because it lives in memory it inherits memory semantics: no per-location mirror, and an address computed as base plus index times stride rather than a fixed register address. This lesson establishes the view-over-memory model and the correct index-to-location mapping, then breaks the signature bug where a wrong stride makes one virtual register overlap its neighbour in memory.
Foundation12 min readUVM RALvirtual registersuvm_vregmemorystride
Chapter 12 · Section 12.2 · Advanced RAL
1. Why Should I Learn This?
Structured data in RAM — descriptor arrays, register files, tables — is everywhere, and virtual registers are how you access it with register-style field semantics instead of raw memory offsets. Understanding that a virtual register is a view over memory (value in the memory, memory semantics, an index-to-location mapping) — and getting that base + i * stride mapping right — is what lets the view actually land on the intended entries instead of overlapping and corrupting its neighbours.
Learning virtual registers builds directly on uvm_mem (Chapter 8) — a virtual register is a structured view over a memory — and it complements dynamic models (12.1) and register arrays (12.3) as ways to model repeated, data-driven structure.
2. Industry Story — the descriptors that overwrote each other
A team models a DMA descriptor array stored in RAM as virtual registers — each descriptor an entry with address/length/control fields, laid over a memory. Each descriptor is 2 memory words wide, but the virtual register array was configured with a stride of 1 word. Descriptor 0 works; the trouble starts at descriptor 1.
Because the stride was half the descriptor's real size, vreg[i] and vreg[i+1] overlapped in memory: writing descriptor 1 landed partly on descriptor 0's second word, and writing descriptor 0 clobbered descriptor 1. Descriptors corrupted each other, producing a bizarre pattern where configuring one descriptor silently changed another. It looked like a memory-corruption or DUT bug, but the memory and the DUT were fine — the virtual-register view's index-to-location mapping was wrong: a stride too small for the descriptor size, so the computed offsets base + i * stride overlapped. Setting the stride to the descriptor's actual word-size (2) spaced the entries correctly and the cross-corruption vanished. The post-mortem lesson: a virtual register's location in memory is computed as base + i * stride, so the stride must equal the entry's real size in the memory — a stride too small makes adjacent virtual registers overlap and corrupt each other, and a wrong base/size mislocates them; it is the view's index-to-location mapping that is wrong, not the memory or the DUT.
3. Concept — a field-structured view over memory, mapped by base + i*stride
A virtual register imposes a register-like field layout on memory locations. The essentials:
- A view, not a register. A virtual register (
uvm_vreg) has no dedicated storage; it is a field structure over a memory (uvm_mem). Its value lives in the underlying memory, and accessing the virtual register reads/writes those memory locations. - It inherits memory semantics. Because the storage is the memory (Chapter 8): there is no per-location mirror like a real register's (8.1) — checking a virtual register's contents is a memory responsibility (scoreboard/backdoor, 8.4), not an auto-mirror; and backdoor access goes through the memory's
hdl_path, not a dedicated register path. - Its address is an index-to-location mapping. A virtual register array of N entries is placed in the memory by a base offset, a stride (memory words per entry), and a size — so
vreg[i]occupies memory starting atbase + i * stride. Thestridemust equal the entry's real size in memory words. - The signature bug is a wrong mapping. A wrong
stride(or base/size) places the virtual registers at the wrong offsets — a too-small stride makes adjacent entries overlap and corrupt each other.
Here is a virtual-register array overlaid on a memory, vreg[i] at base + i * stride:
4. Mental Model — a spreadsheet view over a byte-blob, and the row height must be right
5. Working Example — a descriptor array as virtual registers over memory
Lay a virtual-register array over a memory with the correct stride, and access entries by field:
// A descriptor array in RAM, viewed as virtual registers. Each descriptor is 2 memory words wide.
class descriptor_vreg extends uvm_vreg;
uvm_vreg_field addr_f, len_f, ctrl_f; // register-like FIELDS — but storage is the MEMORY
// ... field layout across the 2 words ...
endclass
// Place the vreg ARRAY over the memory: N entries, at a base, with STRIDE = entry size in words.
descriptor_vreg descriptors;
descriptors = descriptor_vreg::type_id::create("descriptors");
descriptors.configure(this, desc_mem, /*n_regs*/ 16, /*offset base*/ 0, /*stride words*/ 2);
// stride = 2 because each descriptor occupies 2 memory words -> vreg[i] at base + i*2. Correct spacing.// Access an entry the REGISTER way — but it reads/writes the underlying MEMORY locations.
uvm_status_e s;
descriptors.write(s, /*index*/ 5, desc_data); // writes memory at base + 5*stride (2) = offset 10
descriptors.get_reg_by_index(5).len_f.read(s, len); // field-style access to entry 5's length
// The value lives in desc_mem; there is NO per-vreg mirror (8.1) — check contents via scoreboard/backdoor.// The correctness hinge: STRIDE must equal the entry's real memory size.
// stride == descriptor words (2) -> entries spaced correctly, no overlap.
// stride < descriptor words -> vreg[i] overlaps vreg[i+1] -> mutual corruption (next section).The stride matching the descriptor's real size (2 words) spaces the entries so each occupies its own memory; the register-style access maps through to the underlying memory locations. A stride too small overlaps the entries — the bug of the next section.
6. Debugging Session — a wrong stride making virtual registers overlap
A virtual-register array configured with a stride smaller than the entry size makes adjacent entries overlap in memory, so writing one corrupts the other
STRIDE = ENTRY SIZE IN MEMORY WORDS// Each descriptor is 2 memory words, but the vreg array is configured with STRIDE = 1:
descriptors.configure(this, desc_mem, /*n_regs*/ 16, /*offset base*/ 0, /*stride words*/ 1);
// BUG: stride 1 < entry size 2. vreg[i] at base + i*1, but each entry NEEDS 2 words -> entries OVERLAP:
// vreg[0] occupies words 0-1; vreg[1] starts at word 1 -> overlaps vreg[0]'s word 1.
// Writing descriptor 1 clobbers descriptor 0's second word, and vice-versa.Descriptor 0 alone works, but as soon as multiple descriptors are used they corrupt each other: configuring descriptor 1 silently changes part of descriptor 0, and rewriting descriptor 0 clobbers descriptor 1. The pattern is bizarre — adjacent entries interfering — and it looks like memory corruption or a DUT bug, but the memory and DUT are fine. The tell is that the corruption is between neighbours and scales with how many entries are in use: a single entry is fine, any two adjacent entries collide.
The virtual-register view's index-to-location mapping is wrong: the stride was set to 1 memory word while each descriptor actually occupies 2 words. Since vreg[i] is placed at base + i * stride, a stride smaller than the entry size means consecutive entries are spaced too closely and overlap: vreg[0] occupies words 0–1, but vreg[1] (at base + 1*1 = word 1) starts inside vreg[0], so they share word 1. Writing one entry therefore overwrites part of its neighbour. The memory is fine (it faithfully stores whatever is written to each word) and the DUT is fine; the defect is purely in the view's stride — the mapping that decides where in memory each virtual register sits. It is the virtual-register analog of a too-small footprint spacing: not a real memory placed wrong in a map (8.2), but the vreg array's entries mislocated within the memory because the stride does not match the entry size. That is why single entries work (no neighbour to collide with) and any two adjacent entries corrupt each other (their computed regions overlap).
Set the stride to the entry's real size in memory words — here 2, matching the descriptor width — so vreg[i] sits at base + i * 2 and each entry occupies its own, non-overlapping words; the cross-corruption vanishes. In general, ensure stride >= entry size (and normally equal, for a packed array). The rule the bug teaches: a virtual register's memory location is base + i * stride, so the stride must equal the entry's real size in memory words — a stride too small makes adjacent virtual registers overlap and corrupt each other; the bug is in the view's index-to-location mapping, not the memory or the DUT. More broadly, remember a virtual register is a view over memory: its storage and checking are memory semantics (no per-vreg mirror, 8.1; check via scoreboard/backdoor), and its placement is a stride/base/size mapping that must match the entries' real memory footprint.
7. Common Mistakes
- Stride smaller than the entry size. Makes adjacent virtual registers overlap in memory and corrupt each other — set
strideto the entry's real size in memory words. - Treating a virtual register like a real register. Its value lives in memory (no per-vreg mirror, 8.1) — check contents via scoreboard/backdoor, not an auto-mirror.
- Wrong base/size in the mapping. Mislocates the whole virtual-register array in the memory — the placement is
base + i * stride, all three must be right. - Expecting a dedicated backdoor path. Backdoor reaches the memory's
hdl_path, not a per-virtual-register register path (8.4). - Reading neighbour corruption as a memory/DUT bug. Adjacent virtual registers interfering is a stride (view-mapping) bug, not memory corruption.
8. Industry Best Practices
- Set the stride to the entry's real memory footprint.
stridein memory words must equal the entry size so entries do not overlap (base + i * stride). - Model structured RAM data as virtual registers. Descriptor arrays, register files, and tables get register-style field access while storage stays in memory.
- Check virtual-register contents as memory. Scoreboard or backdoor compare (no per-vreg mirror, 8.1) — do not rely on an auto-mirror.
- Reach the backdoor through the memory. The virtual register's storage is the memory's
hdl_path, not a dedicated register path. - Diagnose neighbour corruption as a stride bug. Adjacent entries interfering points at a stride smaller than the entry size, not the memory or DUT.
9. Interview / Review Questions
10. Key Takeaways
- A virtual register (
uvm_vreg) has no dedicated hardware register — it is a field-structured view over the locations of a memory, giving register-style field access to structured RAM data (descriptor arrays, register files, tables). - Its value lives in the underlying memory, so it inherits memory semantics: no per-location mirror (8.1) — check contents via scoreboard/backdoor — and backdoor reaches the memory's
hdl_path, not a dedicated register path. - A virtual-register array's address is an index-to-location mapping: entry
isits atbase + i * stride, and thestridemust equal the entry's real size in memory words. - The signature bug is a wrong stride: a stride too small makes adjacent virtual registers overlap and corrupt each other — a view-mapping bug, not a memory or DUT bug, and distinct from a real memory's placement in a map (8.2).
- Diagnose neighbour corruption that scales with adjacent entries as a stride bug (fix
strideto the entry's memory footprint), and treat virtual-register storage and checking as memory, its placement as base/stride/size.