Skip to content

UVM RAL · Chapter 3 · Register Maps

Address Maps

The register map is the object that owns addressing at run time, and it is where four decisions about the bus live. Its base address is where the block sits in the system address space. Its granularity is how many bytes the bus moves per word, which sets how registers are spaced and how a register wider than the bus is split. Its endianness sets the word order of a multi-word register on the bus. And it holds the binding to a sequencer and adapter, which is how an abstract register access becomes a real transaction. This page is the deep dive on that object: the four create-map parameters, how a register is placed, and how the map routes an access. It ends with the endian-mismatch bug that silently swaps the words of every wide register.

Foundation12 min readUVM RALuvm_reg_mapcreate_mapEndiannessn_bytesAddress

Chapter 3 · Section 3.1 · Register Maps

1. Why Should I Learn This?

The map is where every addressing fact about a block converges — base, granularity, endianness, and the bus binding — so a wrong map parameter is a bug that affects every register at once, not one field. Get n_bytes wrong and register spacing and wide-register splitting go wrong across the block; get endianness wrong and every multi-word register is corrupted; forget the sequencer/adapter binding and nothing reaches the bus at all. These are high-blast-radius mistakes precisely because the map is shared by every register.

Learning the map object — what each create_map parameter controls and how the map routes an access — lets you configure a block's addressing correctly in one place, reason about how a register access becomes a bus transaction, and recognize the whole-block symptoms that point at a map misconfiguration rather than a per-register bug.

2. Industry Story — the 64-bit register that read back scrambled

A block has a 64-bit TIMESTAMP register on a 32-bit bus, so each access is two bus transfers — a low word and a high word. The verification engineer creates the map but sets its endianness to big-endian, copying a template from a different project, while the actual bus and DUT are little-endian.

Single-word (32-bit) registers all work, so the map looks correct and the suite is mostly green. But every access to TIMESTAMP — and to the block's other 64-bit registers — comes back with its two words swapped: the model writes 0x0000_0001_DEAD_BEEF, the bus (per the wrong endianness) places the high word where the DUT expects the low, and a read returns 0xDEAD_BEEF_0000_0001. The failure is confined to wide registers, so it hides behind the many single-word ones that are immune, and it looks like a data-corruption or byte-lane bug in the DUT. Days go into probing the RTL's word assembly, which is flawless. The real cause is one map parameter: the endianness the map uses to order a wide register's words on the bus did not match the bus. The lesson: the map's endianness and granularity govern how a register spans the bus, so a wide register that reads back word-swapped is a map-parameter bug, not a DUT one — and single-word registers will not reveal it.

3. Concept — the map object and its four decisions

A map is created with create_map(name, base_addr, n_bytes, endian) and populated with add_reg, and those parameters are the four addressing decisions:

  • base_addr — where the block's registers begin in the address space. A register's address is base_addr + offset. In a unit testbench the base is often 0; in a system it is the block's system base.
  • n_bytes — the bus granularity: how many bytes the bus transfers per addressable word (the bus data width in bytes). It determines the natural offset step (4-byte registers step by 4 on a 4-byte bus) and, crucially, how a register wider than the bus is split into multiple transfers.
  • endian (UVM_LITTLE_ENDIAN / UVM_BIG_ENDIAN / others) — the order in which a multi-word register's words are placed on the bus. It only matters for registers wider than n_bytes, which is exactly why single-word registers never expose an endian mistake.
  • The sequencer + adapter binding — set with map.set_sequencer(sequencer, adapter) (Chapter 5). This is how the map routes an access to a real bus: the adapter translates, the sequencer drives.

At run time the map is a router: given a register access, it looks up the register's resolved address, splits the access into n_bytes-sized transfers if the register is wider than the bus, orders those transfers by endian, and hands each to the adapter and sequencer. Here is the map at the centre of that flow:

Registers feed the map; the map holds base, n_bytes, endian, and binds to adapter and sequencer to drive the busresolvebase+offsetsplit by n_bytes, order by endiansplit byn_bytes, order…driveregistersadded at offsets (add_reg)uvm_reg_mapbase_addr · n_bytes ·endianadapterreg2bus / bus2regsequencer → bus → DUTdrives the real transaction12
Figure 1 — the uvm_reg_map as the addressing hub. Registers are added to the map at their offsets; the map holds the base address, the bus granularity (n_bytes), and the endianness, and is bound to a sequencer and adapter. At run time it resolves a register access to base + offset, splits a register wider than n_bytes into ordered transfers (by endian), and routes each through the adapter to the sequencer that drives the bus. Base and n_bytes and endian are block-wide, so a wrong one is a whole-block bug.

4. Mental Model — the map is the register file's postal service and its bus binding

5. Working Example — creating a map and placing registers

The map's four decisions are all in create_map, and add_reg places each register at its offset:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
virtual function void build();
  // ... create + configure + build each register (2.1) ...
 
  // The map: base address, bus granularity (bytes), and endianness — from the BUS.
  default_map = create_map(.name("default_map"),
                           .base_addr('h0),            // block base (0 in a unit TB)
                           .n_bytes(4),                // 32-bit bus -> 4 bytes per transfer
                           .endian(UVM_LITTLE_ENDIAN)); // MATCH the bus/DUT
 
  // Place each register at its spec offset. 4-byte registers step by 4.
  default_map.add_reg(ctrl,      .offset('h00), .rights("RW"));
  default_map.add_reg(status,    .offset('h04), .rights("RO"));
  default_map.add_reg(timestamp, .offset('h08), .rights("RW")); // 64-bit: TWO 4-byte transfers
 
  lock_model();   // resolve offsets -> addresses
endfunction

At run time the map routes an access, splitting a wide register by n_bytes and ordering by endian:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
uvm_status_e s;  logic [63:0] got;
 
// 32-bit register: one transfer to base + 0x00.
ctrl.write(s, 32'h3);
 
// 64-bit register on a 32-bit bus: the map splits it into TWO transfers at 0x08 and 0x0C,
// ordered by the map's endianness. With endian MATCHING the bus, the words land correctly.
timestamp.write(s, 64'h0000_0001_DEAD_BEEF);
timestamp.read (s, got);   // returns 0x0000_0001_DEAD_BEEF — words in the right order

n_bytes(4) is why timestamp becomes two transfers, and UVM_LITTLE_ENDIAN (matching the bus) is why those two words land in the order the DUT expects. Change either to disagree with the bus and timestamp — but not ctrl — breaks.

6. Debugging Session — the endianness that swapped the words

1

A map endianness that does not match the bus swaps the words of every multi-word register, while single-word registers stay clean

ENDIAN MISMATCH
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The bus and DUT are little-endian, but the map is created big-endian (copied template):
default_map = create_map("default_map", .base_addr('h0),
                         .n_bytes(4), .endian(UVM_BIG_ENDIAN));   // BUG: bus is little-endian
// 64-bit TIMESTAMP is two 32-bit transfers; the wrong endian orders them backwards:
timestamp.write(s, 64'h0000_0001_DEAD_BEEF);
timestamp.read (s, got);   // got == 0xDEAD_BEEF_0000_0001  -- words swapped
Symptom

Wide (multi-word) registers read back with their words swapped, while every single-word register is perfectly fine — so the suite is mostly green and the failures cluster on exactly the 64-bit registers. It looks like a data-corruption or byte-lane bug in the DUT's handling of wide registers, and the investigation goes to the RTL that assembles the two words, which is correct. The tell is the selectivity: the corruption hits only registers wider than the bus, and it is a clean word swap, not random garbage — a signature of an ordering, not a data, error.

Root Cause

The map's endianness does not match the bus. A register wider than n_bytes is split into multiple bus transfers, and the map's endian decides the order those transfers are placed on the bus; set to big-endian against a little-endian bus, it sends the high word where the DUT expects the low, so the two words arrive swapped. Single-word registers are immune because they are a single transfer with no ordering to get wrong — which is exactly why the bug hid behind them. The DUT and the register model are both correct; the map is ordering the words for the wrong bus.

Fix

Set the map's endianness to the bus's actual endianness — UVM_LITTLE_ENDIAN here — so multi-word registers are ordered the way the bus and DUT expect, and timestamp reads back in order. More broadly, set all four map decisions (base_addr, n_bytes, endian, and the sequencer/adapter binding) from the real bus, and treat any whole-class symptom — all wide registers swapped, all registers off by a spacing, all unreachable — as a map-parameter bug rather than a per-register one. The rule the bug teaches: the map's endianness governs how a register spans the bus, so a word-swapped wide register is a map-endian mistake — and because single-word registers cannot expose it, you must test a multi-word register to catch it.

7. Common Mistakes

  • Endianness not matching the bus. Swaps the words of every multi-word register; single-word registers hide it.
  • n_bytes not matching the bus width. Wrong offset spacing and wrong splitting of wide registers across transfers.
  • Forgetting the sequencer/adapter binding. The map has addresses but no bus — every frontdoor access fails with no traffic (Chapter 5).
  • Baking the base into offsets. Set base_addr once on the map; never add it into individual add_reg offsets.
  • Testing only single-word registers. Endianness and multi-transfer splitting are invisible unless a register wider than the bus is exercised.

8. Industry Best Practices

  • Set the four map decisions from the actual bus, once. base_addr, n_bytes, endian, and the sequencer/adapter binding — all block-wide, all from the real interconnect.
  • Test at least one register wider than the bus. It is the only way to exercise endianness and multi-transfer splitting.
  • Treat whole-block symptoms as map bugs. All-wide-swapped, all-unreachable, all-mis-spaced — look at the map before the registers.
  • Keep base out of register offsets. A base change should relocate the whole block by editing one map parameter.
  • Prefer generated maps for large blocks. Generation sets n_bytes and endian consistently from a machine-readable spec (Chapter 13).

9. Interview / Review Questions

10. Key Takeaways

  • The uvm_reg_map is where addressing lives — it holds the base address, the bus granularity (n_bytes), the endianness, and the sequencer/adapter binding, and routes each register access to a bus address.
  • create_map(name, base_addr, n_bytes, endian) sets the four addressing decisions; add_reg places a register at its offset; locking resolves offsets into addresses.
  • n_bytes is the bus width in bytes — it sets register spacing and how a register wider than the bus is split into transfers; endian orders those transfers and only matters for multi-word registers.
  • The signature bug is an endian mismatch, which swaps the words of every multi-word register while single-word registers stay clean — so you must test a wide register to catch it.
  • Map parameters are block-wide, so a whole-block symptom (all wide registers swapped, all unreachable, all mis-spaced) is a map bug, not a per-register one.