The short answer: The UVM register model (RAL) is a class-based mirror of the DUT’s registers and memories: fields (uvm_reg_field) inside registers (uvm_reg) inside blocks (uvm_reg_block), with an address map. Tests access registers by name with write, read, set/update and mirror; an adapter converts each access into a bus transaction for the agent, and a predictor watches the bus monitor to keep the mirror up to date. Back-door access reads and writes the RTL directly, and built-in sequences test reset values, access policies and bit-bashing.
Most chips have hundreds or thousands of registers. Writing raw bus transactions with hard-coded addresses for each of them doesn’t scale. The register model lets tests say regmodel.ctrl.enable.set(1) and lets the testbench know what every register should contain. This post combines my three older posts on UVM registers, RAL and register abstraction.
- How an access travels
- The model hierarchy
- The adapter
- Connecting it in the environment
- The access API
- Built-in register sequences
Table of Contents
Part 1: How an access travels

Part 2: The model hierarchy
| Class | Represents |
|---|---|
| uvm_reg_field | A field with a width, position, reset value and access policy (RW, RO, W1C, …) |
| uvm_reg | A register made of fields |
| uvm_reg_block | A group of registers, memories and sub-blocks |
| uvm_reg_map | Addresses and the bus used to reach them |
| uvm_mem | A memory region |
Nobody writes these by hand for a real chip. They’re generated from the register specification (IP-XACT, SystemRDL, or a spreadsheet) by a generator, so the RTL, the model and the documentation stay in sync.
Part 3: The adapter
class bus_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(bus_reg_adapter)
function new(string name = "bus_reg_adapter"); super.new(name); endfunction
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
bus_item t = bus_item::type_id::create("t");
t.kind = (rw.kind == UVM_WRITE) ? bus_item::WRITE : bus_item::READ;
t.addr = rw.addr;
t.data = rw.data;
return t;
endfunction
virtual function void bus2reg(uvm_sequence_item bus_item_in, ref uvm_reg_bus_op rw);
bus_item t;
if (!$cast(t, bus_item_in)) `uvm_fatal("ADAPT", "wrong item type")
rw.kind = (t.kind == bus_item::WRITE) ? UVM_WRITE : UVM_READ;
rw.addr = t.addr;
rw.data = t.data;
rw.status = UVM_IS_OK;
endfunction
endclass
Part 4: Connecting it in the environment
// build_phase
regmodel = my_reg_block::type_id::create("regmodel");
regmodel.build();
regmodel.lock_model();
adapter = bus_reg_adapter::type_id::create("adapter");
predictor = uvm_reg_predictor#(bus_item)::type_id::create("predictor", this);
// connect_phase
regmodel.default_map.set_sequencer(agt.sqr, adapter);
predictor.map = regmodel.default_map;
predictor.adapter = adapter;
agt.mon.ap.connect(predictor.bus_in);
regmodel.default_map.set_auto_predict(0); // use the explicit predictor
Explicit prediction (from the monitor) keeps the mirror right even when another master writes registers, which auto-prediction can’t see.
Part 5: The access API
| Method | What it does |
|---|---|
| write / read | Front-door (or back-door) access through the bus |
| set / get | Change or read the desired value in the model only |
| update | Write registers whose desired value differs from the mirror |
| mirror | Read the DUT and update (optionally check) the mirror |
| peek / poke | Back-door read and write, no bus traffic |
| predict | Tell the model a value changed without an access |
uvm_status_e status;
regmodel.ctrl.enable.set(1);
regmodel.ctrl.mode.set(2);
regmodel.ctrl.update(status); // one bus write with both fields
regmodel.stat.mirror(status, UVM_CHECK); // read and compare with the mirror
Part 6: Built-in register sequences
uvm_reg_hw_reset_seq: checks every register’s reset value.uvm_reg_bit_bash_seq: walks ones and zeros through every bit to check access policies.uvm_reg_access_seq: compares front-door and back-door access.uvm_mem_walk_seq: tests memories.
They need back-door paths and correct access policies in the model, which is another reason to generate the model from a single source.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
Design Verification problems · Daily challenge
Previous in my UVM series: Coverage and assertions in UVM. Next: Callbacks.
FAQ
What is UVM RAL?
The UVM register abstraction layer: a model of the DUT’s registers and memories that tests access by name, with an adapter to the bus and a mirror of expected values.
What is the difference between front-door and back-door access?
Front door uses real bus transactions; back door reads or writes the RTL signals directly through HDL paths, with no bus traffic.
What is the difference between set and write?
set changes only the model’s desired value; write performs an access to the DUT. update writes all registers whose desired value differs from the mirror.
Why use an explicit predictor?
It updates the mirror from what the monitor actually saw on the bus, including accesses from other masters.
