Skip to content

UVM RAL · Chapter 12 · Advanced RAL

Indirect & Aliased Registers

Indirect registers are a bank of registers reached not by their own addresses but through a port pair: an index register and a data register. You write the index of the register you want, then read or write the data register, and the hardware routes that data access to the selected register. This lets a hundred registers hide behind two addresses, common for calibration tables and PHY register banks, and RAL models it with a dedicated indirect-data class. The essential thing is that it is a stateful two-step protocol: set the index, then access data. Aliased registers, the companion pattern, are two or more addresses that map to the same underlying storage. This lesson explains the index and data protocol and aliasing, then breaks the signature bug: accessing data without freshly setting the index, so a stale index routes the access to the wrong register.

Foundation12 min readUVM RALindirect registersuvm_reg_indirect_dataaliasedindex

Chapter 12 · Section 12.5 · Advanced RAL

1. Why Should I Learn This?

Large register banks — calibration tables, PHY registers, config arrays — are frequently exposed indirectly through an index/data port pair to save address space, and accessing them correctly means honoring a stateful two-step protocol: set the index, then access data. Knowing that the index persists — so a data access without a fresh index uses a stale one and hits the wrong register — is what keeps indirect accesses landing where you intend instead of silently on the last-indexed register.

Learning indirect and aliased registers rounds out Chapter 12's exotic-access cases: indirect access adds a stateful protocol dimension (set-index-then-data), and aliasing (3.4) adds shared storage — both are common in real IP and both have subtle, silent failure modes.

2. Industry Story — the calibration writes that all went to entry 0

A team programs a large calibration table exposed indirectly: write the entry index to the address-port register, then write the value to the data-port register. Their loop wrote the index once (entry 0) during setup, then looped writing values to the data port — assuming each data write would go to a new entry. Every value landed on entry 0.

The bank is indirect, and the index register persists: after setting the index to 0, it stayed 0 for every subsequent data write, because the loop never updated it. So all the calibration values were written through the data port to the same entry (0), overwriting each other, while entries 1..N stayed unprogrammed. It looked like the data writes were being dropped or the table was broken, but every write succeeded — they just all routed to entry 0, because the index was stale. The fix was to write the index before each data access, so every value went to its intended entry. The post-mortem lesson: an indirect register bank is a stateful two-step protocol — set the index, then access data — and the index register persists, so a data access without a freshly-set index uses a stale index and routes to the previously-indexed register; every data write must be preceded by setting its index, or accesses silently land on the wrong entry.

3. Concept — set the index, then access data; and aliasing shares storage

Two access patterns, each with its own subtlety.

Indirect registers — a bank behind a port pair:

  • The port pair. An index/address-port register selects which bank register the data-port register accesses. The hardware routes a data access to the register named by the current index. Modelled by uvm_reg_indirect_data.
  • The protocol is stateful: set index, then access data. An indirect access is two steps — write the index, then read/write data. The index register holds state that governs where the next data access lands.
  • The index persists. It keeps its last value until rewritten — so every data access must be preceded by setting its index, or it uses a stale index and routes to the previously-indexed register.

Aliased registers (3.4) — companion pattern:

  • Shared storage across addresses. Two or more addresses map to the same underlying register, so a write via one address is visible via the other. The model must reflect one storage seen at multiple addresses (not independent registers).

Here is the indirect routing: the index selects the target, and a stale index routes to the wrong one:

Indirect access: address-port index selects which bank register the data-port accesses; a stale index routes to the wrong registerindex selectsrouted by current indexrouted bycurrent…leftover index routes wrongleftoverindex routes…address-port (INDEX)write index = 5 (selectsthe target) — PERSISTSuntil rewrittendata-port (DATA)read/write here -> HWroutes to the indexedregisterregister bank (behind2 addresses)reg[5] gets the access whenindex==5STALE indexdata access without settingindex -> uses leftoverindex (e.g. 0)wrong registerroutes to reg[0], notreg[5] -> silent wrongentry12
Figure 1 — indirect register access through a port pair. Step 1: write the INDEX (e.g. 5) to the address-port register. Step 2: read/write the DATA-port register; the hardware ROUTES that access to the indirectly-selected register (register 5). Because the index register PERSISTS, a data access WITHOUT freshly setting the index uses whatever index was left over (a STALE index) and routes to the PREVIOUSLY-indexed register (e.g. still 0), silently landing on the wrong entry. Every data access must set its index first. Modelled by uvm_reg_indirect_data.

4. Mental Model — the index is a stateful pointer; a data access follows wherever it points now

5. Working Example — the index/data protocol, and aliasing

Drive indirect access as a coupled set-index-then-data pair, and model aliasing as shared storage:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// INDIRECT: set the INDEX, THEN access DATA — as a coupled pair, EVERY access. The index persists.
task indirect_write(int index, uvm_reg_data_t value);
  uvm_status_e s;
  addr_port_reg.write(s, index);        // Step 1: SET THE INDEX (selects the target register)
  data_port_reg.write(s, value);        // Step 2: data access -> HW routes to register[index]
endtask
// Loop CORRECTLY: set the index for EACH entry, so each value goes to its own entry.
foreach (cal[i]) indirect_write(i, cal[i]);   // index set before every data write
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RAL models the coupling with uvm_reg_indirect_data: the indirect register is tied to the index+data ports.
// (configuration ties the index-port register + data-port register to the indirect register bank)
// The MODEL captures the routing, but USAGE must still set the index before each data access.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ALIASED (3.4): two addresses onto ONE register. Model as shared storage, not independent registers.
// A write via alias_addr_a is visible via alias_addr_b, because they are the SAME storage:
alias_a.write(s, 32'hABCD);
alias_b.read(s, v);      // v == 0xABCD — same underlying register, seen at a second address

Coupling the index and data (set index, then data) every access sends each value to its intended entry; the stale-index bug of the next section is what happens when the index is set once and assumed thereafter.

6. Debugging Session — a stale index routing data to the wrong register

1

Accessing the data port without freshly setting the index uses a stale index, so the data access routes to the previously-indexed register instead of the intended one

SET THE INDEX BEFORE EVERY DATA ACCESS
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The index is set ONCE (to 0) during setup, then a loop writes values to the DATA port only:
addr_port_reg.write(s, 0);                 // index = 0 (set ONCE)
foreach (cal[i]) data_port_reg.write(s, cal[i]);   // BUG: data writes with NO fresh index set
// The index PERSISTS at 0, so EVERY data write routes to register[0] -> all values land on entry 0,
// overwriting each other; entries 1..N stay unprogrammed. Every write 'succeeds' but goes to the wrong entry.
Symptom

The calibration table ends up with entry 0 holding the last value written and entries 1..N unprogrammed — as if all the data writes were dropped or collapsed onto one entry. It looks like the data port is broken or the table is not accepting writes, but every write succeeded on the bus. The tell is that the values are not lost — they all landed on entry 0 (the set-once index), overwriting each other — and that the other entries are untouched. The failure tracks the index staying constant, not the data writes failing.

Root Cause

The indirect bank is a stateful two-step protocol — set index, then access data — and the index register persists. The loop set the index to 0 once and then issued many data writes without updating the index, so every data write routed to register[0] (the stale, leftover index), overwriting entry 0 repeatedly while entries 1..N were never selected and so never written. The hardware behaved correctly: it routed each data access to whatever the current index was, which was 0 the whole time because nothing changed it. This is not an addressing bug (the data-port address is right), not a modelling bug (uvm_reg_indirect_data correctly captures the routing), and not a dropped-write bug (every write succeeded) — it is a sequencing bug: the two-step protocol was collapsed to one step (data-only) under the false assumption that the index was still what the loop intended. The index's persistence — a feature that lets you do multiple data accesses to one entry without re-indexing — became the trap when the code assumed an index it did not set. The values went to the wrong entry because the index was stale, not because anything failed.

Fix

Set the index before every data access — couple the index write and the data access as a pair (set index i, then write data) for each entry — so each value routes to its intended entry; the calibration table then programs correctly across all entries. Treat the index/data as an inseparable two-step protocol and never assume the index persists as you intend (a previous access, reset, or another agent may have left it elsewhere). The rule the bug teaches: an indirect register bank is a stateful protocol — the index register persists, so every data access must be preceded by freshly setting its index, or the access uses a stale index and routes to the previously-indexed register; it is a sequencing error (two steps collapsed to one), not a dropped write or an addressing bug. The tell in the wild: indirect data accesses that all land on the same (last-set-index) entry while others stay unprogrammed — set the index before each access.

7. Common Mistakes

  • Setting the index once and assuming it persists as intended. The index stays where you last put it; every data access needs a fresh index set, or it routes to the previously-indexed register.
  • Collapsing the two-step protocol to one. Indirect access is set index, then data — a data-only access uses whatever index is current (a stale one).
  • Reading 'all writes land on one entry' as dropped writes. They succeeded — they routed to the stale index; the fix is sequencing, not the data port.
  • Modelling aliased addresses as independent registers. Two addresses onto one storage means a write via one is seen via the other — model one shared storage (3.4).
  • Ignoring other agents/reset changing the index. Never assume the index is yours — reset or a concurrent access may have moved it; set it before each access.

8. Industry Best Practices

  • Couple index and data as a pair. Set the index, then access data, for every indirect access — never assume the persisted index.
  • Model indirect banks with uvm_reg_indirect_data. Tie the index-port and data-port registers to the bank so the routing is captured — but still set the index per access in usage.
  • Diagnose all-on-one-entry as a stale index. Indirect accesses landing on the last-set-index entry while others stay unprogrammed is a sequencing bug.
  • Model aliased registers as shared storage. One register seen at multiple addresses; a write via one address is visible via the others (3.4).
  • Re-index defensively after reset or shared access. The index may have moved; set it before relying on it.

9. Interview / Review Questions

10. Key Takeaways

  • Indirect registers reach a bank through a port pair — an index/address-port register and a data-port register: write the index, then access the data port, and the hardware routes the access to the indexed register (many registers behind two addresses; modelled by uvm_reg_indirect_data).
  • The protocol is stateful: an indirect access is two steps (set index, then access data), and the index register persists its value until rewritten.
  • The signature bug is a stale index: accessing the data port without freshly setting the index uses the leftover index and routes to the previously-indexed register — a sequencing error (two steps collapsed to one), not a dropped write or addressing bug.
  • Set the index before every data access (couple index+data as a pair) and never assume the index persists as you intend — reset or another agent may have moved it; the tell is all accesses landing on one entry while others stay unprogrammed.
  • Aliased registers (3.4) are the opposite pattern — many addresses onto one storage — and must be modelled as one shared register seen at multiple addresses, not independent registers, or their mirrors diverge.