Skip to content

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:

SystemVerilog — uvm_config_db set() signature
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

ArgumentTypeMeaning and Usage
cntxtuvm_componentThe 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_namestringA 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_namestringA string identifier — the lookup key. Must match the field_name in the corresponding get() call exactly, character for character. Case-sensitive.
valueT (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.
SystemVerilog — set() call patterns with explanations
// ── 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
);
endfunction

The Get Protocol — Retrieving Values

The complete get() signature mirrors set() — same four arguments, with value as an output:

SystemVerilog — uvm_config_db get() signature and usage
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 acceptable

Never 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:

SystemVerilog — how scope keys are constructed and matched
// ── 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 MATCH

COMPONENT 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

PriorityWhich 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().
2When 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.

SystemVerilog — complete virtual interface connection 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
endclass

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.

SystemVerilog — configuration object pattern
// ── 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;
endclass

Quick Reference

Patternset() callget() 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 agentset(null, "*.env.apb_agent", "cfg", cfg_h)get(this, "", "cfg", cfg)
Set from env to its direct childrenset(this, "drv", "cfg", cfg_h)get(this, "", "cfg", cfg) (in drv)
Check with default (no fatal)void'(get(this, "", "flag", flag_var))
SystemVerilog — config_db cheat sheet
// ── 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: HIT

Code 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

SystemVerilog — Beginner: Integer via config_db
`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 default

Example 2 — Intermediate: Virtual Interface via config_db

SystemVerilog — Intermediate: Virtual Interface Pattern
`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 interface

Example 3 — Verification: Configuration Object Pattern

SystemVerilog — Verification: Config Object with Multiple Fields
`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 object

Example 4 — Tricky: Scoping Wildcard vs Exact Path

SystemVerilog — Tricky: Wildcard Scope vs Exact Path Precedence
`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.

SystemVerilog — Bug 1: Type Mismatch
// ❌ 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.

SystemVerilog — Bug 2: Scope Mismatch
// ❌ 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.

SystemVerilog — config_db_demo.sv (Complete, Ready-to-Run)
// ================================================================
// 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_db is 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 (usually this); 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, call uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.*", "vif", dut_if) BEFORE run_test(). Using null as context and "uvm_test_top.*" makes it globally accessible to all descendants. Using null without run_test() prevents the interface from being set too late.

Best Practices — config_db Patterns That Production Teams Follow

RuleCorrect PatternWhy It Matters
Set before createCall set() before super.build_phase() and component creationComponents resolve config in their build_phase — if set() arrives after, it's too late
Use uvm_fatal on missing critical configif(!get(...)) uvm_fatal("CFG","...")`Silent null handle = crash deep in run_phase with misleading error
Match types exactlyset() and get() must use identical T parameterType mismatch = silent failure with no error message
Use wildcard for virtual interfaces from tb_topset(null, "uvm_test_top.*", "vif", ...)All descendant agents/drivers can reach the interface
Prefer config objects over individual fieldsOne set() with a config object vs many individual set() callsFewer set/get calls, related parameters travel together, easier to extend
Always run with +UVM_CONFIG_DB_TRACE in debugAdd to simulation command during developmentInstantly shows scope mismatches and type errors without code changes
Document the config schema in the agent headerComment: "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_object subclass
  • 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.

ConceptWhat It DoesWhen to Use
set(this, "child", "key", val)Stores value scoped to a specific child pathMost common: parent-to-child configuration
set(this, "*", "key", val)Stores value accessible to all descendantsBlanket configuration for an entire subtree
set(null, "uvm_test_top.*", "vif", ...)Stores from module scope, global reachVirtual interface connection from tb_top
get() returns 0Entry not found — type or scope mismatchUse +UVM_CONFIG_DB_TRACE to diagnose
Config object patternOne set() carries all related parametersAny 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.