Part 9 · Register Model (RAL) · Intermediate
uvm_reg_block Tree: Blocks, Registers, Fields, and Memories
How the RAL hierarchy mirrors hardware ownership: block containers, register composition, field policies, and memory regions.
Why hierarchy is first-class in RAL
A good register model starts with ownership hierarchy , not raw addresses. Hardware teams define IP ownership by blocks and sub-blocks, then assign address windows. RAL mirrors this exactly using nested uvm_reg_block objects so tests can navigate behavior in the same conceptual tree as the spec.
When hierarchy is clear, each block can own local register semantics, reset expectations, coverage intent, and optional backdoor paths. Integration code then composes these blocks with submaps rather than flattening everything into one file.
[UVM][RAL] object relationships
uvm_reg_block (container + map owner)
├─ uvm_reg (addressable register)
│ └─ uvm_reg_field (bit slices + access policy)
├─ uvm_mem (array-like address region)
└─ child uvm_reg_block
hierarchy answers:
- who owns this register?
- where does this map come from?
- what reset/policy domain applies?[RAL] practical organization pattern
top_chip_block
├─ pll_block
│ ├─ pll_ctrl
│ └─ pll_status
├─ uart_block
│ ├─ uart_ctrl
│ ├─ uart_baud
│ └─ uart_fifo_mem
└─ dma_block
├─ global_regs
├─ ch0_regs
└─ ch1_regsBlocks define ownership boundaries and local naming scope.
Registers model addressable words, fields model bit-level behavior, and memories model indexed regions.
Nested blocks let teams reuse IP-level models at subsystem and SoC levels.
Deep dive: blocks, registers, fields, memories
At runtime, uvm_reg_block instances are objects with their own maps, lookup methods, and optional HDL path roots. Registers and memories are configured into that block, then assigned offsets through a map. Fields are configured inside each register with width, lsb position, access policy, reset, volatility, and randomization flags.
class cfg_ctrl_reg extends uvm_reg;
`uvm_object_utils(cfg_ctrl_reg)
rand uvm_reg_field enable;
rand uvm_reg_field mode;
rand uvm_reg_field intr_mask;
function new(string name = "cfg_ctrl_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
enable = uvm_reg_field::type_id::create("enable");
enable.configure(this, 1, 0, "RW", 0, 1'b0, 1, 1, 0);
mode = uvm_reg_field::type_id::create("mode");
mode.configure(this, 2, 1, "RW", 0, 2'b0, 1, 1, 0);
intr_mask = uvm_reg_field::type_id::create("intr_mask");
intr_mask.configure(this, 8, 8, "RW", 0, 8'h00, 1, 1, 0);
endfunction
endclassclass uart_block extends uvm_reg_block;
`uvm_object_utils(uart_block)
rand cfg_ctrl_reg ctrl;
rand uvm_mem tx_fifo;
uvm_reg_map default_map;
function new(string name = "uart_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
ctrl = cfg_ctrl_reg::type_id::create("ctrl");
ctrl.configure(this);
ctrl.build();
tx_fifo = uvm_mem::type_id::create("tx_fifo");
tx_fifo.configure(this, 256, 32, "RW", UVM_NO_COVERAGE);
default_map = create_map("default_map", 'h0, 4, UVM_LITTLE_ENDIAN);
default_map.add_reg(ctrl, 'h00, "RW");
default_map.add_mem(tx_fifo, 'h100, "RW");
endfunction
endclass[RAL] field-to-register mapping example
ctrl_reg [31:0]
[0] enable RW reset=0
[2:1] mode RW reset=0
[7:3] reserved RO reset=0
[15:8] intr_mask RW reset=0x00
[31:16] reserved RO reset=0
spec changes typically impact:
- field width/position
- access policy
- reset valueCall register field build methods before adding the register to maps.
Keep memory regions as uvm_mem objects when random indexed accesses matter.
Model reserved bits and side-effect fields explicitly to avoid mirror confusion.
Walkthrough: assembling a block tree
The common workflow is top-down creation and bottom-up composition: define leaf registers, define block classes that instantiate those leaves, then wire parent blocks through configure/build and submap links.
class soc_block extends uvm_reg_block;
`uvm_object_utils(soc_block)
rand uart_block uart0;
rand uart_block uart1;
rand dma_block dma0;
uvm_reg_map default_map;
function new(string name = "soc_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
uart0 = uart_block::type_id::create("uart0");
uart0.configure(this);
uart0.build();
uart1 = uart_block::type_id::create("uart1");
uart1.configure(this);
uart1.build();
dma0 = dma_block::type_id::create("dma0");
dma0.configure(this);
dma0.build();
default_map = create_map("default_map", 'h0, 4, UVM_LITTLE_ENDIAN);
default_map.add_submap(uart0.default_map, 'h0000_0000);
default_map.add_submap(uart1.default_map, 'h0000_1000);
default_map.add_submap(dma0.default_map, 'h0001_0000);
lock_model();
endfunction
endclass[UVM][RAL] build sequencing for tree assembly
create soc_block
├─ create uart0 block -> configure(parent=soc) -> build()
├─ create uart1 block -> configure(parent=soc) -> build()
├─ create dma0 block -> configure(parent=soc) -> build()
├─ create top map
├─ add_submap child maps at integration offsets
└─ lock_model() once full structure is complete[BUS] lookup path from named register
test calls:
soc.uart1.ctrl.write(...)
map resolution:
uart1.ctrl offset = 0x00
uart1 submap base = 0x1000
soc map base = 0x0000_0000
absolute bus address = 0x1000Leaf blocks should be internally complete before being composed into parent maps.
Lock only after every child and map relation is registered.
Keep submap base assignments aligned with integration address plan documents.
Debugging hierarchy issues
Hierarchy mistakes usually appear as lookup failures, incorrect register paths, or offset collisions during integration. Instrument model traversal utilities early to validate tree shape before deep stimulus.
function void dump_model_shape(uvm_reg_block blk);
uvm_reg regs[$];
uvm_reg_block children[$];
blk.get_registers(regs, UVM_HIER);
blk.get_blocks(children, UVM_HIER);
`uvm_info("RAL_SHAPE",
$sformatf("block=%s regs=%0d child_blocks=%0d",
blk.get_full_name(), regs.size(), children.size()),
UVM_LOW)
endfunction[RAL] hierarchy sanity checklist
1) every child block has configure(parent)
2) every register calls configure(this) and build()
3) every block map is created before add_submap
4) lock_model called once at top after all additions
5) get_full_name paths match spec naming expectationsKey takeaways
Start RAL modeling with ownership hierarchy, then map addresses on top of it.
Blocks, regs, fields, and memories each model a distinct abstraction layer.
Tree assembly requires strict configure/build ordering before map composition.
Early hierarchy introspection avoids late-stage map and lookup failures.
Common pitfalls
Flat mega-block modeling - poor reuse and fragile integration boundaries.
Missing configure() on child objects - broken parent linkage and path resolution.
Calling lock_model too early - later add_reg/add_submap attempts fail unexpectedly.
Ignoring reserved/volatile field semantics - mirror behavior diverges from spec intent.