UVM RAL · Chapter 3 · Register Maps
Aliasing & Shared Registers
Most registers sit at one address, but real maps are full of exceptions, and they all reduce to one question: is this one physical register or two? Aliasing is when the same flip-flops are reachable at more than one address, such as a legacy offset kept alongside a modern one. Sharing is when one register instance is reached through more than one map, such as a fast map and a debug map. In every case the register underneath is one thing with one value and one mirror, so the rule is to model it once and give that one register multiple addresses. The tempting mistake is to model an alias as two independent registers, which creates two mirrors for one set of flip-flops that drift apart. This page shows how to model these cases correctly, then breaks the two-mirror version to show the drift.
Foundation11 min readUVM RALAliasingShared RegistersMirrorAddress MapSingle Instance
Chapter 3 · Section 3.4 · Register Maps
1. Why Should I Learn This?
Aliasing and sharing are where the mirror's correctness meets the address map's structure, and the failure they cause is a mirror that drifts even though every register is modelled and every access completes. Because the drift comes from two mirrors representing one physical register, it is invisible until an access through one alias is checked against state changed through another — and then it looks like a hardware coherency bug rather than a modelling one. These situations are common (legacy aliases, multi-bus registers, mirrored ranges), so recognizing them and modelling the single underlying instance is a routine skill you will need on almost any real map.
Learning the single-instance rule — one physical register, one model, one mirror, however many addresses — lets you model aliases and shared registers so their mirror stays coherent across every path, and lets you spot the two-mirrors mistake that turns one register into two drifting copies.
2. Industry Story — the alias that read its own stale value
A block keeps a legacy CTRL register at its historical offset 0x80 while also exposing it at the modern 0x00, so old software and new software both work — the two addresses decode to the same flip-flops. The verification engineer, seeing two offsets in the spec, models two registers: ctrl_new at 0x00 and ctrl_legacy at 0x80, each a full uvm_reg with its own fields and its own mirror.
A test writes configuration through ctrl_new at 0x00, then a compatibility check reads ctrl_legacy at 0x80 expecting the same value — because in hardware they are the same register. The read mismatches: the DUT returns the value written through 0x00 (correct — one register), but ctrl_legacy's mirror still holds reset, because the write went to ctrl_new's mirror and nothing updated ctrl_legacy's. The engineer chases a phantom 'the legacy alias does not track the new address' hardware bug for a day before realizing the two models are two mirrors for one register, and only one was updated. The RTL is perfectly coherent — the two addresses hit the same flops — but the model split them into independent copies that drift the instant one is written. The lesson: an alias is one physical register at two addresses, so it must be one model with one mirror and two addresses; modelling it as two registers creates two mirrors that cannot stay coherent.
3. Concept — one instance, many addresses
The single-instance rule and how to model each case:
- Aliasing (one register, multiple offsets in a map). The same physical register decodes at more than one address. Model one
uvm_regand add it to the map at each of its offsets — RAL supports a register appearing at multiple offsets. All accesses, whatever address they use, resolve to the one register and update the one mirror. Where the aliases must remain distinct RAL objects for some reason, keep them coherent with a shared backing (a callback or prediction that mirrors one into the other) — but the default and correct choice is one register, multiple addresses. - Shared registers (one register, multiple maps). From 3.2: the same
uvm_regobject is added to more than one map (a fast map and a debug map). It is inherently one instance with one mirror; each map is just a different address/route to it, so coherence is automatic — a write through either map updates the one mirror. - Mirrored ranges / decode aliasing. When a range of addresses all decode to one register (incomplete address decoding), the model still represents one register; you either add it at the representative offset and understand the decode, or, if the alias behaviour must be verified, model the extra addresses as pointing at the same instance.
The unifying idea: the mirror belongs to the physical register, so there must be exactly one mirror per physical register, no matter how many addresses reach it. Here is an alias as it truly is — two addresses, one register:
4. Mental Model — count the flip-flops, not the addresses
5. Working Example — one register at two addresses
Aliasing is modelled by adding the single register to the map at each of its offsets — one uvm_reg, one mirror, two addresses:
virtual function void build();
ctrl = ctrl_reg::type_id::create("ctrl");
ctrl.configure(this); ctrl.build();
default_map = create_map("default_map", .base_addr('h0),
.n_bytes(4), .endian(UVM_LITTLE_ENDIAN));
// ONE register, added at BOTH its addresses. Both decode to the same flip-flops.
default_map.add_reg(ctrl, .offset('h00), .rights("RW")); // modern address
default_map.add_reg(ctrl, .offset('h80), .rights("RW")); // legacy alias — SAME register
lock_model();
endfunctionBecause it is one register with one mirror, coherence across the aliases is automatic:
uvm_status_e s; logic [31:0] got;
ctrl.write(s, 32'hABCD_0001); // updates the one mirror; drives the bus at 0x00 (or its default addr)
ctrl.read (s, got); // reads back the one register — matches
// A shared register across maps (3.2) is the same single instance reached two ways:
periph.ctrl.write(s, 32'h3, .map(fast_map)); // one mirror
periph.ctrl.read (s, got, .map(dbg_map)); // SAME register, same mirror -> coherent, matchesThe register is one object; whether reached at 0x00, at 0x80, through the fast map, or the debug map, every access lands on the one mirror, so the model stays coherent across all of them. The next section shows what happens when that single instance is split into two.
6. Debugging Session — the alias modelled as two registers
Modelling an alias as two independent registers creates two mirrors for one set of flip-flops, so writing one address leaves the other stale
TWO MIRRORS, ONE REGISTER// CTRL is one physical register aliased at 0x00 and 0x80. Modelled as TWO registers:
ctrl_new = ctrl_reg::type_id::create("ctrl_new"); ctrl_new.configure(this); ctrl_new.build();
ctrl_legacy = ctrl_reg::type_id::create("ctrl_legacy"); ctrl_legacy.configure(this); ctrl_legacy.build();
default_map.add_reg(ctrl_new, .offset('h00), .rights("RW")); // BUG: two separate registers...
default_map.add_reg(ctrl_legacy, .offset('h80), .rights("RW")); // ...for one set of flip-flops
// ...
ctrl_new.write(s, 32'hABCD_0001); // updates ctrl_new's mirror only
ctrl_legacy.read(s, got); // DUT returns 0xABCD_0001 (one register), but ctrl_legacy's mirror = reset
// UVM_ERROR: ctrl_legacy mirror mismatch: mirrored reset, got 0xABCD0001A write through one alias address is not reflected when reading through the other: write CTRL at 0x00, read it at 0x80, and the read mismatches — the DUT returns the written value (correct, one register), but the second model's mirror still holds reset. It looks like the legacy alias 'does not track' the modern address, a hardware coherency bug, and the investigation goes to the RTL's address decode, which is correct — both addresses hit the same flops. The tell is that the DUT value is right and only the mirror is wrong, and only for the alias that was not written.
One physical register was modelled as two independent uvm_reg objects, creating two mirrors for one set of flip-flops. A write updates the mirror of the register it was issued through and nothing else, so the other model's mirror is never told the shared hardware changed and goes stale. The hardware is perfectly coherent — both addresses are the same register — but the model split that one register into two copies whose mirrors have no way to stay in sync, so any access through one alias invalidates the other's mirror. The mismatch is the model's incoherence, not the DUT's.
Model the physical register once and add that single uvm_reg to the map at both offsets (Section 5), so every access — at 0x00 or 0x80 — resolves to the one register and updates the one mirror; coherence is then automatic and the cross-alias read matches. If two distinct model objects are truly unavoidable, they must share a backing so a write to one predicts the other (a callback or explicit prediction that keeps the two mirrors identical), but the default and correct choice is one register with multiple addresses. The rule the bug teaches: count flip-flops, not addresses — one physical register is one model with one mirror, however many addresses reach it; two mirrors for one register cannot stay coherent.
7. Common Mistakes
- Modelling an alias as two independent registers. Two mirrors for one set of flip-flops drift the instant one address is written.
- Counting addresses instead of flip-flops. Two addresses to the same storage is one register; two addresses to different storage is two.
- Forgetting shared registers are already single-instance. A register in two maps (3.2) is one object with one mirror — do not duplicate it per map.
- Ignoring decode aliasing. Incomplete address decoding can alias a register across a range; the model still represents one register.
- Duplicating a register to 'cover' both addresses in coverage. It double-counts and desynchronizes; one instance covers all its addresses.
8. Industry Best Practices
- Model each physical register once, with multiple addresses. Add the one register at each offset, or to each map; never duplicate the instance.
- Verify alias coherence explicitly. Write through one address, read through another, and confirm they agree — a direct test of the single-instance model.
- If two objects are unavoidable, share a backing. A callback or prediction that keeps their mirrors identical, so they behave as one.
- Treat mismatch-only-on-the-unwritten-alias as a two-mirror bug. DUT correct, one mirror stale — the signature of a split instance.
- Prefer generated models for aliased maps. Generation emits one register with its multiple addresses, avoiding hand-duplication (Chapter 13).
9. Interview / Review Questions
10. Key Takeaways
- Aliasing is one physical register reachable at multiple addresses; sharing is one register reached through multiple maps — both are the same single-instance situation.
- Count flip-flops, not addresses: one set of storage is one register with one value and one mirror, however many addresses reach it.
- Model the physical register once and give it multiple addresses — add the one
uvm_regat each offset, or to each map; a shared register (3.2) is inherently one instance. - The signature bug is modelling an alias as two independent registers, creating two mirrors for one set of flip-flops that drift the instant one address is written.
- The tell is a mismatch where the DUT value is correct but one alias's mirror is stale — model incoherence, not a hardware coherency bug.