Skip to content

UVM

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.

uvm_config_db architecture diagram
uvm_config_db architecture diagram

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Scoping rules visualization
Scoping rules visualization

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

Two competing set() calls are resolved by a precedence number, not by pattern specificity and not by ordering. Getting this backwards is the single most common architectural misunderstanding of config_db, because it inverts the rule that makes top-down configuration work at all.

During build_phase, precedence is derived from the hierarchy depth of the setting context, computed as uvm_resource_base::default_precedence - cntxt.get_depth(). A shallower context yields a larger number, so:

A set() from higher in the hierarchy beats a set() from lower in the hierarchy — regardless of which ran first, and regardless of which pattern is more specific.

That is the entire basis of layered configuration. The environment installs a default; the test overrides it; the test wins because it is above the environment, not because it ran later.

SituationWhich entry winsWhy
Test set() vs env set(), same key, both in build_phasethe testshallower context ⇒ higher precedence
Two set() calls from the same context in build_phasethe later oneequal precedence ⇒ last write wins
Any set() calls made after build timethe later oneall use default_precedence ⇒ last-wins semantics
A broad wildcard vs a narrow path, set from the same contextthe later onespecificity is not part of the resolution

Because the depth-based rule applies only during build time, a configuration written in run_phase behaves differently from the same configuration written in build_phase: after build, every entry carries the default precedence and the last writer wins outright. Configuration that must be layered belongs in build_phase.

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ────────────────────────────────────────────────────────────────────
// 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ 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)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ================================================================
// 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"
// ================================================================

Runnable Proof — Four get() Calls, One of Them Deliberately Broken

Reading about type mismatch and scope mismatch does not build the reflex. Running a file in which three lookups succeed and one fails — for a reason you can point at — does.

SystemVerilog — config_db_proof.sv (ready-to-run)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ================================================================
// config_db_proof.sv — three good lookups, one intentional failure.
//   Questa : vlog -sv -L uvm_1_2 config_db_proof.sv
//            vsim -c -L uvm_1_2 config_db_proof_top -do "run -all; quit"
//   VCS    : vcs -sverilog -ntb_opts uvm-1.2 config_db_proof.sv && ./simv
//   Xcelium: xrun -sv -uvm config_db_proof.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
 
class leaf extends uvm_component;
    `uvm_component_utils(leaf)
    int      good_int;
    int      wrong_type;
    int      wrong_path;
    string   label;
    function new(string n, uvm_component p); super.new(n,p); endfunction
 
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
 
        // (1) Matching type, matching scope, matching field name → succeeds.
        if (!uvm_config_db#(int)::get(this, "", "good_int", good_int))
            `uvm_fatal("CFG", "good_int not found")
 
        // (2) Set as int, fetched as int, but through a DIFFERENT field name.
        //     Returns 0. The variable keeps its previous value — here, 0 —
        //     which is indistinguishable from a legitimately configured 0.
        if (!uvm_config_db#(int)::get(this, "", "typo_int", wrong_path))
            `uvm_warning("CFG", "typo_int not found (expected: name mismatch)")
 
        // (3) Stored as int, requested as byte. Type T is part of the KEY, so
        //     this is a different entry entirely — not a conversion.
        if (!uvm_config_db#(byte)::get(this, "", "good_int", wrong_type))
            `uvm_warning("CFG", "good_int/byte not found (expected: type mismatch)")
 
        // (4) The guarded pattern every real lookup should use.
        if (!uvm_config_db#(string)::get(this, "", "label", label))
            `uvm_fatal("CFG", $sformatf(
                "%s: no 'label' visible at this scope. set() must run before this build_phase.",
                get_full_name()))
 
        `uvm_info("CFG", $sformatf("good_int=%0d wrong_path=%0d wrong_type=%0d label=%s",
                  good_int, wrong_path, wrong_type, label), UVM_LOW)
    endfunction
endclass
 
class env extends uvm_env;
    `uvm_component_utils(env)
    leaf l;
    function new(string n, uvm_component p); super.new(n,p); endfunction
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // Env installs a DEFAULT. The test's competing set() is shallower and
        // therefore higher precedence, so the test's value is what leaf sees.
        uvm_config_db#(string)::set(this, "l", "label", "ENV_DEFAULT");
        l = leaf::type_id::create("l", this);
    endfunction
endclass
 
class cfg_test extends uvm_test;
    `uvm_component_utils(cfg_test)
    env e;
    function new(string n, uvm_component p); super.new(n,p); endfunction
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_config_db#(int)::set(this, "e.l", "good_int", 42);
        uvm_config_db#(string)::set(this, "e.l", "label", "TEST_WINS");
        e = env::type_id::create("e", this);
    endfunction
    task run_phase(uvm_phase p); p.raise_objection(this); p.drop_objection(this); endtask
endclass
 
module config_db_proof_top;
    initial run_test("cfg_test");
endmodule
 
// Expected:
//   UVM_WARNING [CFG] typo_int not found (expected: name mismatch)
//   UVM_WARNING [CFG] good_int/byte not found (expected: type mismatch)
//   UVM_INFO    [CFG] good_int=42 wrong_path=0 wrong_type=0 label=TEST_WINS
//                                                            ↑ not ENV_DEFAULT

The label result is the one to sit with. Both the test and the environment set it, the environment's call runs later in simulation time, and the test's value still wins — because during build_phase precedence comes from hierarchy depth, not from ordering.

And note what lookups (2) and (3) have in common: both return 0, and both leave the destination variable holding 0. A missing entry does not zero anything and does not warn on its own. The zero you observe is the value the variable already had, which is why an unguarded get() is a silent-failure generator.

1

A test's configuration was ignored, and moving one line to a different file fixed it

CONFIG-DB-PRECEDENCE-DEPTH
Symptom

A test set num_transactions to 5000 for a long soak run. The sequence ran 100 — the environment's default. The set() call was demonstrably executing; a uvm_info beside it printed on every run.

Reordering the calls made no difference. Making the test's path pattern narrower and more specific made no difference either.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class soak_test extends base_test;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);            // builds env, which sets its default
        // Intended: override the env's default for this test
        uvm_config_db#(int)::set(e.seqr, "", "num_transactions", 5000);
    endfunction
endclass
 
class my_env extends uvm_env;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_config_db#(int)::set(this, "seqr", "num_transactions", 100);
        seqr = my_sequencer::type_id::create("seqr", this);
    endfunction
endclass
Diagnostic Evidence

+UVM_CONFIG_DB_TRACE showed both writes and the read, and the read returned 100:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
CFGDB/SET  uvm_test_top.env.seqr  num_transactions (int) = 100
CFGDB/SET  uvm_test_top.env.seqr  num_transactions (int) = 5000
CFGDB/GET  uvm_test_top.env.seqr  num_transactions (int) -> 100

That output is what made the bug confusing: the 5000 write happened second and still lost. Under a last-wins model this is impossible, which is why the team spent a day suspecting the trace itself.

Root Cause

Two things combined, and the second is the real lesson.

First, the ordering: the test called super.build_phase() before its own set(), so the environment — and the sequencer's own get() — had already run. Even a correct precedence would not help a value written after it was read.

Second, and more importantly, the test's set() used e.seqr as the context. That makes the setting context the sequencer itself, at depth 3. The environment's set() used this — the env, at depth 2. During build_phase precedence is default_precedence - cntxt.get_depth(), so the env's write carried higher precedence than the test's. The test had unintentionally demoted itself below the component it was trying to configure.

This is why narrowing the pattern did not help: specificity plays no part in resolution.

Fix

Set from the test's own context, before the environment is built:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class soak_test extends base_test;
    function void build_phase(uvm_phase phase);
        // Context = this (the test, depth 1) → higher precedence than the env.
        // Written before super so it is in place when the sequencer reads it.
        uvm_config_db#(int)::set(this, "env.seqr", "num_transactions", 5000);
        super.build_phase(phase);
    endfunction
endclass

Two rules generalise from this. Configure downward, never sideways: the cntxt argument should be the component doing the configuring, with the path expressing the target — not the target passed as context. And write before you build: any set() intended to be visible to a component's build_phase must precede the super.build_phase() call that constructs it.

The guard worth adding is the fatal-on-miss pattern from the runnable example. It would not have caught this particular bug — the get() succeeded, it simply returned the other value — which is exactly why the second guard matters too: log the configured value at end_of_elaboration_phase, where a wrong-but-present value is visible as a number rather than as behaviour three hours into a regression.

Questions Engineers Actually Ask

Because a failed get() does nothing at all — including nothing to the variable you passed it.

It returns 0 and leaves the destination untouched, holding whatever it held before: 0 for an int, null for a virtual interface handle. An unguarded lookup therefore produces a plausible-looking default rather than an error, and the failure surfaces much later as a null-handle crash in the driver or as a test that quietly runs zero transactions.

The guard is one line and it should be reflexive:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (!uvm_config_db#(virtual apb_if)::get(this, "", "apb_vif", vif))
    `uvm_fatal("CFG", "apb_vif not found — check the set() in tb_top")

The field name is only one of four things that must match. The full key is the type T, the resolved scope, and the field name — and the getter's own position determines whether a scope pattern applies to it.

The one that catches people is type. uvm_config_db#(int)::set(...) and uvm_config_db#(bit[31:0])::get(...) are different entries, not a conversion, and no warning connects them. uvm_config_db#(uvm_object) versus a specific object type behaves the same way.

+UVM_CONFIG_DB_TRACE prints the type alongside every set and get, which usually makes the mismatch obvious in one run.

Where This Is Specified

  • IEEE 1800.2-2020 (UVM) — uvm_config_db#(T). The set() and get() signatures, the cntxt / inst_name / field_name triple, and the parameterised type as part of the entry's identity.
  • IEEE 1800.2-2020 — configuration precedence. Derivation of a setting's precedence from the context's hierarchical depth during build time, and the reversion to a uniform default precedence afterwards.
  • IEEE 1800.2-2020 — uvm_resource_db and the resource pool. The storage layer beneath uvm_config_db, including precedence-ordered resolution of multiple matching entries.
  • IEEE 1800.2-2020 — uvm_component::get_full_name and get_depth. The hierarchical name matched by scope patterns, and the depth value that feeds the precedence calculation.
  • IEEE 1800.2-2020 — command-line processing. +UVM_CONFIG_DB_TRACE and +UVM_RESOURCE_DB_TRACE as the supported way to observe set and get activity.
  • IEEE 1800.2-2020 — UVM phasing. The ordering of build_phase top-down construction, which is what makes "set before super.build_phase" a correctness requirement rather than a style preference.

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.

Part of UVM Fundamentals·Configuration with config_db·Lesson 29 of 33

View program

Continue learning