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.
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 ( | One monitor and one predictor whose |
// 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.
// 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.
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:
// 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
endclassAddresses 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.
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 : 0Offset 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.
The opening story, reproduced: the same scratch test with the debug predictor left unconnected (Verilator):
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 : 1The 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.
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.
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.
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.