UVM Basics

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 bit
  • cntxt — a component establishing the reference point inst_path is relative to. From inside a component, this is almost always this. From a plain module (which isn't a component at all — chapter 3's top-level initial block, for instance), there's no component to use, so it's null, meaning "resolve inst_path as an absolute path from the top of the hierarchy."
  • inst_path — a string describing which component(s) this entry applies to, relative to cntxt. It supports */? wildcards. An empty string "" means "exactly cntxt itself, nothing else."
  • field_name — an arbitrary string key you choose, like "vif". set() and get() only match when this string is identical on both sides — a typo here is indistinguishable from never having called set() at all, which is exactly the failure chapter 3's `uvm_fatal check was written to catch.
  • value — the data itself (get() writes into it by reference, and returns 1 on success, 0 if 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_testmy_envmy_agentmy_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 same get(), config_db prefers the most specific inst_path (an exact path beats a wildcard), and among equally specific matches, the most recently executed set() 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 a virtual interface, which is the real reason config_db exists.
  • set(cntxt, inst_path, field_name, value) / get(cntxt, inst_path, field_name, value): cntxt is the reference point (this inside a component, null from a plain module), inst_path is a path relative to cntxt (supports */? wildcards, "" meaning exactly cntxt), and field_name is a string key that must match exactly between set() and get().
  • A "*"-wildcarded set() 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_phase runs top-down — the top-level set() (before run_test() even starts) is guaranteed to exist before any component's build_phase calls get().

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?