UVM
Factory Failures
Applying the debugging methodology to the factory — why a factory override silently does nothing and the base type runs instead: the object was constructed with new() instead of create() so the factory never saw it, the type wasn't registered with uvm_*_utils, the override was set after the target was already built, or the wrong override flavor or instance path was used; how to read the symptom, observe it with the factory print and topology, and fix each root cause so the override actually takes.
UVM Debugging · Module 27 · Page 27.2
The Engineering Problem
You registered a factory override — your extended driver should replace the base, your error-injecting monitor should replace the normal one — and nothing changed. The base type ran as if the override didn't exist. No error, no warning — the testbench built and ran, just with the wrong type. This is the factory's characteristic failure (from 27.1's symptom map: a base type running where an override was expected → factory), and it's especially confusing because the most common cause leaves the override looking perfectly correct: you can print the factory and see your override registered, exactly as written — and it still doesn't take. The reason is a truth about the factory that's easy to forget: the factory can only override what it creates. An override is a substitution rule the factory applies when it is asked to construct a type — and if the object was constructed some other way (a direct new()), the factory never saw the request, so no rule could apply. Beyond that, the override can fail to register (the type wasn't registered with the factory), register too late (after the target was already built), or register with the wrong scope (a type override where an instance was needed, or an instance path that doesn't match). The problem this chapter solves is factory failures: applying the methodology (27.1) to the factory — reading the symptom (base type ran), observing with the factory print and topology, and confirming and fixing each of the few specific root causes so the override actually takes.
Factory failures are the silent non-application of a factory override — the base type runs where your override was expected — caused by one of a few specific conditions. The root causes: the object was constructed with new() instead of create(), so the factory never saw it (the most common — and the override still shows as registered, which misleads); the type wasn't registered with the factory (missing uvm_component_utils / uvm_object_utils), so the factory can't substitute it; the override was set after the target was already built (an ordering problem — overrides must be registered before the factory creates the target); or the wrong override flavor or scope was used (a type override where an instance override was needed, or an instance override with a path that doesn't match where the component actually sits). The methodology applied: the symptom is the base type ran (your behavior absent, the original's present); localize to the factory; observe with factory.print() (are the overrides registered? correct base→override? correct scope?) and uvm_top.print_topology() (what type was actually built?); confirm which root cause; and fix (use create(), register the type, set the override before build, correct the flavor/path). The cardinal truth: the factory can only override what it creates — so the single most common factory failure is constructing with new() instead of create(), which leaves the override correctly registered yet never applied. This chapter is factory failures: the symptom, the root causes, the observation, and the fixes.
Why does a factory override silently do nothing — the base type running instead — and how do you tell which root cause it is (new() instead of create(), unregistered type, override set too late, or wrong flavor/path) by observing the factory print and topology, then fix it so the override actually takes?
Motivation — why factory overrides fail silently
The factory's power — substituting types without editing code — comes with silent failure modes, because an override that doesn't apply produces no error, just the original behavior. The reasons it's worth knowing the specific causes:
- The failure is silent — the base type just runs. An override that doesn't take doesn't error; the testbench builds and runs with the base type. So you don't get told it failed — you discover it from a downstream symptom (your error injection didn't happen, your extended check didn't run).
- The most common cause looks correct.
create()-versus-new()is insidious because the override is registered correctly —factory.print()shows it — and it still doesn't apply. The override isn't wrong; the construction bypassed the factory. Without knowing this, you stare at a correct override and can't see why. - Ordering is invisible. Whether the override was set before or after the target was created is a matter of phase and call order you can't see from the override line itself — and build runs top-down, so an override set too late applies to nothing already built.
- Type-versus-instance scope is easy to get wrong. A type override hits all instances; an instance override hits one by path. Using the wrong flavor, or an instance path that doesn't match the actual hierarchy (a typo, or the hierarchy changed), means the override targets nothing.
- The fix differs per cause. Each root cause has a different fix (use
create(), register the type, reorder, correct the path) — so you must know which one it is, which means observing the factory and topology, not guessing.
The motivation, in one line: factory overrides fail silently (the base type just runs, no error), the most common cause (new() instead of create()) leaves the override looking correct, ordering and scope problems are invisible from the override line, and each cause has a different fix — so you must observe the factory print and topology to confirm which root cause it is, then apply the matching fix.
Mental Model
Hold a factory override as a substitution rule at a supply desk — applied only to orders placed at the desk, never to parts grabbed off the shelf directly:
Picture a parts supply desk in a workshop. There's a clerk, and you can register substitution rules with them: "whenever someone orders part A, give them part B instead." From then on, every order placed at the desk for part A is quietly fulfilled with part B. This works beautifully — you can change what everyone receives without anyone changing their order — but only under conditions. First and most important: the substitution happens only for orders placed at the desk. If a worker walks past the desk and grabs part A straight off the shelf themselves, the clerk never saw the order, so no substitution happens — that worker gets part A, no matter how correctly your rule is written in the clerk's book. The rule is right there, registered, and it simply never had a chance to apply. Second, the rule has to be posted before the orders come in; a rule added after the morning's orders were already filled does nothing for them. Third, the rule has to name the right part — a rule to substitute part A does nothing for someone ordering part C — and the right scope: "substitute for everyone" versus "substitute only for the order under this specific job number," and if you wrote the wrong job number, the order you cared about isn't covered. So when someone receives the original part A despite your substitution rule, the rule being in the book is not the answer — you have to ask: did they order at the desk or grab it off the shelf, was the rule posted in time, and does it name the right part and scope? Picture a parts supply desk. A clerk applies substitution rules you register: "whenever someone orders part A, give them part B." Every order placed at the desk for A is fulfilled with B. This works — you change what everyone receives without anyone changing their order — but only under conditions. First and most important: the substitution happens only for orders placed at the desk. If a worker grabs part A off the shelf directly, the clerk never saw the order, so no substitution — that worker gets A, no matter how correctly your rule is written. The rule is right there, registered, and it simply never had a chance to apply. Second, the rule must be posted before the orders come in. Third, the rule must name the right part (a rule for A does nothing for C) and the right scope (everyone versus this specific job number — and a wrong job number misses the order you cared about). So when someone receives the original A despite your rule, the rule being in the book is not the answer — ask: did they order at the desk or grab it off the shelf, was the rule posted in time, and does it name the right part and scope?
So a factory override is a substitution rule at a supply desk: the factory is the clerk, create() is ordering at the desk (the factory sees the request and applies the rule), and new() is grabbing the part off the shelf (the factory never sees it, so no override applies — the base type, every time, no matter how the override is registered). The conditions map directly: the rule must be posted before orders (set the override before the target is built), name the right part (override the right base type), and have the right scope (type override for all, instance override for one by path — wrong path misses). And the diagnostic is the clerk's question: when the base type ran despite a registered override, the override being registered is not the answer — ask the four questions: was it created via the factory (create) or built directly (new), was the type registered, was the override set in time, and does it name the right base and scope. Treat the override as a desk-substitution rule: it applies only to what the factory creates, only if posted before construction, only for the right type and scope — so when the base type runs, observe the factory and topology to find which condition failed. The factory can only override what it creates.
Visual Explanation — the four root causes
The defining picture is the four root causes of a silent factory failure — the checklist the base-type-ran symptom routes you through.
The figure shows the four root causes. new() instead of create() (the warning-colored — the most common): the factory never saw the object, so no override could apply — and the override still prints as registered, which misleads. Type not registered (brand-colored): missing uvm_component_utils/uvm_object_utils — the factory can't substitute a type it doesn't know. Override set too late (success-colored): registered after the factory already built the target — the already-built component is the base type. Wrong flavor or path (default-colored): a type override where instance was needed, or an instance override whose path doesn't match the actual hierarchy. The crucial reading is that the symptom is identical for all four — the base type ran — so you cannot tell them apart from the symptom alone; observation (the factory print and topology) is what distinguishes them. The warning color on new()-instead-of-create() marks it as both the most common and the most misleading: with the other three, the factory print often shows something wrong (the override absent — for unregistered or too-late, depending; or with a mismatched path), but with new(), the factory print shows the override perfectly registered and it still doesn't apply — because the failure isn't in the override, it's in the construction (the object bypassed the factory). This is why the topology print is essential alongside the factory print: the factory print tells you what overrides are registered, but the topology print tells you what type was actually built — and comparing them reveals the new() case (override registered, base type built). The diagram is the factory failure differential: four causes, one symptom, distinguished by observation. A silent factory failure is one of four specific causes — new()-not-create(), unregistered type, too-late override, or wrong flavor/path — all with the same base-type-ran symptom, told apart only by observing the factory and topology.
RTL / Simulation Perspective — the four causes and their fixes
In code, each root cause is concrete and has a specific fix. The example shows all four and the observation that confirms each.
// === CAUSE 1 (most common): new() instead of create() — the factory never sees the object ===
class my_env extends uvm_env;
my_driver drv;
function void build_phase(uvm_phase phase);
drv = new("drv", this); // ✗ direct new() — BYPASSES the factory
// factory.print() SHOWS the override registered, but the base/this type ran anyway
drv = my_driver::type_id::create("drv", this); // ✓ FIX: create() routes through the factory
endfunction
endclass
// === CAUSE 2: type not registered — the factory can't substitute it ===
class my_driver extends base_driver;
// ✗ missing registration
`uvm_component_utils(my_driver) // ✓ FIX: register with the factory
endclass
// === CAUSE 3: override set too late — after the target was already built ===
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ✗ this already CREATED drv as the base type
base_driver::type_id::set_type_override(my_driver::get_type()); // too late for drv
endfunction
function void build_phase(uvm_phase phase);
base_driver::type_id::set_type_override(my_driver::get_type()); // ✓ FIX: override BEFORE create
super.build_phase(phase); // now create() sees the override
endfunction
// === CAUSE 4: wrong flavor / path — instance override with a path that doesn't match ===
// ✗ instance override targeting a path that doesn't exist in the topology:
base_driver::type_id::set_inst_override(my_driver::get_type(), "env.agnt.drv"); // actual path: env.agent.drv
// ✓ FIX: correct the path (or use set_type_override for all instances):
base_driver::type_id::set_inst_override(my_driver::get_type(), "env.agent.drv");
// === OBSERVE to confirm which cause ===
function void end_of_elaboration_phase(uvm_phase phase);
factory.print(); // are the overrides registered? correct base→override? correct path?
uvm_top.print_topology(); // what type was ACTUALLY built? (override registered + base built → new())
endfunctionThe code shows the four causes, their fixes, and the observation. Cause 1 (new()): drv = new(...) bypasses the factory — the fix is my_driver::type_id::create(...), which routes through the factory; the tell is that factory.print() shows the override but the base type ran. Cause 2 (not registered): missing uvm_component_utils — the fix is to add it, so the factory knows the type. Cause 3 (too late): set_type_override after super.build_phase (which already created drv) — the fix is to set the override before super.build_phase, so create() sees it. Cause 4 (wrong path): set_inst_override with a path that doesn't match (env.agnt.drv vs the actual env.agent.drv) — the fix is to correct the path (or use set_type_override for all instances). Observe: factory.print() shows what's registered (overrides, base→override, scope/path), and uvm_top.print_topology() shows what was actually built — and comparing them confirms the cause (override registered + base type built → the new() case). The shape to carry: each cause is a specific, fixable condition, and the factory print plus topology print distinguish them — the factory print alone isn't enough (it can't reveal the new() case, where the override is registered), so you pair it with the topology to see what was actually constructed. Observe the factory print and topology together — the factory print shows what's registered, the topology shows what was built, and comparing them confirms which of the four causes it is.
Verification Perspective — why create() is the linchpin
The defining truth — the factory can only override what it creates — makes create() versus new() the linchpin of the whole mechanism. Seeing why clarifies the most common failure.
The figure shows create() routing through the factory and new() bypassing it. When a component is constructed with type_id::create, the request goes through the factory (the success → brand path), which consults its registered overrides and substitutes the override type if one applies — so the override takes (override type runs). When a component is constructed with a direct new(), it never goes through the factory at all (the warning → default path bypasses the brand factory node); the factory has no knowledge of the request and no chance to substitute, so the base type is constructed regardless of any registered override. The verification insight is why this makes create() the linchpin: the factory's entire mechanism — consulting overrides and substituting — only runs when the factory is asked to construct, and create() is that ask. new() constructs without asking — it directly instantiates the named type — so the factory is simply not in the loop. This is the deep reason the factory can only override what it creates: an override is not a global rewrite of "whenever this type is constructed"; it's a rule the factory applies when the factory does the constructing. The crucial consequence is that a perfectly registered override silently does nothing when the target was built with new() — and this is the case that misleads, because the override isn't wrong (it's registered exactly as intended), the construction simply bypassed the mechanism that applies it. The success-colored create path → override runs versus the warning-colored new path → base runs make the fork explicit: the single choice of create() versus new() determines whether the override can apply at all. The crucial point is that this is why create() is non-negotiable for any type you might ever want to override — and since reuse (Module 26) means you might want to override almost anything, the discipline is to always construct components and overridable objects with create(), never new(). The diagram is the linchpin: create() lets the override apply; new() bypasses the factory so the base type always runs. Always construct with type_id::create, never new() — the factory can only override what it creates, so a direct new() makes even a perfectly registered override silently do nothing.
Runtime / Execution Flow — diagnosing a factory failure
At run time, diagnosing a factory failure follows the methodology: symptom → observe factory + topology → identify the cause → fix. The flow shows the factory-specific diagnostic.
The flow shows the factory-specific diagnostic. Symptom (step 1): the base type ran — your override's behavior absent, the original's present — which localizes to the factory. Print the factory (step 2): is the override registered? with the correct base, override type, and scope/path? Print the topology (step 3): what type was actually built at the target location? Read together → cause → fix (step 4): if the override is absent → not registered or set too late; if present with a mismatched instance path → fix the path; if present and correct but the topology shows the base type was built → new() instead of create(). The runtime insight is that the two prints together are what turn the identical symptom into a precise diagnosis — neither alone suffices. The factory print alone can mislead: in the new() case, it shows the override correctly registered, so if you stop there, you conclude the override is fine and don't know why it didn't apply. The topology print is what closes the gap: it shows what was actually built, and when that's the base type despite a correctly registered override, the only explanation is that the construction bypassed the factory (new()). Conversely, if the factory print shows the override absent, you don't need the topology to know it's a registration or ordering problem. So the diagnostic logic is: factory print establishes what the factory knows, topology print establishes what actually happened, and the combination uniquely identifies the cause. The brand (symptom) → success (two prints) → warning (identify/fix) flow shows evidence (the two prints) preceding the conclusion. The crucial point is that this is the methodology (27.1) specialized to the factory: observe with the factory's specific introspection (factory print + topology), confirm the cause from what they show, fix the matching cause — not guess. The flow is the factory diagnostic: base type ran → factory print + topology print → read together → cause → fix. Diagnose a factory failure by printing both the factory and the topology and reading them together — the factory print shows what's registered, the topology shows what was built, and only the combination distinguishes the four causes.
Waveform Perspective — the base type's behavior, observed
A factory failure is observable as base-type behavior where override behavior was expected — and distinct from a sequencer failure because the driver is running, just as the wrong type. The waveform shows it.
A factory failure observed — the driver runs, but with base-type behavior, not the override's
12 cyclesThe waveform shows the base type's behavior, observed. The driver is active — valid pulses as transactions are driven, so this is not a "nothing drives" sequencer failure. But the mode field carries the base type's default value (0) on every transfer, when the override was expected to drive its custom value (2). The crucial reading is the combination: the driver is running (transactions happen — valid pulses) but with base-type behavior (mode = 0, the default) rather than the override's (mode = 2). That combination is the signature of a factory failure: the wrong type was constructed, so the original behavior appears — the driver works, it's just the base driver, not yours. This distinguishes it sharply from the sequencer failure of 27.1 (where nothing would drive at all — valid flat): here, stimulus is flowing, so the sequence started and the driver runs — the problem is which driver. Reading the symptom this way — is the driver running, and is its behavior the base's or the override's? — localizes to the factory (the override didn't take) rather than the sequencer (no stimulus) or the DUT (the testbench side is producing base behavior, not a DUT response). The picture to carry is that a factory failure looks like "the right activity, the wrong flavor" — transactions happen, but with the base type's behavior, because the base type was built. Once you've read this — driver running, base behavior — you print the factory and topology to find which of the four causes prevented the override. The driver active but driving base-type values is the waveform signature that says factory, not sequencer. A factory failure shows the driver running with base-type behavior, not the override's — transactions happen but in the wrong flavor, which distinguishes it from a sequencer failure (nothing drives) and points to the factory.
DebugLab — the correctly-registered override that never took
A factory override that printed as correctly registered yet never applied, because the target was built with new()
A verification engineer needed an error-injecting driver for a fault campaign. They wrote err_driver extends apb_driver, registered it, and added a type override in the test: apb_driver::type_id::set_type_override(err_driver::get_type()). They ran the campaign — and no errors were injected. The base apb_driver ran: transactions were clean, the fault campaign found nothing, because the faults were never driven. Puzzled, the engineer did the right first thing — they printed the factory in end_of_elaboration_phase. And the factory print showed the override perfectly registered: apb_driver overridden by err_driver, type override, correct types. Exactly as written. The engineer was stuck: the override was right there in the factory, correctly registered — why was the base driver still running? They spent a day re-checking the override syntax, re-reading the registration, trying instance overrides, moving the override to different places — all while the override was, and had always been, correctly registered. The override was not the problem.
The agent constructed its driver with a direct new() instead of type_id::create(), so the driver bypassed the factory entirely — the factory never saw the construction request, and no override could apply, no matter how correctly it was registered:
✗ THE TARGET WAS BUILT WITH new() — bypassing the factory:
class apb_agent extends uvm_agent;
apb_driver drv;
function void build_phase(uvm_phase phase);
drv = new("drv", this); // ✗ direct new() — the factory is NOT in the loop
endfunction
endclass
// factory.print() shows: apb_driver -> err_driver (type override) ← REGISTERED, correct
// topology print shows: env.agent.drv (type apb_driver) ← BASE type was BUILT
// → the override is correct; the construction bypassed the factory → base ran (override never applied)
✓ CONSTRUCT WITH create() — route through the factory:
function void build_phase(uvm_phase phase);
drv = apb_driver::type_id::create("drv", this); // ✓ factory sees the request → applies override
endfunction
// now topology print shows: env.agent.drv (type err_driver) ← the override TOOKThis is the new()-instead-of-create() bug — the most common and most misleading factory failure. The override was flawless: correctly registered, correct types, correct flavor — the factory print confirmed it. But the apb_agent constructed its driver with drv = new("drv", this) — a direct new() — so the driver bypassed the factory entirely. The factory's substitution mechanism only runs when the factory is asked to construct (via create()), and a direct new() never asks — it directly instantiates apb_driver — so the factory never saw the request and no override could apply. The base apb_driver ran, the faults were never injected, and the campaign found nothing. The reason this misled for a day is exactly the trap: the engineer printed the factory (the right move) and saw the override correctly registered, so they concluded the override was fine and looked everywhere except the construction — re-checking syntax, trying other override flavors — all irrelevant, because the override was never the problem. What was missing was the second observation — the topology print — which would have shown env.agent.drv was built as type apb_driver (the base), and that, next to the correctly-registered override, uniquely points to new(): override registered, base type built → the construction bypassed the factory. The fix is one line: drv = apb_driver::type_id::create("drv", this) — route through the factory, which now sees the request and applies the override, so the topology then shows drv built as type err_driver. The general lesson, and the chapter's thesis: the factory can only override what it creates — so when a correctly-registered override silently does nothing, the override is not the problem; check whether the target was constructed with create() or a direct new(), by printing the topology alongside the factory — a new() bypasses the factory, so even a flawless override never applies; always construct overridable components with type_id::create, never new(), because the factory print showing the override registered proves the override is correct, not that it was applied — only the topology print shows what was actually built. A correctly-registered override that never takes is the signature of a new() bypassing the factory — print the topology, and construct with create().
The tell is an override that the factory print shows as correctly registered yet that never applies. Diagnose the new() bypass:
- Print the topology, not just the factory. The factory print shows the override is registered; the topology print shows what type was actually built. A base type built under a correct override means the factory was bypassed.
- Find the construction of the target. Search for how the component is built; a direct new() rather than type_id::create is the bypass.
- Rule out the other causes first by the factory print. If the override is absent in the factory print, it's registration or ordering; if present with a wrong path, it's the path. Only a present, correct override with a base type built points to new().
- Check inherited and copied code. new() often hides in older base classes or copy-pasted build phases that predate factory discipline.
Always construct through the factory:
- Use type_id::create for every component and overridable object. Never new() for anything you might want to override — which, with reuse, is almost everything.
- Print the topology to confirm the built type. After registering an override, verify the topology shows the override type built, not just that the factory lists it.
- Audit build phases for new(). Grep for direct new() of components in build phases; it's the silent bypass.
- Don't trust a factory print alone. A registered override proves correctness of the override, not that it was applied; pair it with the topology.
The one-sentence lesson: the factory can only override what it creates, so a target constructed with a direct new() bypasses the factory and runs the base type no matter how correctly the override is registered — always construct overridable components with type_id::create, and confirm an override by printing the topology to see what was actually built, not just the factory print that shows what was registered.
Common Mistakes
- Constructing with new() instead of create(). A direct new() bypasses the factory, so no override applies; always use type_id::create for overridable components and objects.
- Trusting the factory print alone. A registered override proves the override is correct, not that it was applied; print the topology to see what was actually built.
- Forgetting to register the type. Without uvm_component_utils or uvm_object_utils, the factory can't substitute the type; register every overridable type.
- Setting the override too late. An override registered after the target was built applies to nothing already constructed; set overrides before the factory creates the target.
- Using the wrong override flavor or path. A type override hits all instances, an instance override hits one by path; a mismatched instance path targets nothing.
- Assuming the override is wrong when the base ran. The override is often correct; the construction or ordering is the failure — observe both factory and topology.
Senior Design Review Notes
Interview Insights
A factory override silently does nothing — the base type runs instead — for one of a few specific reasons, and the symptom is the same for all of them, so you distinguish them by observation. The most common and most misleading cause is that the target was constructed with a direct new() instead of type_id::create. The factory can only override what it creates: an override is a substitution rule the factory applies when it is asked to construct a type, and create is that ask. A direct new() constructs the named type without going through the factory at all, so the factory never sees the request and no override can apply — and critically, the override still shows as correctly registered in the factory print, which misleads you into thinking the override is the problem when it's the construction. The second cause is that the type wasn't registered with the factory — without uvm_component_utils or uvm_object_utils, the factory doesn't know the type and can't substitute it. The third is that the override was set too late — after the factory already built the target. Build runs top-down, so if you register the override after the target was created, for instance after super.build_phase already constructed it, the already-built component is the base type. The override has to be in place before the factory creates the target. The fourth is the wrong override flavor or scope — a type override applies to all instances of a type, an instance override applies to one specific instance by path, and if you use an instance override with a path that doesn't match where the component actually sits, or use type when you needed instance, the override targets nothing. Because the symptom — the base type ran — is identical across all four, you can't tell them apart by the symptom. You observe: print the factory to see what's registered and with what scope, and print the topology to see what type was actually built. Reading the two together identifies the cause — for example, a correctly registered override plus a base type in the topology uniquely points to new() instead of create. Then each cause has its own fix.
It means a factory override only applies to objects that are constructed through the factory, via type_id::create, and has no effect on objects constructed directly with new — because the override is a rule the factory applies at the moment it does the constructing, and a direct new bypasses the factory entirely. Think about what an override actually is. When you register a type override, you're telling the factory: whenever you are asked to construct this base type, construct this override type instead. The key phrase is whenever you are asked to construct. The factory's substitution logic only runs when the factory is the one doing the construction. type_id::create is how you ask the factory to construct — it routes the request through the factory, which consults its registered overrides, sees that this base type should be substituted, and constructs the override type instead. A direct new is fundamentally different: it directly instantiates the exact type you named, immediately, without involving the factory at all. The factory has no knowledge of that construction, no hook into it, no chance to substitute. So an override, no matter how correctly registered, simply never has an opportunity to apply, because the mechanism that applies it — the factory — was never invoked. This is why an override is not a global rewrite of whenever this type is constructed anywhere; it's specifically a rule the factory applies when the factory constructs. The practical consequence is decisive: any component or object you might ever want to override must be constructed with type_id::create, never new. And with reuse, you might want to override almost anything — a base driver, monitor, sequence item, any of it — so the discipline is to always construct overridable things through the factory. The most common factory bug is exactly a violation of this: a target built with new while an override is correctly registered, so the override silently never takes. The tell is that the factory print shows the override but the topology print shows the base type was built, which means the construction bypassed the factory. So the phrase is both the explanation of how the factory works and the rule you follow to make overrides reliable: construct with create, because the factory can only override what it creates.
You diagnose it by printing both the factory and the topology and reading them together, because neither alone distinguishes the causes. Start from the symptom: the base type ran where your override was expected — your extended behavior is absent and the original's is present — which localizes the failure to the factory. Then observe. First, print the factory, typically in end_of_elaboration_phase with factory.print. This shows every registered override: the base type, the override type, and the scope — type override, or instance override with its path. From this you learn what the factory knows. If your override is absent, it was never registered or was set too late, so investigate registration and ordering. If it's present but the instance path doesn't match your hierarchy, the path is wrong. But here's the catch: if the override is present and correct, the factory print alone can't tell you why it didn't apply — and this is exactly the most common case. So second, print the topology, with uvm_top.print_topology. This shows what was actually built at each location, including the type of each component. Now read the two together. If the factory shows a correct override but the topology shows the base type was built at the target, the only explanation is that the target was constructed with new instead of create — the construction bypassed the factory. That combination — correct override, base type built — uniquely identifies the new bypass, which the factory print alone would never reveal. If instead the override was absent from the factory, the topology confirms the base type and you focus on registration or ordering. The discipline is that the factory print establishes what the factory knows and the topology print establishes what actually happened, and their combination identifies the cause. This is the general debugging methodology specialized to the factory: don't guess or re-check syntax, observe the actual structure with the factory's introspection, confirm which cause from what the prints show, and apply the matching fix. The single most useful habit is to always print the topology, not just the factory, because the factory print showing your override is reassuring but proves only that the override is correct, not that it was applied.
A type override replaces every instance of a base type throughout the testbench, while an instance override replaces only one specific instance identified by its hierarchical path — and each fails in a characteristic way when misused. A type override, set with set_type_override, says: anywhere the factory constructs this base type, construct the override type instead. It's broad — all drivers of that type, everywhere, become the override. You use it when you want a blanket substitution. An instance override, set with set_inst_override, says: only when the factory constructs the component at this specific path, use the override type; everywhere else, the base type still applies. It's surgical — you can override just env.agent_a.drv while leaving env.agent_b.drv as the base. You use it when you want to customize one instance and not others. Now the failure modes. An instance override fails when the path doesn't match where the component actually sits. The path is a string, matched against the topology, so a typo — env.agnt.drv instead of env.agent.drv — or a hierarchy that changed since the override was written means the override targets a location that doesn't exist, and it silently applies to nothing; the base type runs at the real location. This is common because paths are brittle strings. A type override can fail by being too broad — if you meant to override only one instance but used a type override, you replace all of them, which may not be what you wanted, though that's more a correctness-of-intent issue than a non-application. Using the wrong flavor is the other failure: using a type override when you needed per-instance differentiation, or an instance override when you wanted all of them, gives you the wrong set of substitutions. You diagnose these with the factory print, which shows each override's scope and, for instance overrides, the path — so you can compare the registered path against the actual topology path and spot a mismatch. The fix for a path mismatch is to correct the path to match the topology, or switch to a type override if you actually want all instances. The general point is that scope is part of getting an override right: the override has to name not just the right types but the right scope, and an instance override especially has to name a path that matches the real hierarchy, or it targets nothing.
A factory override must be set before the target is built because the override is consulted at the moment of construction, and UVM's build phase constructs the hierarchy top-down, so once the factory has already created a component, registering an override afterward applies to nothing already built. Walk through the timing. The override is a rule the factory checks when it constructs a type — when create is called, the factory looks up whether an override is registered for that type and substitutes accordingly. So the override has to be registered before that create call happens for the target, or the create call sees no override and constructs the base type. Now consider UVM's phasing. The build phase runs top-down: a parent's build_phase constructs its children, typically by calling create, and each child's build_phase then constructs its own children, and so on down the hierarchy. So if you want to override a component deep in the hierarchy, the override must be registered before the create call that builds it — which means before the parent's build_phase reaches that create. The common ordering mistake is registering the override inside a build_phase after super.build_phase, when super.build_phase already constructed the target. By the time your set_type_override runs, the target is already built as the base type, and the override is too late — it would only affect future constructions, of which there are none for that component. The fix is to register the override before the construction: in the test, set overrides at the start of build_phase before super.build_phase, or in an earlier point like the build of a higher-level component, or before the run starts. Practically, overrides are usually set in the test's build_phase before calling super, or in a configuration step, precisely so they're in place before the environment's build phase constructs the components they target. You diagnose a too-late override by the factory print: if the override isn't listed at end_of_elaboration, or the topology shows the base type built despite your intent, and you find the override registered after the target's construction, that's the ordering bug. The general principle is that the override has to exist in the factory before the factory is asked to construct the target, and because build is top-down, that means setting it before the build phase reaches that construction.
Exercises
- Identify the cause. For each — factory print shows the override but topology shows the base type; factory print doesn't list the override; instance override path is env.agnt.drv but topology shows env.agent.drv — name the root cause and the fix.
- Find the bypass. Given an agent whose build_phase contains drv = new("drv", this), explain why a correct override doesn't take and rewrite the line.
- Order the override. Given an override set after super.build_phase, explain why it's too late and where to move it.
- Choose the flavor. Decide whether to use a type or instance override to replace only one of three identical agents' drivers, and write the call.
Summary
- Factory failures are the silent non-application of a factory override — the base type runs — caused by one of four specific conditions, all with the same symptom.
- The root causes: new() instead of create() (the factory never saw the object — the most common, and the override still prints as registered); type not registered (missing
uvm_component_utils/uvm_object_utils); override set too late (after the target was built); or wrong flavor/path (type where instance was needed, or an instance path that doesn't match). - The cardinal truth: the factory can only override what it creates — an override is a substitution rule applied when the factory constructs, so a direct new() bypasses it and runs the base type no matter how correctly the override is registered.
- Observe with both
factory.print()(what's registered — base, override, scope) anduvm_top.print_topology()(what was actually built); reading them together distinguishes the causes — a correct override + base type built uniquely points to new(). - The durable rule of thumb: when a factory override silently does nothing and the base type runs, don't assume the override is wrong — print both the factory and the topology and read them together; a registered override with the base type built means the target was constructed with new() instead of create(), so the factory was bypassed (the factory can only override what it creates) — always construct overridable components with type_id::create, register every overridable type, set overrides before the target is built, and match the override flavor and instance path to the actual hierarchy.
Next — Config DB Failures: the methodology turns to the configuration database. Why a component receives a default or null value it was configured to override — a set/get path or scope mismatch, a type mismatch between set and get, the set happening after the get, or the wrong field name — how to read the symptom (a default value, or a null-handle crash), how to observe it (the config-DB trace), and how to fix each so the configuration actually reaches the component.