UVM
Resource DB vs Config DB
How uvm_config_db relates to uvm_resource_db — config_db as a typed, hierarchical convenience layer over the general resource_db, what it adds (component-path scoping, hierarchical precedence, a familiar set/get), when to use each, why they share one underlying pool but construct scope and name differently, and the scope-mismatch trap of mixing the two.
Resource Database · Module 23 · Page 23.2
The Engineering Problem
You have used uvm_config_db#(T)::set and ::get to pass virtual interfaces, config objects, and parameters down the testbench — and the previous chapter introduced uvm_resource_db, the lower-level store underneath. But which should you reach for, and why are there two? The confusion is real and costly. Treat them as unrelated and you'll mix them — config_db::set here, a raw resource_db::read there — and find the read silently misses the value (returning a null handle that crashes downstream), because config_db constructs the scope and name from the component hierarchy while your raw read used a literal string that doesn't match. Use raw resource_db where you needed config_db's hierarchical precedence, and a test-level override will silently not apply. Use config_db where you needed resource_db's generality, and you'll fight the hierarchy. The two are not rivals and not interchangeable-by-accident: config_db is a typed, hierarchical convenience layer built on resource_db, and knowing what it adds, when to use each, and that they share one pool is the difference between clean configuration and baffling misses. The problem this chapter solves is resource DB versus config DB: the relationship, what config_db adds, when to use each, and the traps of mixing them.
uvm_config_db is a typed, hierarchical convenience layer over the general uvm_resource_db — they write to one underlying resource pool, but config_db adds three things for the common case of configuration flowing down the hierarchy. Hierarchical scoping: config_db::set(this, "path.to.comp", "field", value) addresses by the component instance path (with wildcards), constructing the resource's scope/name from the hierarchy — where resource_db::set("scope", "name", value) takes a literal string scope (a regex/glob), more general but not tied to the component tree. Hierarchical precedence: config_db resolves a get by purpose-built rules — a set from a higher component, or set later, wins — designed so configuration flows down and higher levels override; resource_db's precedence is more manual (pool order, scope). Typed convenience: a familiar, type-safe set/get. Use config_db by default for component configuration (interfaces, config objects, parameters flowing top-down); reach for resource_db deliberately for non-hierarchical/general sharing or finer scope control. The crucial caveat: because config_db constructs the scope/name (it's not a literal string), mixing a config_db set with a raw resource_db get requires matching the exact constructed scope/name, or the get misses. This chapter is config DB versus resource DB: the layered relationship, what config_db adds, when to use each, and the mixing traps.
How does uvm_config_db relate to uvm_resource_db — what does config_db add over the general resource_db (hierarchical scoping, hierarchical precedence, typed convenience), when do you use each, why do they share one pool but address it differently, and what is the trap of mixing them?
Motivation — why there are two, and why it matters which you use
UVM provides both a general store and a hierarchical layer because configuration has a common shape (top-down) that deserves purpose-built support — but the generality is sometimes needed. The reasons the distinction matters:
- Configuration usually flows down the hierarchy. The dominant pattern is a higher component (test, env) configuring a lower one (agent, driver) — and overriding from above.
config_db's hierarchical scoping and precedence are built for exactly this, so it's the right default. - resource_db is more general but less convenient.
resource_dbis a flat, string-scoped store — more flexible (sharing not tied to the component tree) but without the hierarchical precedence that makes top-down configuration just work. Using it where config_db fits loses the override-from-above behavior. - They share one pool, so mixing is possible — and dangerous. A
config_dbset is aresource_dbset under the hood — so they're interoperable, butconfig_dbconstructs the scope/name from the hierarchy, so a rawresource_dbget with a naive string won't match. Mixing without understanding the mapping causes silent misses. - The wrong choice fails subtly.
resource_dbwhereconfig_dbwas needed → overrides silently don't apply (no hierarchical precedence).config_dbwhereresource_dbwas needed → fighting the hierarchy for non-hierarchical sharing. Mixing with mismatched scope → null handles and crashes. The failures are quiet and confusing. - Most users only need config_db. The practical upshot: use config_db for almost everything; reach for resource_db deliberately, when its generality is genuinely needed — and know they share the pool.
The motivation, in one line: there are two because configuration usually flows top-down (deserving config_db's purpose-built hierarchical scoping and precedence) but general sharing is sometimes needed (the lower-level resource_db) — and which you use matters because the wrong choice fails subtly (overrides silently not applying, fighting the hierarchy, or null-handle misses from mixing with mismatched scope) — so use config_db by default, reach for resource_db deliberately, and understand they share one pool addressed differently.
Mental Model
Hold config_db and resource_db as a hierarchical memo system built on a general bulletin board — same board underneath, but config_db addresses memos by org-chart path with override-from-above:
The resource database is a general bulletin board in a building: anyone can post a value pinned under a (location, name) tag, and anyone whose location matches the tag can read it. It's flat and general — the tag is just a string, and the board doesn't know anything about who reports to whom. The config database is a memo system built on top of that same board. Instead of posting under a raw location string, you address a memo by org-chart path — to this department, this team, this person — and the memo system translates that into a board posting under a constructed tag. And it adds a delivery rule that matches how organizations work: a memo from higher up the org chart, or sent later, supersedes a conflicting one from below, so directives flow down and the boss can override the team. For the common case — configuration flowing down the hierarchy, with higher levels overriding — the memo system is exactly right: you address by org-chart path and get override-from-above for free. But it's the same board underneath, so if you bypass the memo system and read the raw board directly, you have to know the exact constructed tag the memo system used, or you won't find the memo — it's there, but under a tag you didn't expect. Use the memo system for normal top-down configuration; go to the raw board only when you need general, non-hierarchical posting that doesn't fit the org chart. Picture
resource_dbas a general bulletin board: anyone posts a value under a (scope, name) tag, and anyone whose scope matches can read it — flat and general (the tag is just a string; the board knows nothing about hierarchy).config_dbis a memo system built on that same board: instead of posting under a raw scope string, you address a memo by org-chart path (component instance path), and the memo system translates that into a board posting under a constructed tag. It adds a delivery rule matching how organizations work: a memo from higher up, or sent later, supersedes a conflicting one from below — so directives flow down and the boss can override the team (hierarchical precedence). For the common case — configuration flowing down with higher levels overriding — the memo system is exactly right. But it's the same board underneath: if you bypass the memo system and read the raw board directly, you must know the exact constructed tag, or you won't find the memo (it's there, under a tag you didn't expect). Use the memo system (config_db) for normal top-down configuration; go to the raw board (resource_db) only when you need general, non-hierarchical posting that doesn't fit the org chart.
So config_db and resource_db are a hierarchical memo system on a general bulletin board: resource_db is the flat, string-keyed board (general, no hierarchy), and config_db addresses by org-chart path (component hierarchy) with override-from-above (hierarchical precedence) — translating to a constructed tag on the same board. Use config_db for top-down configuration (the common case); reach for resource_db for general, non-hierarchical sharing — and remember mixing requires knowing the constructed tag, or the raw read misses the memo. Address configuration by org-chart path with config_db; drop to the raw resource board only for general, non-hierarchical sharing.
Visual Explanation — config_db as a layer over resource_db
The defining picture is the layering: config_db is a typed, hierarchical layer built on the general resource_db, both writing the same pool.
The figure shows config_db as a layer over resource_db. uvm_config_db (the brand-colored top — typed, hierarchical view) exposes a set/get addressed by component instance path, with hierarchical precedence — the convenient default for configuration. It is built on (the warning-colored connector) the uvm_resource_db (the success-colored — general, string-scoped store), which is set/get by a literal string scope and name — general, flat, not tied to the component tree. Both write to one underlying resource pool (the success-colored bottom — one store, two views). The crucial reading is the "built on" relationship: a config_db set is a resource_db set underneath — with a hierarchically-constructed scope and name. So config_db doesn't replace resource_db; it wraps it, adding the hierarchy (instance-path addressing) and precedence (override-from-above) and typed convenience on top, while resource_db provides the general substrate underneath. This is why they're interoperable (same pool) but addressed differently (config_db constructs the scope/name; resource_db takes a literal one). The warning-colored "built on" layer is the key (warning because it's where the mixing trap lives): the config_db set translates the component path into a constructed resource scope/name — so a raw resource_db get must match that construction to find it. The single pool (the success-colored bottom) is what makes them one store — and why a value set one way can be read the other way if the scope/name aligns. The diagram is the layering: config_db (hierarchical, typed) → built on → resource_db (general) → one pool — one store, two views, with config_db the convenient hierarchical default and resource_db the general substrate. config_db is a hierarchical, typed layer over the general resource_db — one pool, two views.
RTL / Simulation Perspective — the two APIs side by side
In code, the difference is the addressing: config_db by component path (constructed), resource_db by literal string. The example shows both setting and getting the same kind of value, and the mixing trap.
// === CONFIG_DB: addressed by COMPONENT PATH, with hierarchical precedence (the default) ===
// in the test/env (higher level), configure a lower component:
uvm_config_db#(virtual bus_if)::set(this, "env.agent.driver", "vif", vif); // by INSTANCE PATH
// in the driver (lower level), retrieve:
uvm_config_db#(virtual bus_if)::get(this, "", "vif", vif); // matched by hierarchy
// precedence: a set from a HIGHER component, or LATER, WINS → test overrides env overrides default
// === RESOURCE_DB: addressed by a LITERAL STRING scope, general, manual precedence ===
uvm_resource_db#(int)::set("global_cfg", "max_outstanding", 8, this); // literal scope string
uvm_resource_db#(int)::read_by_name("global_cfg", "max_outstanding", val, this);
// general: NOT tied to the component tree — any reader matching the scope string can read it
// === THE MIXING TRAP: config_db CONSTRUCTS the scope/name; a raw resource_db read won't match ===
uvm_config_db#(virtual bus_if)::set(this, "env.agent.driver", "vif", vif);
// ✗ raw resource_db read with a NAIVE string → MISSES (config_db stored it under a CONSTRUCTED scope):
// uvm_resource_db#(virtual bus_if)::read_by_name("env.agent.driver", "vif", got); // returns 0 → null
// → got stays null → driver crashes on null vif (DebugLab)
// ✓ read it back with config_db (matching the hierarchical construction), OR use resource_db for BOTH
// CHOOSING: config_db for top-down COMPONENT config; resource_db for general, non-hierarchical sharingThe code shows the two APIs. config_db is addressed by component path: set(this, "env.agent.driver", "vif", vif) sets by instance path, and get(this, "", "vif", vif) retrieves matched by hierarchy — with precedence such that a set from a higher component, or later, wins (test overrides env overrides default). resource_db is addressed by a literal string scope: set("global_cfg", "max_outstanding", 8, this) uses a literal scope string, and read_by_name("global_cfg", ...) reads it — general, not tied to the component tree (any reader matching the scope string can read it). The mixing trap: a config_db set constructs the scope/name from the hierarchy, so a raw resource_db::read_by_name with a naive string ("env.agent.driver") misses — config_db stored it under a constructed scope, so the read returns 0 (the handle stays null), and the driver crashes on a null vif (the DebugLab). The fix: read it back with config_db (matching the construction), or use resource_db for both set and get. The shape to carry: the two APIs differ in addressing — config_db by component path (constructed scope/name, hierarchical precedence) versus resource_db by literal string (general, manual precedence) — and because they share the pool, mixing them requires matching the exact scope/name config_db constructs, or the cross-API read misses. The choosing rule: config_db for top-down component config (interfaces, config objects), resource_db for general, non-hierarchical sharing (a global parameter, a resource not tied to a component path). The config_db precedence (higher/later wins) is what gives you top-down override; the resource_db generality is what gives you non-hierarchical sharing. Use config_db's component-path addressing for configuration; don't cross-read a config_db set with a raw resource_db string — it won't match.
Verification Perspective — what config_db adds, and when to use each
The decision is which to use — and it follows from what config_db adds over the general resource_db. Seeing the additions and the use-case split clarifies the choice.
The figure shows what config_db adds and when to use each. config_db (the brand-colored — typed, hierarchical layer) adds three things over resource_db: hierarchical scoping (address by component instance path), hierarchical precedence (a higher or later set wins, so configuration flows down and overrides from above), and typed convenience (type-safe set/get). So you use config_db for top-down component configuration (the warning-colored — virtual interfaces, config objects, parameters that flow down the hierarchy with overrides). resource_db (the general, string-scoped store) → you use it for general, non-hierarchical sharing (the default-colored — a resource keyed by name not path, or finer scope control — the lower-level cases). The verification insight is that the additions drive the use-case split: config_db's hierarchy and precedence are exactly what component configuration needs (config flows down, higher levels override), so it's the default for that; resource_db's generality (string scope, not tied to the tree) is what non-hierarchical sharing needs (a global resource, a named lookup independent of the component path). The practical rule is config_db is the default, resource_db is the deliberate, occasional substrate: most configuration is top-down component config (→ config_db), and you reach for resource_db only when the sharing is genuinely non-hierarchical or you need finer scope control than the component path gives. The key discriminator is hierarchy: is this configuration flowing down the component tree, with overrides from above? → config_db. Is this a general resource shared by name, not by hierarchy? → resource_db. The brand-colored mechanisms map through their additions/generality to the warning/default-colored use cases. The diagram is the decision: config_db (hierarchy + precedence + typed) → top-down config; resource_db (general) → non-hierarchical sharing — default to config_db, reach for resource_db deliberately. config_db adds hierarchy and precedence for top-down config; use resource_db only for general, non-hierarchical sharing.
Runtime / Execution Flow — how config_db precedence resolves a get
At run time, config_db's value-add is visible in precedence resolution: when multiple sets target the same field, the get resolves to the highest-precedence one — higher in the hierarchy, or set later, wins. The flow shows it.
The flow shows how config_db precedence resolves a get. Sets (step 1): several components set the same field — the env sets a default, the test (a higher level) sets an override. Get (step 2): the lower component calls config_db::get for the field, with multiple matching sets in the pool. Resolve (step 3): config_db resolves by hierarchical precedence — a set from a higher component, or set later, wins. Receive (step 4): the test's override beats the env's default — override-from-above. The runtime insight is that this precedence resolution is config_db's core value-add — and it's exactly what top-down configuration needs. The common pattern is layered configuration: a default set at a low level, overridden by a higher level (env overrides agent default, test overrides env). config_db's hierarchical precedence makes this just work: the higher (or later) set wins, so the component receives the most-specific override. This is why config_db is the default for configuration — the override-from-above is built in. resource_db, by contrast, has manual precedence (pool order, scope matching) — it does not give override-from-above for free; if you used raw resource_db for component config, a test-level override might not take precedence over an env set the way you'd expect (you'd have to manage precedence yourself). So the precedence flow is where config_db earns its place: it resolves the layered sets by hierarchy, delivering the override. The brand-colored sets/get feed the success-colored precedence resolution, yielding the default-colored override received. The flow is the precedence resolution: multiple sets → get → hierarchical precedence (higher/later wins) → override received — the top-down override config_db provides. config_db resolves layered sets by hierarchical precedence, so higher-level overrides win — the override-from-above top-down config needs.
Waveform Perspective — a higher-level set overriding a lower-level set
The precedence is visible on a timeline: a low-level set establishes a default, a higher-level set overrides it, and the get resolves to the override. The waveform shows the override-from-above.
A higher-level set overrides a lower-level default; the get resolves to the higher-precedence value
12 cyclesThe waveform shows config_db precedence resolving to the override. During build, the env sets the field to a default (set_env, value DFLT). Then the test — a higher level — sets the same field to an override (set_test, value OVR). Both are in the pool. When the driver gets the field (get_drv), config_db resolves by hierarchical precedence: the test's higher-level set wins, so the driver receives OVR, not DFLT (drv_recv). The crucial reading is the override-from-above: two sets target the same field — a lower-level default (env) and a higher-level override (test) — and the config_db get resolves to the higher-precedence one (the test's), so the override takes effect. This is the purpose of config_db's precedence: configuration flows down, and higher levels override lower ones — the test can override the env's default without the env or driver knowing. The value the driver receives (OVR) is not the first set (DFLT) but the highest-precedence one (the test's) — that's the override-from-above built into config_db. The picture to carry is that config_db's precedence is hierarchical: among competing sets, the one from higher up (or later) wins, so the get delivers the most-authoritative configuration. With raw resource_db, this override-from-above is not automatic — you'd manage precedence yourself, and a test override might not win over an env set as you'd expect. Reading the waveform this way — which competing set wins when the component gets the field? — is seeing config_db's hierarchical precedence. The test's OVR winning over the env's DFLT at the get is the signature of override-from-above — the value config_db adds for top-down configuration. config_db resolves competing sets by hierarchical precedence, so a higher-level override wins — the override-from-above that top-down configuration needs.
DebugLab — the null interface from a config_db set read by raw resource_db
A virtual interface set via config_db, read via raw resource_db, that silently came back null
A driver crashed on a null virtual interface — a fatal null-handle dereference the moment it tried to drive. The virtual interface had been set correctly: the test did uvm_config_db#(virtual bus_if)::set(this, "env.agent.driver", "vif", vif), and the value was definitely in the pool (a resource_db dump showed it). But a teammate, debugging a different issue, had added a helper in the driver that peeked at the interface via a raw uvm_resource_db#(virtual bus_if)::read_by_name("env.agent.driver", "vif", got) — expecting to read the same value the test set. That read_by_name returned 0 (not found), leaving got null — and the helper, trusting it, used the null handle. The value was in the pool, set by config_db — yet the raw resource_db read with the literal path string missed it, returning null, and the driver crashed. The config_db get (had they used it) would have found it fine.
config_db constructs the resource's scope and name from the component hierarchy — it is not stored under the literal string you passed — so a raw resource_db read_by_name with that literal string did not match the constructed scope/name, missing the value and returning null:
✗ SET with config_db, READ with raw resource_db using the LITERAL path string:
uvm_config_db#(virtual bus_if)::set(this, "env.agent.driver", "vif", vif);
// config_db stores it under a CONSTRUCTED scope/name derived from the hierarchy + field — NOT
// the literal "env.agent.driver"/"vif" you might assume
uvm_resource_db#(virtual bus_if)::read_by_name("env.agent.driver", "vif", got); // returns 0 → null
// the literal scope string doesn't match config_db's constructed scope → MISS → got stays null
// → driver uses null vif → CRASH (yet the value WAS in the pool!)
✓ READ it back with the SAME API (config_db) that constructed the scope/name:
uvm_config_db#(virtual bus_if)::get(this, "", "vif", got); // matches the hierarchical construction → found
// OR, if you genuinely need resource_db for both, SET and GET with resource_db consistently:
uvm_resource_db#(virtual bus_if)::set("my_scope", "vif", vif, this);
uvm_resource_db#(virtual bus_if)::read_by_name("my_scope", "vif", got, this); // matching literal scope → foundThis is the config-db-set-read-by-resource-db bug — the cardinal config-vs-resource mixing trap. The virtual interface was set via config_db and was in the pool — but a raw resource_db::read_by_name with the literal path string ("env.agent.driver"/"vif") returned 0 (not found), leaving the handle null, and the driver crashed. The deep reason is that config_db constructs the resource's scope and name from the component hierarchy — it's not stored under the literal string you passed to config_db::set (config_db transforms the instance path and field into a constructed scope/name for the pool). So a raw resource_db read with that literal string does not match the constructed scope/name — the value is in the pool, but under a tag the raw read didn't expect, so the read misses. The insidious part is that the value was definitely there (the dump showed it) and the config_db get would have found it — so the symptom (null handle, crash) contradicts the evidence (value in pool), which is baffling until you realize the two APIs address the pool differently. The fix is to read with the same API that set it: config_db::get(this, "", "vif", got) matches the hierarchical construction and finds the value; or, if you genuinely need resource_db for both, set and get with resource_db consistently (matching literal scope). The general lesson, and the chapter's thesis: config_db and resource_db share one pool but address it differently — config_db constructs the scope/name from the component hierarchy, so it is not stored under the literal string you passed, and a raw resource_db read with that literal string misses (returns null), causing a baffling null-handle crash even though the value is in the pool; so read a value with the same API that set it (config_db get for a config_db set), and only mix the APIs when you understand the exact scope/name mapping — use one API consistently per value, and reach for resource_db deliberately (set and get) when its generality is needed. A value set by config_db lives under a hierarchically-constructed tag, not the literal path — read it back with config_db, or a raw resource_db read finds nothing and hands you null.
The tell is a null/missing value that a dump shows is actually in the pool. Diagnose a cross-API scope mismatch:
- Confirm the value is in the pool. A resource dump showing the value present, while a read returns null, points at a scope/name mismatch, not a missing set.
- Check whether set and get use the same API. A config_db set read by raw resource_db (or vice versa) with a literal string won't match the constructed scope/name.
- Read with the API that set it. Switch the read to config_db::get if it was a config_db::set; if it then succeeds, the mismatch is confirmed.
- Inspect the constructed scope. config_db transforms the instance path and field into a constructed scope/name; a raw read must match that exactly.
Use one API consistently per value:
- Read a value with the same API that set it. config_db get for a config_db set; resource_db read for a resource_db set — matching the addressing.
- Don't peek at config_db values via raw resource_db with literal strings. The constructed scope/name won't match a naive literal path.
- Reach for resource_db deliberately, for both set and get. When you genuinely need general, non-hierarchical sharing, use resource_db consistently with a matching literal scope.
- When a value 'isn't there,' check the API and scope before the set. A dump confirming the value is in the pool redirects the debug from a missing set to a scope mismatch.
The one-sentence lesson: config_db and resource_db share one pool but address it differently — config_db constructs the scope/name from the component hierarchy, so a value it set is not under the literal string you passed, and a raw resource_db read with that literal string misses and returns null even though the value is in the pool, so read a value with the same API that set it and only mix the two when you understand the exact scope/name mapping.
Common Mistakes
- Reading a config_db value via raw resource_db with a literal string. config_db constructs the scope/name from the hierarchy; a naive literal read misses and returns null — read with the API that set it.
- Using raw resource_db where config_db's hierarchical precedence is needed. Top-down component config needs override-from-above; raw resource_db's manual precedence won't give it for free.
- Using config_db for genuinely non-hierarchical sharing. A resource keyed by name, not tied to the component tree, fits resource_db; forcing it into config_db fights the hierarchy.
- Assuming the two are unrelated. They share one pool — interoperable but addressed differently; mixing requires understanding the scope/name mapping.
- Mismatched scope strings in resource_db. resource_db precedence and matching are by literal scope; a reader's scope string must match the writer's.
- Reaching for resource_db by default. config_db is the default for almost all configuration; resource_db is the deliberate, occasional substrate for general sharing.
Senior Design Review Notes
Interview Insights
uvm_config_db is a typed, hierarchical convenience layer built on top of the general uvm_resource_db — they share one underlying resource pool, and a config_db set is a resource_db set underneath with a hierarchically-constructed scope and name. The resource_db is the general, lower-level store: you set and get values by a literal string scope and name, flat and not tied to the component tree. The config_db wraps it, adding three things for the common case of configuration flowing down the hierarchy. First, hierarchical scoping: instead of a literal scope string, you address by component instance path — config_db set takes the setting component, a relative path to the target, and a field name, and constructs the resource scope and name from the hierarchy. Second, hierarchical precedence: config_db resolves a get among competing sets by a purpose-built rule where a set from a higher component, or set later, wins, so configuration flows down and higher levels override lower ones. Third, typed convenience: a familiar, type-safe set/get API. Because they share one pool, they're interoperable — a value set one way can in principle be read the other way — but the crucial catch is that config_db constructs the scope and name, so they're not stored under the literal string you passed. The mental model is a hierarchical memo system built on a general bulletin board: resource_db is the flat board where you post under a string tag, and config_db addresses memos by org-chart path with override-from-above, translating to a constructed tag on the same board. The practical implication is that config_db is the default for component configuration, and resource_db is the lower-level substrate you reach for occasionally when you need general, non-hierarchical sharing. And because they share the pool but address it differently, mixing them — setting with config_db and reading with raw resource_db using a literal string — misses, because the literal string doesn't match the constructed scope. So they're one store with two views, and you should use one view consistently per value.
Config_db adds hierarchical scoping, hierarchical precedence, and typed convenience over the general resource_db, and you use config_db for top-down component configuration while using resource_db for general, non-hierarchical sharing. The three additions are what make config_db right for configuration. Hierarchical scoping means you address by component instance path, so configuration is naturally targeted at components in the tree — set this field on env.agent.driver — rather than under an arbitrary string. Hierarchical precedence means config_db resolves competing sets so that a higher-level or later set wins, which is exactly the override-from-above that layered configuration needs: an env sets a default, a test overrides it, and the test's override takes effect automatically. Typed convenience means a type-safe, familiar set/get. Together these make config_db the natural default for component configuration — virtual interfaces handed down to drivers, config objects passed to agents, parameters that flow down the hierarchy with overrides from above. Resource_db, by contrast, is the general, string-scoped store without the hierarchical precedence built in. You use it when the sharing isn't hierarchical — a resource keyed by name rather than by component path, shared across the testbench independent of the component tree, or a case needing finer scope control than the component path gives. These are the lower-level, less common cases. The decision rule is to ask whether the value is configuration flowing down the component tree with overrides from above — if so, config_db — or a general resource shared by name independent of hierarchy — if so, resource_db. In practice, config_db is the default for almost everything; most users rarely touch resource_db directly. Reaching for resource_db should be deliberate, when its generality is genuinely needed, and you should understand that it shares the pool with config_db. Using the wrong one fails subtly: raw resource_db where config_db was needed loses override-from-above, so a test override might not apply; config_db where resource_db was needed means fighting the hierarchy for non-hierarchical sharing. So match the mechanism to the nature of the sharing — hierarchical and top-down to config_db, general and flat to resource_db.
Because config_db constructs the resource's scope and name from the component hierarchy, so the value is not stored under the literal string you passed, and a raw resource_db read with that literal string doesn't match the constructed scope and name — it misses and returns null, even though the value is in the pool. When you call config_db set with a setting component, a path like env.agent.driver, and a field like vif, config_db doesn't simply store the value under scope env.agent.driver and name vif. It transforms the instance path and field into a constructed scope and name appropriate for hierarchical lookup. So in the underlying pool, the resource is tagged with that constructed scope/name, not the literal strings you might assume. Now if someone tries to read that value with raw resource_db read_by_name using the literal path string env.agent.driver and vif, that literal scope doesn't match the constructed scope config_db used, so the read fails to find it and returns zero, leaving the handle null. The value is genuinely in the pool — a resource dump would show it — but under a different tag than the raw read looked for. The result is baffling: the symptom is a null handle and a crash, but the evidence says the value is present, which seems contradictory until you realize the two APIs address the pool differently. The fix is to read the value with the same API that set it: config_db get matches the hierarchical construction and finds it. If you genuinely need resource_db for both set and get, use resource_db consistently with a matching literal scope. The general principle is to use one API consistently per value and not cross-read a config_db set with a raw resource_db literal string. The diagnostic clue is exactly this contradiction — a dump shows the value present, but a read returns null — which redirects the debugging from a missing set to a scope or API mismatch. So config_db-set values live under hierarchically-constructed tags, and you must read them back through config_db, or a literal resource_db read will miss and hand you null.
Config_db's hierarchical precedence resolves competing sets to the same field so that a set from a higher component, or set later, wins, and it matters because it provides override-from-above automatically, which is exactly what top-down configuration needs. In a typical testbench, the same configuration field can be set at multiple levels — an agent or env establishes a default, and a higher level like the test sets an override. Both sets land in the pool. When the lower component gets the field, config_db must resolve among the matching sets, and its precedence rule is purpose-built for hierarchy: a set from a component higher in the hierarchy wins over a lower one, and among comparable sets, a later set wins. So the test's override beats the env's default, and the component receives the most-authoritative value. This is the override-from-above pattern: a higher level can override a lower level's configuration without the lower levels knowing, which is the natural way configuration flows in a hierarchical testbench. It matters because it makes layered configuration just work. You can set sensible defaults deep in the environment and override them from the test for a particular scenario, and the override automatically takes precedence. Without this, you'd have to manually manage which set wins, which is error-prone. The contrast with resource_db is important: resource_db has manual precedence based on pool order and scope matching, not hierarchical precedence. So if you used raw resource_db for component configuration, a test-level override might not take precedence over an env set the way you'd expect — you'd have to manage the precedence yourself. This is one of the main reasons config_db is the default for component configuration: the hierarchical precedence delivers override-from-above for free. So when you get a field via config_db and multiple sets exist, you receive the highest-precedence one — higher in the hierarchy or set later — and that's what lets tests cleanly override environment defaults, which is the backbone of configurable, reusable testbenches.
You reach for resource_db directly when the sharing is genuinely non-hierarchical — a resource keyed by name rather than by component path, shared across the testbench independent of the component tree — or when you need finer scope control than the component path provides; otherwise config_db is the default. Config_db is built for configuration that flows down the component hierarchy with overrides from above, and that covers the vast majority of cases — virtual interfaces, config objects, parameters handed from higher components to lower ones. For all of that, config_db's hierarchical scoping and precedence are exactly right, and you should use it. Resource_db is the lower-level, general substrate, and you go to it deliberately when config_db's hierarchical model doesn't fit. The main case is non-hierarchical sharing: a resource that isn't naturally addressed by a component path — something shared globally or by a logical name independent of where components sit in the tree. If you have a resource that many unrelated components need to look up by a common name, and the hierarchy isn't the right addressing scheme, resource_db's flat, string-scoped store is the better fit. Another case is when you need finer or different scope control than the component instance path gives — resource_db lets you define arbitrary scope strings and matching, which is more flexible than config_db's hierarchy-derived scopes. There are also cases where you're interacting with the resource pool more directly — querying, dumping, or manipulating resources at the lower level for debugging or advanced patterns. But these are the exceptions. The guidance is that config_db is the default for almost everything, and resource_db is the occasional, deliberate choice when generality is genuinely needed. When you do use resource_db, use it consistently — set and get both with resource_db and matching scope strings — rather than mixing it with config_db on the same value, which causes scope-mismatch misses. And remember they share one pool, so resource_db operations can see config_db-set values if you match the constructed scope, which is mainly relevant for debugging and dumps. So the rule of thumb: default to config_db; reach for resource_db only when the sharing is non-hierarchical or needs finer scope control than the component tree provides.
Exercises
- Map the relationship. Explain how a config_db set becomes a resource_db set underneath, and what config_db constructs.
- Choose the DB. For a virtual interface handed to a driver and for a globally-shared named resource, pick config_db or resource_db for each and why.
- Fix the null. A config_db-set value reads null via raw resource_db. Explain the cause and the two fixes.
- Show the precedence. Describe how an env default and a test override resolve when a component gets the field via config_db.
Summary
uvm_config_dbis a typed, hierarchical convenience layer over the generaluvm_resource_db— they share one underlying resource pool, with aconfig_dbset being aresource_dbset with a hierarchically-constructed scope and name.- config_db adds three things over resource_db for top-down configuration: hierarchical scoping (address by component instance path), hierarchical precedence (a higher or later set wins — override-from-above), and typed convenience.
- Use config_db by default for component configuration (interfaces, config objects, parameters flowing down with overrides); reach for resource_db deliberately for general, non-hierarchical sharing or finer scope control — config_db is the default, resource_db the occasional substrate.
- They share one pool but address it differently: config_db constructs the scope/name from the hierarchy, so mixing a config_db set with a raw resource_db read using a literal string misses (null handle, crash) even though the value is in the pool — read a value with the same API that set it.
- The durable rule of thumb: config_db is a typed, hierarchical layer over the general resource_db, sharing one pool — use config_db by default for top-down component configuration (it adds hierarchical scoping and override-from-above precedence), reach for resource_db deliberately for general, non-hierarchical sharing, and always read a value with the same API that set it, because config_db stores values under a hierarchically-constructed scope/name, not the literal string you passed, so a cross-API read with a naive string silently misses and returns null.
Next — Usage Patterns: with the relationship clear, the next chapter covers the practical patterns — how resources and config are written and read across the testbench: the canonical set-in-build, get-in-build flow; configuring across the hierarchy; sharing virtual interfaces and config objects; wildcard scoping; and the conventions that keep configuration predictable and debuggable at scale.