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
    • Register Access
    • Registers in Verification
      • Built-in Register Sequences and NO_* Attributes
      • Register Coverage
      • Memories in the Register Model
      • Multiple Maps and Address Spaces
      • Shadow, Alias and Paged Registers
      • Interrupts with the Register Model
      • The Register Model at SoC Scale
      • RAL Interview Set
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Registers in Verification
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. Registers in Verification
  5. Multiple Maps and Address Spaces

Part 9 · Register Model (RAL) · Intermediate

Multiple Maps and Address Spaces

One register on two buses: per-map offsets and rights, a sequencer and a predictor per map, the default map, and sequences that choose the bus.

On this page

    A chip-level test has two masters: the CPU's register bus, and a debug port that a JTAG sequence drives to poke configuration during bring-up tests. The debug sequence writes CFG = 0x5A. A moment later, a CPU-side mirror(UVM_CHECK) fails: value read from DUT (0x5a) does not match mirrored value (0x0). The DUT is right, the register model is right about everything it was told, and the test is red. The model was simply never told about the second bus.

    Registers reachable from more than one bus are normal: a CPU bus and a debug bus, a secure and a non-secure view, a host interface and a sideband. This lesson shows how one register lives in several maps, how rights differ per map, how each bus keeps the one shared mirror honest, and how a sequence chooses the bus it uses.

    The idea: one room, two doors, a visitor log at each

    Picture an office with two doors on different streets, so it has two addresses. Whoever comes in through either door changes what is inside, and a receptionist at each door logs the visitors. If one door has no receptionist, the office still changes, but the log at the other door never hears about it.

    In the register model the office is the register, with one mirror no matter how many maps hold it. Each map (uvm_reg_map) is one door: one bus's view, with its own base address, bus width, endianness, offsets, rights, sequencer and adapter. add_reg(rg, offset, rights) puts the same register object into a second map at a different offset; nothing is copied. The receptionist is the predictor: a uvm_reg_predictor serves exactly one map, because it turns a bus address into a register with map.get_reg_by_offset(addr), and an address only means something on its own bus.

    Per register

    Per map

    Per bus

    One mirror, one desired value, one set of fields and policies

    Offset, rights ("RW", "RO", "WO"), sequencer and adapter (set_sequencer)

    One monitor and one predictor whose map is that bus's map

    The picture

    Flowchart: a register model with a cpu map and a dbg map; each map drives its own APB agent into the slave; each agent's monitor feeds its own predictor, and both predictors update the register model.
    Two maps hold the same register objects. Each map drives its own agent; each agent's monitor feeds a predictor that decodes addresses with that agent's map. Both predictors update the same mirror.

    Build it: the scratch slave with two ports

    The block: the same registers, a second map

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: from the scratch block's build()
    cpu_map = create_map("cpu_map", 'h0, 4, UVM_LITTLE_ENDIAN);
    cpu_map.add_reg(cfg,       'h00);
    cpu_map.add_reg(thresh,    'h04);
    cpu_map.add_reg(commit,    'h08, "WO");
    cpu_map.add_reg(cfg_alias, 'h0C);
    cpu_map.add_mem(sram,      'h40);
    
    dbg_map = create_map("dbg_map", 'h0, 4, UVM_LITTLE_ENDIAN);
    dbg_map.add_reg(cfg,       'h20);           // the same cfg object, another address
    dbg_map.add_reg(thresh,    'h24, "RO");     // the debug port cannot write THRESH
    dbg_map.add_mem(sram,      'h80);
    
    set_default_map(cpu_map);                   // where an access goes when no map is named
    lock_model();

    The rights argument restricts the register in that map only. With "RO", every field is seen as read-only through dbg_map (get_access(dbg_map) returns "RO" for THRESH's RW field), so a write observed on the debug bus predicts no change. A frontdoor write through that map is still sent: the library does not refuse it. The scratch slave drops it; a real slave may alias it, fault or hang, so a sequence that may run on a restricted map checks get_rights(map) first. set_default_map matters because create_map makes the first map created the default; name the one you mean.

    The env: a sequencer, an adapter and a predictor per map

    design.sv
    1
    2
    3
    4
    5
    // snippet: scratch env connect_phase
    rm.cpu_map.set_sequencer(cpu.sqr, cpu_adp);
    rm.dbg_map.set_sequencer(dbg.sqr, dbg_adp);
    cpu_pred.map = rm.cpu_map;  cpu_pred.adapter = cpu_adp;  cpu.ap.connect(cpu_pred.bus_in);
    dbg_pred.map = rm.dbg_map;  dbg_pred.adapter = dbg_adp;  dbg.ap.connect(dbg_pred.bus_in);

    Both buses here are APB, so the two adapters are two instances of one class. With different protocols (APB and a JTAG-to-register bridge, say) each map gets the adapter for its own bus item, and each predictor is parameterised on that bus item type.

    The test: choose the bus per access

    Every access method takes a map argument. Leave it out and the register's default map is used, which is the block's default map when the register is in it. A sequence that should run on either bus takes the map as a property and passes it on, so the same sequence covers both ports:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: scratch test, plus a sequence that picks the bus
    rm.cfg.write(st, 8'h5A, .map(rm.dbg_map));     // the debugger writes CFG
    #20ns;                                          // let the debug-side prediction land
    rm.cfg.mirror(st, UVM_CHECK);                   // default map: the CPU bus
    
    class cfg_rw_seq extends uvm_reg_sequence;
      `uvm_object_utils(cfg_rw_seq)
      mini_block  rm;
      uvm_reg_map map;                              // set by the test: cpu_map or dbg_map
      function new(string name = "cfg_rw_seq");
        super.new(name);
      endfunction
      task body();
        uvm_status_e st;
        rm.cfg.write(st, 8'hA5, .map(map), .parent(this));
        rm.cfg.mirror(st, UVM_CHECK, .map(map), .parent(this));
      endtask
    endclass

    Addresses are resolved per map too. get_reg_by_offset answers only for its own map, which is what makes a predictor per bus necessary, and get_address(map) gives a register's address on a given bus.

    Watch it run

    Diagram
    scratch run, Verilator
    UVM_INFO mini_tb.sv(382) @ 0: uvm_test_top [MAPS] dbg_map 0x20 -> rm.cfg; cpu_map 0x20 -> null; cfg in cpu_map @0, in dbg_map @20; default map cpu_map
    UVM_INFO mini_tb.sv(384) @ 0: uvm_test_top [MAPS] thresh rights: cpu_map RW, dbg_map RO; field access in dbg_map RO
    UVM_INFO mini_tb.sv(389) @ 175000: uvm_test_top [MAPS] after a debug-port write, CPU-side mirror check: mirror=5a
    UVM_INFO mini_tb.sv(391) @ 205000: uvm_test_top [MAPS] thresh write through dbg_map: status UVM_IS_OK
    UVM_INFO mini_tb.sv(394) @ 265000: uvm_test_top [MAPS] sram[5] written via dbg, read via cpu: d00d
    UVM_ERROR :    0
    • Offset 0x20 means CFG on the debug bus and nothing on the CPU bus. A single predictor decoding both buses with one map would credit debug traffic to the wrong register, or to none.

    • The debug write reached the CPU-side mirror because the debug predictor saw it. The check passed with mirror = 5A.

    • The THRESH write through the read-only map returned `UVM_IS_OK`. Rights shape prediction and what the built-in sequences attempt; they are not an access guard. If writing through that map is a test bug, check get_rights(map) in your own sequence.

    What breaks: the second master nobody predicted

    The opening story, reproduced: the same scratch test with the debug predictor left unconnected (Verilator):

    Diagram
    UVM_ERROR reg/uvm_reg.svh(2812) @ 175000: reporter [RegModel] Register "rm.cfg" value read from DUT (0x000000000000005a) does not match mirrored value (0x0000000000000000) (valid bit mask = 0x00000000000000ff)
    UVM_INFO reg/uvm_reg.svh(2833) @ 175000: reporter [RegModel] Field mode (rm.cfg[7:0]) mismatch read=8'h5a mirrored=8'h0
    UVM_INFO mini_tb.sv(389) @ 175000: uvm_test_top [MAPS] after a debug-port write, CPU-side mirror check: mirror=0
    UVM_ERROR :    1

    The write went out through dbg_map, the slave took it, and no predictor connected to the model saw it, so the mirror stayed 0. Turning on auto prediction for the debug map would hide this particular failure, since the write was made through the model, but not the general one: a debug VIP sequence that drives the JTAG pins directly, a DMA engine writing configuration registers, or firmware running on a processor model all change registers without calling the model. The rule that survives all of them is one predictor per bus that can write the registers, fed by that bus's monitor. The bench's dma_ral_busy_len_test shows the cousin of this failure: there the model misses a rule of the DUT (LEN writes are ignored while BUSY) instead of a bus, and the RegModel error looks identical. When a mirror check fails, list every agent that can write that register, and every rule that can stop a write, before blaming the DUT.

    Tip

    To prove that a register in several maps really is one storage element, uvm_reg_mem_shared_access_seq writes through each map and reads back through all the others. It skips registers that appear in only one map, so it costs nothing to add to the built-in set.

    Interview

    "A register is reachable from two buses. How do you model it, and how do you keep the mirror correct?"

    A weak answer: "Create the register twice, once per bus, and keep them in sync."

    A strong answer: "One register object, added to two maps with add_reg at each bus's offset, with rights per map. Each map gets its own sequencer and adapter with set_sequencer. Each bus monitor feeds its own uvm_reg_predictor whose map is that bus's map, because the predictor decodes addresses with get_reg_by_offset on its map. Both predictors update the one mirror. Sequences take the map as a parameter, or use the default map, so the same sequence runs on either bus. The shared-access built-in sequence checks that the two addresses really hit one storage element."

    Follow-ups: What does 'RO' rights in the debug map do to a debug write? (the field is RO in that map: the write is still issued, and predicts no change). Why not one predictor for both buses? (one map per predictor: the same offset means different registers on different buses).

    Check yourself A test calls rm.cfg.write(st, v) with no map, and CFG is in both maps. Which bus is used?

    The register's default map. The library uses the parent block's default map if the register is in it, and the scratch block sets cpu_map as the default with set_default_map, so the CPU bus is used. Without that call, the first map created is the default, which is fragile: reorder two lines in build() and every unqualified access changes bus.

    Check yourself Only the CPU bus has a predictor. A test reads CFG through the debug map with read(..., .map(rm.dbg_map)). Does the mirror update?

    Not from the bus: no predictor watches the debug bus. With auto prediction off, the read returns the DUT value to the caller but the mirror keeps its old value. With auto prediction on for that map, the model predicts from its own read inside read(). Either way, accesses made outside the model on that bus stay invisible until the bus has its own predictor.

    Check yourself Why is uvm_reg_mem_shared_access_seq worth running even when both maps are generated from one spec?

    The generator can only promise that the model says the two addresses are one register. The sequence checks that the RTL agrees: a write through one map must read back through every other map. A decode bug that sends the debug address to a copy, or to nothing, passes every single-map test and fails here.

    Next: two addresses for one register was the easy case. Shadow, Alias and Paged Registers handles storage where a write does not land where a read looks.

    Related topics

    • Up: Registers in Verification

    • Next: Shadow, Alias and Paged Registers

    • Nested blocks and submaps

    Previous

    164 / 280

    Next: Shadow, Alias and Paged Registers

    Up next

    Up nextShadow, Alias and Paged Registers

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Built-in Register Sequences and NO_* Attributes
    • Register Coverage
    • Interrupts with the Register Model