Chapter 5 of 10
config_db and Virtual Interface Handoff
Why uvm_component's fixed factory constructor signature rules out passing a virtual interface as a constructor argument, and the full mechanics of uvm_config_db::set()/get() that solve it -- context, instance path, wildcards, and why chapter 2's build-phase ordering is what makes it work.
Chapter 3 already used uvm_config_db::set()/get() to get vif from the top-level module into the driver, with a promise to explain why it works later. This is that chapter — and it comes now, right after chapter 4, rather than later in the track, because everything it depends on is already in place: chapter 2's component tree and build_phase ordering. Nothing here needs the factory (chapter 6) or sequences (chapter 7), so there's no reason to make chapter 3's forward reference wait for them.
Why a constructor argument doesn't scale
The SV Basics capstone's driver class had a bespoke constructor: new(virtual mux2_if.tb_mp vif, mailbox #(mux_txn) mbx). That worked because it was a plain SystemVerilog class, constructed with a plain new(...) call the author fully controlled.
A uvm_component doesn't have that freedom. Chapter 3 constructed the driver with my_driver::type_id::create("drv", this) instead of new(...) — that's the UVM factory (full explanation in chapter 6), and it works by calling new(name, parent) internally, with exactly those two arguments, on whatever type ends up actually getting constructed. There's no room in that fixed signature for a third argument like vif. This isn't a style preference — it's a hard constraint: any uvm_component meant to support factory overrides has to keep the standard two-argument constructor, which means configuration data like a virtual interface has to get in some other way.
uvm_config_db is that other way: a global, associative lookup table that any component can publish to or query, independent of the constructor entirely.
set() and get(): the four arguments
Both calls share the same shape:
uvm_config_db#(T)::set(cntxt, inst_path, field_name, value);
uvm_config_db#(T)::get(cntxt, inst_path, field_name, value); // get() returns bitcntxt— a component establishing the reference pointinst_pathis relative to. From inside a component, this is almost alwaysthis. From a plain module (which isn't a component at all — chapter 3's top-levelinitialblock, for instance), there's no component to use, so it'snull, meaning "resolveinst_pathas an absolute path from the top of the hierarchy."inst_path— a string describing which component(s) this entry applies to, relative tocntxt. It supports*/?wildcards. An empty string""means "exactlycntxtitself, nothing else."field_name— an arbitrary string key you choose, like"vif".set()andget()only match when this string is identical on both sides — a typo here is indistinguishable from never having calledset()at all, which is exactly the failure chapter 3's`uvm_fatalcheck was written to catch.value— the data itself (get()writes into it by reference, and returns1on success,0if nothing matched).
Chapter 3's calls, decoded
// top-level module -- not a component, so cntxt is null
uvm_config_db#(virtual mux2_if.tb_mp)::set(null, "*", "vif", bus_if);null means "absolute path from the top." "*" is a wildcard matching any component path at any depth — so this one call reaches every current and future component that asks for a field named "vif", no matter how deep in the hierarchy it lives.
// inside my_driver's build_phase -- cntxt is this component itself
uvm_config_db#(virtual mux2_if.tb_mp)::get(this, "", "vif", vif);this plus an empty inst_path ("") means "is there a value for "vif" visible at my own exact path?" — and because the set() call used the "*" wildcard, the answer is yes, regardless of where my_driver actually sits in the tree.
Why this scales and a constructor argument wouldn't
Imagine the driver isn't a direct child of the test, but three levels down — my_test → my_env → my_agent → my_driver. With a constructor argument, vif would have to be threaded through every intermediate level's constructor, even though my_env and my_agent never use it themselves — pure plumbing, and it breaks the moment any of those levels is reused somewhere with a different depth. With config_db, nothing changes: the same "*"-wildcarded set() at the top still reaches my_driver, and none of the intermediate components need to know vif exists at all.
This is only safe because of chapter 2's phase ordering: build_phase runs top-down, so the top-level module's set() — which happens even before run_test() starts the phase engine at all — is guaranteed to already be there by the time any component's build_phase, at any depth, calls get().
One more thing worth knowing, even though this track's examples never trigger it: if more than one
set()call could match the sameget(),config_dbprefers the most specificinst_path(an exact path beats a wildcard), and among equally specific matches, the most recently executedset()wins. Useful to know when reading someone else's testbench; not something you need to design around here.
Summary
uvm_component's factory-created constructor is fixed to(name, parent)— there's no room for extra configuration arguments like avirtual interface, which is the real reasonconfig_dbexists.set(cntxt, inst_path, field_name, value)/get(cntxt, inst_path, field_name, value):cntxtis the reference point (thisinside a component,nullfrom a plain module),inst_pathis a path relative tocntxt(supports*/?wildcards,""meaning exactlycntxt), andfield_nameis a string key that must match exactly betweenset()andget().- A
"*"-wildcardedset()from the top-level module reaches every component that asks for that field name, at any depth, without threading the value through any intermediate component's constructor. - This depends on chapter 2's guarantee that
build_phaseruns top-down — the top-levelset()(beforerun_test()even starts) is guaranteed to exist before any component'sbuild_phasecallsget().
Why can't a virtual interface just be passed as a third constructor argument to a uvm_component, the way the SV capstone's plain driver class took one directly?
In uvm_config_db#(virtual mux2_if.tb_mp)::set(null, '*', 'vif', bus_if);, what does the '*' argument actually do?
A driver calls uvm_config_db#(virtual mux2_if.tb_mp)::get(this, '', 'VIF', vif) (capital VIF), but the top module called set(null, '*', 'vif', bus_if) (lowercase vif). What happens?
Why does the top-level module's uvm_config_db::set() call need to happen before run_test(), specifically because of chapter 2's phasing material?