Part 9 · Register Model (RAL) · Intermediate

block build lifecycle: build(), configure(), lock_model()

Step-by-step lifecycle for constructing a robust register model and freezing it at the right time.

Lifecycle intent and ordering

RAL creation is a stateful lifecycle : objects are created, linked, structurally built, map-connected, then frozen. Reordering these steps leads to partial models, missing maps, or runtime errors.

A practical rule: configure children immediately after creation, build local structure before composing parent maps, and lock at the top level once no structural edits remain.

diagram
[RAL] lifecycle skeleton

 new():
  construct object shell

 configure(parent):
  establish ownership linkage and context

 build():
  instantiate fields/registers/maps/sub-blocks

 lock_model():
  freeze structure for runtime access APIs
diagram
[UVM][RAL] phase placement guideline

 build_phase:
  create + configure + build + lock_model

 connect_phase:
  set_sequencer(adapter) + predictor connections

 run_phase:
  issue accesses (write/read/update/mirror)
  • Treat build/lock as structural setup; never mix with run-time stimulus logic.

  • Keep lifecycle methods deterministic so model shape does not depend on test order.

  • Document lifecycle contract in env code for future maintainers.


Step-by-step implementation

This walkthrough shows the complete order of API calls in a block with two registers and one memory, including explicit map setup and lock timing.

systemverilog
class cfg_block extends uvm_reg_block;
  `uvm_object_utils(cfg_block)

  rand cfg_ctrl_reg ctrl;
  rand cfg_stat_reg stat;
  rand uvm_mem      table_mem;
  uvm_reg_map       default_map;

  function new(string name = "cfg_block");
    super.new(name, UVM_NO_COVERAGE);
  endfunction

  virtual function void build();
    // 1) create child objects
    ctrl = cfg_ctrl_reg::type_id::create("ctrl");
    stat = cfg_stat_reg::type_id::create("stat");
    table_mem = uvm_mem::type_id::create("table_mem");

    // 2) configure ownership
    ctrl.configure(this);
    stat.configure(this);
    table_mem.configure(this, 64, 32, "RW", UVM_NO_COVERAGE);

    // 3) build child internals
    ctrl.build();
    stat.build();

    // 4) create and populate map
    default_map = create_map("default_map", 'h0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_reg(ctrl, 'h00, "RW");
    default_map.add_reg(stat, 'h04, "RO");
    default_map.add_mem(table_mem, 'h100, "RW");
  endfunction
endclass
systemverilog
class chip_env extends uvm_env;
  `uvm_component_utils(chip_env)
  cfg_block reg_model;
  apb_reg_adapter adapter;
  uvm_reg_predictor #(apb_item) predictor;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    reg_model = cfg_block::type_id::create("reg_model");
    reg_model.build();
    reg_model.lock_model();

    adapter = apb_reg_adapter::type_id::create("adapter");
    predictor = uvm_reg_predictor#(apb_item)::type_id::create("predictor", this);
  endfunction
endclass
diagram
[RAL] build-to-lock milestone view

 build() complete:
   - all children exist
   - maps created
   - offsets assigned

 lock_model() complete:
   - structure frozen
   - lookup and access APIs safe for run phase
   - accidental structural mutation blocked
  • Keep creation/configure/build grouped for each child to reduce omissions.

  • Call lock_model at the top owner after all submaps and registers are added.

  • Do not defer lock_model to run_phase; lock before any dynamic accesses begin.


Why lock_model is not optional

lock_model validates and freezes the object graph. Without it, some lookup paths and assumptions inside register APIs are undefined, and accidental late add_reg/add_submap calls can produce inconsistent models.

diagram
[UVM][RAL] what lock_model protects

 before lock:
   structure mutable
   useful during build composition

 after lock:
   structure immutable
   safe for:
     - name/path lookups
     - map iteration
     - reg sequence traversal
     - stable debug dumps
diagram
[RAL] late mutation anti-pattern

 run_phase:
   if (feature_x) default_map.add_reg(extra_reg, 'h200, "RW");

 problems:
   - test-order dependent model shape
   - inconsistent mirror behavior
   - impossible reproducibility across regressions
systemverilog
function void ensure_model_locked(uvm_reg_block blk);
  if (!blk.is_locked()) begin
    `uvm_fatal("RAL_LOCK", $sformatf("Model %s must be locked before run", blk.get_full_name()))
  end
endfunction
  • Locking is a correctness step, not just a style preference.

  • Mutable model structure in run-phase causes non-repeatable failures.

  • Assert lock status early in test startup for fast failure.


Walkthrough: full lifecycle with parent and child blocks

In multi-block designs, each child builds itself, then parent composes submaps, then top-level lock occurs once composition is complete.

systemverilog
class top_block extends uvm_reg_block;
  `uvm_object_utils(top_block)
  rand cfg_block cfg;
  rand dma_block dma;
  uvm_reg_map default_map;

  function new(string name = "top_block");
    super.new(name, UVM_NO_COVERAGE);
  endfunction

  virtual function void build();
    cfg = cfg_block::type_id::create("cfg");
    cfg.configure(this);
    cfg.build();

    dma = dma_block::type_id::create("dma");
    dma.configure(this);
    dma.build();

    default_map = create_map("default_map", 'h0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_submap(cfg.default_map, 'h0000);
    default_map.add_submap(dma.default_map, 'h8000);
  endfunction
endclass
diagram
[UVM][RAL] execution walkthrough

 1) top_block.build()
 2) cfg child build complete
 3) dma child build complete
 4) top map adds cfg and dma submaps
 5) env calls top_block.lock_model()
 6) connect adapter/predictor
 7) tests run accesses against frozen tree
diagram
[BUS] integration readiness checkpoint

 before first frontdoor access, verify:
   - model locked
   - default_map has sequencer + adapter
   - predictor.map and adapter set
   - monitor connected to predictor.bus_in
   - no pending structural mutations

Key takeaways

  • RAL lifecycle is deterministic: create -> configure -> build -> compose maps -> lock -> connect.

  • lock_model is the transition from construction to safe operational use.

  • Parent-child composition should complete before top-level lock.

  • Early lifecycle assertions prevent deep runtime debug sessions.

Common pitfalls

  • Calling lock_model before add_submap/add_reg completes.

  • Forgetting lock_model and assuming model is production-ready.

  • Injecting structure changes in run-phase based on test conditions.

  • Separating configure and build too far apart, causing missing ownership links.