Using Verification Components
Reusable Verification Components covered what makes a VC safe to drop into any environment. This page covers the other half: how a larger environment actually composes one — or several — reusable VCs, and where the line falls between what the VC itself owns and what the environment composing it owns instead.
UVCs live in the environment, not the test
Every environment built across this curriculum already followed this without it being named: fifo_agent is instantiated inside fifo_env, never directly inside a test class, and the PWM's reg_bus_agent is instantiated inside pwm_env the same way. A test interacts with a VC only through the environment's handles or a virtual sequence started on it — never by building or configuring the VC's components itself. This keeps the test layer thin and swappable, exactly the property The Test & Testbench-Top relied on when error_injection_test reused fifo_base_test's entire build_phase unmodified: a test that never touched the VC directly has nothing VC-specific to break when the VC itself changes.
An umbrella config object for environments composing several VCs
A single-VC environment can get away with a couple of individual uvm_config_db::set() calls in a test's build_phase. An environment composing several VCs — a register-driven peripheral alongside a streaming data interface, say — scattering one set() call per VC-specific setting across the test quickly becomes hard to read and easy to miss one of. A single umbrella config object — an ordinary uvm_object with one field per VC's own settings — set once, is the usual fix:
class soc_tb_config extends uvm_object;
`uvm_object_utils(soc_tb_config)
fifo_agent_config fifo_cfg;
reg_bus_agent_config bus_cfg;
function new(string name = "soc_tb_config");
super.new(name);
endfunction
endclass
// In the test's build_phase:
soc_tb_config cfg = soc_tb_config::type_id::create("cfg");
cfg.fifo_cfg = fifo_agent_config::type_id::create("fifo_cfg");
cfg.bus_cfg = reg_bus_agent_config::type_id::create("bus_cfg");
cfg.fifo_cfg.is_active = UVM_ACTIVE;
uvm_config_db#(soc_tb_config)::set(this, "*", "tb_cfg", cfg);
The environment's own build_phase then retrieves the one umbrella object and distributes each field to the VC that owns it, rather than each VC independently reaching into uvm_config_db for its own scattered settings. One set() call, one readable object — instead of a test file full of individually-addressed configuration calls that are easy to lose track of as the number of composed VCs grows.
Composing more than one instance of the same VC
Everything above assumes one instance per VC type. A SoC-style environment often needs several — two fifo_agents watching independent FIFOs on the same chip, say — and that breaks the umbrella-config pattern's simplest form: cfg.fifo_cfg can only hold one fifo_agent_config, but there are now two agents that each need their own.
The fix is the same umbrella object, restructured to hold one config per instance instead of one config per VC type — an associative array keyed by instance name, or simply named fields (fifo_cfg_a, fifo_cfg_b) when the instance count is fixed and known up front:
class soc_tb_config extends uvm_object;
`uvm_object_utils(soc_tb_config)
fifo_agent_config fifo_cfg[string]; // keyed by instance name: "fifo_a", "fifo_b"
function new(string name = "soc_tb_config");
super.new(name);
endfunction
endclass
The environment's build_phase then constructs each fifo_agent with its own distinct instance name (fifo_agent::type_id::create("fifo_a", this), create("fifo_b", this)) and looks up that same name in cfg.fifo_cfg[...] when distributing settings — instance name is what disambiguates "which fifo_agent_config belongs to which fifo_agent" once there's more than one of the same type. The same discipline applies one level lower if a test ever needs to reach a specific instance directly through uvm_config_db instead of the umbrella object: a wildcard set(this, "*", ...) reaches every instance identically, which is exactly wrong the moment two instances need different settings — a per-instance path (or a per-instance key in a shared object, as above) is what multiple-instance composition actually requires.
Checking and coverage: protocol-level stays in the VC, DUT-specific moves to the environment
This is the line Reusable Verification Components flagged without drawing precisely — here's where it actually falls:
- Protocol-level checking — traffic obeying the interface's own timing and legality rules, independent of any specific DUT — belongs inside the VC's own monitor.
- DUT-specific, end-to-end checking — verifying that data pushed into this specific FIFO actually comes back out correctly — belongs to the environment composing the VC, not the VC itself.
fifo_scoreboardliving infifo_env, not insidefifo_agent, is exactly this: the scoreboard's reference-model logic is specific to this project's use of the FIFO, not a universal property of the FIFO protocol the agent understands.
Coverage follows the identical split: a VC can reasonably ship a base covergroup for protocol-level coverage (every operation type seen, every field's legal range exercised) as part of its own monitor, while scenario-specific cross-coverage — combinations only one particular project's verification plan cares about — belongs at the environment or test level, layered on top of the VC's coverage rather than baked into it.
Neither fifo_agent nor reg_bus_agent ever contained end-to-end checking logic — fifo_scoreboard sits in fifo_env, and the PWM's correctness check (the frontdoor-vs-backdoor comparison in RAL Worked Example) lives in the test's sequence, not inside reg_bus_agent. This page names a discipline the curriculum's own two examples already respected by construction — the same way The Singleton Pattern named a pattern this curriculum had already used three times before explaining it directly.
What's next
This closes out the discipline half of the curriculum — what makes a VC reusable, and how a larger environment correctly composes one. The final page is a different kind of page entirely: a set of original interview-style questions, drawn from the same themes covered throughout this curriculum, for review rather than new material.