Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
    • RAL Overview
      • Why a Register Model: Desired, Mirrored and the DUT
      • Blocks, Registers, Fields and Memories
      • Maps, Offsets and Endianness
      • Nested Blocks and Submaps
      • Build, Lock and Generated Models
      • The Frontdoor Path, End to End
      • The Register Layer: Full Source
    • Register Access
    • Registers in Verification
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹RAL Overview
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. RAL Overview
  5. Maps, Offsets and Endianness

Part 9 · Register Model (RAL) · Intermediate

Maps, Offsets and Endianness

How a register name becomes a bus address and an observed address becomes a register: create_map and add_reg, address math, get_reg_by_offset, byte addressing, and what endianness really orders.

On this page

    The integration team moves the DMA from address 0 to 0x4000_0000. The block's hand-written tests, with 'h08 for LEN, now write into some other block's registers; the regression turns red in confusing ways. The register-model tests change nothing at all, because they never knew LEN's address. One number changed, in one place: the map's base.

    This lesson is about that one place. It shows how the DMA's map turns names into addresses, how the predictor turns addresses back into names, and what the map's width and endianness settings really do.

    The idea: an address book for one bus

    A map (uvm_reg_map) is the address book of one block as seen from one bus interface. It has a base address, a bus width in bytes (n_bytes), an endianness, and a flag saying whether addresses count bytes or bus words. Each register is placed in it at an offset with access rights (RW, RO or WO). A block with two bus ports, say APB for software and a debug port, has two maps, and a register can appear in both at different offsets.

    The map works in both directions. Going out, rm.len.write() asks the map for LEN's address: base plus offset. Coming back, the predictor sees an APB transfer at some address and asks the map which register is that? with get_reg_by_offset(). Both directions come from the same table, so they can never disagree with each other, only with the hardware.

    The picture

    Flowchart: a write to len by name goes to the map, which adds base 0 and offset 0x08; the adapter makes an APB write to 0x08; the monitor sees the write at 08; the predictor asks the map for the register at offset 0x08 and gets regmodel.len, which it updates to 6.
    One table, two directions: the map turns LEN into 0x08 for the adapter, and turns the monitor's 0x08 back into LEN for the predictor.

    Build it: the DMA's map

    These are the last lines of dma_reg_block::build(), shown in full in the previous lesson:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet: the end of dma_reg_block::build()
    // One APB map: base 0, 4-byte bus, little endian.
    map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
    map.add_reg(ctrl,   'h00, "RW");
    map.add_reg(status, 'h04, "RW");
    map.add_reg(len,    'h08, "RW");
    map.add_reg(key,    'h0C, "RW");
    map.add_reg(count,  'h10, "RO");

    create_map(name, base, n_bytes, endian, byte_addressing = 1)

    • base is added to every offset in the map. For the DMA it is 0; the subsystem model in the next lesson places a second DMA at 0x1000.

    • n_bytes is the bus width: 4 for the DMA's 32-bit APB. A register wider than this takes several bus accesses.

    • endian orders those accesses (below). The first map created in a block also becomes its default_map.

    • byte_addressing (default 1) means one address per byte, so consecutive 32-bit registers are 4 apart, as in the DMA. With 0, addresses count bus words, and COUNT would sit at offset 4, not 0x10.

    add_reg(rg, offset, rights = "RW", unmapped = 0, frontdoor = null)

    The offset is relative to the map's base. rights record how this map may reach the register: COUNT is added RO, while STATUS stays RW because its W1C bits must be written to clear them. Rights are coarse; the field policies decide how each write and read is predicted. unmapped and frontdoor are for registers with no plain address, such as indirect registers, which Register Access covers.

    The address math

    Register

    Offset

    Address (base 0)

    Address (base 0x4000_0000)

    ctrl

    0x00

    0x00

    0x4000_0000

    status

    0x04

    0x04

    0x4000_0004

    len

    0x08

    0x08

    0x4000_0008

    key

    0x0C

    0x0C

    0x4000_000C

    count

    0x10

    0x10

    0x4000_0010

    rg.get_address() returns a register's bus address through its default map (including any parent map's offsets), and map.get_reg_by_offset(addr) goes the other way. The reverse lookup works only after lock_model(), because locking is when the library builds its address table, and it returns null for an address no register covers, such as 0x14 on the DMA.

    What endianness does, and what it does not

    Map endianness orders the bus beats when a register is wider than the bus. In UVM 1.2 that is all it does: it does not swap bytes inside one bus word. Every DMA register is 32 bits on a 4-byte bus, so each one is a single beat and the endianness setting changes nothing. Put a 32-bit register on a 2-byte bus and it becomes two beats: UVM_LITTLE_ENDIAN puts the low half at the lower address, UVM_BIG_ENDIAN puts the high half there. Where each byte travels within a beat is the adapter's and the driver's business (byte enables, strobes, lane wiring), not the map's.

    Diagram
    32'hA1B2C3D4 at offset 0x20
    
    n_bytes = 4 (the DMA): one beat, endianness has no effect
      0x20 -> A1B2C3D4
    
    n_bytes = 2, UVM_LITTLE_ENDIAN        n_bytes = 2, UVM_BIG_ENDIAN
      0x20 -> C3D4                          0x20 -> A1B2
      0x22 -> A1B2                          0x22 -> C3D4

    Note

    In IEEE 1800.2: the 2020 reference library goes one step further for UVM_BIG_ENDIAN. Its perform_accesses also byte-reverses the data of each write before calling reg2bus, and byte-reverses read data on the way back. A big-endian map can therefore change the bytes your adapter sees, not just the beat order. Check what your library does before you rely on either behaviour, and keep little-endian maps unless the bus really is big-endian.

    Watch it run

    Every APB transfer in dma_ral_job_test goes through both directions of the map. At +UVM_VERBOSITY=UVM_HIGH the log shows the outgoing access, the monitor's view of the bus, and the predictor's reverse lookup landing on the right register (paths shortened):

    Diagram
    UVM_INFO @ 115000: reporter [RegModel] Wrote register via map regmodel.map: regmodel.len=0x6
    UVM_INFO dma_tb.sv(151) @ 115000: uvm_test_top.env.apb.mon [APB_MON] WR addr=08 data=00000006
    UVM_INFO reg/uvm_reg_predictor.svh(248) @ 115000: uvm_test_top.env.predictor [REG_PREDICT] Observed WRITE transaction to register regmodel.len: value='h6 : updated value = 'h6

    The model wrote LEN; the monitor saw address 08; the predictor turned 08 back into regmodel.len. A scratch run that builds a second, standalone dma_reg_block and asks its map directly gives the same answer: solo map, 'h08 -> solo.len, len @ 8.

    What breaks: a map that agrees with itself

    A team once spent two days on a DMA that moved the wrong number of words. Their generated model had LEN and KEY swapped, 0x0C and 0x08, because a spreadsheet row had been reordered. Every register test passed. A frontdoor write to "LEN" went to 0x0C, and a frontdoor read of "LEN" came back from 0x0C, so the model and the DUT agreed perfectly about the wrong register. Even the scoreboard, decoding KEY from the APB monitor by address, agreed. Only the data path, with the DUT's real LEN holding the low bits of a key, told the truth.

    The lesson: frontdoor tests prove the map is consistent, not that it is right. Something must compare the model with the design by name, outside the bus. That is what the backdoor does in dma_ral_job_test: it reads KEY from the RTL signal key and compares it with the mirror. A swapped map fails that check at once. Generating the model, the RTL decoder and the documentation from one source removes the cause.

    A quieter failure is an access to an address no register covers. The predictor looks it up, gets null, and drops the transfer with a message at UVM_FULL verbosity, so at normal verbosity a write to a misdecoded address leaves no trace in the model.

    Interview

    "How does a register model compute an address, and what does map endianness do?"

    A weak answer: "base plus offset, and endianness swaps the bytes." The second half is the most common wrong answer on this topic.

    A strong answer: "The address is the map's base plus the register's offset, plus each enclosing submap's offset if the map is nested; byte_addressing decides whether offsets count bytes or bus words. Endianness only matters when a register is wider than the map's n_bytes: it decides which part of the register goes to the lower address. It never swaps bytes inside one bus word; lane placement is the adapter's and driver's job. The predictor uses the same map backwards, get_reg_by_offset, after lock_model has built the address table."

    Follow-ups: What does the predictor do with an address that is not in the map? (looks it up, gets null and ignores it, with a UVM_FULL message) and Two registers at one address? (legal only as an RO and WO pair; otherwise the library warns at lock).

    Check yourself A 64-bit register sits at offset 0x40 in a map with n_bytes = 4 and UVM_BIG_ENDIAN. Which half goes to address 0x40?

    The high 32 bits. The register needs two beats, at 0x40 and 0x44, and big endian puts the most significant part at the lower address. With little endian the low 32 bits would go to 0x40. In both cases the bytes inside each 32-bit beat are not reordered by the map.

    Check yourself The DMA's map is created with byte_addressing = 0. Where does COUNT land, and what breaks?

    Offsets now count 4-byte words, so offset 0x10 means word 16, byte address 0x40, and the model's 'h10 no longer matches the RTL's decoder. Frontdoor accesses go to the wrong place. The offsets in a word-addressed map would have to be 0, 1, 2, 3 and 4.

    Check yourself Why does get_reg_by_offset() fail when called before lock_model()?

    The address table it searches is built when the model is locked: until then the library has not resolved offsets, submap bases and multi-beat registers into addresses. Called early, it reports Cannot get register by offset: Block ... is not locked and returns null.

    Next: one DMA is one block. Nested Blocks and Submaps puts two of them into one subsystem model.

    Related topics

    • Nested blocks and submaps

    • Blocks, registers, fields and memories

    • The frontdoor path, end to end

    Previous

    149 / 280

    Next: Nested Blocks and Submaps

    Up next

    Up nextNested Blocks and Submaps

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why a Register Model: Desired, Mirrored and the DUT
    • Build, Lock and Generated Models
    • The Frontdoor Path, End to End