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.

diagram
[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?
diagram
[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_regs
  • Blocks 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.

systemverilog
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
endclass
systemverilog
class 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
diagram
[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 value
  • Call 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.

systemverilog
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
diagram
[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
diagram
[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   = 0x1000
  • Leaf 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.

systemverilog
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
diagram
[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 expectations

Key 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.