Part 9 · Register Model (RAL) · Intermediate
Integration Reference Environment: Full reg_env Walkthrough
Complete reference reg_env code with build, connect, reset sequencing hooks, and detailed integration walkthrough.
Complete reference reg_env source
This reference environment shows a production-grade baseline: one shared model, explicit predictor, backdoor-ready model setup, and deterministic diagnostics.
class reg_env extends uvm_env;
`uvm_component_utils(reg_env)
// ------------------------------------------------------------
// Infrastructure
// ------------------------------------------------------------
apb_agent apb;
apb_reg_adapter adapter;
uvm_reg_predictor #(apb_item) predictor;
my_reg_block reg_model;
// optional helper components
reg_window_filter reg_filter;
bit use_addr_filter = 0;
uvm_reg_addr_t reg_base = 'h4000_0000;
uvm_reg_addr_t reg_last = 'h4000_0FFF;
function new(string name = "reg_env", uvm_component parent = null);
super.new(name, parent);
endfunction
// ------------------------------------------------------------
// build_phase: create everything structural
// ------------------------------------------------------------
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Pull optional knobs first
void'(uvm_config_db#(bit)::get(this, "", "use_addr_filter", use_addr_filter));
void'(uvm_config_db#(uvm_reg_addr_t)::get(this, "", "reg_base", reg_base));
void'(uvm_config_db#(uvm_reg_addr_t)::get(this, "", "reg_last", reg_last));
// Agent + adapter + predictor
apb = apb_agent::type_id::create("apb", this);
adapter = apb_reg_adapter::type_id::create("adapter");
predictor = uvm_reg_predictor#(apb_item)::type_id::create("predictor", this);
adapter.base_addr = reg_base;
if (use_addr_filter) begin
reg_filter = reg_window_filter::type_id::create("reg_filter", this);
reg_filter.base = reg_base;
reg_filter.limit = reg_last;
end
// Try to get shared model first
if (!uvm_config_db#(my_reg_block)::get(this, "", "reg_model", reg_model)) begin
`uvm_info("RAL_CFG", "No external reg_model found; creating local model", UVM_LOW)
reg_model = my_reg_block::type_id::create("reg_model");
reg_model.build();
reg_model.lock_model();
end
// Ensure lock even for externally provided model
if (!reg_model.is_locked())
reg_model.lock_model();
// Optional: if your generated model exposes explicit HDL root setup
// reg_model.set_hdl_root("tb_top.dut");
// Publish same handle for children and sequences
uvm_config_db#(my_reg_block)::set(this, "*", "reg_model", reg_model);
uvm_config_db#(my_reg_block)::set(null, {get_full_name(), ".*"}, "reg_model", reg_model);
`uvm_info("RAL_CFG", $sformatf("reg_model handle=%0p", reg_model), UVM_LOW)
endfunction
// ------------------------------------------------------------
// connect_phase: bind transport and prediction
// ------------------------------------------------------------
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Frontdoor route
reg_model.default_map.set_sequencer(apb.sqr, adapter);
reg_model.default_map.set_auto_predict(0); // explicit predictor only
// Predictor core binding
predictor.map = reg_model.default_map;
predictor.adapter = adapter;
// Monitor feed: direct or filtered
if (use_addr_filter) begin
apb.mon.ap.connect(reg_filter.analysis_export);
reg_filter.out_ap.connect(predictor.bus_in);
end
else begin
apb.mon.ap.connect(predictor.bus_in);
end
`uvm_info("RAL_CFG", "set_sequencer + predictor wiring complete", UVM_LOW)
endfunction
// ------------------------------------------------------------
// start_of_simulation diagnostics
// ------------------------------------------------------------
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
`uvm_info("RAL_CFG",
$sformatf("auto_predict=%0d base=0x%0h filter=%0d",
reg_model.default_map.get_auto_predict(),
adapter.base_addr,
use_addr_filter),
UVM_LOW)
endfunction
// ------------------------------------------------------------
// Utility: quick sanity check callable from tests
// ------------------------------------------------------------
task ral_sanity_check(uvm_object parent = null);
uvm_status_e status;
uvm_reg_data_t rdata;
uvm_reg rg;
// Pick one known register from model
rg = reg_model.ctrl;
if (rg == null) begin
`uvm_error("RAL_SANITY", "reg_model.ctrl is null")
return;
end
rg.write(status, 32'h1, UVM_FRONTDOOR, .parent(parent));
if (status != UVM_IS_OK) begin
`uvm_error("RAL_SANITY", "frontdoor write failed")
return;
end
rg.read(status, rdata, UVM_FRONTDOOR, .parent(parent));
if (status != UVM_IS_OK) begin
`uvm_error("RAL_SANITY", "frontdoor read failed")
return;
end
rg.mirror(status, UVM_CHECK, UVM_FRONTDOOR, .parent(parent));
if (status != UVM_IS_OK)
`uvm_error("RAL_SANITY", "mirror check failed")
endtask
// ------------------------------------------------------------
// Utility: show predictor callback chain quickly
// ------------------------------------------------------------
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("RAL_CFG", "reg_env report complete", UVM_LOW)
endfunction
endclassWalkthrough by region
Infrastructure and knobs
Declare one adapter and one predictor for the map to keep semantics aligned.
Expose optional base address and address-window filter knobs through config_db.
Use explicit handles for agent, map, and model to make debug prints meaningful.
build_phase responsibilities
Read knobs before object creation so components initialize with final config.
Create agent/adapter/predictor deterministically.
Get shared model or create local fallback with explicit info log.
Always ensure lock_model before any frontdoor run.
Publish model handle to children and sequences via config_db.
connect_phase responsibilities
Bind map transport using set_sequencer with the same adapter used for predictor.
Disable auto_predict when explicit prediction is expected.
Bind predictor.map and predictor.adapter before analysis connections.
Connect monitor directly or via filter based on address scope.
[UVM][RAL] region-to-risk map
Code region Primary risk if wrong
--------------------------------------------------------------
adapter.base_addr writes to wrong register offsets
reg_model get/create duplicate model instances
lock_model guard late structural mutation errors
set_sequencer binding frontdoor unable to route
predictor.map/adapter mirror not updated correctly
monitor.ap connect no explicit prediction pathTreat each region as a separate checkpoint during bring-up.
Log key handles and knob values at UVM_LOW for first-run visibility.
Keep set_sequencer and predictor setup physically close in code.
Minimal companion test using the reference env
class reg_env_smoke_test extends uvm_test;
`uvm_component_utils(reg_env_smoke_test)
reg_env env;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = reg_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
env.ral_sanity_check(this);
phase.drop_objection(this);
endtask
endclassExpected logs in healthy run
[UVM][RAL] expected log sketch
UVM_INFO ... RAL_CFG ... reg_model handle=0x...
UVM_INFO ... RAL_CFG ... set_sequencer + predictor wiring complete
UVM_INFO ... RAL_CFG ... auto_predict=0 base=0x40000000 filter=0
UVM_INFO ... RAL_SANITY ... write/read/mirror completed
UVM_INFO ... report summary ...Key takeaways
A full reference env should include knobs, diagnostics, and one sanity task.
Keep ownership clear: build creates/binds; connect wires flow; tests drive intent.
Co-locating code and walkthrough accelerates onboarding for new team members.
Common pitfalls
Using reference code but removing handle logs, then losing debug visibility.
Over-customizing before first sanity check passes.
Adding predictors per sub-agent without clear map ownership.