uvm_config_db Architecture
Architecture, get/set protocol, scoping rules, virtual interface pattern, configuration objects.
UVM Fundamentals · Module 8
The Problem config_db Solves
A driver needs a virtual interface handle to drive the DUT. The interface is instantiated in tb_top. The driver lives at uvm_test_top.env.apb_agent.drv — four levels deep inside the hierarchy. How does the virtual interface get from tb_top to the driver without a direct reference that would couple them together?
- Option A — Constructor Arguments — Pass the interface through new(name, parent, vif) all the way down. Every intermediate class must carry the argument, know about the interface type, and forward it. Adding a second interface breaks everything.
- Option B — Global Variables — Declare apb_if vif as a global or package-level variable. Works for one interface, fails for multiple instances. Not test-specific. Kills any possibility of reuse.
- Option C — uvm_config_db — Store the virtual interface handle in config_db from tb_top. The driver retrieves it by name in its build_phase(). Neither side knows about the other. Hierarchy changes don't break the connection.
config_db is a global hierarchical key-value store. It decouples the provider of configuration data from the consumers, using path-based scoping to ensure each component gets exactly the data intended for it.
Architecture — What config_db Actually Is
uvm_config_db #(T) is a parameterised class. The type parameter T is the type of value being stored. Every distinct type has its own separate database — a uvm_config_db #(int) entry and a uvm_config_db #(virtual apb_if) entry with the same key are completely independent.
SET (stores data)tb_top (module)cntxt = null (not a component)base_testcntxt = thismy_envcntxt = thisset()set()set()uvm_config_db (global singleton)TYPESCOPE (set path)KEY + VALUEvirtual apb_ifuvm_test_top."vif" → apb_vif_hintuvm_test_top."num_txns" → 200apb_agent_cfg*.apb_agent"cfg" → cfg_obj... (parameterised per type)Type T is part of the key.config_db #(int) ≠ config_db #(string) for the same key string.GET (retrieves data)apb_drivercntxt = this inst=""apb_monitorcntxt = this inst=""apb_agentcntxt = this inst=""get()get()get()set() — storesget() — retrieves
Figure 1 — config_db architecture. A parameterised global singleton stores (type, scope, key, value) tuples. set() deposits data from any component or module. get() retrieves by matching scope against the getter's full path.
There are four pieces of information in every database entry: the type (parameterised T), the scope (derived from who set it and the inst_name pattern), the key (a string field_name), and the value itself. A get() succeeds only when all four match.
The Set Protocol — Storing Values
The complete signature is:
static function void set(
uvm_component cntxt, // who is setting (null = from module scope)
string inst_name, // hierarchical path pattern (wildcards OK)
string field_name, // string key — must match get() exactly
T value // the data to store (type T = parameterised)
);Understanding Each Argument
| Argument | Type | Meaning and Usage |
|---|---|---|
| cntxt | uvm_component | The component doing the setting. Its get_full_name() path becomes the root of the scope. Pass null when calling from a module (tb_top) since modules are not uvm_components. |
| inst_name | string | A path pattern appended to cntxt's path. Determines which components can retrieve the value. Supports * wildcards. "uvm_test_top.*" (from null cntxt) makes the value visible to all components under the test root. |
| field_name | string | A string identifier — the lookup key. Must match the field_name in the corresponding get() call exactly, character for character. Case-sensitive. |
| value | T (parameterised) | The data to store. Type T is part of the key — a config_db #(int) set and a config_db #(string) get with the same field_name will NOT match. |
// ── From tb_top (module, NOT a component — use null cntxt) ───────────
initial begin
// Scope: "" + "uvm_test_top.*" = "uvm_test_top.*"
// Visible to: every component under uvm_test_top
uvm_config_db#(virtual apb_if)::set(
null, // cntxt: null because tb_top is a module
"uvm_test_top.*", // inst_name: wildcard — all components under root
"apb_vif", // field_name: the lookup key
apb_vif // value: the interface instance
);
run_test();
end
// ── From base_test (component — use 'this') ───────────────────────────
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Scope: "uvm_test_top" + "*.apb_agent" = "uvm_test_top.*.apb_agent"
// Visible to: any apb_agent component anywhere under the test
uvm_config_db#(int)::set(
this, // cntxt: 'this' = uvm_test_top (the test component)
"*.apb_agent", // inst_name: any apb_agent under this test
"num_txns", // field_name
200 // value
);
endfunction
// ── From my_env (narrower scope) ──────────────────────────────────────
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Scope: "uvm_test_top.env" + "apb_agent_0.*"
// Visible to: ONLY components under apb_agent_0 in THIS env
uvm_config_db#(bit)::set(
this, "apb_agent_0.*", "is_active", 1'b1
);
endfunctionThe Get Protocol — Retrieving Values
The complete get() signature mirrors set() — same four arguments, with value as an output:
static function bit get(
uvm_component cntxt, // the component doing the getting ('this')
string inst_name, // usually "" — appended to cntxt's path
string field_name, // must exactly match the set() field_name
ref T value // output — populated on success
); // returns 1 if found, 0 if not found — ALWAYS check the return value
// ── Standard get() pattern with mandatory error handling ─────────────
class apb_driver extends uvm_driver #(apb_seq_item);
`uvm_component_utils(apb_driver)
virtual apb_if vif;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// cntxt = this (driver at "uvm_test_top.env.apb_agent.drv")
// inst_name = "" (no additional path — look up for 'this' component)
// field_name must match what was set() exactly
if (!uvm_config_db#(virtual apb_if)::get(this, "", "apb_vif", vif))
`uvm_fatal("CFG",
"apb_vif not found in config_db — check tb_top set() call")
// If get() returned 0, uvm_fatal stops simulation — no null pointer crash later
endfunction
endclass
// ── Getting a simple integer ──────────────────────────────────────────
int num_txns = 100; // default value
void'(uvm_config_db#(int)::get(this, "", "num_txns", num_txns));
// void'() suppresses "return value ignored" warning when absence is acceptableNever Ignore the get() Return Value for Critical Resources
uvm_config_db::get() returns 0 silently when the entry is not found. If you ignore that return value and proceed, the output variable keeps its uninitialized value — for a virtual interface, that is null. The first time the driver tries to drive a signal through the null handle, the simulator crashes with a null-pointer access error — nowhere near the actual cause. Always use ``uvm_fatal` on get() failure for interfaces and objects that the component cannot function without.
Scoping Rules — How the Lookup Algorithm Works
When a component calls get(this, "", "apb_vif", vif), the config_db constructs a lookup key from:
// ── SET side: scope key construction ─────────────────────────────────
// set(cntxt, inst_name, field_name, value)
// Scope key = cntxt.get_full_name() + "." + inst_name
// = "" + "uvm_test_top.*" (null cntxt has empty path)
// = "uvm_test_top.*"
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.*", "apb_vif", vif_h);
// ── GET side: lookup key construction ────────────────────────────────
// get(cntxt, inst_name, field_name, value)
// Lookup key = cntxt.get_full_name() + "." + inst_name (if inst_name not empty)
// = "uvm_test_top.env.apb_agent.drv" + "." + ""
// = "uvm_test_top.env.apb_agent.drv"
uvm_config_db#(virtual apb_if)::get(this, "", "apb_vif", vif);
// ── Matching: does "uvm_test_top.*" cover "uvm_test_top.env.apb_agent.drv"?
// Pattern: "uvm_test_top.*"
// Key: "uvm_test_top.env.apb_agent.drv"
// The wildcard * matches ".env.apb_agent.drv" → MATCH → get() returns 1 ✓
// ── Narrower scope example ────────────────────────────────────────────
// If set was called as:
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb_agent_0.*", "apb_vif", vif0);
// Scope: "uvm_test_top.env.apb_agent_0.*"
// Covers: any component under apb_agent_0 only
// drv under apb_agent_1 → "uvm_test_top.env.apb_agent_1.drv" → NO MATCHCOMPONENT HIERARCHYuvm_test_topmy_envapb_agent_0drvmonapb_agent_1drvmonSCOPING ZONES — who can get() each entryZone A — set(null, "uvm_test_top.", "apb_vif", vif_h)Scope covers: every component under uvm_test_topAll drivers and monitors can get("apb_vif") → returns vif_hZone B — set(env, "apb_agent_0.", "special_cfg", cfg)Scope: "uvm_test_top.env.apb_agent_0."apb_agent_0.drv/mon → get("special_cfg") SUCCEEDSZone C — apb_agent_1.drv tries get("special_cfg")Lookup key: "uvm_test_top.env.apb_agent_1.drv"Does not match ".apb_agent_0.*" → get() returns 0 (MISS)Priority rule: More specific scope wins over broader scope. Later set() for same key wins over earlier set().If both Zone A and Zone B match a getter: the most recently set() entry for the same (T, field_name) wins. Figure 2 — Scoping zones. Zone A covers all components (wildcard to root). Zone B covers only apb_agent_0 children (narrow scope). Zone C shows a missed get() — agent_1's driver is outside Zone B's scope.
Scope Resolution Priority
| Priority | Which Entry Wins |
|---|---|
| 1 (highest) | The most recently registered entry that matches all four of: type T, scope pattern, field_name, and getter path. Later set() for the same key beats earlier set(). |
| 2 | When multiple entries match with equal specificity, the one with a more specific (longer, more precise) scope pattern wins. |
| 3 (lowest) | A broader wildcard scope (e.g., "uvm_test_top.*") is used only when no more specific scope matches the getter's path. |
The Virtual Interface Pattern — The Most Important Use Case
Passing virtual interface handles from the top module to drivers and monitors is the single most universal application of config_db. Every UVM testbench with a real DUT connection uses this pattern.
// ────────────────────────────────────────────────────────────────────
// tb_top.sv — the module that owns the interface
// ────────────────────────────────────────────────────────────────────
module tb_top;
`include "uvm_macros.svh"
import uvm_pkg::*;
import test_pkg::*;
// Interface instances
apb_if apb_vif(); // creates the interface
my_dut dut (.apb(apb_vif)); // DUT connection
initial begin
// Store handle BEFORE run_test() — run_test() triggers build_phase
uvm_config_db#(virtual apb_if)::set(
null, // null because tb_top is a module, not uvm_component
"uvm_test_top.*", // visible to ALL components under the test root
"apb_vif", // the key — exactly this string must be used in get()
apb_vif // the interface instance — modport not needed here
);
run_test(); // UVM takes over — build_phase cascade begins
end
endmodule
// ────────────────────────────────────────────────────────────────────
// apb_driver.sv — the component that needs the interface
// ────────────────────────────────────────────────────────────────────
class apb_driver extends uvm_driver #(apb_seq_item);
`uvm_component_utils(apb_driver)
virtual apb_if vif; // will hold the interface handle after build_phase
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Retrieve the interface — fatal if not found
if (!uvm_config_db#(virtual apb_if)::get(
this, // 'this' = drv at "uvm_test_top.env.apb_agent.drv"
"", // no additional path — lookup for 'this' component
"apb_vif", // must match the set() key exactly
vif)) // populated on success
`uvm_fatal("CFG", "apb_vif not found — check tb_top set() call")
endfunction
task run_phase(uvm_phase phase);
// vif is now valid — safe to drive signals
@(posedge vif.clk);
vif.paddr = req.addr;
vif.pwdata = req.data;
endtask
endclassConfiguration Objects — Grouping Related Settings
Passing individual integers and bits through config_db becomes unmanageable when a component has ten configuration parameters. The clean solution: create a configuration class and pass the whole object as a single config_db entry.
// ── Step 1: Define the configuration class ────────────────────────────
class apb_agent_config extends uvm_object;
`uvm_object_utils(apb_agent_config) // register with factory (good practice)
// Interface handle — part of the config object
virtual apb_if vif;
// Behavioural parameters
int num_txns = 100;
bit is_active = 1; // 1 = active (driver+monitor), 0 = passive (monitor only)
int max_wait_cycles = 20;
function new(string name = "apb_agent_config");
super.new(name);
endfunction
endclass
// ── Step 2: Create and set from tb_top or test ────────────────────────
initial begin
apb_agent_config cfg = new("cfg"); // or use factory create
cfg.vif = apb_vif; // attach the virtual interface
cfg.num_txns = 500;
cfg.is_active = 1;
cfg.max_wait_cycles = 10;
uvm_config_db#(apb_agent_config)::set(
null, "uvm_test_top.env.apb_agent", "cfg", cfg
);
run_test();
end
// ── Step 3: Retrieve in the agent's build_phase ───────────────────────
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_agent_config cfg;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(apb_agent_config)::get(this, "", "cfg", cfg))
`uvm_fatal("CFG", "apb_agent_config not found")
// cfg is now populated — pass it down to driver and monitor
drv = apb_driver::type_id::create("drv", this);
uvm_config_db#(apb_agent_config)::set(this, "drv", "cfg", cfg);
endfunction
apb_driver drv;
endclassQuick Reference
| Pattern | set() call | get() call |
|---|---|---|
| Virtual interface (from module) | set(null, "uvm_test_top.*", "vif", vif_h) | get(this, "", "vif", vif) |
| Integer from test (global) | set(this, "*", "num_txns", 200) | get(this, "", "num_txns", n) |
| Config object to specific agent | set(null, "*.env.apb_agent", "cfg", cfg_h) | get(this, "", "cfg", cfg) |
| Set from env to its direct children | set(this, "drv", "cfg", cfg_h) | get(this, "", "cfg", cfg) (in drv) |
| Check with default (no fatal) | — | void'(get(this, "", "flag", flag_var)) |
// ── The four arguments ────────────────────────────────────────────────
// set(cntxt, inst_name, field_name, value)
// get(cntxt, inst_name, field_name, value) ← returns bit (1=found)
//
// cntxt : who is setting/getting. null from module. 'this' from component.
// inst_name : path suffix appended to cntxt's full_name. Wildcards OK.
// field_name: string key. Case-sensitive. Must match exactly.
// value : the data (type T = the parameterised class argument)
// ── Scope key formula ─────────────────────────────────────────────────
// set scope = cntxt.get_full_name() + "." + inst_name
// get key = cntxt.get_full_name() + "." + inst_name
// Match if: the set scope pattern covers the get key path
// ── Essential rules ───────────────────────────────────────────────────
// 1. set() before run_test() for interfaces (needed in build_phase)
// 2. Always check get() return value for critical data
// 3. Type T in set() and get() must be IDENTICAL
// 4. field_name must match EXACTLY — case, spelling, no spaces
// 5. For debug: add +UVM_CONFIG_DB_TRACE to simulation command
// ── Debug trace output format ─────────────────────────────────────────
// vsim +UVM_CONFIG_DB_TRACE work.tb_top
//
// UVM_INFO: SET uvm_config_db #(virtual apb_if) ...
// by: null context: uvm_test_top.* field: apb_vif
// UVM_INFO: GET uvm_config_db #(virtual apb_if) ...
// by: uvm_test_top.env.apb_agent.drv field: apb_vif result: HITCode Examples — config_db From Simple to Production
config_db is one of those mechanisms that looks simple in isolation but hides surprising behavior when components are deep in the hierarchy or when multiple tests share the same testbench. These four examples build from the minimal working pattern to the production configuration object pattern used on large SoC projects.
Example 1 — Beginner: Passing an Integer to a Component
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Consumer: component that reads config ──────────────────────────
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
int num_checks = 10; // default value
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Retrieve config — override default if found
if(!uvm_config_db#(int)::get(this, "", "num_checks", num_checks))
`uvm_info("CFG", "num_checks not found, using default=10", UVM_MEDIUM)
else
`uvm_info("CFG", $sformatf("num_checks=%0d", num_checks), UVM_MEDIUM)
endfunction
endclass
// ── Provider: test that sets config ────────────────────────────────
class basic_cfg_test extends uvm_test;
`uvm_component_utils(basic_cfg_test)
my_monitor mon;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Set BEFORE creating the component so it sees the value in its build_phase
uvm_config_db#(int)::set(this, "mon", "num_checks", 50);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module basic_cfg_top;
initial run_test("basic_cfg_test");
endmodule
// Expected Output:
// UVM_INFO [CFG] num_checks=50 ← config_db value overrides defaultExample 2 — Intermediate: Virtual Interface via config_db
`include "uvm_macros.svh"
import uvm_pkg::*;
// Interface definition (normally in a separate .sv file) ──────────
interface apb_if (input logic clk);
logic[31:0] paddr;
logic[31:0] pwdata;
logic pwrite;
logic psel;
logic penable;
logic pready;
endinterface
// Driver reads vif from config_db ──────────────────────────────────
class apb_driver extends uvm_driver#(uvm_sequence_item);
`uvm_component_utils(apb_driver)
virtual apb_if vif;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Fatal if vif not set — driver cannot function without it
if(!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
`uvm_fatal("CFG", "Virtual interface 'vif' not set in config_db")
else
`uvm_info("CFG", "vif connected successfully", UVM_MEDIUM)
endfunction
endclass
// Top module wires interface and sets it in config_db ──────────────
module apb_tb_top;
logic clk;
initial clk = 0;
always #5 clk = ~clk;
apb_if dut_if(.clk(clk)); // interface instance
initial begin
// Set BEFORE run_test — driver needs it in build_phase
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.*", "vif", dut_if);
run_test("apb_test");
end
endmodule
// Key points:
// 1. null context + wildcard path = accessible from any component
// 2. set() called BEFORE run_test() so it's available in build_phase
// 3. uvm_fatal ensures the driver can't silently run with a null interfaceExample 3 — Verification: Configuration Object Pattern
`include "uvm_macros.svh"
import uvm_pkg::*;
// Config object: one object, multiple parameters ───────────────────
class apb_agent_config extends uvm_object;
`uvm_object_utils_begin(apb_agent_config)
`uvm_field_int(is_active, UVM_ALL_ON)
`uvm_field_int(num_txns, UVM_ALL_ON)
`uvm_field_int(bus_width, UVM_ALL_ON)
`uvm_object_utils_end
bit is_active = 1; // 1=active agent, 0=passive monitor only
int num_txns = 100; // number of transactions to generate
int bus_width = 32; // APB bus width in bits
function new(string n="apb_agent_config"); super.new(n); endfunction
endclass
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_agent_config cfg;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(apb_agent_config)::get(this, "", "cfg", cfg))
`uvm_fatal("CFG", "apb_agent_config not found")
`uvm_info("CFG", $sformatf(
"active=%0b txns=%0d width=%0d",
cfg.is_active, cfg.num_txns, cfg.bus_width), UVM_MEDIUM)
endfunction
endclass
class cfg_obj_test extends uvm_test;
`uvm_component_utils(cfg_obj_test)
apb_agent agent;
apb_agent_config cfg;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
cfg = apb_agent_config::type_id::create("cfg");
cfg.is_active = 1;
cfg.num_txns = 250;
cfg.bus_width = 32;
uvm_config_db#(apb_agent_config)::set(this, "agent", "cfg", cfg);
super.build_phase(phase);
agent = apb_agent::type_id::create("agent", this);
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module cfg_obj_top;
initial run_test("cfg_obj_test");
endmodule
// Expected Output:
// [CFG] active=1 txns=250 width=32 ← all three fields from config objectExample 4 — Tricky: Scoping Wildcard vs Exact Path
`include "uvm_macros.svh"
import uvm_pkg::*;
class scope_consumer extends uvm_component;
`uvm_component_utils(scope_consumer)
int timeout;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(int)::get(this, "", "timeout", timeout));
$display(" [%s] timeout=%0d", get_full_name(), timeout);
endfunction
endclass
class scope_test extends uvm_test;
`uvm_component_utils(scope_test)
scope_consumer c1, c2;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Wildcard: applies to all children
uvm_config_db#(int)::set(this, "*", "timeout", 100);
// Exact path: overrides wildcard for c2 specifically
uvm_config_db#(int)::set(this, "c2", "timeout", 500);
super.build_phase(phase);
c1 = scope_consumer::type_id::create("c1", this);
c2 = scope_consumer::type_id::create("c2", this);
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module scope_top;
initial run_test("scope_test");
endmodule
// Expected Output:
// [uvm_test_top.c1] timeout=100 ← wildcard applied
// [uvm_test_top.c2] timeout=500 ← exact path overrides wildcard
//
// Key insight: More specific path wins. "c2" is more specific than "*".Common Bugs — config_db Failures That Look Like Something Else
config_db bugs are uniquely frustrating because they manifest as null pointer crashes, wrong values deep in run_phase, or components that silently use default values without ever warning you. The root cause is always set/get mismatch — but finding which mismatch requires knowing exactly what to look for.
⚠️ Bug 1 — Type Mismatch: set() and get() Use Different Types
Symptom: get() returns 0 (failure) even though set() was definitely called with the correct field name. The component uses its default value. No error message.
Root cause: The T parameter in uvm_config_db#(T) must match between set() and get(). If set() uses #(int) and get() uses #(integer) or #(bit[31:0]), they resolve to different database entries and the get() finds nothing.
// ❌ WRONG: type mismatch between set and get
// In test:
uvm_config_db#(int)::set(this, "mon", "timeout", 100); // stores as 'int'
// In monitor:
bit[31:0] timeout;
uvm_config_db#(bit[31:0])::get(this, "", "timeout", timeout);
// get() returns 0 — different type = different entry in database
// timeout remains at its initialization value, no warning
// ✓ CORRECT: matching types
uvm_config_db#(int)::set(this, "mon", "timeout", 100);
int timeout; // ← same type as set()
if(!uvm_config_db#(int)::get(this, "", "timeout", timeout))
`uvm_error("CFG", "timeout not found in config_db")
// Debug: add +UVM_CONFIG_DB_TRACE to simulation command
// It shows every set/get call with types and paths — type mismatches are obvious⚠️ Bug 2 — Wrong Context or Path: get() Scope Doesn't Match set() Scope
Symptom: get() returns 0 even though types match. The component receives a default value. Happens most often when a component is deeply nested or when the inst_path is wrong.
Root cause: UVM resolves get() by building a scope from the component's hierarchy: get_full_name() + "." + inst_name. If the set() scope doesn't match this computed string, get() finds nothing.
// ❌ WRONG: path doesn't reach the deep component
// Testbench topology: test → env → agent → driver
// Component full path: "uvm_test_top.env.agent.drv"
// In test.build_phase:
uvm_config_db#(int)::set(this, "drv", "speed", 100);
// set scope = "uvm_test_top.drv" ← WRONG, driver is at "...env.agent.drv"
// ✓ FIX 1: Use wildcard to reach any level
uvm_config_db#(int)::set(this, "env.agent.drv", "speed", 100);
// ✓ FIX 2: Global wildcard — reaches all components named "drv"
uvm_config_db#(int)::set(null, "uvm_test_top.*", "speed", 100);
// Debug: print the consumer's path INSIDE get():
// `uvm_info("CFG", $sformatf("my path: %s", get_full_name()), UVM_DEBUG)
// Use +UVM_CONFIG_DB_TRACE to see scope resolution in real time🔍 Debug Insight — The +UVM_CONFIG_DB_TRACE Lifesaver
Adding +UVM_CONFIG_DB_TRACE to your simulation command prints every set() and get() call with full type and scope information. When get() fails, you immediately see the scope mismatch. This single plusarg resolves 90% of config_db bugs without any code changes:
vsim ... +UVM_CONFIG_DB_TRACE work.tb_top # Example trace output: # UVM_INFO: set(uvm_test_top, "mon", "timeout", int, 100) # UVM_INFO: get(uvm_test_top.mon, "", "timeout", int) → MATCH: 100 # UVM_INFO: get(uvm_test_top.mon, "", "speed", int) → MISS (not found)
Ready-to-Run: Complete config_db Demo
This single-file testbench demonstrates all major config_db patterns: integer, string, wildcard scope, exact path override, and the configuration object pattern. Save as config_db_demo.sv and run it.
// ================================================================
// config_db_demo.sv — config_db Architecture Demonstration
// Covers: integer, string, scope, wildcard, config object
//
// Compile (Questa):
// vlog -sv -L uvm_1_2 config_db_demo.sv
// vsim -c -L uvm_1_2 config_db_demo_top -do "run -all; quit"
//
// Compile (VCS):
// vcs -sverilog -ntb_opts uvm-1.2 config_db_demo.sv && ./simv
//
// Compile (Xcelium):
// xrun -sv -uvm config_db_demo.sv
//
// Debug mode (any simulator):
// Add +UVM_CONFIG_DB_TRACE to see all set/get operations
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Config object ──────────────────────────────────────────────────
class agent_cfg extends uvm_object;
`uvm_object_utils(agent_cfg)
int max_txns = 100;
string protocol = "APB";
bit is_active = 1;
function new(string n="agent_cfg"); super.new(n); endfunction
endclass
// ── Generic consumer component ─────────────────────────────────────
class demo_comp extends uvm_component;
`uvm_component_utils(demo_comp)
int num_pkts = 10; // integer config
string mode = "SLOW"; // string config
agent_cfg cfg; // object config
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Get all three config items
void'(uvm_config_db#(int)::get(this, "", "num_pkts", num_pkts));
void'(uvm_config_db#(string)::get(this, "", "mode", mode));
void'(uvm_config_db#(agent_cfg)::get(this, "", "cfg", cfg));
$display(" [%s] num_pkts=%0d mode=%s", get_name(), num_pkts, mode);
if(cfg != null)
$display(" [%s] cfg: max_txns=%0d protocol=%s active=%0b",
get_name(), cfg.max_txns, cfg.protocol, cfg.is_active);
else
$display(" [%s] cfg: not set", get_name());
endfunction
endclass
// ── Test: sets different configs for two components ─────────────────
class full_demo_test extends uvm_test;
`uvm_component_utils(full_demo_test)
demo_comp c_alpha, c_beta;
agent_cfg cfg_a;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// ── Integer: wildcard applies to all children ──────────
uvm_config_db#(int)::set(this, "*", "num_pkts", 50);
// ── String: per-component exact path ───────────────────
uvm_config_db#(string)::set(this, "c_alpha", "mode", "FAST");
uvm_config_db#(string)::set(this, "c_beta", "mode", "SAFE");
// ── Config object: only c_alpha gets one ───────────────
cfg_a = agent_cfg::type_id::create("cfg_a");
cfg_a.max_txns = 200;
cfg_a.protocol = "AXI";
cfg_a.is_active = 1;
uvm_config_db#(agent_cfg)::set(this, "c_alpha", "cfg", cfg_a);
super.build_phase(phase);
c_alpha = demo_comp::type_id::create("c_alpha", this);
c_beta = demo_comp::type_id::create("c_beta", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
phase.drop_objection(this);
endtask
endclass
module config_db_demo_top;
initial run_test("full_demo_test");
endmodule
// ================================================================
// Expected Output:
//
// [c_alpha] num_pkts=50 mode=FAST
// [c_alpha] cfg: max_txns=200 protocol=AXI active=1
//
// [c_beta] num_pkts=50 mode=SAFE
// [c_beta] cfg: not set
//
// Analysis:
// num_pkts=50: wildcard "*" set applies to both
// mode=FAST/SAFE: exact paths set different values per component
// cfg: only c_alpha received it; c_beta gets null → "not set"
// ================================================================Interview Questions — config_db Questions From Real Interviews
Beginner Level
- Q1 What is uvm_config_db and why do we use it instead of passing values through constructors? Answer:
uvm_config_dbis UVM's global key-value database for passing configuration from tests to components without creating direct references between them. We use it instead of constructors because: (1) Components should be reusable across projects — hardcoding configuration in constructors ties them to specific tests; (2) Deep hierarchies would require passing values through many intermediate layers; (3) Tests should be able to change component behavior without modifying VIP source code. config_db creates a clean separation between test configuration and component implementation. - Q2 What are the four arguments to uvm_config_db::set() and what does each do? Answer:
set(context, inst_name, field_name, value). (1) context — the component doing the setting (usuallythis); determines the base scope; (2) inst_name — path relative to context that determines which components can retrieve this value; use"*"for wildcard; (3) field_name — the key string, must match exactly in get(); (4) value — the data to store, type must match the template parameter T. The scope for resolution is built by combining context.get_full_name() + "." + inst_name.
Intermediate Level
- Q3 get() returns 0 (failure) even though you can see set() was called. List three possible causes. Answer: (1) Type mismatch: set() used
#(int)but get() uses#(integer)or vice versa — different template parameters create different database entries. (2) Scope mismatch: the inst_name in set() doesn't reach the component calling get() — component's full path doesn't match the computed scope. (3) Timing issue: set() was called after the component's build_phase already executed — config_db resolution happens at get() call time, and if build_phase ran before set() was called, the value doesn't exist yet. Always call set() before creating the component (before super.build_phase()).
Senior / Architect Level
- Q4 Both a test and a base_test set the same config key. Which value does the component receive and why? Answer: The most recently registered entry with the most specific scope wins. UVM resolves config_db by walking the hierarchy to find the best match — "best" means the most specific path that still covers the requesting component. If both entries have the same specificity (same path pattern), the one set last wins because config_db uses a FIFO with last-write-wins semantics for same-specificity entries. In practice: the derived test's set() should be called after super.build_phase() inherits the base_test's set(), ensuring the derived test can override the base. This is the standard pattern for test inheritance with config_db.
- Q5 You're passing a virtual interface via config_db but the driver gets a null handle. You confirmed the interface is set in tb_top. What's the most common cause and fix? Answer: The most common cause is that
run_test()was called before the virtual interface was set in config_db, or the interface was set with a scope that doesn't cover the driver's full path. Fix: in tb_top, calluvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.*", "vif", dut_if)BEFORErun_test(). Usingnullas context and"uvm_test_top.*"makes it globally accessible to all descendants. Usingnullwithoutrun_test()prevents the interface from being set too late.
Best Practices — config_db Patterns That Production Teams Follow
| Rule | Correct Pattern | Why It Matters |
|---|---|---|
| Set before create | Call set() before super.build_phase() and component creation | Components resolve config in their build_phase — if set() arrives after, it's too late |
| Use uvm_fatal on missing critical config | if(!get(...)) uvm_fatal("CFG","...")` | Silent null handle = crash deep in run_phase with misleading error |
| Match types exactly | set() and get() must use identical T parameter | Type mismatch = silent failure with no error message |
| Use wildcard for virtual interfaces from tb_top | set(null, "uvm_test_top.*", "vif", ...) | All descendant agents/drivers can reach the interface |
| Prefer config objects over individual fields | One set() with a config object vs many individual set() calls | Fewer set/get calls, related parameters travel together, easier to extend |
| Always run with +UVM_CONFIG_DB_TRACE in debug | Add to simulation command during development | Instantly shows scope mismatches and type errors without code changes |
| Document the config schema in the agent header | Comment: "Requires config_db #(agent_cfg) at path ".*"" | Prevents integration errors when teams share VIPs |
💡 Pro Tip — The Config Object Pattern Scales Better
Early-stage testbenches use individual set() calls for each parameter. As the project grows, this creates maintenance chaos — adding one parameter requires updating every test. The config object pattern solves this permanently:
- Define all agent parameters in one
uvm_objectsubclass - Base test creates the config object with defaults
- Derived tests only modify what they need before passing it
- Adding a new parameter? Update the config class and base test only — derived tests automatically inherit the new field with its default value
Summary — config_db Is the Configuration Bus of Your Testbench
Every time a driver needs a virtual interface, a monitor needs a timeout value, or a scoreboard needs to know the bus width — config_db is the answer. It separates test configuration from component implementation, which is what makes VIPs reusable across projects without source modification.
| Concept | What It Does | When to Use |
|---|---|---|
| set(this, "child", "key", val) | Stores value scoped to a specific child path | Most common: parent-to-child configuration |
| set(this, "*", "key", val) | Stores value accessible to all descendants | Blanket configuration for an entire subtree |
| set(null, "uvm_test_top.*", "vif", ...) | Stores from module scope, global reach | Virtual interface connection from tb_top |
| get() returns 0 | Entry not found — type or scope mismatch | Use +UVM_CONFIG_DB_TRACE to diagnose |
| Config object pattern | One set() carries all related parameters | Any agent/component with 3+ parameters |
- 1 Set before the component builds. config_db is resolved at get() call time, which happens in build_phase. If set() hasn't been called yet, get() finds nothing. Always set before super.build_phase() or before run_test().
- 2 Type mismatch is the silent killer. set() and get() must use identical template parameters. A wrong type creates a completely separate database entry and get() never finds it. No error, no warning.
- 3 +UVM_CONFIG_DB_TRACE is mandatory for debug. One simulation flag shows every set/get operation with full type and scope information. Use it always during development. Gate it behind a plusarg in regression.