Connecting a UVM register environment means telling the register model which bus agent carries its transactions, and making sure the predictor sees that agent's traffic. A register environment contains the register model, the adapter and the predictor, but none of them can reach the DUT until it is linked to an agent's sequencer and monitor in the top-level environment.
7 min read | Intermediate Level
What You'll Learn
- The two connections that join a register environment to a bus agent
- Why the order of UVM build and connect phases matters for these connections
- Use
set_sequencer()to route front-door accesses through an agent - Access the connected register model from a test
What Needs to be Connected?
A register environment needs exactly two links to the bus agent:
- Map to sequencer: the register model's address map must know which sequencer to send bus items to, and which adapter to use for converting them. This is done with
set_sequencer(). - Monitor to predictor: the agent's analysis port must be connected to the predictor's
bus_inport, so the predictor sees every transaction on the bus.
Both links are made in the top-level environment, which contains both the agent and the register environment.
Syntax
// Route front-door register accesses through a sequencer, using an adapter
MAP.set_sequencer (SEQUENCER, ADAPTER);
// Let the predictor observe every bus transaction
AGENT_OR_MONITOR_AP.connect (PREDICTOR.bus_in);
MAP- the register model's address map, usuallydefault_map(required)SEQUENCER- the sequencer of the bus agent that reaches the registers (required)ADAPTER- theuvm_reg_adapterthat converts register items for that bus (required for front-door access)
Connecting the Register Environment
In the top-level environment my_env, the bus agent m_agent and the register environment m_reg_env are created as siblings.
class my_env extends uvm_env;
`uvm_component_utils (my_env)
bus_agent m_agent;
reg_env m_reg_env;
function new (string name = "my_env", uvm_component parent);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_agent = bus_agent::type_id::create ("m_agent", this);
m_reg_env = reg_env::type_id::create ("m_reg_env", this);
// Give reg_env the agent handle now, because reg_env's connect_phase
// runs before this one and uses m_agent to connect its predictor
m_reg_env.m_agent = m_agent;
endfunction
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
// The predictor is already connected inside reg_env, so only the map
// needs to know which sequencer and adapter to use for front-door accesses
m_reg_env.m_ral_model.default_map.set_sequencer (m_agent.m_seqr, m_reg_env.m_apb_adapter);
endfunction
endclass
This code works together with the reg_env class from the UVM Register Environment article, which has a handle m_agent and connects m_agent.ap to its predictor in its own connect_phase.
Why the Handle is Set in build_phase
UVM runs build_phase from the top of the hierarchy down, and connect_phase from the bottom up. That order decides where each step must go:
| Step | Phase and order | What happens |
|---|---|---|
| 1 | my_env.build_phase | Creates m_agent and m_reg_env, then sets m_reg_env.m_agent |
| 2 | reg_env.build_phase, agent build_phase | The register model, adapter, predictor, sequencer, driver and monitor are created |
| 3 | reg_env.connect_phase (child first) | Connects m_agent.ap to the predictor, which needs m_agent to be set already |
| 4 | my_env.connect_phase (parent last) | Links the map to m_agent.m_seqr and the adapter |
If m_agent were assigned in my_env.connect_phase instead, it would still be null when reg_env.connect_phase runs in step 3, and the simulation would stop with a null object access.
Quick Check
Q: Why can set_sequencer() be called in my_env.connect_phase, even though it uses objects created by the children?
Show Answer
Every build_phase in the whole hierarchy finishes before any connect_phase starts. By then, the register model, the adapter and the agent's sequencer all exist.
Where is This Pattern Used?
Keeping the register model, adapter and predictor in their own environment means the same reg_env can be dropped into a block-level testbench and a full-chip testbench unchanged. Only the top-level environment, which knows which agent reaches the registers in that testbench, changes.
Using the Connected Model in a Test
Once connected, the model can be used from any test or sequence. reg_env places the model in the configuration database for uvm_test_top, so the test can get it after the environment has been built.
class reg_test extends uvm_test;
`uvm_component_utils (reg_test)
my_env m_env;
ral_my_design m_ral_model;
// Rest of the code (constructor, build_phase that creates m_env)
virtual task run_phase (uvm_phase phase);
uvm_status_e status;
uvm_reg_data_t rdata;
phase.raise_objection (this);
// reg_env stored the model during its build_phase, which has finished by now
if (!uvm_config_db #(ral_my_design)::get (this, "", "m_ral_model", m_ral_model))
`uvm_fatal ("TEST", "Register model not found in config_db")
m_ral_model.m_reg_ctl.write (status, 'h3A); // Front door: map -> adapter -> m_seqr
m_ral_model.m_reg_ctl.read (status, rdata); // Predictor updates the mirror from m_mon
`uvm_info ("TEST", $sformatf ("REG_CTL = 0x%0h", rdata), UVM_LOW)
phase.drop_objection (this);
endtask
endclass
Register sequences that extend uvm_reg_sequence have a model handle. Assign the register model to it before starting the sequence, and the sequence can access every register by name.
Where are Register Tests Run First?
The first tests on a new register block are usually the UVM built-in reset and bit-bash sequences. They need exactly the connections in this article and nothing else, so they are a quick check that the map, adapter, sequencer and predictor are all wired correctly.
Common Mistakes
Mistake #1: Assigning the agent handle too late
Wrong: m_reg_env.m_agent = m_agent; inside my_env.connect_phase
Right: make the assignment in my_env.build_phase, right after creating both components
Why? connect_phase runs bottom-up, so reg_env connects its predictor before the parent's connect_phase runs. The handle must already be valid.
Mistake #2: Connecting the predictor twice
If reg_env already connects the agent's analysis port to its predictor, connecting the monitor to bus_in again in my_env makes the predictor receive every transaction twice. Make the connection in exactly one place.
Mistake #3: Getting the model from config_db in the test's build_phase
Wrong: calling uvm_config_db #(ral_my_design)::get(...) in reg_test.build_phase
Right: get it in end_of_elaboration_phase or run_phase
Why? The test's build_phase runs before the environment's, so reg_env has not created and stored the model yet, and the get() fails.
Practice Exercise
Challenge: Support a Passive Agent
In a system-level testbench, the APB bus is driven by a CPU model, and the APB agent is configured as passive (it has a monitor but no sequencer or driver). Change my_env so the register model still tracks every register write made by the CPU, without trying to drive the bus itself.
Hint
Only one of the two connections needs a sequencer. Use the agent's get_is_active() method to decide whether to make it.
Solution
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
// The predictor link (made inside reg_env) works for active and passive agents.
// The map link needs a sequencer, which only an active agent has.
if (m_agent.get_is_active () == UVM_ACTIVE)
m_reg_env.m_ral_model.default_map.set_sequencer (m_agent.m_seqr, m_reg_env.m_apb_adapter);
else
`uvm_info ("ENV", "Passive APB agent: register model updated by prediction only", UVM_LOW)
endfunction
With a passive agent, tests can still read the mirror with get_mirrored_value() or use back-door access, but front-door write() and read() calls are not possible.
Summary
In this tutorial, you learned:
- A register environment needs two links to a bus agent: map to sequencer, and monitor to predictor
set_sequencer(sequencer, adapter)routes front-door register accesses through an agent- The agent handle for the register environment is set in
build_phase, becauseconnect_phaseruns bottom-up - Each connection should be made in exactly one place to avoid duplicate predictions
- The model stored in
uvm_config_dbis available to the test fromend_of_elaboration_phaseonward - With a passive agent, prediction still keeps the model in sync, but front-door accesses are not available