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.
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.
uvm_config_db#(virtual my_if) and uvm_config_db#(int) are completely separate stores with separate key spaces.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 );
| Argument | Type | What to put here |
|---|---|---|
cntxt | uvm_component | Pass null from a top-level module. Pass this from inside a UVM component to set relative paths. |
inst_name | string | Full (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_name | string | The key string. Must match exactly (case-sensitive) the string used in get(). Convention: use the variable name, e.g. "vif", "cfg". |
value | T | The value. For virtual interfaces, this is the interface handle. For config objects, a class handle. |
// 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
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
`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.This is the most critical config_db use case. Here is the complete flow with every file involved:
// 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
// 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
// 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
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.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.
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
// 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:
set() + one get() instead of N pairsrand fields + randomize())print(), compare(), copy()UVM config_db supports two wildcard characters in the inst_name argument of set():
| Wildcard | Meaning | Example |
|---|---|---|
* | 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);
"*" 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.When multiple set() calls match the same get() request, UVM uses the following precedence rules to decide which value wins:
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.| Scenario | Which value the get() receives |
|---|---|
| Exact path + wildcard both match | Exact path wins regardless of call order |
| Two wildcards both match | The 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_phase | Component's set wins (later in program order) |
+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.Most config_db bugs fall into a small number of categories. The table below lists the error symptom, root cause, and fix:
| Symptom | Root Cause | Fix |
|---|---|---|
| Fatal: vif not found in config_db | inst_name in set() doesn't match component's UVM path | Run +UVM_CONFIG_DB_TRACE; compare set() path vs component's get_full_name() |
| Null pointer dereference in run_phase | get() returned 0 but wasn't checked; vif stayed null | Always if(!get()) `uvm_fatal |
| Wrong field name | "Vif" vs "vif" — case mismatch | Standardize: always lowercase field names; grep for mismatches |
| get() returns 0 despite set() being called | Type 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 broad | Narrow the wildcard or use exact paths |
| get() always fails in passive agent | is_active=UVM_PASSIVE so driver never calls get(); monitor path is wrong | Check monitor path separately; ensure set() also covers monitor's path |
| Config object is null inside driver | Agent retrieved cfg but didn't pass the handle to driver before creating it | Set config_db for driver's path too, or pass cfg via a class member after creation |
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:
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.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 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 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 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 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
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.
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.
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.
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.