Skip to content
VLSI Mentor

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.

Dimensionuvm_config_dbuvm_resource_db
Primary PurposeComponent configuration — passing interface handles, config objects down the hierarchyGlobal shared data — flags, counters, test parameters accessible from anywhere
Scoping ModelHierarchical — derived from component path (get_full_name())Explicit scope strings — you specify the scope directly, no hierarchy dependency
Multiple ValuesLast set() wins (later takes precedence)Explicit precedence control — you can set which entry wins regardless of call order
Access from SequencesNeeds a component contextWorks from anywhere — sequences, functions, tasks, even non-UVM code
Audit / Debug+UVM_CONFIG_DB_TRACE plusargBuilt-in dump() method shows everything including access history
ImplementationThin wrapper around uvm_resource_dbThe actual implementation — directly interacts with the resource pool
Null Contextset(null, ...) from tb_top moduleNo 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.

SystemVerilog — complete resource_db API reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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 entries

The Precedence System — What Makes resource_db Unique

Precedence ConstantValueWhen to Use
uvm_resource_base::LOW_PRECEDENCE0Default values that should be overridable by anything else
uvm_resource_base::DEFAULT_PRECEDENCE1000Standard entries — what set() uses if you don't specify
uvm_resource_base::HIGH_PRECEDENCE2000Test-level settings that override VIP defaults
Custom integer valueAnyFine-grained control — higher integer wins

§4 — Precedence and Lookup — How the Database Resolves

Resource database lookup and precedence resolution
Resource database lookup and precedence resolution

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

QueryCandidates FoundScope Match?Winner (highest prec)Result
read_by_name("*.env.*", "flag", val)3 entries with name "flag"All three match ".env."Highest precedence among 3value 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 pathHighest precedence among 2 matchesvalue 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/AN/Areturns 0, val unchanged

§5 — Code Examples — From Basic to Production

Example 1 — Global Test Flag Accessible From Anywhere

SystemVerilog — global error injection flag via resource_db
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
endclass

Example 2 — Layered Defaults with Precedence Control

SystemVerilog — three-layer precedence: library default → VIP → test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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

SystemVerilog — shared mutable counter via write_by_name
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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.

SystemVerilog — dump() output and audit trail
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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 CaseWhy resource_db (not config_db)Example
Global test flagsSequences are not components — config_db needs this (component context), resource_db does notError injection enable, slow-mode flag, debug verbosity toggle
Simulation-wide countersMultiple components increment/read; no single owner — resource_db's write_by_name() allows any code to updateProtocol error counter, total transaction count, coverage hole counter
Layered VIP defaultsPrecedence system allows library → VIP → test layering without conditional codeTimeout values, retry counts, bus protocol mode
Testbench auditdump() gives full visibility into what's been set, by whom, and who has read itPost-test resource usage audit, debugging "why is this value wrong?"
Cross-package shared stateWhen two packages (e.g., APB agent and AXI agent) need shared data without import dependenciesInter-agent synchronization flags, shared DMA descriptor base address

§8 — Common Bugs and Debugging Scenarios

SystemVerilog — scope mismatch bug and fix + write_by_name pitfall
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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

SystemVerilog — resource_db_demo.sv (copy and run)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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_db is a thin wrapper around uvm_resource_db. Every config_db set() call creates a resource entry in the same global resource pool. This means uvm_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_TRACE and resource_db's dump() 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 call set("*", "timeout", 5000) in the VIP and later set("*", "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

PracticeReasoning
Use "*" scope for truly global resourcesAvoids 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 defaultsLibrary 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 debuggingIt 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 readUse 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_dbConfig_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

Standards & specifications

Governing standard
IEEE Std 1800.2 (UVM)(opens IEEE in a new tab)

Defines the UVM class library's API and semantics. Testbench structure, naming conventions and project methodology around that API are engineering practice, not requirements of the standard.

Reference implementation
Accellera UVM reference implementation(opens Accellera Systems Initiative in a new tab)

The open-source class library and user guide that most simulators ship. Useful alongside IEEE 1800.2 when a page discusses the implementation rather than the standardised API.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the UVM curriculum.