Every piece this chapter has built - reg_agent with its sequencer, driver, and monitor, plus reg_scoreboard sitting separately - still exists as two unconnected components. This article builds the environment that finally owns both, completing the connection UVM Scoreboard only described in advance.
9 min read | Intermediate Level
What You'll Learn
- What an environment is, and why it shouldn't be skipped in favor of building everything in the test
- How to build reg_env and instantiate reg_agent and reg_scoreboard inside it
- How to connect reg_agent's monitor to reg_scoreboard
- Why environments are built to nest and to be reused across multiple tests
What Is an Environment?
An environment contains a set of reusable verification components and wires them together, with whatever default configuration a given application needs. A larger environment might hold several agents for different interfaces, a shared scoreboard, and a coverage collector - this tutorial's version is intentionally smaller, holding exactly the pieces already built: one agent, one scoreboard.
Why Not Just Build Everything in the Test?
It's technically possible to skip the environment entirely and instantiate reg_agent and reg_scoreboard directly inside a test class:
// What NOT to do
class reg_test extends uvm_test;
`uvm_component_utils(reg_test)
reg_agent m_agent;
reg_scoreboard m_scoreboard;
virtual function void build_phase(uvm_phase phase);
// Instantiate and connect everything here instead of in an environment
endfunction
endclass
This works, but it doesn't scale well once more than one test exists. Every test built this way needs to know the same hierarchy details, and any change to how the components connect has to be repeated in every test that built them directly. Putting reg_agent and reg_scoreboard inside an environment instead means that structure is written exactly once, and every test that needs it simply instantiates the environment.
Building reg_env
1. Declare the Class
class reg_env extends uvm_env;
`uvm_component_utils(reg_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// Code for rest of the steps come here
endclass
reg_env follows the same shape every component in this chapter has used - a constructor taking a name and a parent, registered with the factory the same way. uvm_env itself adds nothing beyond uvm_component; the same recommended-practice reasoning behind extending uvm_monitor or uvm_scoreboard instead of plain uvm_component applies here too.
2. Instantiate Components in build_phase
reg_agent m_agent;
reg_scoreboard m_scoreboard;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_agent = reg_agent::type_id::create("m_agent", this);
m_scoreboard = reg_scoreboard::type_id::create("m_scoreboard", this);
endfunction
Both handles use exactly the names UVM Scoreboard already assumed they'd have - m_agent and m_scoreboard.
3. Connect Components in connect_phase
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
m_agent.m_monitor.mon_analysis_port.connect(m_scoreboard.ap_imp);
endfunction
This is the exact line UVM Scoreboard showed and explicitly deferred, with no changes - reg_env is simply the component it was waiting to be written inside of. reg_agent's monitor was already built to broadcast on mon_analysis_port regardless of who connects to it, and reg_scoreboard's ap_imp was already built to accept a connection from anywhere - neither one needed to change at all once a real parent existed to wire them together.
Quick Check
Q: reg_agent also builds reg_coverage internally and connects it to the same monitor. Does reg_env need to do anything to make that connection happen too?
Show Answer
No. That connection is entirely internal to reg_agent, made in reg_agent's own connect_phase back when it was built. reg_env only needs to make connections between components it directly owns - m_agent and m_scoreboard - not connections that already happened one level deeper inside m_agent itself.
Environments Can Hold More Than This
reg_env is intentionally small - one agent, one scoreboard - because that matches everything this tutorial has actually built. A larger environment isn't limited to that shape: it can hold several agents side by side, for different interfaces on the same DUT, and an environment can itself be nested inside a larger one, the same way reg_agent is nested inside reg_env.
This is also where an environment's own configuration object would typically come from, retrieved with the same uvm_config_db::get() pattern used throughout this chapter - useful once an environment has enough knobs of its own to be worth bundling into one object. reg_env doesn't need one yet, since everything it builds is fixed.
Where is This Used in Practice
A block like a DMA controller is often verified on its own first, with its own dedicated environment. When that same DMA controller gets integrated into a larger chip, that same environment can be instantiated again, nested inside the chip-level environment, alongside environments for every other block - without rewriting any of the DMA-specific verification logic. This is the entire point of building reg_env as a self-contained, reusable unit rather than a one-off pile of connections specific to a single test.
Common Mistakes
Mistake #1: Connecting components that belong to a child, not to this environment
It's tempting to reach directly into m_agent's internals from reg_env's connect_phase - for instance, trying to connect reg_coverage to something from here. Any connection entirely contained within reg_agent belongs in reg_agent's own connect_phase, not the parent's. reg_env should only ever connect handles it directly owns.
Mistake #2: Building components in connect_phase instead of build_phase
m_agent and m_scoreboard both have to exist before connect_phase can wire anything between them - the same reasoning UVM Agent gave for why its own driver-sequencer connection happens after both are built. Moving the create() calls into connect_phase would mean the components don't exist yet when other components try to connect to them.
Practice Exercise
Challenge: Add a Second Scoreboard Connection
Suppose reg_agent also exposed a second analysis port for reg_coverage's data at the agent level, separate from the monitor's own port. Write the connect_phase line that would connect it to a second implementation port, cov_imp, on reg_scoreboard.
Hint
This follows exactly the same shape as the existing connection - a port belonging to m_agent connecting to an implementation port belonging to m_scoreboard.
Solution
virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); m_agent.m_monitor.mon_analysis_port.connect(m_scoreboard.ap_imp); m_agent.cov_analysis_port.connect(m_scoreboard.cov_imp); endfunction
Each connection in connect_phase is independent - adding a second one doesn't change or interfere with the first, since they're two separate port-to-implementation pairs.
Summary
In this article, you learned:
- An environment bundles reusable verification components together, instead of building them directly inside every test that needs them
- reg_env instantiates reg_agent and reg_scoreboard in
build_phase, and connects them inconnect_phase - An environment only connects the components it directly owns - connections internal to a child component, like an agent, stay inside that child
- Environments are built to nest and be reused across multiple tests and even multiple chip-level integrations
Going Deeper
- Passing configuration down the hierarchy: Configure Components
- Scope strings and precedence in config_db: UVM Config Database
- Replacing components in a derived test: UVM Factory Override