Skip to content

UVM RAL · Chapter 12 · Advanced RAL

Register Arrays

A register array is an array of real, identical register instances: the same register repeated many times, such as sixteen channel-configuration registers, an interrupt-enable register per source, or a bank of identical timers. Unlike a virtual register, which is a view over memory with no storage of its own, each element here is a genuine, separately-instanced register with its own storage, its own mirror, and its own address, sharing only the field layout. You build one in a loop: for each index, create a new instance, configure it, and add it to the map at its own offset. That word new is the whole subject, because the signature bug is rooted in the fact that SystemVerilog class handles are references. If the loop stores the same handle in every slot, all elements alias one object and share one mirror, so writing one silently changes another. This lesson breaks that aliasing bug caused by creating the instance outside the loop.

Foundation12 min readUVM RALregister arraysinstancesaliasinghandles

Chapter 12 · Section 12.3 · Advanced RAL

1. Why Should I Learn This?

Repeated identical registers — per-channel, per-source, per-entry — are everywhere, and a register array is how you model them without hand-writing N copies. But the loop that builds them hides a SystemVerilog trap: forget to create a fresh instance each iteration and all N elements become the same register, sharing one mirror, so writes to one silently appear on all. Knowing to create a new instance per element (and add each at its own offset) is what makes a register array N distinct registers instead of one register with N names.

Learning register arrays complements dynamic models (12.1, which decides how many) and virtual registers (12.2, a view over memory): a register array is N real, distinct instances, and its correctness hinges on the SystemVerilog reference-semantics discipline of a fresh create per element.

2. Industry Story — the sixteen registers that were one

A team models sixteen identical channel-config registers as a register array, built in a loop. But create was called once, outside the loop, and the same handle was stored in all sixteen array slots. Channel 0 tests pass. Then things get strange: configuring channel 3 also changes what channel 5 reads back; the sixteen 'registers' behave as if they are one.

They were one. Because a class handle is a reference, storing the same created object in all sixteen slots made array[0] through array[15] alias a single uvm_reg instance — one object, sixteen names. That single object had one mirror, so writing array[3] and reading array[5] touched the same mirrored value; and the map had one register, not sixteen. It looked like a bizarre cross-coupling bug, but there was no coupling — there was one register pretending to be sixteen. The fix was to move create inside the loop with a unique name per iteration, so each element was a distinct instance with its own storage, mirror, and offset. The post-mortem lesson: class handles are references, so a register array must create a fresh instance for each element inside the loop — reusing one handle across all slots makes every element alias the same register (one shared mirror, one identity), so a write to one appears on all; it is not an offset or coupling bug, it is N array slots pointing at one object, and the fix is a per-element create with a unique name, each added at its own offset.

3. Concept — N distinct instances, a fresh create per element

A register array is N real, distinct register instances that share a field layout. Building it correctly:

  • Each element is its own instance. Every array[i] is a separate uvm_reg with its own storage, mirror, and address — they share only the class (field layout), not the object.
  • Create a fresh instance per element, inside the loop. For each i: create a new instance with a unique name (e.g. $sformatf("cfg%0d", i)), configure/build it, and add_reg it to the map at its own offset (base + i * stride). The unique name and the per-iteration create are what make the elements distinct.
  • The trap is SystemVerilog reference semantics. A class handle is a reference; assigning one created object to all slots (or create outside the loop) makes every slot alias the same object. Then N 'registers' are one register with N names — one mirror, one identity — so a write to one appears on all.
  • Aliasing is an object bug, not an offset bug. The elements are the same object; even distinct offsets cannot separate them, because there is only one register underneath.

Here is what aliasing does: a write to array[3] shows up on array[5] because they are the same object with one mirror:

Register-array aliasing: array[3] and array[5] are the same object, so writing array[3] changes array[5]'s mirrored valuetestthe ONE shared uvm_reg(all slots alias it)read array[5]write array[3] = 0xF -> updates the SHARED mirrorwrite array[3] = 0xF-> updates the…now read array[5]array[5] IS the same object as array[3]array[5] IS thesame object as…returns 0xF (array[3]'s value) — aliased, not coupledreturns 0xF(array[3]'s value) …FIX: fresh create per element -> distinct instances, own mirrorsFIX: fresh createper element ->…
Figure 1 — the register-array aliasing bug. When every array slot holds the SAME created instance (create outside the loop, or one handle assigned to all), array[3] and array[5] are the SAME uvm_reg object sharing ONE mirror. So a write to array[3] updates that shared mirror, and a read of array[5] returns array[3]'s value — the elements are indistinguishable because there is only ONE register with N names. The fix is a FRESH create per element (unique name) inside the loop, so each array[i] is a DISTINCT instance with its own storage, mirror, and offset (base + i*stride).

4. Mental Model — N slots must hold N objects, not N copies of one reference

5. Working Example — building an array of distinct instances

Create a fresh instance per element inside the loop, each added at its own offset:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A register array: N identical channel-config registers, each a DISTINCT instance.
class dma_reg_block extends uvm_reg_block;
  cfg_reg ch_cfg[];                         // an array of HANDLES — N labels, need N objects
  virtual function void build();
    default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
    ch_cfg = new[16];
    foreach (ch_cfg[i]) begin
      // CREATE A FRESH INSTANCE per element, with a UNIQUE NAME — this is what makes them distinct:
      ch_cfg[i] = cfg_reg::type_id::create($sformatf("ch_cfg%0d", i));   // fresh object each iteration
      ch_cfg[i].configure(this);
      ch_cfg[i].build();
      default_map.add_reg(ch_cfg[i], 'h100 + i*'h4);                     // its OWN offset (base + i*stride)
    end
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Because each element is a DISTINCT object, they have INDEPENDENT mirrors and addresses:
uvm_status_e s;  uvm_reg_data_t v5;
ch_cfg[3].write(s, 32'h0000_000F);   // writes ONLY channel 3's register/mirror
ch_cfg[5].read(s, v5);               // reads channel 5's OWN value — unaffected by channel 3
// v5 reflects channel 5 alone, because ch_cfg[3] and ch_cfg[5] are SEPARATE uvm_reg objects.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The correctness hinge: create() is INSIDE the loop with a UNIQUE name per element.
// create inside loop + unique name -> N distinct instances (N mirrors, N offsets).  CORRECT.
// create OUTSIDE loop / one handle in all slots -> N labels aliasing ONE object.       BUG (next section).

Creating a fresh instance each iteration makes the sixteen elements sixteen distinct registers with independent mirrors and offsets. Creating one instance and reusing its handle — the bug of the next section — makes them one register with sixteen names.

6. Debugging Session — every array element aliasing one register

1

Creating one instance outside the loop and storing its handle in every slot makes all array elements alias the same register, so a write to one appears on all

FRESH create PER ELEMENT
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// create() is OUTSIDE the loop; the SAME handle is stored in every array slot:
cfg_reg one = cfg_reg::type_id::create("ch_cfg");   // ONE instance created, ONCE
one.configure(this); one.build();
ch_cfg = new[16];
foreach (ch_cfg[i]) begin
  ch_cfg[i] = one;                                  // BUG: every slot holds the SAME object (a reference)
  default_map.add_reg(ch_cfg[i], 'h100 + i*'h4);    // adds the SAME register 16 times
end
// ch_cfg[0..15] all ALIAS 'one' -> ONE mirror, ONE identity. Writing ch_cfg[3] changes ch_cfg[5].
Symptom

Channel 0 tests pass, but the sixteen 'registers' behave as one: configuring channel 3 also changes what channel 5 reads back, and every element seems to hold the same value regardless of which one you write. It looks like a bizarre cross-coupling between channels — as if the hardware were wiring them together — but the DUT is fine. The tell is that any write to any element appears on all elements: not partial coupling, but complete sharing, because they are literally the same object.

Root Cause

SystemVerilog class handles are references, and here a single uvm_reg instance (one) was created outside the loop and its handle stored in every array slot. So ch_cfg[0] through ch_cfg[15] do not name sixteen registers — they are sixteen references to the same object. That one object has one mirror and one identity, so writing ch_cfg[3] updates the mirror and reading ch_cfg[5] reads the same mirror — they are indistinguishable because there is only one register underneath. Adding ch_cfg[i] to the map sixteen times added the same register at sixteen offsets (aliasing at the map level too). This is not an offset bug (distinct offsets cannot separate one object) and not hardware coupling (the DUT has sixteen real registers); it is aliasing — N array slots pointing at one instance because create was called once instead of per element. Channel 0 'passed' only because, with one shared object, any single-element test is self-consistent; the aliasing shows only when two different elements are used and found to share state.

Fix

Create a fresh instance for each element inside the loop, with a unique name: ch_cfg[i] = cfg_reg::type_id::create($sformatf("ch_cfg%0d", i)), then configure/build and add each at its own offset. Now each element is a distinct uvm_reg with its own storage, mirror, and address, and writing ch_cfg[3] no longer touches ch_cfg[5]. The rule the bug teaches: a register array is N distinct instances, so you must create a fresh instance for each element (unique name, inside the loop) — because class handles are references, reusing one handle across all slots makes every element alias the same register (one shared mirror, one identity), so a write to one appears on all. It is an object-identity bug, not an offset or coupling bug: when array elements cross-affect each other completely, count how many times create was called — the fix is a per-element create, not a change to the offsets or the DUT.

7. Common Mistakes

  • Calling create once outside the loop. Storing that one handle in every slot aliases all elements to one register — create a fresh instance per element, inside the loop.
  • Reusing one handle across the array. Class handles are references; one object in N slots is one register with N names, sharing one mirror.
  • Duplicate or missing element names. Each instance needs a unique name (e.g. index-suffixed) — non-unique names cause factory/lookup collisions.
  • Adding all elements at the same offset. Each element needs its own offset (base + i * stride) — even distinct objects at one offset collide in the map.
  • Reading complete cross-affecting as coupling. If a write to any element appears on all, it is aliasing (one object), not hardware coupling — check the create.

8. Industry Best Practices

  • Create a fresh instance per element, inside the loop. Unique name each iteration ($sformatf(...)) so each array[i] is a distinct object.
  • Add each element at its own offset. base + i * stride, so the N distinct registers occupy N distinct addresses.
  • Model repeated identical registers as arrays. Per-channel/per-source/per-entry registers — build them in a loop rather than hand-writing N copies.
  • Diagnose element cross-affecting as aliasing. Complete sharing across elements points at a shared instance (one create), not coupling or offsets.
  • Keep element names unique and index-derived. So instances are distinguishable in the factory, hierarchy, and reports.

9. Interview / Review Questions

10. Key Takeaways

  • A register array is an array of real, distinct register instances — the same register repeated N times (per-channel/per-source/per-entry) — each with its own storage, mirror, and address; unlike a virtual register (12.2), which is a view over memory.
  • Build it in a loop that, for each element, creates a fresh instance with a unique name, configures/builds it, and adds it at its own offset (base + i * stride).
  • The signature bug is aliasing from SystemVerilog reference semantics: create once (outside the loop) and store that one handle in every slot, and all elements reference the same objectone mirror, one identity — so a write to one appears on all.
  • Aliasing is an object-identity bug, not an offset bug (distinct offsets can't separate one object) or hardware coupling (the DUT has N real registers) — its tell is complete cross-affecting (write any element, all change).
  • A single-element test passes even when aliased (one shared object is self-consistent) — the definitive check is writing distinct values to different elements and confirming each reads back independently; the fix is a per-element fresh create, not offsets or the DUT.