In short: uvm_config_db is UVM's type-parameterized, hierarchy-aware registry that lets a parent component set configuration objects, virtual interfaces, and scalar knobs into a scope addressed by instance path, which any descendant later gets by name — decoupling who-configures from who-consumes, with wildcards for broadcasting to many instances at once.
Every serious UVM testbench has the same plumbing problem: the DUT's virtual interface is bound in the top-level module, but the driver that wiggles its pins lives four levels deep inside an agent inside an environment inside the test. Passing that handle down through constructors by hand would couple every layer to every other layer. uvm_config_db solves this with a single global, string-keyed, type-safe database scoped to the component hierarchy. It is arguably the most-used API in day-to-day verification, and interviewers lean on it precisely because a confident answer proves you understand UVM phasing and topology, not just syntax.
The mental model: a scoped key-value store
Think of uvm_config_db#(T) as an associative array whose key is a two-part string — a hierarchical scope plus a field name — and whose value is a handle of type T. The class is parameterized by T, so uvm_config_db#(virtual my_if) and uvm_config_db#(int) are distinct databases. A set writes an entry; a get reads it back. The elegance is that the scope is resolved against the UVM component tree, so a setter can target descendants it has never directly instantiated.
// Top module: publish the DUT interface for the whole test
initial begin
uvm_config_db#(virtual axi_if)::set(null, "uvm_test_top.env.m_agent.drv",
"vif", dut_if);
run_test("axi_write_test");
end
// Deep inside the driver's build_phase: retrieve it
if (!uvm_config_db#(virtual axi_if)::get(this, "", "vif", m_vif))
`uvm_fatal("NOVIF", "virtual interface not set for driver")
set at the top writes directly into the driver's scope; the driver reads it with a local get, bypassing every intermediate layer.set() and get() signatures, argument by argument
Both calls take four positional arguments: (cntxt, inst_name, field_name, value). The effective scope is the concatenation of cntxt's full hierarchical name with inst_name. When cntxt is null, the scope is treated as absolute from the root. The two idioms you will write 90% of the time:
| Call | cntxt | inst_name | Resulting scope |
|---|---|---|---|
| set from top | null | "uvm_test_top.env.*" | absolute path with wildcard |
| set from a parent | this | "m_agent.drv" | this.get_full_name() + ".m_agent.drv" |
| get in a child | this | "" | this.get_full_name() |
Type match is strict. A set of uvm_config_db#(int) is invisible to a get of uvm_config_db#(int unsigned) or uvm_config_db#(bit[31:0]). Mismatched parameterization is the single most common "I set it but get returns 0/null" bug — the entry exists, your query just looks in a different typed table.
Why get() returns a bit, and why it matters
Both get and the convenience get inside uvm_component return a bit: 1 if a matching entry was found, 0 otherwise. Always branch on it. A silent get that quietly leaves a handle null produces a null-pointer dereference thousands of cycles later in the run phase, with a stack trace that points at the dereference, not the missing configuration. Guarding every virtual-interface fetch with `uvm_fatal turns a late, confusing crash into an immediate, self-describing one — this discipline is exactly what distinguishes a maintained testbench from a demo.
Phasing: set before the consumer's build_phase
Configuration must be written before the component that reads it runs build_phase, because that is where handles are typically fetched and sub-components are constructed. UVM builds top-down, so a parent's build_phase runs before its children's — meaning a parent can safely set for its descendants in its own build_phase. Interface handles are the exception: they are set from the static initial block in the HDL top before run_test(), so they are already present when any component builds.
set into the typed store, then out to a later get — the two ends never reference each other directly.Wildcards: configuring many instances at once
The inst_name string supports glob-style wildcards, resolved with UVM's internal pattern matcher. Writing set(null, "uvm_test_top.env.*", "vif", dut_if) targets every component directly under env whose remaining path matches, while "*.drv" hits every driver anywhere in the tree. This is how you push one recording policy, one coverage-enable bit, or one common interface onto an array of ten agents without ten calls.
Precedence rule: when several sets match the same get, the one from the higher component in the hierarchy wins, and among equal heights the later call wins. A base test's broad wildcard set is therefore cleanly overridden by a derived test's more specific set — the mechanism behind layered test configuration. It pairs naturally with the UVM factory and type/instance overrides, where the factory swaps which class is built and the config_db tunes how that class behaves.
set calls match one get, the higher component wins; at equal height the later call wins.Config objects vs. scalar knobs
For a handful of integers you can set each directly, but production testbenches bundle related knobs into a uvm_object-derived configuration class (e.g. axi_agent_cfg carrying is_active, has_coverage, and the virtual interface) and set that single object into the agent's scope. The agent gets the object once and distributes its fields to its driver, monitor, and sequencer. This keeps the number of database entries small and gives you one typed, randomizable, printable place for all agent policy — the pattern Synopsys VCS/Verdi, Cadence Xcelium, and Siemens Questa reference flows all recommend.
When you are architecting a reusable VIP or a full SoC environment, treating the config object as a first-class, constraint-driven data structure is what makes the testbench portable across projects — a service Avecas design-services teams apply when delivering verification IP to clients. For the full agent-building flow, see our walkthrough of UVM agent architecture and the active/passive pattern.
Ready to build a real UVM environment from interface to scoreboard? The CourseTron Verification & UVM track walks you through config_db, the factory, sequences, and functional coverage on an industry-grade AXI testbench.
Frequently asked questions
What is the difference between uvm_config_db and uvm_resource_db?
uvm_config_db is a thin, hierarchy-aware convenience layer built on top of uvm_resource_db. The config_db interprets the scope as a component path and applies the "higher-in-hierarchy wins" precedence; the resource_db is the lower-level, scope-agnostic store. In day-to-day work you almost always use uvm_config_db.
Why does my get() return 0 even though I called set()?
Three usual causes: the type parameter T differs between set and get, the scope string does not resolve to the getter's path, or the set ran after the getter's build_phase. Dump the database with +UVM_CONFIG_DB_TRACE to see exactly which entries exist and where.
Can a child component configure its parent?
Technically the database is global so a child can write an entry, but the parent will already have passed its build_phase, so the value usually arrives too late to be read. Configuration is meant to flow top-down; a child pushing upward is an anti-pattern.
How do I debug which set() won a given get()?
Run with the plusarg +UVM_CONFIG_DB_TRACE. UVM then prints every set and get with scope, field, type, and the winning entry, making precedence conflicts obvious.
References & Further Reading
- Accellera — Universal Verification Methodology (UVM) standard and reference implementation
- IEEE 1800.2 — Standard for UVM Language Reference Manual
- Siemens Verification Academy — UVM cookbook and config_db topics
- General SystemVerilog/UVM testbench reference documentation
- Wikipedia — Universal Verification Methodology overview