UVM
Resource Database
uvm_resource_db vs uvm_config_db — precedence, scope matching, dump() debugging.
UVM Fundamentals · Module 19
§1 — The Database Under the Database
There's a moment on most large SoC projects where someone needs to share a piece of data across the entire testbench — not just down to one agent, but globally, accessible from any component, any sequence, any phase, without knowing the hierarchy. Maybe it's a global error injection flag. Maybe it's a simulation performance counter maintained by multiple components. Maybe it's a test-wide timeout value that both the driver and the monitor need to respect.
You reach for uvm_config_db. You write set(null, "uvm_test_top.*", "flag", 1) and it works. But then someone asks: "Can we have two teams set different values for the same flag and let the higher-priority one win?" Or: "Can we dump everything in this database for debugging without adding trace plusargs?" Config DB doesn't have clean answers for either question.
That's when you discover uvm_resource_db. It's not a replacement for config_db — use config_db for hierarchical component configuration, every time. But resource_db fills a specific gap: global, non-hierarchical data with explicit precedence control and a built-in audit trail. And since config_db is literally a thin wrapper around resource_db, understanding it makes you better at debugging config_db problems too.
§2 — Resource DB vs Config DB — Build the Intuition
Config DB and Resource DB solve different problems. They look similar at the API level — both are parameterised by type, both use string keys, both are global singletons. The differences are in the mental model and the access pattern.
| Dimension | uvm_config_db | uvm_resource_db |
|---|---|---|
| Primary Purpose | Component configuration — passing interface handles, config objects down the hierarchy | Global shared data — flags, counters, test parameters accessible from anywhere |
| Scoping Model | Hierarchical — derived from component path (get_full_name()) | Explicit scope strings — you specify the scope directly, no hierarchy dependency |
| Multiple Values | Last set() wins (later takes precedence) | Explicit precedence control — you can set which entry wins regardless of call order |
| Access from Sequences | Needs a component context | Works from anywhere — sequences, functions, tasks, even non-UVM code |
| Audit / Debug | +UVM_CONFIG_DB_TRACE plusarg | Built-in dump() method shows everything including access history |
| Implementation | Thin wrapper around uvm_resource_db | The actual implementation — directly interacts with the resource pool |
| Null Context | set(null, ...) from tb_top module | No context needed — direct database access via class methods |
The key mental model shift: config_db thinks in terms of component paths. Resource_db thinks in terms of named entries with scope patterns and precedence. When you want a configuration to flow down the hierarchy to a specific component, use config_db. When you want a shared value that any piece of code can access — regardless of where it sits in the hierarchy — use resource_db.
§3 — The API — set(), read_by_name(), write_by_name()
The resource_db API is more explicit than config_db. You're directly talking to the database rather than relying on component hierarchy for scoping.
// ── set(): create or update a named resource ──────────────────────────
// uvm_resource_db #(T)::set(scope, name, val, accessor)
// scope : string — wildcard-matchable pattern for lookup context
// name : string — lookup key
// val : T — the value to store
// accessor : uvm_object (optional) — who is writing, for audit trail
uvm_resource_db#(int)::set("uvm_test_top.*", "timeout_cycles", 5000, this);
uvm_resource_db#(bit)::set("*", "inject_errors", 1, null);
// ── read_by_name(): get value matching scope+name ─────────────────────
// Returns 1 on success, 0 if not found
int timeout;
if (!uvm_resource_db#(int)::read_by_name("uvm_test_top", "timeout_cycles",
timeout, this))
`uvm_warn("RES", "timeout_cycles not found, using default")
// ── write_by_name(): update existing resource value ───────────────────
// Returns 1 on success (resource must already exist)
if (!uvm_resource_db#(int)::write_by_name("uvm_test_top.*", "timeout_cycles",
10000, this))
`uvm_error("RES", "timeout_cycles resource not found to update")
// ── read_by_type(): get resource by type alone (no name) ──────────────
bit flag;
uvm_resource_db#(bit)::read_by_type("uvm_test_top", flag, this);
// ── get_by_name(): returns the resource object itself ─────────────────
uvm_resource#(int) res;
res = uvm_resource_db#(int)::get_by_name("*", "timeout_cycles", 1);
if (res != null) begin
`uvm_info("RES", $sformatf("Found: %s = %0d precedence=%0d",
res.get_name(), res.read(), res.precedence), UVM_LOW)
end
// ── Directly manipulating uvm_resource objects ────────────────────────
uvm_resource#(int) my_res = new("timeout_cycles", "uvm_test_top.*");
my_res.write(5000, this); // set value
my_res.set_precedence(3000); // set custom precedence
uvm_resource_pool::get().set(my_res); // register with the pool
int val = my_res.read(this); // read value
// ── dump(): print entire resource database ────────────────────────────
uvm_resource_pool::get().dump(); // shows ALL resources including config_db entriesThe Precedence System — What Makes resource_db Unique
| Precedence Constant | Value | When to Use |
|---|---|---|
uvm_resource_base::LOW_PRECEDENCE | 0 | Default values that should be overridable by anything else |
uvm_resource_base::DEFAULT_PRECEDENCE | 1000 | Standard entries — what set() uses if you don't specify |
uvm_resource_base::HIGH_PRECEDENCE | 2000 | Test-level settings that override VIP defaults |
| Custom integer value | Any | Fine-grained control — higher integer wins |
§4 — Precedence and Lookup — How the Database Resolves
Figure 1 — Resource database lookup with three entries sharing the same name. All matching entries are found, then filtered by scope pattern, then the highest-precedence entry wins. Unlike config_db, the caller doesn't control which entry wins through path placement — explicit precedence values do.
Precedence Resolution — Step-by-Step Table
| Query | Candidates Found | Scope Match? | Winner (highest prec) | Result |
|---|---|---|---|---|
read_by_name("*.env.*", "flag", val) | 3 entries with name "flag" | All three match ".env." | Highest precedence among 3 | value from highest prec entry |
read_by_name("uvm_test_top.env.agent_0", "flag", val) | 3 entries with name "flag" | Only 2 match this exact path | Highest precedence among 2 matches | value from highest prec of matching 2 |
read_by_name("uvm_test_top.env.agent_0", "not_set", val) | 0 entries with name "not_set" | N/A | N/A | returns 0, val unchanged |
§5 — Code Examples — From Basic to Production
Example 1 — Global Test Flag Accessible From Anywhere
// ── Test sets the flag once — anyone can read it ──────────────────────
class error_inject_test extends base_test;
`uvm_component_utils(error_inject_test)
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// Global scope "*" — readable from anywhere without scope dependency
uvm_resource_db#(bit)::set("*", "inject_errors", 1'b1, this);
uvm_resource_db#(int)::set("*", "error_rate_pct", 10, this);
`uvm_info("TEST","Error injection enabled: rate=10%",UVM_LOW)
endfunction
endclass
// ── Driver reads it — no component context needed ─────────────────────
class apb_driver extends uvm_driver#(apb_txn);
`uvm_component_utils(apb_driver)
bit inject_errors = 0;
int error_rate = 0;
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// Scope can be anything that matches "*" — use get_full_name() for specificity
void'(uvm_resource_db#(bit)::read_by_name(
get_full_name(), "inject_errors", inject_errors, this));
void'(uvm_resource_db#(int)::read_by_name(
get_full_name(), "error_rate_pct", error_rate, this));
`uvm_info("DRV",$sformatf("inject=%0b rate=%0d%%",inject_errors,error_rate),UVM_LOW)
endfunction
endclass
// ── Sequence reads it — no component, no context argument needed ───────
class apb_sequence extends uvm_sequence#(apb_txn);
`uvm_object_utils(apb_sequence)
function new(string n="apb_sequence"); super.new(n); endfunction
task body();
bit inject;
int rate;
// Sequences are not components — resource_db works, config_db doesn't (needs 'this')
void'(uvm_resource_db#(bit)::read_by_name("*", "inject_errors", inject, null));
void'(uvm_resource_db#(int)::read_by_name("*", "error_rate_pct", rate, null));
`uvm_info("SEQ",$sformatf("Running with inject=%0b rate=%0d",inject,rate),UVM_LOW)
endtask
endclassExample 2 — Layered Defaults with Precedence Control
// ── Layer 1: Library package sets a rock-bottom default ───────────────
// In apb_agent_pkg.sv — the default that should never prevent overrides
initial begin
uvm_resource#(int) r = new("max_retries", "*");
r.write(3, null);
r.set_precedence(uvm_resource_base::LOW_PRECEDENCE); // 0 — lowest possible
uvm_resource_pool::get().set(r);
end
// ── Layer 2: Environment sets a protocol-appropriate default ──────────
// In my_env.build_phase()
uvm_resource_db#(int)::set("*", "max_retries", 5, this);
// DEFAULT_PRECEDENCE (1000) — wins over library default
// ── Layer 3: Stress test needs more retries ───────────────────────────
// In stress_test.start_of_simulation_phase()
begin
uvm_resource#(int) r = new("max_retries", "*");
r.write(20, this);
r.set_precedence(uvm_resource_base::HIGH_PRECEDENCE); // 2000 — wins over both
uvm_resource_pool::get().set(r);
end
// ── Reader gets the right value regardless of lookup scope ────────────
int retries;
uvm_resource_db#(int)::read_by_name("*", "max_retries", retries, this);
// → In stress_test: retries = 20 (HIGH_PRECEDENCE wins)
// → In normal_test: retries = 5 (DEFAULT wins — stress layer not added)
// → In VIP alone: retries = 3 (LOW_PRECEDENCE — no overrides registered)Example 3 — Shared Performance Counter Between Components
// ── Initialize shared counter ─────────────────────────────────────────
// In test.start_of_simulation_phase()
uvm_resource_db#(int)::set("*", "total_errors", 0, this);
// ── Monitor 0 increments on protocol error ────────────────────────────
function void report_protocol_error();
int current;
void'(uvm_resource_db#(int)::read_by_name("*", "total_errors", current, this));
uvm_resource_db#(int)::write_by_name("*", "total_errors", current + 1, this);
endfunction
// ── Scoreboard reads the global count in check_phase ──────────────────
function void check_phase(uvm_phase phase);
int total;
void'(uvm_resource_db#(int)::read_by_name("*", "total_errors", total, this));
if (total > 0)
`uvm_error("SCB", $sformatf("Test ended with %0d protocol errors", total))
else
`uvm_info("SCB", "No protocol errors recorded ✓", UVM_LOW)
endfunction
// NOTE: write_by_name() updates an existing resource value in-place
// The resource must already exist (from a prior set() call)
// If it doesn't exist, write_by_name returns 0 — check the return value§6 — Simulation Thinking — How the Resource Pool Works
The resource pool is a singleton list of uvm_resource_base objects. It is not hierarchical — there's one flat list, and lookup is done by iterating that list and matching name, type, and scope. This means lookup performance degrades linearly with the number of entries. In a large environment with thousands of config_db calls (which all add entries here), that's worth keeping in mind.
// ── Trigger a full dump of the resource pool ──────────────────────────
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
uvm_resource_pool::get().dump();
endfunction
// ── What dump() shows (abridged example): ─────────────────────────────
//
// =================================================================
// resources
// =================================================================
// uvm_resource #(int)
// name: timeout_cycles
// scope: *
// value: 5000
// precedence: 1000
// written by: uvm_test_top.env
// read by: uvm_test_top.env.apb_agent.drv
// uvm_test_top.env.apb_agent.mon
//
// uvm_resource #(bit)
// name: inject_errors
// scope: *
// value: 1
// precedence: 1000
// written by: uvm_test_top
// read by: (none yet)
//
// uvm_resource #(virtual apb_if) <-- this is from config_db!
// name: __M_uvm_config_db_internal__apb_vif
// scope: uvm_test_top.*
// ...
//
// Note the config_db entries — they appear with internal name prefixes
// ── Checking resource accessor history ───────────────────────────────
uvm_resource#(int) r = uvm_resource_db#(int)::get_by_name("*", "timeout_cycles", 1);
if (r != null) begin
// r.m_access_history shows list of {accessor, read/write, timestamp}
// Useful for: "who read this value and when?"
`uvm_info("AUDIT", r.convert2string(), UVM_LOW)
end§7 — Real Verification Usage — Where resource_db Actually Fits
| Use Case | Why resource_db (not config_db) | Example |
|---|---|---|
| Global test flags | Sequences are not components — config_db needs this (component context), resource_db does not | Error injection enable, slow-mode flag, debug verbosity toggle |
| Simulation-wide counters | Multiple components increment/read; no single owner — resource_db's write_by_name() allows any code to update | Protocol error counter, total transaction count, coverage hole counter |
| Layered VIP defaults | Precedence system allows library → VIP → test layering without conditional code | Timeout values, retry counts, bus protocol mode |
| Testbench audit | dump() gives full visibility into what's been set, by whom, and who has read it | Post-test resource usage audit, debugging "why is this value wrong?" |
| Cross-package shared state | When two packages (e.g., APB agent and AXI agent) need shared data without import dependencies | Inter-agent synchronization flags, shared DMA descriptor base address |
§8 — Common Bugs and Debugging Scenarios
// ── Bug 1: Scope mismatch ─────────────────────────────────────────────
// ❌ WRONG — set scope and read scope do not overlap
uvm_resource_db#(int)::set("apb_agent_pkg", "timeout", 5000, null);
// set scope: "apb_agent_pkg"
int t;
uvm_resource_db#(int)::read_by_name("uvm_test_top.env.drv", "timeout", t, this);
// query scope: "uvm_test_top.env.drv"
// Match test: does "apb_agent_pkg" match scope "uvm_test_top.env.drv"?
// → NO. Returns 0. t is unchanged.
// ✓ CORRECT — use "*" for global resources
uvm_resource_db#(int)::set("*", "timeout", 5000, null);
// set scope: "*" — matches ANY query scope string
uvm_resource_db#(int)::read_by_name(get_full_name(), "timeout", t, this);
// query scope: "uvm_test_top.env.drv"
// Match test: does "*" match "uvm_test_top.env.drv"?
// → YES. Returns 1. t = 5000. ✓
// ── Bug 2: write_by_name on non-existent resource ────────────────────
// ❌ WRONG — write_by_name requires resource to already exist
int ok = uvm_resource_db#(int)::write_by_name("*", "not_yet_set", 99, this);
// ok = 0 — resource doesn't exist, nothing written
// t stays at its previous value — silently wrong
// ✓ CORRECT — set() first, then write_by_name() can update
uvm_resource_db#(int)::set("*", "counter", 0, null); // creates it
uvm_resource_db#(int)::write_by_name("*", "counter", 1, this); // updates it
// ── Bug 3: Type mismatch — same name, wrong T parameter ───────────────
uvm_resource_db#(int)::set("*", "flag", 1, null);
bit b;
uvm_resource_db#(bit)::read_by_name("*", "flag", b, this); // ❌ different type!
// int ≠ bit → resource not found → b stays 0 → silent failure
// ✓ CORRECT — type must match exactly
int i;
uvm_resource_db#(int)::read_by_name("*", "flag", i, this); // int matches int ✓§9 — Ready-to-Run Demo
Ready to Run — Questa / VCS / Xcelium
// resource_db_demo.sv — demonstrates uvm_resource_db fundamentals
// Questa : vlog -sv resource_db_demo.sv && vsim -c resource_db_top -do "run -all; quit"
// VCS : vcs -sverilog -ntb_opts uvm resource_db_demo.sv && ./simv
// Xcelium: xrun -sv -uvm resource_db_demo.sv -input "run; exit"
`include "uvm_macros.svh"
import uvm_pkg::*;
class resource_test extends uvm_test;
`uvm_component_utils(resource_test)
function new(string n, uvm_component p); super.new(n,p); endfunction
task run_phase(uvm_phase phase);
int ival;
string sval;
bit bval;
int ok;
phase.raise_objection(this);
`uvm_info("TEST","═══ DEMO 1: Basic set and read ═══",UVM_NONE)
uvm_resource_db#(int)::set("*","timeout",5000,this);
void'(uvm_resource_db#(int)::read_by_name("*","timeout",ival,this));
`uvm_info("TEST",$sformatf("timeout = %0d (expect 5000)",ival),UVM_NONE)
`uvm_info("TEST","═══ DEMO 2: write_by_name updates existing resource ═══",UVM_NONE)
void'(uvm_resource_db#(int)::write_by_name("*","timeout",10000,this));
void'(uvm_resource_db#(int)::read_by_name("*","timeout",ival,this));
`uvm_info("TEST",$sformatf("timeout after write = %0d (expect 10000)",ival),UVM_NONE)
`uvm_info("TEST","═══ DEMO 3: Precedence — high prec wins over default ═══",UVM_NONE)
uvm_resource_db#(int)::set("*","retries",3,null); // DEFAULT_PRECEDENCE=1000
begin
uvm_resource#(int) r = new("retries","*");
r.write(20,this);
r.set_precedence(2000); // HIGH — wins
uvm_resource_pool::get().set(r);
end
void'(uvm_resource_db#(int)::read_by_name("*","retries",ival,this));
`uvm_info("TEST",$sformatf("retries = %0d (expect 20 — high prec wins)",ival),UVM_NONE)
`uvm_info("TEST","═══ DEMO 4: Type mismatch → not found ═══",UVM_NONE)
uvm_resource_db#(int)::set("*","myval",42,null);
bval = 0;
ok = uvm_resource_db#(bit)::read_by_name("*","myval",bval,this);
`uvm_info("TEST",$sformatf("bit read: ok=%0d bval=%0b (type mismatch→ok=0)",ok,bval),UVM_NONE)
`uvm_info("TEST","═══ DEMO 5: write_by_name on non-existent ═══",UVM_NONE)
ok = uvm_resource_db#(int)::write_by_name("*","ghost",999,this);
`uvm_info("TEST",$sformatf("write_by_name on non-existent: ok=%0d (expect 0)",ok),UVM_NONE)
`uvm_info("TEST","═══ DEMO 6: dump() — see everything ═══",UVM_NONE)
uvm_resource_pool::get().dump();
phase.drop_objection(this);
endtask
endclass
module resource_db_top;
initial run_test("resource_test");
endmodule
// EXPECTED OUTPUT:
// TEST: ═══ DEMO 1: Basic set and read ═══
// TEST: timeout = 5000 (expect 5000)
// TEST: ═══ DEMO 2: write_by_name updates existing resource ═══
// TEST: timeout after write = 10000 (expect 10000)
// TEST: ═══ DEMO 3: Precedence — high prec wins over default ═══
// TEST: retries = 20 (expect 20 — high prec wins)
// TEST: ═══ DEMO 4: Type mismatch → not found ═══
// TEST: bit read: ok=0 bval=0 (type mismatch→ok=0)
// TEST: ═══ DEMO 5: write_by_name on non-existent ═══
// TEST: write_by_name on non-existent: ok=0 (expect 0)
// TEST: ═══ DEMO 6: dump() — see everything ═══
// [dump output showing all resources including timeout, retries, myval]§10 — Interview Questions
- What is the relationship between uvm_config_db and uvm_resource_db?
uvm_config_dbis a thin wrapper arounduvm_resource_db. Every config_dbset()call creates a resource entry in the same global resource pool. This meansuvm_resource_pool::get().dump()shows all config_db entries too — they just have internally prefixed names. Understanding this relationship explains why config_db's+UVM_CONFIG_DB_TRACEand resource_db'sdump()provide complementary views of the same underlying data. - Why would you use resource_db instead of config_db for a global test flag? Two reasons. First, config_db's scope resolution requires a component context — sequences and static functions don't have one. Resource_db's
read_by_name()accepts any scope string and works from non-component code. Second, resource_db's explicit precedence system lets you create layered defaults (library default at LOW_PRECEDENCE → test override at HIGH_PRECEDENCE) that config_db cannot express without duplicate key management. For component configuration that travels down the hierarchy, use config_db. For global shared state, use resource_db. - read_by_name() returns 0 despite set() being called earlier. What are the three most common causes? (1) Scope mismatch: the scope pattern in set() doesn't match the query scope in read_by_name(). Test with scope="*" for both to isolate. (2) Type mismatch: set uses
#(int)but read uses#(bit)— different parameterised types are different databases. (3) Timing: set() was called after read_by_name() in the simulation timeline. resource_db is not prophetic — values must be written before they can be read. - Tricky: Two set() calls register the same name "timeout" with DEFAULT_PRECEDENCE. Which one does read_by_name() return? When multiple entries have exactly equal precedence, UVM returns the most recently registered one — which is the last
set()call. The resource pool uses a sorted list: new entries are appended, and when scanning for highest precedence among equals, the last-added entry wins. This is subtle: if you callset("*", "timeout", 5000)in the VIP and laterset("*", "timeout", 8000)in the test (both at DEFAULT_PRECEDENCE), the test value wins simply by being registered later. This is different from if the test had used HIGH_PRECEDENCE, where it would win regardless of order.
§11 — Best Practices and Summary
| Practice | Reasoning |
|---|---|
| Use "*" scope for truly global resources | Avoids scope mismatch bugs. Any reader with any scope string will find the resource. Restrict scope only when intentional — and document why. |
| Always check return values of read_by_name() | A failed read leaves the output variable unchanged. Silent defaults can pass regression but produce wrong behavior on edge cases. |
| Use set() before write_by_name() | write_by_name() requires the resource to already exist. Calling it first is a no-op that returns 0 — easily missed without a return value check. |
| Use explicit precedence for layered defaults | Library code at LOW_PRECEDENCE (0), environment defaults at DEFAULT_PRECEDENCE (1000), test overrides at HIGH_PRECEDENCE (2000). This pattern makes override order predictable regardless of call order. |
| Use dump() in start_of_simulation for debugging | It shows everything: resource_db entries, config_db entries, who wrote them, who read them. Gate it behind a plusarg to avoid cluttering clean regression logs. |
| Keep type consistent between set and read | Use int everywhere or bit everywhere — never mix. A type mismatch is a silent failure with no error message. |
| Don't replace config_db with resource_db | Config_db's hierarchical scoping is a feature, not a limitation. Virtual interfaces should stay in config_db — they need hierarchy-aware delivery. |
Resource_db is not a better version of config_db — it's a different tool. Config_db delivers configuration down a component hierarchy using paths. Resource_db shares data globally using names and precedence. The fact that config_db is built on resource_db means the two always stay consistent, and dump() becomes a single window into everything both mechanisms have registered.
Continue learning
Related tutorials
- Recommended next
Resource Usage Patterns
The practical patterns for configuration across a UVM testbench — the canonical set-higher / get-lower in build_phase flow and its timing, the virtual interface handoff, the config object pattern, wildcard scoping across the hierarchy, and the disciplines that keep configuration predictable and debuggable at scale, above all always checking that config_db::get succeeded.
- Recommended next
Callback Reuse Strategies
Using callbacks to build reusable, maintainable customization — designing the hook API as a stable contract, building reusable callback libraries, layering project/team/test callbacks that coexist independently, keeping callbacks self-contained and composable, choosing config versus override versus callback, and why an unstable hook API reintroduces the very reuse problem callbacks exist to solve.