Skip to main content

Resource DB & Config DB

Testbench-top, back in The Test & Testbench-Top, had one loose thread: it created a virtual interface in a plain-Verilog module, but the driver and monitor — buried several levels deep inside uvm_test → uvm_env → uvm_agent — needed that same handle. Passing it as a constructor argument would mean threading it through every layer of the hierarchy by hand, and re-plumbing all of them the moment the tree's shape changes. uvm_config_db is what closed that loop, in every build_phase throughout this section — this page covers the mechanism properly, and the more general system it's actually built on top of.

uvm_config_db is a convenience layer over uvm_resource_db​

uvm_config_db is the one every example in this section has used directly, but underneath it sits uvm_resource_db — a more general-purpose, lower-level store that uvm_config_db wraps with type-safety and a hierarchy-aware API. In practice, almost every testbench should reach for uvm_config_db rather than uvm_resource_db directly — it's worth knowing the relationship mainly so uvm_resource_db doesn't look like an unrelated, competing mechanism the first time it shows up in someone else's code or in error messages.

The set/get pattern​

// In testbench-top (see The Test & Testbench-Top) — sets the value once
uvm_config_db#(virtual fifo_if)::set(null, "*", "vif", fifo_if_inst);

Reading it apart:

  • #(virtual fifo_if) — the type being stored; get() on the other end must ask for the same type.
  • null — the context component (null means "start from the very top of the tree").
  • "*" — the hierarchical path this applies to, as a string glob. "*" matches every component, everywhere — appropriate here since almost everything in the tree eventually needs the interface.
  • "vif" — the field name being set, matched against the name used in get().

On the receiving end, typically in build_phase (since the value needs to exist before connect_phase or run_phase can use it) — the exact pattern every driver and monitor in this section has already used:

function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual fifo_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "Virtual interface not set for fifo_driver")
endfunction

get() returns a bit indicating success — checking it and failing loudly with `uvm_fatal (see Reporting & Messaging) turns a silent null virtual interface, which would otherwise surface as a confusing runtime crash much later, into an immediate, readable error at build time.

Wildcards work on set(), not on get()​

set()'s hierarchical-path argument is a glob pattern, matched against every component's actual path — "*" (every component), "env.agent*" (every component whose path starts with env.agent), and so on. get(), by contrast, doesn't itself perform wildcard matching — it's called from a specific component's own context (this) with a plain, literal path segment (often just "", meaning "this exact component"), and it succeeds if some earlier set()'s wildcard pattern happens to match that component's real, concrete path. The wildcard logic lives entirely on the set() side; get() is always a literal lookup from wherever it's called.

This is also why, from The Agent & Environment, setting is_active scoped too broadly or too narrowly matters — the exact same mechanism configures ordinary settings, not just interfaces:

// In a test's build_phase, before the environment is built:
uvm_config_db#(int)::set(this, "env.agent", "is_active", UVM_PASSIVE);
uvm_config_db#(bit)::set(this, "env.scoreboard", "coverage_enable", 1);

A set() targeting "env.agent*" will not be seen by a get() called from env itself — the wildcard scope has to structurally match the calling component's own path, not just look related to a human reader.

The most common pitfall​

get-before-set

uvm_config_db has no concept of "waiting" — if get() runs before the matching set() has executed, it simply fails, returning 0 (or silently leaving the variable at its default, if the return value isn't checked). Since build_phase runs top-down, this mostly resolves itself as long as set() calls happen either in testbench-top (before run_test() even starts phasing) or in a parent's build_phase before super.build_phase() hands control to children. The other common cause is a hierarchical-path typo in set()'s second argument — "agent.driver" vs. the component's actual instance name — which fails exactly the same way: silently, unless the return value is checked.

A third cause is easy to miss because nothing about it looks wrong: #(T) is part of what get() matches against, not just a type annotation on the variable being filled in. uvm_config_db#(int)::set(this, "env.agent", "is_active", UVM_PASSIVE) paired with a get() written as uvm_config_db#(bit [31:0])::get(this, "", "is_active", val) are asking for two different keys — int and bit [31:0] don't match as types, even though both could hold the same value — so the get() fails silently, for exactly the same reason a path typo does: the match has to be exact on every part of the key, not just look equivalent to a human reader.

When two set() calls target the same field​

Nothing stops two different set() calls from writing the same (path, field) combination with different values — a test's build_phase setting is_active for "env.agent*", followed by an even more specific set() from somewhere else targeting "env.agent0" with a different value. When that happens, the underlying resource database resolves it by precedence, with the most recently written value winning ties — practically, since build_phase runs top-down, a set() made later in that top-down walk (i.e. closer to, or inside, the component it targets) overrides one made earlier by a more distant ancestor. This is exactly why a test-level set() deliberately made broad ("*" or "env.agent*") can still be overridden for one specific instance by a later, more targeted set() — the mechanism doesn't error on the conflict or merge the two values, it simply keeps the latest one.

Debugging set/get mismatches​

+UVM_CONFIG_DB_TRACE, passed on the simulator command line with no code changes required, logs every uvm_config_db set() and get() call as it happens — including failed get() attempts, showing the exact path and field name each side actually used. When a get() is failing and the cause isn't obvious from reading the code, this is almost always faster than adding temporary `uvm_info calls around suspected set()/get() pairs — it shows both sides' actual resolved paths side by side, which is usually exactly where a mismatch turns out to be.

What's next​

With components built, connected, configured — and now with the tool to debug configuration itself — the next page covers how they report what's happening during simulation: `uvm_info, warnings, and errors.