UVM-08: uvm_config_db — VLSI Trainers
VLSI Trainers UVM Series · UVM-08
UVM Series · UVM-08

uvm_config_db

The complete config_db API — set() and get() signatures, the four-part key, path wildcards and precedence, what types can be stored, the exists() and dump() debug methods, and every common pitfall explained with the exact fix.

📋 What config_db Is

uvm_config_db is a global, type-parameterised key-value store built into the UVM infrastructure. It allows any component to place a value into the database and any other component to retrieve it by key — without either component needing a direct handle to the other.

It solves a fundamental problem in deep hierarchies: how does the test (at the top) pass configuration to a driver (at the bottom) without every intermediate component explicitly forwarding the value? The answer is config_db — the test sets the value with a path that matches the driver’s location, and the driver retrieves it without knowing who set it.

config_db — Global Key-Value Store Between Components Without config_db — manual forwarding test env → pass it down agent → pass it down Every level must forward — tight coupling With config_db — direct set/get test driver config_db (global) set() get() No intermediate forwarding — env/agent need not know vlsitrainers.com
Figure 1 — config_db vs manual forwarding. Without config_db (left), every intermediate component must explicitly forward the value downward — creating tight coupling. With config_db (right), the test sets directly and the driver gets directly using a path key. The env and agent are completely unaware.

📋 set() and get() API

The config_db has two primary methods. The type parameter T must match exactly between set() and get() — mismatched types silently return 0 from get().

// ── set() — place a value into the database ──────────────
// static function bit set(
//   uvm_component cntxt,    // context: this component, or null for top
//   string        inst_name, // path suffix — often a child name or wildcard
//   string        field_name,// the key string — must match get()
//   T             value      // the value to store
// );
uvm_config_db #(T)::set(cntxt, inst_name, field_name, value);

// ── get() — retrieve a value from the database ───────────
// static function bit get(
//   uvm_component cntxt,    // context: this component
//   string        inst_name, // additional path suffix (usually "")
//   string        field_name,// the key string — must match set()
//   ref T         value      // output: receives the stored value
// );
bit found = uvm_config_db #(T)::get(cntxt, inst_name, field_name, value);

Both functions are static — they are called on the class type, not on an instance. The type parameter is the only way the database knows which “slot” to look in. A config_db #(int) and a config_db #(my_cfg) are completely separate databases even if they use the same key string.

Always check the return value of get(). If get() returns 0, the value was not found. Most teams use `uvm_fatal on a failed get() for mandatory configuration, and `uvm_warning or a default value for optional configuration. Ignoring the return value and proceeding with an uninitialised variable is the most common source of mysterious simulation failures.

📋 The Four-Part Key

Every entry in config_db is identified by a four-part key: the type T, the setting scope (derived from cntxt + inst_name), and the field_name. Understanding how the scope is built is essential for writing correct set/get pairs.

config_db Four-Part Key — How the Lookup Path is Constructed set() — builds the scope path T = type scope = cntxt.get_full_name() + “.” + inst_name field_name = “cfg” (the key string) value = the object/value to store Example: set(this, “m_agent”, “cfg”, m_cfg) → scope = “uvm_test_top.env.m_agent” key = “cfg” get() — matches against the stored scope path T = type lookup path = cntxt.get_full_name() + “.” + inst_name (usually just cntxt) field_name = “cfg” (must match set) Example: get(this, “”, “cfg”, m_cfg) → lookup = “uvm_test_top.env.m_agent” key = “cfg” → MATCH ✓ vlsitrainers.com
Figure 2 — Four-part key construction. The set() scope is built by combining the context component’s full path with the inst_name suffix. The get() lookup matches that path against stored entries. If this component calling get() is at “uvm_test_top.env.m_agent” and field_name matches, the entry is found.
// ── Concrete examples of set/get pairs ───────────────────

// Set from test — targeting env's child "m_agent"
// this = uvm_test_top, inst_name = "m_env.m_agent"
// scope stored = "uvm_test_top.m_env.m_agent"
uvm_config_db #(apb_agent_cfg)::set(
  this, "m_env.m_agent", "cfg", m_agent_cfg);

// Get in agent — this = uvm_test_top.m_env.m_agent, inst = ""
// lookup = "uvm_test_top.m_env.m_agent" → MATCH ✓
if (!uvm_config_db #(apb_agent_cfg)::get(
      this, "", "cfg", m_cfg))
  `uvm_fatal("CFG", "Agent config not found!")

// Wildcard: set for all agents in env
uvm_config_db #(apb_agent_cfg)::set(
  this, "m_env.*", "cfg", m_agent_cfg);  // * matches any child name

// Null context: set from tb_top static initial block
uvm_config_db #(virtual apb_if)::set(
  null, "uvm_test_top", "apb_vif", apb_bus);
// null context → scope = "" + inst_name = "uvm_test_top"
// Accessible by test (path = "uvm_test_top") and any descendant

📋 Path Strings and Wildcards

The inst_name parameter in set() supports SystemVerilog glob-style wildcards, allowing one set() call to reach multiple components:

PatternMatchesUse case
"" (empty)Only the context component itselfset() to self; get() in any component using just this
"m_agent"The child named exactly “m_agent”Target a specific child component
"m_env.m_agent"Grandchild — env’s child named m_agentTarget a deep component from a high-level parent
"*"Any single component at any depthSet for all components in the hierarchy
"m_env.*"Any direct child of m_envSet for all agents inside env
"*.m_drv"Any component named m_drv at any levelSet for all drivers regardless of their parent
Avoid using "*" alone as the path. A wildcard that matches everything makes the configuration global — every component that calls get() with the same field_name will find it, including components you did not intend to configure. Use specific paths or at least "m_env.*" to limit scope. The tighter the path, the easier the testbench is to debug.

📋 Lookup Precedence

When multiple set() calls match the same get() request, config_db returns the most specific match — the one with the longest matching path. If two entries have the same specificity, the most recently set one wins.

PrioritySpecificityExample scope
HighestExact full path match"uvm_test_top.env.agent.drv"
Partial wildcard — more specific"uvm_test_top.env.*.drv"
Single wildcard"*.drv"
LowestPure wildcard match"*"

This precedence rule is what makes test-time overrides work cleanly. A base test can set a default configuration with a broad path. A derived test can override it for one specific component with a more precise path — and the more specific entry wins.

📋 Types You Can Store

config_db is parameterised on any SystemVerilog type. The most common types used in practice:

TypeTypical useExample
Config object classAgent config, env config — the primary use caseuvm_config_db #(apb_agent_cfg)::set(...)
virtual <if>Passing virtual interface from tb_top to testuvm_config_db #(virtual apb_if)::set(...)
intVerbosity, iteration counts, enable flagsuvm_config_db #(int)::set(this, "*", "verbosity", 5)
bitFeature enable/disable flagsuvm_config_db #(bit)::set(this, "m_env", "has_cov", 1)
stringTest name, log file pathsuvm_config_db #(string)::set(...)
uvm_objectBase class — if type not known at set() timeAvoid — use specific type for type safety
Always use the most specific type possible. Storing everything as uvm_object requires $cast at get() time, adds runtime overhead, and loses type checking. Store as the actual config class type — the compiler will catch type mismatches at compile time rather than at simulation runtime.

📋 Configuration Objects Pattern

The recommended pattern is to group all configuration for a component into a single config object class that extends uvm_object. One config object per component level — one for the env, one for each agent, one for any complex sub-component.

// ── Config object for an APB agent ───────────────────────
class apb_agent_cfg extends uvm_object;
  `uvm_object_utils(apb_agent_cfg)

  // Mandatory: virtual interface and active/passive mode
  virtual apb_if          vif;
  uvm_active_passive_enum is_active       = UVM_ACTIVE;

  // Optional sub-component enables (with safe defaults)
  bit has_coverage       = 1;
  bit has_err_injection  = 0;

  // Protocol-specific parameters
  int  max_wait_states   = 4;
  bit  check_pslverr     = 1;

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

// ── Test configures it, sets it, agent retrieves it ──────
// In test build_phase:
m_cfg = apb_agent_cfg::type_id::create("m_cfg");
m_cfg.is_active        = UVM_ACTIVE;
m_cfg.has_err_injection = 1;          // override for error test
uvm_config_db #(apb_agent_cfg)::set(
  this, "m_env.m_agent", "cfg", m_cfg);

// In agent build_phase:
if (!uvm_config_db #(apb_agent_cfg)::get(
      this, "", "cfg", m_cfg))
  `uvm_fatal("CFG", "No agent config!")

📋 Nested Configuration

For multi-agent environments the recommended pattern is to nest config objects inside each other, mirroring the component hierarchy. The test creates all config objects, populates them, nests the agent configs inside the env config, and sets only the env config into config_db. The env then re-distributes the nested agent configs to each agent.

// ── Env config holds agent configs ───────────────────────
class gpio_env_cfg extends uvm_object;
  `uvm_object_utils(gpio_env_cfg)
  apb_agent_cfg  m_apb_cfg;   // nested agent config
  bit            has_sb = 1;
  function new(string name="gpio_env_cfg"); super.new(name); endfunction
endclass

// ── Test: create all, nest, set once ─────────────────────
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  m_env_cfg         = gpio_env_cfg::type_id::create("m_env_cfg");
  m_env_cfg.m_apb_cfg = apb_agent_cfg::type_id::create("m_apb_cfg");
  // Assign virtual interface into nested config
  if (!uvm_config_db #(virtual apb_if)::get(
        this, "", "apb_vif", m_env_cfg.m_apb_cfg.vif))
    `uvm_fatal("TEST", "No vif!")
  // Set only the top-level env config — one set() call
  uvm_config_db #(gpio_env_cfg)::set(
    this, "m_env", "cfg", m_env_cfg);
  m_env = gpio_env::type_id::create("m_env", this);
endfunction

// ── Env: get top-level config, re-distribute nested configs
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if (!uvm_config_db #(gpio_env_cfg)::get(
        this, "", "cfg", m_cfg))
    `uvm_fatal("ENV", "No env config!")
  // Push nested agent config down to agent
  uvm_config_db #(apb_agent_cfg)::set(
    this, "m_agent", "cfg", m_cfg.m_apb_cfg);
  m_agent = gpio_agent::type_id::create("m_agent", this);
endfunction

📋 Debug — exists() and dump()

When get() unexpectedly returns 0, use the built-in debug facilities to inspect what is actually stored in config_db.

// ── Check if an entry exists without retrieving it ───────
if (uvm_config_db #(apb_agent_cfg)::exists(
      this, "", "cfg"))
  `uvm_info("CFG", "cfg exists in config_db", UVM_LOW)

// ── Dump ALL config_db entries to transcript ──────────────
// Very useful: shows every stored (type, scope, field) → value
// Call from end_of_elaboration_phase for a clean snapshot
function void end_of_elaboration_phase(uvm_phase phase);
  uvm_config_db #(apb_agent_cfg)::dump();  // type-specific dump
endfunction

// ── Wait — set() before or after get()? ──────────────────
// set() in tb_top initial block → must be before run_test()
// set() in test build_phase → available to all children
// set() must always precede the component's build_phase
// because get() is called in build_phase

// ── UVM verbosity flag to enable config_db tracing ───────
// +UVM_CONFIG_DB_TRACE on command line prints every set/get
+UVM_CONFIG_DB_TRACE is the fastest debug tool. Add this plusarg to your simulator command line and every config_db set() and get() call is printed with its full path and result. You can immediately see whether your set() is storing at the right scope and whether your get() is looking at the right path.

📋 Common Pitfalls

PitfallSymptomFix
Type mismatch between set() and get()get() returns 0 silently — even though the field_name and path are correct. Very hard to spot.The T in config_db #(T) must be identical in set() and get(). A derived class type is not the same as its base class type.
get() return value ignoredComponent silently uses uninitialised value (null handle, random int, etc). Crashes later in run_phase.Always use `uvm_fatal or at least `uvm_warning when get() returns 0.
set() called after build_phase startsComponent’s build_phase runs and calls get() before the value is set. get() returns 0.set() must be called before the target component’s build_phase. Set from the parent’s build_phase (or tb_top initial block for global values).
Wrong inst_name path in set()get() returns 0. The scope stored does not match the component calling get().Use +UVM_CONFIG_DB_TRACE to see the exact scopes. Verify the component path with get_full_name() in the get() component.
Using null context in get()Lookup fails — null context means an empty base path, which rarely matches stored scopes.Always use this as the context in get(). Only use null in set() from tb_top’s static initial block.
set() after child’s build_phase (top-down overlap)Config not available to child — build is top-down, so child builds before parent finishes if parent sets after creating the child.Always set config_db entries before calling create() on the child that needs to get() them.
Overusing wildcardsUnexpected components receive configuration. Debug becomes very difficult.Use the most specific path that works. Reserve "*" for truly global settings like verbosity.

📋 Quick Reference

ItemKey fact
config_db purposeGlobal type-parameterised key-value store. Decouples configuration source from consumer.
set() signatureconfig_db #(T)::set(cntxt, inst_name, field_name, value)
get() signatureconfig_db #(T)::get(cntxt, inst_name, field_name, ref value) → bit
T must matchIdentical type in set() and get() — derived class ≠ base class
Scope formulascope = cntxt.get_full_name() + "." + inst_name
Null context scopenull context → scope starts from "", combine with inst_name e.g. "uvm_test_top"
get() contextAlways use this — null context in get() almost never works
Empty inst_name in get()"" → lookup path = just this.get_full_name()
Wildcard "*"Matches any single component at any depth below cntxt
PrecedenceMost specific path wins. Tie = most recent set() wins.
Always check returnif (!config_db::get(...)) `uvm_fatal(...) for mandatory config
set() timingMust happen before the component that calls get() runs its build_phase
Recommended patternStore config objects (not primitives). One config class per component level. Nest configs.
Debug: exists()config_db #(T)::exists(this, "", "key") — checks without retrieving
Debug: dump()config_db #(T)::dump() — prints all entries of type T
Debug: command line+UVM_CONFIG_DB_TRACE — prints every set() and get() with path and result
Scroll to Top