RAL UVM

Inside UVM RAL: Maps, Bus Operations, Adapters, and Mirror Prediction

Cover image for Inside UVM RAL: Maps, Bus Operations, Adapters, and Mirror Prediction

A register access can produce a perfectly correct APB waveform and still leave get_mirrored_value() stale. The opposite failure is just as confusing: the mirror changes even though the transfer failed or never reached the DUT. Occasionally, one bus transaction updates the mirror twice.

Those symptoms make sense once you stop treating the UVM Register Abstraction Layer as a black box. RAL is best understood as a routing table plus a ledger. The routing side lowers an abstract register operation into one or more physical transfers. The ledger side tracks desired and predicted state.

Operation pipeline:
uvm_reg → uvm_reg_item → uvm_reg_map → uvm_reg_bus_op
        → uvm_reg_adapter → bus item → sequencer/driver → DUT

Observation and state pipeline:
RAL completion or monitored bus item → prediction
                                     → field semantics
                                     → desired/mirrored state

These pipelines cooperate, but they are not the same thing. Successful bus execution does not guarantee prediction occurred, and a mirror update is not independent proof that the DUT changed.

The public behavior discussed here follows IEEE 1800.2-compatible UVM APIs. Implementation-sensitive observations were checked against the Accellera UVM 2020.3.1 reference release. Private helper methods, lookup structures, callback nesting, and temporary objects are not portable APIs and may differ in simulator-supplied libraries.

RAL Is an Object Graph, Not an Address Dictionary

A register model starts with structural containment. Fields belong to registers; registers, memories, register files, maps, and child blocks belong to register blocks.

uvm_reg_block: peripheral
├── uvm_reg: CTRL
│   ├── field: ENABLE
│   └── field: MODE
├── uvm_reg: STATUS
└── uvm_reg_map: apb_map
    ├── CTRL   @ 0x14, RW
    └── STATUS @ 0x18, RO

Maps add address-space relationships to that structure. The same register object can appear in several maps:

apb_map.add_reg(ctrl, 32'h0014, "RW");
dbg_map.add_reg(ctrl, 32'h1014, "RW");

ctrl still has one identity, one field layout, and one model-side state. Its address, access rights, bus geometry, sequencer, and adapter depend on the selected map. Consequently, “the address of CTRL” is incomplete unless the map is known.

A representative block construction sequence looks like this:

virtual function void build();

  ctrl = ctrl_reg::type_id::create("ctrl");
  ctrl.configure(this);
  ctrl.build();

  apb_map = create_map("apb_map",
                       32'h4000_0000,
                       4,
                       UVM_LITTLE_ENDIAN,
                       1);

  dbg_map = create_map("dbg_map",
                       32'h0000_0000,
                       4,
                       UVM_LITTLE_ENDIAN,
                       1);

  apb_map.add_reg(ctrl, 32'h0014, "RW");
  dbg_map.add_reg(ctrl, 32'h1014, "RW");

  set_default_map(apb_map);
  lock_model();

endfunction

Here, ctrl_reg::build() is the user-defined method that creates and configures the register’s fields. Generated models may organize construction differently, but the dependencies remain the same: objects must exist before configuration, fields must be configured into their register, mappings must be established, and structural construction must finish before locking.

lock_model() finalizes the model and enables implementation work needed for address lookup and mapping. It prevents later structural changes that would invalidate those results.

It does not initialize hardware, assert reset, read the DUT, connect a sequencer, validate HDL paths, or prove that modeled addresses match RTL decode. Model finalization and hardware initialization are separate jobs.


Desired, Mirrored, and DUT Values Are Different Facts

RAL does not maintain one universal register value. It manages model-side state while the DUT keeps its own state.

The desired value represents what the model wants a field or register to contain. set() changes desired state without accessing hardware, and get() returns the assembled desired value.

The mirrored value is the model’s current prediction of architectural DUT state. get_mirrored_value() returns that stored estimate; it is not a live read.

The DUT value exists in RTL. It can change because of reset, autonomous hardware logic, firmware, another bus master, read side effects, write side effects, or backdoor activity.

uvm_status_e status;

ral.ctrl.set(32'h0000_0005);

`uvm_info("RAL",
  $sformatf("desired=%08h mirrored=%08h",
            ral.ctrl.get(),
            ral.ctrl.get_mirrored_value()),
  UVM_LOW)

ral.ctrl.update(status,
                UVM_FRONTDOOR,
                ral.apb_map,
                this);

After set(), the desired value can be 5 while the mirror retains its previous value. update() checks whether the model needs updating and issues a write when appropriate. If no update is needed, it can complete without a bus transfer.

This gives a useful contrast:

write(value):       request a register access now
set(value):         modify desired model state only
set() + update():   defer the physical reconciliation

A successful update() does not by itself prove that RTL contains the desired value. The access must complete successfully, prediction must follow the intended policy, and the DUT must implement the modeled semantics.

Calling reset() on a register or block changes desired and mirrored model state according to configured reset kinds. It does not drive the DUT reset signal.


uvm_reg_item Carries the Abstract Operation

A public call such as write(), read(), update(), or mirror() creates register-layer operation context in a uvm_reg_item.

Architecturally, that item carries information such as the target register or memory element, operation kind, value, requested path, selected map, status, parent sequence, priority, extension object, and optional source metadata.

It is not normally sent to the bus driver. It remains an abstract operation while RAL determines how the request should execute.

Callbacks and custom access mechanisms can inspect or modify the operation. If the bus sees a different value from the original API argument, inspect register and field callbacks before blaming the adapter. Exact private callback ordering is release-specific and should be checked against the UVM library actually compiled by the project.


Tracing a Frontdoor Write

Consider a 32-bit CTRL register at offset 0x14 in a 32-bit little-endian APB map:

Root map base:    0x4000_0000
Local offset:     0x0000_0014
Physical address: 0x4000_0014
Write value:      0x0000_0005

The access explicitly names the map:

ral.ctrl.write(.status(status),
               .value (32'h0000_0005),
               .path  (UVM_FRONTDOOR),
               .map   (ral.apb_map),
               .parent(this));

Passing the map removes ambiguity when the register appears in multiple address spaces. Supplying parent preserves sequence context used for arbitration, transaction relationships, and agent-specific sequencing conventions.

Serialization Is Not Hardware Atomicity

RAL serializes conflicting register-layer operations so that its own multi-step access machinery does not overlap incorrectly on the same modeled register.

That protection is model-side serialization, not hardware atomicity. It does not prevent firmware, autonomous RTL, or another bus master from modifying the register between physical transfers. A field operation expanded into read-modify-write remains vulnerable to a real hardware race.

The Map Resolves the Route

An explicitly supplied map defines the access view. Otherwise, RAL resolves an applicable or default map. Mapping information contributes the local register offset, map-specific rights, bus width, endianness, byte addressing, and any parent-map or submap translation.

For hierarchical maps, log these values separately:

leaf register offset
leaf map base or submap offset
root-map physical address

Combining them into one “address” field hides many submap integration defects.

Map rights and field access policies are different layers. A map entry marked "RW" says the register is visible as readable and writable through that map. It does not turn an RO, W1C, RC, or volatile field into a plain RW field. Neither layer guarantees DUT enforcement.

A configured uvm_reg_frontdoor can replace ordinary map-to-bus lowering. Indirect registers, indexed windows, unlock sequences, and other multi-step protocols are common reasons to use one.

The Map Produces Generic Bus Operations

The selected map reduces the register request into one or more uvm_reg_bus_op values. This generic structure carries operation kind, address, data, byte enables, and completion status.

For the simple write, the conceptual result is:

kind:    UVM_WRITE
addr:    0x4000_0014
data:    0x0000_0005
byte_en: 0b1111
status:  unresolved

A single register operation is not necessarily a single bus transaction. A 64-bit register on a 32-bit map requires multiple generic transfers. Endianness determines how register slices are assigned to those transfers:

Value = 0x11223344_AABBCCDD

32-bit little-endian map:
base + 0x0 → 0xAABBCCDD
base + 0x4 → 0x11223344

32-bit big-endian map:
base + 0x0 → 0x11223344
base + 0x4 → 0xAABBCCDD

This illustrates word ordering; lane behavior also depends on map geometry and protocol rules. The adapter should consume the addresses, data, and enables produced by the map rather than inventing another endian conversion.

The Adapter Creates a Protocol Item

The adapter translates between uvm_reg_bus_op and the bus agent’s sequence item. The following APB-like item and adapter are intentionally illustrative. Real agents use different field names and may separate command, write data, read data, strobes, and response status differently.

class apb_item extends uvm_sequence_item;
  `uvm_object_utils(apb_item)

  bit                   write;
  uvm_reg_addr_t        addr;
  uvm_reg_data_t        wdata;
  uvm_reg_data_t        rdata;
  uvm_reg_byte_en_t     strb;
  bit                   slverr;

  function new(string name = "apb_item");
    super.new(name);
  endfunction
endclass


class apb_reg_adapter extends uvm_reg_adapter;
  `uvm_object_utils(apb_reg_adapter)

  function new(string name = "apb_reg_adapter");
    super.new(name);
    supports_byte_enable = 1;
    provides_responses   = 0;
  endfunction

  virtual function uvm_sequence_item reg2bus(
      const ref uvm_reg_bus_op rw);

    apb_item tr = apb_item::type_id::create("tr");

    tr.write = (rw.kind == UVM_WRITE);
    tr.addr  = rw.addr;
    tr.wdata = rw.data;
    tr.strb  = rw.byte_en;

    return tr;
  endfunction

  virtual function void bus2reg(
      uvm_sequence_item bus_item,
      ref uvm_reg_bus_op rw);

    apb_item tr;

    if (!$cast(tr, bus_item)) begin
      rw.status = UVM_NOT_OK;
      `uvm_error("APB_ADAPT", "Unexpected bus item type")
      return;
    end

    rw.kind    = tr.write ? UVM_WRITE : UVM_READ;
    rw.addr    = tr.addr;
    rw.data    = tr.write ? tr.wdata : tr.rdata;
    rw.byte_en = tr.strb;
    rw.status  = tr.slverr ? UVM_NOT_OK : UVM_IS_OK;
  endfunction
endclass

In this example, the driver places read data in rdata and completion failure in slverr. That is an agent-specific contract, not a requirement imposed by UVM or APB. Another agent might overwrite one data field, attach a response enum, or return a separate response object.

provides_responses = 0 specifically assumes the driver completes the original request object in place. Before calling item_done(), it must put read data and final status into that same object. If the driver returns a distinct response item, the adapter contract must instead use provides_responses = 1.

Likewise, supports_byte_enable = 1 is valid only if meaningful byte enables survive through the sequence item, driver, monitor, and predictor. Advertising support while dropping strobes creates incorrect partial writes and incorrect mirror prediction.

A compact standalone adapter test should exercise the semantic round trip:

uvm_reg_bus_op
→ reg2bus()
→ completed request, response, or monitor item
→ bus2reg()
→ equivalent generic operation

Test reads, writes, every supported strobe pattern, successful and failed responses, and both completion models used by the agent.

The Sequencer and Driver Execute the Transfer

The map must be connected to the appropriate sequencer and adapter:

apb_reg_adapter adapter;

adapter = apb_reg_adapter::type_id::create("adapter");

ral.apb_map.set_sequencer(apb_agent.sequencer, adapter);

The adapter only translates representations. The sequencer arbitrates the item, the driver executes the protocol, and the DUT receives the transfer.

An adapter can also provide a protocol-specific parent_sequence when an agent requires bus items to run under a wrapper sequence. That is distinct from the parent argument supplied to the register API.

Completion and Prediction Return Separately

The active return path is approximately:

driver completion
→ original request or separate response
→ adapter.bus2reg()
→ uvm_reg_bus_op data/status
→ uvm_reg_item value/status
→ caller

Prediction is a related state-update path, not merely another name for completion. Failed operations must propagate UVM_NOT_OK; returned data and resulting model state should not be trusted without checking uvm_status_e.

For successful write prediction, field policy matters. RW fields normally adopt the written value. RO fields are not treated as freely writable. A W1C field clears mirrored bits written as one instead of storing those ones. Custom policies and callbacks can alter the result further.


Reads, mirror(), and Multi-Beat Reconstruction

A frontdoor read() uses the same outbound route: register item, selected map, one or more generic reads, adapter conversion, sequencer, and driver.

The important path runs back from the DUT:

DUT read data
→ completed bus item or response
→ adapter.bus2reg()
→ generic read data and status
→ reconstructed register value
→ caller and prediction

For a wide register, RAL reconstructs the logical value from completed beats using map width and endianness. An explicit predictor also needs complete observed transfers. In the Accellera 2020.3.1 implementation, uvm_reg_predictor accumulates the mapped bus operations needed for a register prediction rather than predicting each partial beat as a complete register value. Protocol monitors must still publish usable completed operations in the form expected by the adapter.

mirror() performs a read specifically to synchronize the model:

uvm_reg_data_t before;

before = ral.status.get_mirrored_value();

ral.status.mirror(.status(status),
                  .check (UVM_CHECK),
                  .path  (UVM_FRONTDOOR),
                  .map   (ral.apb_map),
                  .parent(this));

With UVM_CHECK, the observed read value is checked against the mirrored expectation that existed before the read. It is not predicted first and then compared with itself. After a successful operation, model state is updated according to field read semantics.

That distinction matters for read side effects. An RC field can return its pre-clear value while the read causes the DUT field to clear. Prediction must represent the modeled post-read state, not blindly retain the returned word. If the transfer fails, checking and prediction must not treat the failed access as valid observation.


Choose One Prediction Authority

Auto-predict and explicit prediction answer the same question from different evidence: what event is authoritative enough to update the mirror?

With auto-predict enabled, completed RAL accesses through that map update model state:

ral.apb_map.set_auto_predict(1);

This is practical when relevant traffic originates through RAL and the active bus agent reports completion reliably. It does not observe firmware, another master, direct bus sequences, or autonomous hardware changes.

Explicit prediction uses monitor traffic instead:

ral.apb_map.set_auto_predict(0);

predictor.map     = ral.apb_map;
predictor.adapter = adapter;

apb_agent.monitor.ap.connect(predictor.bus_in);

The resulting path is:

completed monitor item
→ uvm_reg_predictor
→ adapter.bus2reg()
→ map address lookup
→ register prediction
→ field desired/mirrored state

This architecture can observe traffic from multiple initiators, provided the monitor publishes the correct physical address, direction, data, byte enables, and completion status. Split, pipelined, or out-of-order protocols may require monitor-side reconstruction before an item is suitable for prediction.

Predict only confirmed completed transfers. A request observation is not enough if the operation can later be aborted or rejected.

Do not casually enable both auto-predict and an explicit predictor for the same traffic. Duplicate prediction can trigger callbacks, coverage, diagnostics, and side-effect handling twice. Conversely, disabling auto-predict without a valid predictor leaves the mirror stale.

One prediction authority per bus stream and map is the simplest policy to reason about.


Partial Fields and the Atomicity Trap

A partial field update must preserve byte-enable meaning through every layer:

field intent
→ register access strategy
→ uvm_reg_bus_op.byte_en
→ protocol strobes
→ DUT byte lanes
→ monitor strobes
→ predictor byte enables

If a field cannot be independently accessed through valid byte lanes, the register layer may require a full-register read-modify-write. That can be unsafe for mixed control/status registers.

Suppose the first read captures a hardware-updated status bit. Hardware changes it again before the write. The write then restores stale read data along with the intended control-field update. RAL serialization does not prevent this race because the competing activity is outside the register model.

Prefer byte-aligned register design, register-level writes with explicitly safe values, a custom frontdoor, alias set/clear registers, or hardware-supported atomic operations when read-modify-write is unsafe.


Backdoor Access Bypasses the Bus, Not Necessarily RAL

A register read() or write() using UVM_BACKDOOR still represents a register-layer operation. It can use configured HDL paths or a custom uvm_reg_backdoor, participate in callbacks and status handling, and update model state according to the operation’s semantics.

What it bypasses is map decomposition, the adapter, sequencer, driver, bus protocol, and decode logic.

peek() and poke() are more raw-oriented backdoor operations and should not be treated as architectural equivalents of frontdoor reads and writes.

Backdoor access does not test address decode, protection, arbitration, wait states, bus errors, byte lanes, or protocol-triggered side effects. An HDL deposit may also bypass write pulses, shadow-register commits, parity generation, unlock logic, or read-clear behavior implemented in the bus interface.

Built-in HDL accesses often complete without advancing simulation time, but a custom backdoor can wait for clocks or execute a multi-step procedure. HDL paths can also be stale, incorrectly sliced, optimized away, or mapped to storage that does not represent the software-visible value.

A bus-monitor predictor cannot observe backdoor traffic. The environment therefore needs an explicit state-synchronization policy for accesses outside the monitored bus.


A Layer-by-Layer Debugging Playbook

Debug in the direction the operation flows.

At the model boundary, confirm the register handle, requested path, explicit map, parent sequence, and returned status. Verify construction completed before lock_model().

At map resolution, log the local register offset and final physical address separately. Include submap translation, map width, endianness, byte addressing, and beat count.

At the adapter boundary, verify direction, address, data, and byte enables in both reg2bus() and bus2reg(). Confirm the response mode matches the driver and that failures become UVM_NOT_OK.

At prediction, identify the single intended authority: auto-predict, explicit predictor, direct predict(), or backdoor operation. Then compare desired state, prior mirror, returned data, resulting mirror, and independently observed DUT state as separate facts.

Typical symptoms point to specific boundaries:

Symptom Likely cause
Correct write waveform, stale mirror Missing or delayed prediction
Mirror updates twice Auto and explicit prediction both active
Correct register, wrong valid address Wrong map or submap translation
Correct read waveform, returned zero Wrong read-data source in bus2reg()
Mirror changes after bus error Lost status or prediction before completion
Field write corrupts status bits Unsafe read-modify-write
Full writes pass, partial writes fail Dropped or misdeclared byte enables

For difficult failures, produce one compact trace:

register and selected map
→ local offset and root physical address
→ generated uvm_reg_bus_op beat(s)
→ protocol item(s)
→ completed data and status
→ prediction source
→ desired and mirrored result

That trace usually reveals whether the defect belongs to model topology, address lowering, adapter conversion, agent completion, DUT behavior, or prediction ownership.


Public API Versus Accellera Implementation

The stable concepts are the relationships among blocks, registers, fields, and maps; frontdoor and backdoor APIs; uvm_reg_item; adapter conversion through uvm_reg_bus_op; desired and mirrored state; and auto or explicit prediction.

Treat private methods, semaphores, address caches, helper sequences, callback implementation order, and predictor bookkeeping as version-specific. Use documented adapters, predictors, callbacks, frontdoors, and backdoors instead of reaching into implementation internals.

The source references below are pinned to the Accellera UVM 2020.3.1 release used for this review:

IEEE 1800.2 remains the normative specification. The Accellera source is useful for understanding one compatible implementation, not for turning its private call stack into a project-level contract.

The practical mental model is straightforward: a register identifies modeled state, a map selects an address-space route, an adapter translates generic operations, the bus agent executes them, and prediction decides what evidence updates the ledger. Trace those boundaries in order, and most RAL failures stop looking mysterious.

SEO title: UVM RAL Internals: Maps, Adapters, and Mirror Prediction

SEO description: Trace UVM RAL through maps, adapters, bus operations, and mirror prediction, including reads, writes, errors, and backdoor access.


Discussion

0 comments

No comments yet — start the conversation.

Leave a comment

Comments are moderated and appear here after review.

Name
Email
Won't be published.
Comment