Day 13 / 25
← Day 12 Day 14 →
HomeVerificationDay 13 — UVM Config DB
Track 2 — UVM Core

UVM Config DB

By EcrioniX · Updated June 2026

uvm_config_db is UVM's global configuration store — the bridge between the static SystemVerilog module world (where interfaces live) and the dynamic UVM class hierarchy. Every non-trivial testbench uses it to pass virtual interfaces into drivers and monitors, distribute configuration objects to agents, and tune parameters without editing component source code.

⏱ 28 min read📖 Day 13 of 25🎯 config_db · vif passing · wildcards · debug
top module (static) interface my_if dut_if() uvm_config_db "uvm_test_top.env.agt.drv" → vif "uvm_test_top.env.agt" → cfg UVM class hierarchy uvm_test env agent driver ← get(vif) monitor ← get(vif) set() get() in build_phase
Contents
  1. What is uvm_config_db?
  2. set() API — Four Arguments Explained
  3. get() in build_phase
  4. Virtual Interface Passing — Step by Step
  5. Configuration Objects vs Individual Fields
  6. Path Wildcards: * and %
  7. Precedence Rules
  8. Common Errors
  9. +UVM_CONFIG_DB_TRACE Debug
  10. Complete Example — vif + Config Object
  11. FAQ

1. What is uvm_config_db?

uvm_config_db is a parameterised static class that acts as a global key-value store shared across an entire UVM simulation. It replaced the older set_config_int / set_config_object API introduced in UVM 1.1 and is now the standard mechanism for all configuration distribution.

The fundamental problem it solves is the module-to-class boundary. SystemVerilog interfaces are modules — they exist in static elaboration space. UVM components are dynamic objects created at runtime. There is no language mechanism that lets a module directly hand a virtual interface to a class constructor. uvm_config_db is the handshake point: the module calls set() at time zero, and UVM components call get() later during their build_phase.

Beyond virtual interfaces, uvm_config_db distributes integers, strings, enumerated types, and entire configuration objects. The type parameter #(T) determines which database (there is one per type) is searched, keeping different types from colliding.

Key insight: There is no single "config database" — there is one database per parameterised type. uvm_config_db#(virtual my_if) and uvm_config_db#(int) are completely separate stores with separate key spaces.

2. set() API — Four Arguments Explained

The full signature is:

static function void set(
  uvm_component  cntxt,      // who is setting (null = top module)
  string         inst_name,  // hierarchical path (wildcards OK)
  string         field_name, // key name (case-sensitive)
  T              value       // the value to store
);
ArgumentTypeWhat to put here
cntxtuvm_componentPass null from a top-level module. Pass this from inside a UVM component to set relative paths.
inst_namestringFull (or relative) hierarchical path to the component that will call get(). When cntxt is null, this is the absolute UVM path starting with uvm_test_top.
field_namestringThe key string. Must match exactly (case-sensitive) the string used in get(). Convention: use the variable name, e.g. "vif", "cfg".
valueTThe value. For virtual interfaces, this is the interface handle. For config objects, a class handle.

Typical set() calls from a top module

// top_tb.sv — initial block, time 0
module top_tb;
  // 1. Instantiate interface
  my_if dut_if(.clk(clk), .rst(rst));

  initial begin
    // 2. Pass vif to driver — absolute path, exact match
    uvm_config_db#(virtual my_if)::set(
      null,
      "uvm_test_top.env.agent.driver",
      "vif",
      dut_if
    );

    // 3. Pass vif to monitor (same interface, different path)
    uvm_config_db#(virtual my_if)::set(
      null,
      "uvm_test_top.env.agent.monitor",
      "vif",
      dut_if
    );

    // 4. Pass an integer config parameter
    uvm_config_db#(int)::set(
      null,
      "uvm_test_top.*",
      "timeout_ns",
      5000
    );

    run_test();
  end
endmodule

3. get() in build_phase

Components call get() during build_phase — the only UVM phase that runs before simulation time advances. The full signature mirrors set() but returns a bit indicating success:

static function bit get(
  uvm_component  cntxt,       // the requesting component (usually `this`)
  string         inst_name,   // usually "" (use component's own path)
  string         field_name,  // must match set() field_name exactly
  ref T          value        // output — written if found
);

Always check the return value. If get() returns 0, the value was not found and the variable stays uninitialized (null for handles). A null virtual interface accessed later in run_phase will crash the simulator with a fatal or segfault.

// Inside my_driver — build_phase
function void build_phase(uvm_phase phase);
  super.build_phase(phase);

  // Retrieve virtual interface
  if (!uvm_config_db#(virtual my_if)::get(
        this,   // context — this component's path is used
        "",     // inst_name — "" means use this component's path
        "vif",  // field name — must match set() exactly
        vif))   // output variable
  begin
    `uvm_fatal("CFG", "virtual interface vif not found in config_db")
  end
endfunction
Use `uvm_fatal, not `uvm_error: A missing virtual interface means nothing will work. `uvm_error allows simulation to continue into run_phase where a null-vif dereference causes an unpredictable crash. `uvm_fatal stops cleanly at the source of the problem.

4. Virtual Interface Passing — Step by Step

This is the most critical config_db use case. Here is the complete flow with every file involved:

Step 1 — Declare the interface

// my_if.sv
interface my_if(
  input logic clk,
  input logic rst
);
  logic [31:0] addr;
  logic [31:0] data;
  logic        write;
  logic        valid;
  logic        ready;

  // clocking block for driver (active)
  clocking drv_cb @(posedge clk);
    default input #1ns output #1ns;
    output addr, data, write, valid;
    input  ready;
  endclocking

  // clocking block for monitor (passive)
  clocking mon_cb @(posedge clk);
    default input #1ns;
    input addr, data, write, valid, ready;
  endclocking
endinterface

Step 2 — Declare virtual interface variable in driver and monitor

// my_driver.sv — class member
class my_driver extends uvm_driver#(my_trans);
  `uvm_component_utils(my_driver)

  virtual my_if vif;  // ← virtual interface handle

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("CFG", "Driver: vif not found in config_db")
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      my_trans tr;
      seq_item_port.get_next_item(tr);
      @(vif.drv_cb);                    // use interface via clocking block
      vif.drv_cb.addr  <= tr.addr;
      vif.drv_cb.data  <= tr.data;
      vif.drv_cb.write <= tr.write;
      vif.drv_cb.valid <= 1;
      @(posedge vif.drv_cb.ready);
      vif.drv_cb.valid <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

Step 3 — Set from the top module before run_test()

// top_tb.sv
module top_tb;
  logic clk = 0;
  logic rst = 1;
  always #5 clk = ~clk;

  my_if dut_if(.clk(clk), .rst(rst));
  dut u_dut(.clk(clk), .rst(rst), .addr(dut_if.addr),
            .data(dut_if.data), .write(dut_if.write),
            .valid(dut_if.valid), .ready(dut_if.ready));

  import uvm_pkg::*;
  `include "uvm_macros.svh"

  initial begin
    rst = 1; @(posedge clk); @(posedge clk); rst = 0;

    // MUST happen before run_test() — sets before build phases run
    uvm_config_db#(virtual my_if)::set(
      null, "uvm_test_top.env.agent.driver",  "vif", dut_if);
    uvm_config_db#(virtual my_if)::set(
      null, "uvm_test_top.env.agent.monitor", "vif", dut_if);

    run_test();
  end
endmodule
Wildcard shortcut: You can replace both set() calls above with a single wildcard: uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.*", "vif", dut_if) — this matches driver and monitor at any depth under env. Use with care when multiple agents have different interfaces.

5. Configuration Objects vs Individual Fields

When a component needs more than a couple of parameters, it is better to bundle them all into a configuration object — a class that extends uvm_object — and pass the whole object with one set() call.

Define the configuration class

class agent_cfg extends uvm_object;
  `uvm_object_utils_begin(agent_cfg)
    `uvm_field_enum(uvm_active_passive_enum, is_active, UVM_ALL_ON)
    `uvm_field_int(bus_width,         UVM_ALL_ON)
    `uvm_field_int(enable_coverage,   UVM_ALL_ON)
    `uvm_field_int(timeout_ns,        UVM_ALL_ON)
    `uvm_field_string(protocol_name,  UVM_ALL_ON)
  `uvm_object_utils_end

  uvm_active_passive_enum is_active = UVM_ACTIVE;
  int    bus_width       = 32;
  bit    enable_coverage = 1;
  int    timeout_ns      = 10_000;
  string protocol_name   = "AXI4";

  function new(string name = "agent_cfg");
    super.new(name);
  endfunction
endclass

Set and get the config object

// In the test or env — set before build_phase
agent_cfg agt_cfg = agent_cfg::type_id::create("agt_cfg");
agt_cfg.bus_width       = 64;
agt_cfg.enable_coverage = 0;
agt_cfg.timeout_ns      = 20_000;
uvm_config_db#(agent_cfg)::set(
  this,
  "env.agent",
  "cfg",
  agt_cfg);

// In the agent — build_phase
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if (!uvm_config_db#(agent_cfg)::get(this, "", "cfg", cfg))
    `uvm_fatal("CFG", "agent_cfg not found")
  // Use cfg to conditionally build sub-components
  if (cfg.is_active == UVM_ACTIVE)
    driver  = my_driver::type_id::create("driver", this);
  monitor = my_monitor::type_id::create("monitor", this);
endfunction

Config objects are cleaner than individual set() calls because:

6. Path Wildcards: * and %

UVM config_db supports two wildcard characters in the inst_name argument of set():

WildcardMeaningExample
*Matches any sequence of characters including dots (hierarchy levels). Equivalent to .* in the path."uvm_test_top.*" matches any component at any depth
%Matches any sequence of characters but NOT a dot. Matches exactly one hierarchy level."uvm_test_top.env.%" matches only direct children of env
// Match driver and monitor at any depth under agent
uvm_config_db#(virtual my_if)::set(
  null, "uvm_test_top.env.agent*", "vif", dut_if);

// Match ALL components at any depth in the test (broadest)
uvm_config_db#(virtual my_if)::set(
  null, "*", "vif", dut_if);

// Match only direct children of env (% stops at dots)
uvm_config_db#(agent_cfg)::set(
  null, "uvm_test_top.env.%", "cfg", agt_cfg);

// Match agents in all envs: env0.agent, env1.agent, etc.
uvm_config_db#(agent_cfg)::set(
  null, "uvm_test_top.*.agent", "cfg", agt_cfg);
Wildcard risk: Using "*" as the inst_name floods every component with the same value. If two different agents have different interfaces, both will see the last-set value for "vif" and one will have the wrong interface. Always use the minimum wildcard needed.

7. Precedence Rules

When multiple set() calls match the same get() request, UVM uses the following precedence rules to decide which value wins:

  1. More specific path wins over wildcard: An exact match beats a wildcard match.
  2. Later set() wins over earlier set() at the same specificity: If two calls with equal specificity (e.g. both use the same wildcard) match the same get(), the one that was called later in simulation time (or later in program order within time 0) wins.
  3. Higher component context wins: A set() from a component higher in the hierarchy (closer to uvm_test_top) takes precedence over a set() from a lower component when both match the same path and field.
ScenarioWhich value the get() receives
Exact path + wildcard both matchExact path wins regardless of call order
Two wildcards both matchThe one called later wins
Test sets, then env also sets (same field)Test's set wins (higher hierarchy, called earlier)
Module sets at time 0, component sets in build_phaseComponent's set wins (later in program order)
Tip: When in doubt about precedence, run with +UVM_CONFIG_DB_TRACE and look at the order of set() calls in the log. The last matching set() (after specificity is resolved) is what get() returns.

8. Common Errors

Most config_db bugs fall into a small number of categories. The table below lists the error symptom, root cause, and fix:

SymptomRoot CauseFix
Fatal: vif not found in config_dbinst_name in set() doesn't match component's UVM pathRun +UVM_CONFIG_DB_TRACE; compare set() path vs component's get_full_name()
Null pointer dereference in run_phaseget() returned 0 but wasn't checked; vif stayed nullAlways if(!get()) `uvm_fatal
Wrong field name"Vif" vs "vif" — case mismatchStandardize: always lowercase field names; grep for mismatches
get() returns 0 despite set() being calledType mismatch: set#(virtual my_if) but get#(virtual other_if)Ensure both sides use identical type parameter
Component gets wrong value (another agent's vif)Wildcard in set() is too broadNarrow the wildcard or use exact paths
get() always fails in passive agentis_active=UVM_PASSIVE so driver never calls get(); monitor path is wrongCheck monitor path separately; ensure set() also covers monitor's path
Config object is null inside driverAgent retrieved cfg but didn't pass the handle to driver before creating itSet config_db for driver's path too, or pass cfg via a class member after creation

9. +UVM_CONFIG_DB_TRACE Debug

Add +UVM_CONFIG_DB_TRACE to your simulator command line to get a log entry for every set() and get() call. This is the fastest way to debug a missing virtual interface.

# Simulator command line — works with VCS, Xcelium, ModelSim/Questa
vcs -sverilog ... +UVM_TESTNAME=my_test +UVM_CONFIG_DB_TRACE

# Xcelium
xrun ... +UVM_TESTNAME=my_test +UVM_CONFIG_DB_TRACE

The trace output looks like this — each set() and get() is logged with the resolved path:

# UVM_CONFIG_DB_TRACE output — typical entries
UVM_INFO @ 0: reporter [CFGDB/SET] Configuration 'uvm_test_top.env.agent.driver.vif'
              (type virtual my_if) set by  {null,"uvm_test_top.env.agent.driver","vif"}

UVM_INFO @ 0: reporter [CFGDB/GET] Configuration 'uvm_test_top.env.agent.driver.vif'
              (type virtual my_if) read by uvm_test_top.env.agent.driver {this,"","vif"}
              = <my_if handle>

# If get() fails, you'll see:
UVM_INFO @ 0: reporter [CFGDB/GET] Configuration 'uvm_test_top.env.agent.monitor.vif'
              (type virtual my_if) NOT FOUND by uvm_test_top.env.agent.monitor

From the trace you can see:

Additional debug: Inside any component you can call uvm_config_db#(T)::dump() to print all entries of that type to stdout. Also call this.get_full_name() in a build_phase to print the component's exact UVM path and compare it against your set() inst_name.

10. Complete Example — vif + Config Object

This is a production-ready minimal testbench showing all config_db patterns: virtual interface passing, configuration object distribution, and proper error checking. Copy all files into your simulator project directory.

my_cfg.sv — Configuration object

// my_cfg.sv
class my_cfg extends uvm_object;
  `uvm_object_utils_begin(my_cfg)
    `uvm_field_int(bus_width,       UVM_ALL_ON)
    `uvm_field_int(enable_coverage, UVM_ALL_ON)
    `uvm_field_int(num_transactions, UVM_ALL_ON)
    `uvm_field_enum(uvm_active_passive_enum, is_active, UVM_ALL_ON)
  `uvm_object_utils_end

  int  bus_width        = 32;
  bit  enable_coverage  = 1;
  int  num_transactions = 100;
  uvm_active_passive_enum is_active = UVM_ACTIVE;

  function new(string name = "my_cfg");
    super.new(name);
  endfunction
endclass

my_driver.sv — get() both vif and cfg

// my_driver.sv
class my_driver extends uvm_driver#(my_trans);
  `uvm_component_utils(my_driver)

  virtual my_if vif;
  my_cfg        cfg;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // 1. Retrieve virtual interface
    if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("CFG",{get_full_name(),": vif not found in config_db"})

    // 2. Retrieve configuration object
    if (!uvm_config_db#(my_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("CFG",{get_full_name(),": cfg not found in config_db"})

    `uvm_info("CFG", $sformatf("bus_width=%0d coverage=%0b",
              cfg.bus_width, cfg.enable_coverage), UVM_MEDIUM)
  endfunction

  task run_phase(uvm_phase phase);
    my_trans tr;
    @(negedge vif.rst);             // wait for reset deassert
    repeat(cfg.num_transactions) begin
      seq_item_port.get_next_item(tr);
      // Drive via clocking block
      @(vif.drv_cb);
      vif.drv_cb.addr  <= tr.addr;
      vif.drv_cb.data  <= tr.data;
      vif.drv_cb.write <= tr.write;
      vif.drv_cb.valid <= 1;
      @(posedge vif.drv_cb.ready);
      vif.drv_cb.valid <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

my_agent.sv — distribute cfg to sub-components

// my_agent.sv
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)

  my_driver  driver;
  my_monitor monitor;
  my_seqr    seqr;
  my_cfg     cfg;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // Get config from DB (set by env or test)
    if (!uvm_config_db#(my_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("CFG", "my_cfg not found")

    // Pass cfg object down to driver and monitor via config_db
    uvm_config_db#(my_cfg)::set(this, "driver",  "cfg", cfg);
    uvm_config_db#(my_cfg)::set(this, "monitor", "cfg", cfg);

    // Conditionally build driver and sequencer
    if (cfg.is_active == UVM_ACTIVE) begin
      driver  = my_driver::type_id::create("driver",  this);
      seqr    = my_seqr::type_id::create("seqr",    this);
    end
    monitor = my_monitor::type_id::create("monitor", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    if (cfg.is_active == UVM_ACTIVE)
      driver.seq_item_port.connect(seqr.seq_item_export);
  endfunction
endclass

base_test.sv — test sets everything before build

// base_test.sv
class base_test extends uvm_test;
  `uvm_component_utils(base_test)

  my_env env;
  my_cfg cfg;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    // 1. Create cfg BEFORE calling super.build_phase()
    cfg = my_cfg::type_id::create("cfg");
    cfg.bus_width       = 64;
    cfg.enable_coverage = 1;
    cfg.num_transactions = 500;

    // 2. Push cfg into DB — must happen before env.build_phase()
    uvm_config_db#(my_cfg)::set(this, "env.agent", "cfg", cfg);

    // 3. Now let super.build_phase() trigger env and agent build
    super.build_phase(phase);

    env = my_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    my_seq seq = my_seq::type_id::create("seq");
    phase.raise_objection(this);
    seq.start(env.agent.seqr);
    phase.drop_objection(this);
  endtask
endclass

Key Takeaways

FAQ

What is uvm_config_db and why is it needed?
uvm_config_db is UVM's global key-value store that lets top-level testbench modules pass virtual interfaces, integers, strings, and configuration objects down into the UVM component hierarchy without the components needing a direct reference to each other. It solves the problem of bridging the static SystemVerilog module world (where interfaces live) with the dynamic UVM class world. The top module calls uvm_config_db#(virtual my_if)::set() at time zero, and any UVM component can call ::get() during build_phase to retrieve it.
What is the difference between set() scope and instance path?
The set() call takes four arguments: (context, inst_path, field_name, value). The context is the component doing the set — use null from a module. The inst_path is a hierarchical path relative to that context — it can include wildcards (* for any characters including dots, % for any characters excluding dots). For example, uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.agent*", "vif", my_vif) will match any component whose UVM path contains "uvm_test_top.env.agent". The get() call uses the calling component as context, so the path must resolve correctly relative to that component's full hierarchical path.
Why does my driver get a null virtual interface even though I called set()?
The most common causes are: (1) The inst_path in set() doesn't match the component's actual UVM path — run with +UVM_CONFIG_DB_TRACE to see exactly what paths are being tried. (2) set() is called after build_phase has already run for the component that calls get() — always call set() before run_test(). (3) The field name string in set() and get() don't match exactly (case-sensitive). (4) The type parameter doesn't match — make sure both set() and get() use the same #(type) parameter, including the virtual keyword.
When should I use a configuration object instead of individual set() calls?
Use a configuration object (a class extending uvm_object) when you have more than two or three parameters to pass to a component or group of components. Bundle all related settings (enable_coverage, is_active, bus_width, timeout_ns, etc.) into one config class, then pass the whole object with a single uvm_config_db#(my_cfg)::set() call. This is cleaner, easier to randomize, and the config object can itself be printed, compared, and copied using UVM field macros, which simplifies debug enormously. The config object can also be held by the agent and passed directly as a class member to its driver and monitor.
Day 12
UVM Factory
type_id::create, overrides, debug
Day 14 — Up Next
UVM Phasing
build, connect, run, shutdown, objections