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
uvm_resource_pool (global singleton)Name: "timeout_cycles" | Type: intVIP default: scope="" val=5000 prec=1000 (DEFAULT)set by: apb_agent_pkg (library code)Test override: scope="uvm_test_top." val=10000 prec=2000set by: error_inject_test (HIGH_PRECEDENCE)Factory default: scope="" val=1000 prec=0 (LOW)set by: global_setup (safety net)read_by_name("uvm_test_top.env.drv", "timeout_cycles", val)Resolution AlgorithmStep 1: Find all entries with name "timeout_cycles"→ Found 3 entries (all match name)Step 2: Filter by scope matchscope "uvm_test_top.env.drv" matches: • "" ✓ (matches any) • "uvm_test_top." ✓ (matches prefix) • "" ✓ (matches any)Step 3: Highest precedence wins Test override (prec=2000) > VIP default (prec=1000) > Safety (prec=0)Result: val = 10000 (from test override, prec=2000) 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: "Resource Not Found" Despite Being Set
Symptom: read_by_name() returns 0 even though you clearly called set() earlier. The resource exists — dump() shows it — but the reader can't find it.
Root Cause: The query scope does not match the set scope. If set with scope "uvm_test_top.*" but read with scope "my_scoreboard", the glob match fails because "uvm_test_top.*" does not match "my_scoreboard".
Fix: Use "*" as the scope for truly global resources. Use get_full_name() as the query scope so the pattern is tested against the component's actual path.
// ── 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.