UVM
Resource DB Debugging
Debugging configuration when a value doesn't arrive or is wrong — using the resource database dump as ground truth, classifying a missed get by its cause (scope, naming, timing, or type mismatch), reading precedence to find which set won, the type-parameterization trap where scope and name match but the type doesn't, and the systematic process from a wrong or missing value to its root cause.
Resource Database · Module 23 · Page 23.4
The Engineering Problem
A configuration value didn't arrive — a driver got a null interface, a component used a default it shouldn't have — or it arrived wrong (an unexpected value, not the one you set). The previous chapters gave you the patterns and the check-the-get discipline (Module 23.3), so the get fired loudly — but now you must find why. And configuration misses are uniquely confusing because the value is often in the pool — set correctly, present — yet the get misses, so the symptom (a default value, a crash) seems to contradict the evidence (the value is there). The cause is a mismatch between the set and the get — in scope, naming, timing, or — the trickiest — type: config_db is type-parameterized, so a set#(int) and a get#(bit[31:0]) with matching scope and name still miss, because the types differ. Or the value arrived wrong because multiple sets competed and precedence picked a different winner than you expected. The problem this chapter solves is resource DB debugging: using the dump as ground truth, classifying a missed get by its cause, reading precedence to find which set won, and the systematic process from a wrong or missing value to its root cause.
Resource DB debugging is finding why a configuration value didn't arrive or is wrong. The primary tool is the resource database dump (uvm_config_db::dump(), print_resources()) — it prints what's actually in the pool: every resource's scope, name, value, type, who set it, and precedence. The dump is ground truth — compare it to your mental model to find the discrepancy. For a missed get, classify the cause by auditing the set/get pair: scope mismatch (paths don't match), naming mismatch (field-name typo), timing (set after the get), or type mismatch (the trickiest — config_db is type-parameterized, so set#(int) ≠ get#(bit[31:0]) even with matching scope and name). For a wrong value (not missing), read the precedence: multiple sets matched, and hierarchical precedence (higher/later wins) picked the winner — the dump shows the competing sets, so you trace which won and why. The systematic process: dump → compare to expectation → classify the miss (or read precedence) → fix the root cause. The combo that localizes: the checked get (loud failure location) + the dump (pool contents) + the set/get pair audit (scope/name/type/timing). This chapter is resource DB debugging: the dump, classifying misses, reading precedence, the type trap, and the systematic process.
How do you debug configuration — use the resource database dump to see what's actually in the pool, classify a missed get by its cause (scope, naming, timing, or type mismatch), read precedence to find which set won when a value is wrong, and follow a systematic process from a missing or wrong value to its root cause?
Motivation — why config bugs need the dump, not guessing
Configuration bugs are confusing precisely because the value is often present but missed — so guessing fails and the dump is essential. The reasons:
- The symptom contradicts the evidence. A missed get produces a default/null (crash or wrong behavior), but the value is often in the pool (set correctly). So the symptom ("the value isn't there") contradicts the evidence ("the value is there") — baffling until you dump and see the discrepancy.
- The dump is ground truth. Guessing about what's in the pool is unreliable — the dump shows it: every resource, its scope, name, value, type, setter, precedence. The dump resolves the contradiction by showing what's actually there versus what you looked for.
- The four miss-causes are distinct, and the dump distinguishes them. A miss is a scope, naming, timing, or type mismatch — and each needs a different fix. The dump + the set/get audit tell you which: is the value there (so scope/name/type), and is it there in time (timing)?
- The type mismatch is invisible without the dump. A
set#(int)/get#(bit[31:0])mismatch has matching scope and name — so it looks correct, and only the dump (showing the value under the set's type) reveals that the get's type doesn't match. - A wrong value is a precedence question. When a get returns the wrong value, multiple sets competed, and precedence picked the winner. Reading the precedence (from the dump) tells you which set won and why — not guessable, but visible in the dump.
The motivation, in one line: config bugs are confusing because the value is often present but missed (symptom contradicts evidence), so guessing fails — the dump is essential as ground truth (what's actually in the pool, with scope/name/value/type/precedence), distinguishing the four miss-causes (scope/naming/timing/type), revealing the invisible type mismatch, and showing the precedence winner for a wrong value.
Mental Model
Hold config debugging as reconciling your delivery records against the actual warehouse inventory — the dump is the manifest, and the discrepancy is the cause:
When a delivery doesn't arrive, you don't guess — you pull the warehouse inventory and reconcile it against your records. Configuration debugging is the same. The dump is the warehouse manifest: it lists every parcel actually in the pool, with its address (scope), its label (name), its contents and type (value and parameterized type), who shipped it (the setter), and its priority (precedence). Your records are what you think you set and what the recipient looked for. You compare the two. Often the confusing part is that the parcel is in the inventory — it was shipped and received into the warehouse — but the recipient couldn't find it, which means the recipient looked under the wrong address, the wrong label, the wrong type of parcel, or looked before it arrived. The manifest shows you exactly which: the parcel is there under address A, but the recipient searched address B (scope mismatch); it's labeled X, but they asked for Y (naming); it's a crate of one kind, but they expected another (type); it wasn't there yet when they looked (timing). And if the wrong parcel arrived, the manifest shows two parcels competed for the same slot, and the higher-priority one was delivered (precedence). So you never guess about what's in the pool — you read the manifest, reconcile it against your expectation, and the discrepancy between manifest and expectation is the root cause. Picture config debugging as warehouse reconciliation. When a delivery (config) doesn't arrive, you don't guess — you pull the inventory (the dump) and reconcile it against your records. The dump is the manifest: it lists every parcel actually in the pool, with its address (scope), label (name), contents and type (value and parameterized type), who shipped it (the setter), and its priority (precedence). Your records are what you think you set and what the recipient looked for. You compare. The confusing part is often that the parcel is in the inventory — shipped and received — but the recipient couldn't find it, meaning they looked under the wrong address, label, type, or before it arrived. The manifest shows exactly which: there under address A, but searched address B (scope); labeled X, asked for Y (naming); a crate of one kind, expected another (type); not there yet when they looked (timing). And if the wrong parcel arrived, the manifest shows two parcels competed and the higher-priority one was delivered (precedence). So you never guess about the pool — you read the manifest, reconcile it against expectation, and the discrepancy is the root cause.
So config debugging is warehouse reconciliation: the dump is the manifest (ground truth — every parcel's address, label, type, value, setter, priority), and debugging is comparing the manifest to your expectation (what you set, what the recipient looked for). The discrepancy is the cause: present-but-mis-addressed (scope), mislabeled (naming), wrong-type (type), late (timing), or out-prioritized (precedence). Don't guess the pool — dump it, reconcile against expectation, and the discrepancy is the root cause.
Visual Explanation — the systematic debug process
The defining picture is the process: dump (ground truth) → compare to expectation → classify the miss (or read precedence) → fix.
The figure shows the systematic debug process. Dump (step 1): uvm_config_db::dump() / print_resources() — every resource's scope, name, value, type, setter, precedence. Compare (step 2): is the value present, under the scope/name/type you expected, and was it set before the get? Classify (step 3): missing/mis-keyed → scope/naming/timing/type mismatch (audit set vs get); wrong value → which set won (read precedence). Fix (step 4): align scope/name/type, fix timing, or adjust precedence/override — the specific discrepancy the dump revealed. The crucial reading is that the dump anchors the whole process — it replaces guessing with reconciliation. Without the dump, you'd speculate about what's in the pool (and why the get missed) — unreliable. With it, you see the ground truth and compare it to your expectation, and the discrepancy is right there. The branch at step 3 is key: a missing value (the get found nothing) is a mismatch between set and get (scope/naming/timing/type — one of four, found by auditing the pair), while a wrong value (the get found something, but not what you expected) is a precedence question (multiple sets matched — which won?). The two branches have different diagnostics: classify the mismatch (for missing) versus read the precedence (for wrong). The brand-colored dump/compare are the anchor (ground truth + expectation), the success-colored classify/read-precedence is the diagnosis, and the default-colored fix is the resolution. The diagram is the debug process: dump (ground truth) → compare (vs expectation) → classify-or-read-precedence → fix — reconciliation, not guessing. Dump the pool, compare to expectation, classify the miss or read the precedence, and fix the discrepancy the dump revealed.
RTL / Simulation Perspective — the dump and the set/get audit
In code, the debug is dump the pool and audit the set/get pair. The example shows a dump, and the four miss-causes found by comparing set and get.
// === THE DUMP: ground truth — what's ACTUALLY in the pool ===
uvm_config_db#(int)::dump(); // or: uvm_resource_db::dump(); / print_resources()
// prints each resource: scope, name, value, TYPE, who set it, precedence
// e.g.: scope="uvm_test_top.env.agent.driver" name="addr_width" type=int value=32 set_by=env
// === AUDIT THE SET/GET PAIR for the FOUR miss-causes ===
// the SET (in env):
uvm_config_db#(int)::set(this, "agent.driver", "addr_width", 32);
// (1) SCOPE mismatch — getter's path doesn't resolve to the setter's target
// uvm_config_db#(int)::get(this, "wrong.path", "addr_width", aw); // scope ≠ → MISS
// (2) NAMING mismatch — field name differs
// uvm_config_db#(int)::get(this, "", "addr_w", aw); // "addr_w" ≠ "addr_width" → MISS
// (3) TIMING — get ran before the set (set too late / wrong phase)
// get in build_phase, set in connect_phase → value not in pool yet → MISS
// (4) TYPE mismatch — config_db is TYPE-PARAMETERIZED: different type → MISS (DebugLab)
// uvm_config_db#(bit[31:0])::get(this, "", "addr_width", aw); // bit[31:0] ≠ int → MISS!
// ← scope AND name match, but TYPE differs → the dump shows it present as `int`, get as bit misses
// === FOR A WRONG (not missing) value: read PRECEDENCE — which competing set won? ===
// dump shows two sets for the same scope/name → the HIGHER/LATER one won (hierarchical precedence)
// THE COMBO: checked get (loud WHERE) + dump (pool CONTENTS) + set/get audit (the mismatch)The code shows the debug tools. The dump (uvm_config_db#(int)::dump(), print_resources()) prints each resource: scope, name, value, type, setter, precedence — ground truth. Auditing the set/get pair for the four miss-causes: given a set (set(this, "agent.driver", "addr_width", 32)), a miss is (1) scope mismatch (get(this, "wrong.path", ...) — path doesn't resolve to the setter's target), (2) naming mismatch (get(..., "addr_w", ...) — addr_w ≠ addr_width), (3) timing (get in build_phase, set in connect_phase — value not in pool yet), or (4) type mismatch (get#(bit[31:0]) — config_db is type-parameterized, so bit[31:0] ≠ int → miss, even though scope and name match — the DebugLab). For a wrong value, read precedence: the dump shows two sets for the same scope/name → the higher/later one won. The closing comment marks the combo: checked get (loud where) + dump (pool contents) + set/get audit (the mismatch). The shape to carry: the dump is the ground truth (what's in the pool, with scope/name/value/type/setter/precedence), and auditing the set/get pair against it finds the mismatch — scope (paths), naming (field names), timing (set-before-get), or type (the parameterized type). The type cause is the most insidious — config_db#(T) stores and looks up by T, so set#(int) and get#(bit[31:0]) don't match despite identical scope and name, and only the dump (showing the value as int) reveals it. The precedence read (for a wrong value) is a different diagnostic — the dump shows the competing sets and which won. Dump the pool, audit the set/get pair for scope/name/timing/type, and read precedence for a wrong value.
Verification Perspective — the four causes of a missed get
The core of debugging a missing value is classifying the miss into its cause — scope, naming, timing, or type. Seeing the four together, each a mismatch between set and get, is the diagnostic.
The figure shows the four causes of a missed get, each a mismatch between the set and the get. Scope mismatch: the getter's path doesn't resolve to the setter's target. Naming mismatch: the field names differ (typo). Timing: the get ran before the set, so the value wasn't in the pool yet. Type mismatch (the warning-colored — the trickiest): config_db is type-parameterized, so set and get with different parameterized types miss even when scope and name match exactly. The verification insight is that the dump distinguishes them: a value present under the set's scope/name/type, while the get used a different one, points at scope, naming, or type (the value is in the pool, but the get's key doesn't match); a value absent at get time points at timing (the value wasn't in the pool yet). So the first dump question is "is the value in the pool at all?" — if yes, it's scope/naming/type (a key mismatch); if no (at the get's time), it's timing. Then, for a key mismatch, you compare the set's and get's scope, name, and type to find which differs. The type cause (the warning-colored) is separated out because it's the most deceptive: scope and name match exactly, so the get looks correct, and only the type (the #(T) parameter) differs — invisible without the dump (which shows the value's type) and easy to overlook (you check scope and name, see they match, and miss the type). The warning-colored missed get and type mismatch bracket the brand-colored scope/naming/timing — and classifying the miss into one of the four (via the dump and the set/get audit) is the diagnosis. The diagram is the miss-cause classification: scope (paths), naming (field names), timing (set-before-get), type (parameterized types — scope/name still match!) — four mismatches, distinguished by the dump. A missed get is a scope, naming, timing, or type mismatch — the dump tells you whether the value is present (key mismatch) or absent at get time (timing).
Runtime / Execution Flow — reading precedence for a wrong value
When a value is wrong (not missing), the diagnostic differs: multiple sets matched, and precedence picked the winner — so you read the precedence from the dump. The flow shows it.
The flow shows reading precedence for a wrong value. Wrong value (step 1): the get succeeded (not a miss) but the value isn't what you set — multiple sets matched the resource. Dump (step 2): the pool lists all sets matching that scope/name, each with its setter and precedence. Read (step 3): which set won and why — a higher-component or later set wins (perhaps an unintended wildcard match or a forgotten higher-level override). Fix (step 4): remove the unintended set, narrow the scope, or adjust precedence so the intended set wins. The runtime insight is that a wrong value is a fundamentally different bug than a missing one — and needs a different diagnostic. A missing value means no set matched (a mismatch — Figure 2); a wrong value means the wrong set won (a precedence outcome). For the wrong case, the dump is still the key tool, but you read it differently: not "is the value there?" (it is) but "which of the competing sets won, and why?". The common causes of a surprising winner are an unintended wildcard match (a broad "*" or "env.*.driver" set matched a component you didn't mean to configure) or a forgotten higher-level override (a test or base env set you forgot about, which outranks your intended set by hierarchy). Reading the precedence — the hierarchical proximity and set order the dump shows — reveals which won and why, so you can fix it (remove the unintended set, narrow the over-broad wildcard, or adjust the precedence). The brand-colored wrong-value/dump feed the success-colored precedence read, yielding the default-colored fix. The flow is the precedence diagnostic: unexpected value → dump the competing sets → read which won and why → fix the precedence — the complement to the miss-classification (Figure 2) for the wrong-value case. A wrong value is a precedence outcome — dump the competing sets, read which won and why, and fix the unintended set or precedence.
Waveform Perspective — present in the pool, but missed by the get
The most confusing config bug is visible as a contradiction: the value is in the pool (the dump shows it), yet the get misses — because the get's key (scope/name/type) doesn't match the set's. The waveform shows the present-but-missed signature.
The value is in the pool, yet the get misses — the present-but-missed signature of a key mismatch
12 cyclesThe waveform shows the present-but-missed signature. The set places a value in the pool (set_done), and a dump confirms it is present (dump_shows_present stays high — the value is there). Later, the get runs with a mismatched key — a different scope, name, or type than the set used — so it misses (get_miss), even though the value is present, and the component uses a default (default_used). The crucial reading is the contradiction that defines the hardest config bug: the dump says present, the get says missed — both true, because the value is in the pool (under the set's key) but the get looked under a different key. This contradiction is the diagnostic signature: when a dump confirms the value is present yet the get misses, the discrepancy is in the get's key (scope/name/type), not a missing set. This redirects the debug decisively — away from "the value wasn't set" (it was) and toward "the get's key doesn't match the set's" (compare scope, name, type). The value present the whole time (dump_shows_present high throughout) rules out timing (the value was there before the get) — so it's scope, naming, or type. The picture to carry is that the dump's "present" against the get's "missed" is the signature of a key mismatch — and the only way to see it is to dump the pool (the value's actual key) and compare it to the get's key. Without the dump, you'd assume the value wasn't set (and chase the setter) — wasting effort on the wrong side. Reading the waveform this way — does the dump show the value present while the get misses? — is recognizing a key mismatch. The present-and-missed contradiction is the signature of scope/naming/type (not timing, not missing) — the get is looking in the wrong place. When the dump shows the value present but the get misses, the bug is the get's key (scope/name/type), not a missing set — compare them.
DebugLab — the type mismatch that hid behind a matching scope and name
A config value set as int and got as bit[31:0] that silently missed despite a perfect scope and name match
A driver used a default addr_width of 0 — producing malformed transactions — even though the env clearly configured it: uvm_config_db#(int)::set(this, "agent.driver", "addr_width", 32). The driver's get was checked (per Module 23.3), and the check fired: "addr_width not configured for driver". The verifier triple-checked the scope (agent.driver — correct) and the field name (addr_width — spelled identically in set and get), confirmed the set ran before the get (both in build_phase, env before driver), and was baffled: every visible thing matched, yet the get missed. Then they dumped the pool: the resource was there — scope="...agent.driver" name="addr_width" type=int value=32 — present, under the exact scope and name the get used. The only difference, visible only in the dump, was the type: the resource was type int, but the driver's get was uvm_config_db#(bit[31:0])::get(...) — type bit[31:0]. The scope and name matched perfectly; the type did not.
config_db is type-parameterized — uvm_config_db#(T) stores and looks up resources by type T — so a set#(int) and a get#(bit[31:0]) are different lookups and do not match, even when the scope and name are identical; the get missed on the type, invisibly, because scope and name looked correct:
✗ SET as int, GET as bit[31:0] — scope AND name match, but the TYPE differs:
// env: uvm_config_db#(int) ::set(this, "agent.driver", "addr_width", 32); // type = int
// driver: uvm_config_db#(bit[31:0])::get(this, "", "addr_width", aw); // type = bit[31:0]
// config_db is TYPE-PARAMETERIZED: #(int) and #(bit[31:0]) are DIFFERENT lookups
// scope ("agent.driver") matches, name ("addr_width") matches — but TYPE doesn't → MISS
// the dump shows the value present as `int` → the get (as bit[31:0]) can't find it → baffling
✓ MATCH the parameterized type between set and get (use the dump to spot the type):
// dump reveals: type=int → make the get use the SAME type:
uvm_config_db#(int)::get(this, "", "addr_width", aw); // type=int → MATCHES the set → found
// (or standardize the field's type project-wide so set and get always agree)This is the type-mismatch bug — the trickiest config miss, and the one the dump exists to catch. The env set addr_width as uvm_config_db#(int) (type int), but the driver got it as uvm_config_db#(bit[31:0]) (type bit[31:0]). The deep reason it missed is that config_db is type-parameterized: uvm_config_db#(T) stores and looks up resources by the type T, so #(int)::set and #(bit[31:0])::get are different lookups — the get looks for a bit[31:0] resource and does not find the int one, even though the scope and name are identical. The insidious part is exactly that scope and name match — so the verifier checked the two most-common causes (scope, naming), saw they matched, confirmed timing, and was baffled, because the type — the #(T) parameter — is easy to overlook (it's not in the string arguments; it's the parameterization). Only the dump — which shows the resource's type (int) — revealed that the get's type (bit[31:0]) didn't match. The fix is to match the parameterized type: the dump shows type=int, so make the get use uvm_config_db#(int)::get(...) (or standardize the field's type project-wide so set and get always agree). The general lesson, and the chapter's thesis: config_db is type-parameterized, so a set and a get must match in TYPE, not just scope and name — a type mismatch (set#(int) vs get#(bit[31:0])) makes the get miss even when scope and name match exactly, and because scope and name look correct, the cause is invisible without the dump (which shows the value present under the set's type while the get under a different type misses); so when a checked, correctly-scoped, correctly-named get still misses, dump the pool and compare the type — the dump is the only tool that reveals a type mismatch — and match the parameterized type between set and get (or standardize the field's type). A config get can be perfectly scoped and named and still miss on the type — dump the pool, read the resource's type, and make the get's type match the set's.
The tell is a checked, correctly-scoped, correctly-named get that still misses. Diagnose a type mismatch:
- Dump the pool and read the resource's type. The dump shows each resource's type; a value present under the right scope/name but a different type is a type mismatch.
- Compare the get's parameterized type to the set's. uvm_config_db#(T) — the T in the set and the get must be identical, not just compatible.
- Rule out scope, naming, and timing first. If scope and name match and the value is present before the get, the remaining cause is type.
- Watch for int versus bit-vector and class-type mismatches. int vs bit[31:0], or a base class vs a derived class, are common type mismatches that look correct by scope and name.
Match the type, and dump when scope/name look right:
- Use the same parameterized type in set and get. uvm_config_db#(T) must use identical T; standardize each field's type project-wide.
- Dump the pool when a correctly-scoped, named get misses. The dump reveals a present-but-differently-typed value that scope/name checks can't.
- Prefer config objects for typed bundles. A config object carries fields by member type, avoiding loose per-field type-parameter mismatches.
- Document each config field's type. A component's config interface should specify the type, so setters and getters agree.
The one-sentence lesson: config_db is type-parameterized, so a set and a get must match in type, not just scope and name — a type mismatch (set#(int) vs get#(bit[31:0])) makes the get miss even when scope and name match exactly, and the cause is invisible without the dump, which shows the value present under the set's type while the get under a different type misses, so dump the pool when a correctly-scoped, named get still misses and make the get's type match the set's.
Common Mistakes
- Guessing instead of dumping. Speculating about what's in the pool is unreliable; dump the pool for ground truth and reconcile against expectation.
- Checking scope and name but not type. config_db is type-parameterized; a set#(int) and get#(bit[31:0]) miss despite matching scope and name — compare the type via the dump.
- Assuming a miss means a missing set. The value is often present but mis-keyed; a dump showing it present redirects debug to the get's scope/name/type.
- Confusing a wrong value with a missing one. A missing value is a set/get mismatch; a wrong value is a precedence outcome — read the competing sets for the latter.
- Ignoring unintended wildcard matches. A broad wildcard set can match components you didn't intend, producing a surprising precedence winner; narrow the scope.
- Not localizing with the checked get. A checked get tells you where the miss happened; combined with the dump and the set/get audit, it pinpoints the cause.
Senior Design Review Notes
Interview Insights
You dump the pool to establish ground truth, compare it to your expectation, classify the miss by its cause, and fix the root cause — rather than guessing. The primary tool is the resource database dump, via uvm_config_db dump or print_resources, which prints what's actually in the pool: every resource's scope, name, value, type, who set it, and precedence. This is essential because configuration misses are uniquely confusing — the value is often actually in the pool, set correctly, yet the get misses, so the symptom seems to contradict the evidence. The dump resolves that contradiction by showing what's really there. So the process is: dump the pool, then compare it to your mental model. Is the value present? Under the scope, name, and type you expected? Was it set before the get ran? If the value is present but the get missed, the discrepancy is in the get's key — scope, naming, or type — so you audit the set against the get to find which differs. If the value is absent at get time, it's timing — the set ran after the get. The four miss causes are scope mismatch, where the getter's path doesn't resolve to the setter's target; naming mismatch, a field-name typo; timing, the get before the set; and type mismatch, the trickiest, where config_db's type-parameterization means a set and get with different parameterized types miss even when scope and name match. The dump distinguishes them — a value present under the right scope and name but a different type points at type, a value absent at get time points at timing. The combo that localizes is the checked get, which tells you loudly where the miss happened, plus the dump, which shows the pool contents, plus the set/get audit, which finds the specific mismatch. The mental model is warehouse reconciliation: the dump is the manifest of what's in the pool, and you reconcile it against what you sent and what the recipient looked for, and the discrepancy is the root cause. So you never guess — you dump and reconcile.
Because config_db is type-parameterized — uvm_config_db#(T) stores and looks up resources by the type T — so a set and a get with different parameterized types are different lookups and don't match, even when the scope and name are identical. When you call uvm_config_db#(int)::set with a scope and name, it stores a resource of type int under that scope and name. When you call uvm_config_db#(bit[31:0])::get with the exact same scope and name, it looks for a resource of type bit[31:0], and the int resource doesn't match the bit[31:0] lookup, so the get misses. The scope matches, the name matches, but the type — the hash parameter — differs, and that's enough to miss. This is the trickiest config bug precisely because scope and name are the two things you naturally check, and they look correct, so you're baffled when the get still misses. The type is easy to overlook because it's not in the string arguments to set and get — it's the parameterization, the #(T), which is separate from the scope and name strings. The only tool that reveals it is the dump, which shows each resource's type. So if you dump the pool and see the value present under the right scope and name but as type int, while your get is parameterized as bit[31:0], you've found it. Common type mismatches that look correct by scope and name are int versus bit-vector — int vs bit[31:0] — and class-type mismatches like a base class versus a derived class. The fix is to match the parameterized type between set and get — use the same T — and ideally standardize each config field's type project-wide so setters and getters always agree, or use config objects that carry fields by member type rather than loose per-field type parameters. So the lesson is that a config get can be perfectly scoped and named and still miss on the type, and when a checked, correctly-scoped, correctly-named get misses, you dump the pool, read the resource's type, and make the get's type match the set's. Scope and name aren't enough — the type is part of the key.
The resource dump is a printout of everything actually in the resource pool — every resource's scope, name, value, type, who set it, and precedence — produced by uvm_config_db dump or print_resources, and it's the primary debug tool because it provides ground truth that replaces guessing with reconciliation. Configuration is invisible by nature: values flow through a shared pool, set in one place and got in another, and when something goes wrong, you can't see the pool's contents by reading the code, because the runtime state — which sets actually landed, under what keys, with what precedence — isn't obvious from the source. The dump makes that runtime state visible. It lists each resource with its full identity: the scope it's under, its name, its value, its parameterized type, the component that set it, and its precedence relative to competing sets. With that, you stop speculating about what's in the pool and start reconciling what's actually there against what you expected. This is decisive for the most confusing config bugs, where the symptom contradicts the evidence — a component uses a default as if the value wasn't set, but the value is in fact in the pool. The dump shows the value present, which immediately redirects debugging from the false hypothesis that the value wasn't set toward the real cause, that the get's key — scope, name, or type — doesn't match the set's. The dump also distinguishes the four miss causes: present under a different scope, name, or type means a key mismatch; absent at get time means timing. And for a wrong value rather than a missing one, the dump shows all the competing sets and their precedence, so you can read which won and why. The mental model is a warehouse manifest: when a delivery doesn't arrive, you pull the inventory and reconcile against your records, rather than guessing. The dump is that manifest. So the dump is the first step in any config debug — you dump the pool, compare to expectation, and the discrepancy between manifest and expectation is the root cause, which is far more reliable than reasoning about the code alone.
A wrong value is a precedence question, not a mismatch — multiple sets matched the same scope and name, and precedence picked a winner you didn't expect — so you dump the pool to see the competing sets and read which won and why. The key distinction is between missing and wrong. A missing value means no set matched the get — a scope, naming, timing, or type mismatch, where you classify the miss. A wrong value means the get succeeded — it found something — but the value isn't what you set, which means more than one set matched the resource and config_db's precedence resolution chose among them. So the diagnostic is different. You still dump the pool, because it's still the ground truth, but you read it differently: not is the value there, but which of the competing sets for this scope and name won, and why. The dump shows all the matching sets, each with its setter and precedence. config_db's precedence is hierarchical — a set from a higher component, or a set made later, wins. So you read the precedence to find the winner. The common causes of a surprising winner are two. One is an unintended wildcard match: a broad wildcard set, like one scoped to env dot star dot driver or just star, matched a component you didn't mean to configure, and that set won or interfered. Two is a forgotten higher-level override: a test or base environment set a value you forgot about, and because it's higher in the hierarchy, it outranks your intended set. Reading the precedence from the dump reveals which it is. Then you fix it according to the cause — remove the unintended set, narrow the over-broad wildcard scope so it stops matching the wrong component, or adjust the precedence so your intended set wins, perhaps by setting it from a higher level or later. So for a wrong value, the workflow is dump the competing sets, read which won by precedence and why, and fix the unintended set or the precedence, which is the complement to classifying a mismatch for a missing value.
The systematic process is: dump the pool, compare it to your expectation, classify the miss or read the precedence depending on whether the value is missing or wrong, and fix the root cause. Step one, dump the pool, using uvm_config_db dump or print_resources, to get ground truth — every resource's scope, name, value, type, setter, and precedence. This replaces guessing with seeing. Step two, compare the dump to your expectation: is the value present, under the scope, name, and type you expected, and was it set before the get ran? This comparison surfaces the discrepancy. Step three branches on the nature of the bug. If the value is missing — the get found nothing — you classify the mismatch between set and get into one of four causes: scope, where the paths differ; naming, where the field names differ; timing, where the get ran before the set so the value wasn't in the pool yet; or type, where config_db's type-parameterization means the set and get types differ even though scope and name match. You distinguish these with the dump: present under a different key means scope, naming, or type; absent at get time means timing; and you compare the set's and get's scope, name, and type to find which differs. If instead the value is wrong — the get succeeded but returned an unexpected value — you read the precedence: the dump shows the competing sets, and you find which won and why, typically an unintended wildcard match or a forgotten higher-level override. Step four, fix the root cause: align the scope, name, or type; fix the timing by setting before the get; or adjust the precedence by removing the unintended set or narrowing a wildcard. Throughout, the checked get from the usage patterns is your ally — it tells you loudly where the miss happened, and combined with the dump and the set/get audit, it pinpoints the cause. The whole process is reconciliation: the dump is the manifest, your expectation is your records, and the discrepancy is the root cause. This systematic approach — dump, compare, classify or read precedence, fix — turns baffling, contradictory config symptoms into a reliable path to the cause, which is far better than reasoning about the code in the dark.
Exercises
- Read the dump. Given a dump showing a resource as type int and a get parameterized as bit[31:0] under the same scope/name, name the bug and the fix.
- Classify the miss. For a get that found nothing, list the four causes and how the dump distinguishes them.
- Trace the precedence. A get returns an unexpected value. Describe how you'd use the dump to find which set won and why.
- Recognize present-but-missed. Explain why a dump showing the value present while the get misses rules out timing and a missing set.
Summary
- Resource DB debugging is finding why a configuration value didn't arrive or is wrong — and the primary tool is the dump (
uvm_config_db::dump()/print_resources()), which prints what's actually in the pool: each resource's scope, name, value, type, setter, and precedence — ground truth to reconcile against expectation. - For a missed get, classify the cause by auditing the set/get pair: scope mismatch, naming mismatch, timing (set after the get), or type mismatch — the trickiest, because
config_dbis type-parameterized, soset#(int)andget#(bit[31:0])miss even when scope and name match exactly. - For a wrong (not missing) value, read the precedence: multiple sets matched, and hierarchical precedence (higher/later wins) chose the winner — the dump shows the competing sets, so you trace which won and why (often an unintended wildcard match or a forgotten higher-level override).
- The present-but-missed contradiction (dump shows present, get misses) is the signature of a key mismatch (scope/naming/type, not timing) — redirecting debug from "value not set" to "get's key is wrong".
- The durable rule of thumb: debug configuration by dumping the pool for ground truth and reconciling it against expectation — for a missing value, classify the set/get mismatch (scope, naming, timing, or type, remembering config_db is type-parameterized so a perfectly-scoped, perfectly-named get still misses on a type mismatch that only the dump reveals); for a wrong value, read the precedence to find which competing set won and why; and use the checked get to localize the miss — never guess about the pool's contents when the dump shows them.
Next — Messaging Infrastructure: the next module turns to how a UVM testbench communicates — the reporting system. Every component emits messages — info, warnings, errors, fatals — and at scale this becomes a flood that must be controlled: filtered by verbosity, routed to logs, counted for pass/fail, and made actionable. The module opens with the messaging infrastructure: the report macros, severities, and the machinery that turns scattered prints into a managed, filterable, accountable reporting system.