UVM
UVM Factory Debug
Reading factory print() output, override chains, simulator debug switches, 5-step workflow.
UVM Fundamentals · Module 7
Why Factory Debug Is Its Own Topic
Compilation errors and runtime crashes are straightforward to diagnose — the simulator points you to the problem. Factory failures are different. The simulation compiles cleanly, elaborates cleanly, and runs to completion. The wrong component type is created and the test either produces incorrect results or the override simply has no observable effect.
There are exactly three ways a factory override can fail silently:
Override Not Registered
The override was set but the factory has no record of it. Root causes: missing ``uvm_*_utils` macro on source or override class, or the package containing the class was never compiled or imported.
Override Registered Too Late
The factory shows the override in its table, but the component was already created before the override was registered. No runtime error. The original type runs with the wrong implementation.
Override Path Mismatch
Instance override registered but path pattern does not match the component's actual get_full_name(). The factory falls through to the type override or the original type without any warning message.
Each failure produces a simulation that runs normally. The only way to distinguish them is with deliberate factory introspection. That is what this module covers.
factory::print() — Anatomy of the Output
uvm_factory::get().print() is the single most important factory debug tool. It dumps the complete factory state: all registered types, all active type overrides, and all active instance overrides. Call it in start_of_simulation_phase so all components have been created and overrides have been applied.
// Add to your base_test or any top-level component — remove before tapeout
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// print(0) — show overrides only (recommended for routine debug)
uvm_factory::get().print(0);
// print(1) — show all registered types + overrides (verbose)
uvm_factory::get().print(1);
endfunctionReading the Factory Print Output
The output has three sections. Each tells you something specific about the factory state.
Figure 1 — Annotated factory::print() output. Five key reading points: total type count, instance override section, active type override table, registered type list, and override class registration confirmation.
Filtering the Factory Print Output
A large project can have 200+ registered types. Printing all of them clutters the log and makes it hard to find what you need. Use the argument to print() and type-specific queries to filter.
| Call | Output | Use When |
|---|---|---|
factory.print(0) | Only the override tables (type + instance). No type list. | Routine debug — confirms which overrides are active without the type noise. |
factory.print(1) | Override tables + complete list of all registered types. | Diagnosing "class not registered" — verify a class appears in the type list. |
factory.debug_pass_one(0) | Simulator-specific — Questa only. Shows factory resolution per create() call in real time. | Tracing exactly which type was resolved for a specific create() call. |
// Control factory print from the command line without editing source code
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
if ($test$plusargs("FACTORY_DEBUG")) begin
`uvm_info("FACTORY", "Factory state at start_of_simulation:", UVM_LOW)
uvm_factory::get().print(1); // verbose — all types + overrides
end else if ($test$plusargs("FACTORY_OVR")) begin
uvm_factory::get().print(0); // concise — overrides only
end
endfunction
// Simulate with: vsim +FACTORY_DEBUG work.tb_top
// Or: vsim +FACTORY_OVR work.tb_topType Verification at Runtime — get_type_name()
Even after confirming the override is registered in the factory, you need to verify that the actual object created is the override type. get_type_name() returns the class name of the object at runtime — not the variable's declared type.
// In apb_agent.sv — after build_phase creates the driver
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// Check what type was actually created
`uvm_info("AGENT",
$sformatf("Driver type: %s Path: %s",
drv.get_type_name(),
drv.get_full_name()),
UVM_LOW)
// Console output if override worked:
// UVM_INFO: Driver type: error_inject_driver Path: ...apb_agent.drv
// Console output if override FAILED:
// UVM_INFO: Driver type: apb_driver Path: ...apb_agent.drv
endfunction
// ── Programmatic type check — fail simulation if wrong type ──────────
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
if (drv.get_type_name() != "error_inject_driver")
`uvm_fatal("TYPE_CHK",
$sformatf("Expected error_inject_driver, got %s. Override failed.",
drv.get_type_name()))
endfunction$cast() as a Type Check
When a base-type handle holds an override object, use $cast() to check whether the actual type is what you expect without triggering a fatal error if it is not:
// drv is declared as apb_driver but may hold error_inject_driver
error_inject_driver eid_drv;
if ($cast(eid_drv, drv)) begin
// $cast returns 1 → drv IS error_inject_driver → override worked
`uvm_info("CAST", "Override confirmed via $cast", UVM_LOW)
eid_drv.set_error_rate(10); // access override-specific method safely
end else begin
// $cast returns 0 → drv is apb_driver → override did NOT work
`uvm_warn("CAST", "Override failed — drv is base apb_driver type")
end
// Note: $cast() is non-destructive — it doesn't change drv or eid_drv
// if cast fails. This makes it safe for runtime type checking.Tracking Override Chains at Runtime
When multiple layers of overrides are registered (A→B, B→C), tracing the chain manually through factory::print() output is straightforward — read the Type Overrides table from top to bottom and follow the chain. For programmatic chain inspection, use find_override_by_type().
// ── find_override_by_type() traces one hop in the chain ──────────────
uvm_object_wrapper resolved;
// IEEE 1800.2 obtains the factory through the core service. `uvm_factory::get()`
// exists in most implementations as a shorthand, but the core-service form is
// the one the standard defines and the one that survives a UVM version bump.
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory = cs.get_factory();
// What does the factory give when apb_driver is requested at path "*.drv"?
resolved = factory.find_override_by_type(
apb_driver::get_type(), // requested type
"uvm_test_top.env.apb_agent.drv" // path context
);
`uvm_info("CHAIN",
$sformatf("apb_driver resolves to: %s", resolved.get_type_name()),
UVM_LOW)
// If override A→B→C is registered:
// find_override_by_type(A) returns C directly — factory follows the full chain
// ── Confirming the chain terminates where you expect ─────────────────
// NOTE: find_override_by_type already follows the WHOLE chain, so it returns
// the final type in one call - it does not step one hop. This loop therefore
// prints a single A -> C line and then terminates; it confirms the endpoint
// rather than enumerating the intermediate hops. To see the individual hops,
// read the Type Overrides table from factory::print() as shown below.
uvm_object_wrapper current = apb_driver::get_type();
uvm_object_wrapper next;
forever begin
next = factory.find_override_by_type(current, "*");
if (next == current) break; // no further override — chain ends
`uvm_info("CHAIN",
$sformatf(" %s resolves to %s", current.get_type_name(),
next.get_type_name()), UVM_LOW)
current = next;
end
`uvm_info("CHAIN", $sformatf("Final resolved type: %s", current.get_type_name()), UVM_LOW)## factory::print(0) output when chain A→B→C is registered:
#### UVM Factory Configuration ####
--- Type Overrides ---
Requested Type Override Type
-------------------- -------------------------
apb_driver protocol_driver ←── Step 1: A → B
protocol_driver error_inject_driver ←── Step 2: B → C
## Reading the chain: start from apb_driver
## apb_driver → protocol_driver → error_inject_driver (no further override)
## Factory creates: error_inject_driver
## Returned as: apb_driver (base type)Simulator-Specific Factory Debug Switches
Beyond calling factory::print() in your code, each major simulator provides command-line mechanisms to enable real-time factory tracing without modifying source.
| Simulator | Switch / Plusarg | Effect |
|---|---|---|
| Questa | +UVM_FACTORY_OVERRIDE_TRACE | Prints a message every time the factory resolves a type, showing what was requested and what was returned. Real-time trace per create() call. |
| VCS | +UVM_FACTORY_OVERRIDE_TRACE | Same as Questa. Supported in UVM 1.2 and later. |
| Xcelium | -uvmfactoryoverridedbg | Xcelium-specific debug flag. Produces similar per-create() trace output. |
| All simulators | +UVM_CONFIG_DB_TRACE | Not factory-specific, but useful alongside factory debug — shows all config_db get/set operations which often reveal whether virtual interfaces reached their targets. |
## Simulation command:
## vsim +UVM_FACTORY_OVERRIDE_TRACE work.tb_top
## Output (one line per create() call):
UVM_INFO @ 0: Factory: create_component_by_type
Requested: apb_driver
Override: error_inject_driver (type override)
Created: error_inject_driver path=uvm_test_top.env.apb_agent.drv
UVM_INFO @ 0: Factory: create_component_by_type
Requested: apb_monitor
Override: (none)
Created: apb_monitor path=uvm_test_top.env.apb_agent.mon
## Key reading: "Override: (none)" means original type used — no override matched
## "Override: error_inject_driver (type override)" means type override matched
## "Override: ... (instance override)" means instance override matched by pathSystematic Five-Step Debug Workflow
Follow these steps in order. Each step either resolves the problem or narrows it to the next step. Skipping steps wastes time — each step takes less than two minutes.
Figure 2 — Five-step factory debug workflow. Each step eliminates one failure category. Steps 1–4 handle most failures; Step 5 is specific to instance override path mismatches.
- Call factory.print(0) in start_of_simulation_phase Look at the "Type Overrides" and "Instance Overrides" sections. Expected: your override appears with correct source → target class names. If missing: add `uvm_*_utils to both classes. Verify the package is compiled and imported in the current compilation unit.
- Verify the component is created with type_id::create() Search the VIP or environment code for the create() call. Confirm it is
apb_driver::type_id::create(...), notnew(...). Expected:type_id::create(name, this)— two arguments for components. If usingnew(): replace withtype_id::create(). Factory override has zero effect on objects created with directnew(). - Check get_type_name() after build_phase completes In start_of_simulation_phase, print
drv.get_type_name(). Compare with what you expected. Expected: prints the override class name (e.g., "error_inject_driver"). If prints original class name: the override was registered too late. Moveset_type_override()beforesuper.build_phase(). - Verify override registration order in build_phase Inspect the test's build_phase. The
set_type_override()orset_inst_override()call must appear beforesuper.build_phase()on the same line or earlier. Expected: override call is the first statement in build_phase. If after super.build_phase(): move it before. If in a base test, ensure the base test's build_phase also places overrides before its own super call. - For instance override: match path pattern to get_full_name() Print the component's actual full path:
drv.get_full_name(). Compare character-by-character with the pattern inset_inst_override(). Expected: the pattern matches the full path (exact or via wildcard). If mismatch: correct the pattern. Use+UVM_FACTORY_OVERRIDE_TRACEfrom the command line to see real-time path matching during create() calls.
Quick Reference — All Factory Debug Tools
| Tool | Call / Usage | What It Tells You |
|---|---|---|
| factory.print(0) | uvm_factory::get().print(0) | All active overrides (type + instance). No type list. Best for routine debug. |
| factory.print(1) | uvm_factory::get().print(1) | Overrides + all registered types. Use to confirm a class was registered. |
| get_type_name() | handle.get_type_name() | Actual runtime class name of the object. Override worked if name ≠ declared type. |
| get_full_name() | comp.get_full_name() | Full hierarchy path. Used to verify instance override path patterns. |
| $cast() | if ($cast(override_h, base_h)) | Non-fatal type check. Returns 1 if actual type is assignment-compatible with override type. |
| find_override_by_type() | factory.find_override_by_type(T::get_type(), path) | Returns the final resolved type for a given requested type and path context. Follows full chain. |
| FACTORY_OVERRIDE_TRACE | Plusarg: +UVM_FACTORY_OVERRIDE_TRACE | Real-time trace of every factory create() call — requested type, resolved type, path. |
| CONFIG_DB_TRACE | Plusarg: +UVM_CONFIG_DB_TRACE | Not factory-specific but useful alongside — shows all config_db get/set to find missing virtual interfaces. |
// Drop this into any component's start_of_simulation_phase while debugging
// Remove before commit. Does not affect simulation behaviour.
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
if ($test$plusargs("FACTORY_DEBUG")) begin
automatic string divider = {60{"="}};
`uvm_info("DBG", divider, UVM_LOW)
`uvm_info("DBG", $sformatf("Component: %s Type: %s",
get_full_name(), get_type_name()), UVM_LOW)
`uvm_info("DBG", "Factory state:", UVM_LOW)
uvm_factory::get().print(0); // overrides only
`uvm_info("DBG", divider, UVM_LOW)
end
endfunction
// Run with: vsim +FACTORY_DEBUG +UVM_FACTORY_OVERRIDE_TRACE work.tb_top
// The combination gives you both the registry state and real-time resolution trace.Code Examples — Factory Debug From Simple to Systematic
The best way to learn factory debugging is to create a broken scenario, observe the output, and fix it. These four examples build progressively from basic factory.print() inspection to the kind of multi-level override chain tracing that production teams use when a VIP behaves incorrectly in regression.
Example 1 — Beginner: Reading factory.print() Output
`include "uvm_macros.svh"
import uvm_pkg::*;
// Three related classes ─────────────────────────────────────────────
class base_txn extends uvm_sequence_item;
`uvm_object_utils(base_txn)
function new(string n="base_txn"); super.new(n); endfunction
endclass
class err_txn extends base_txn;
`uvm_object_utils(err_txn)
function new(string n="err_txn"); super.new(n); endfunction
endclass
class base_drv extends uvm_driver#(base_txn);
`uvm_component_utils(base_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
endclass
class print_test extends uvm_test;
`uvm_component_utils(print_test)
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Register one type override
base_txn::type_id::set_type_override(err_txn::get_type());
super.build_phase(phase);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("\n=== factory.print() output ===");
uvm_factory::get().print(1);
phase.drop_objection(this);
endtask
endclass
module print_demo_top;
initial run_test("print_test");
endmodule
// Annotated Expected Output:
//
// === factory.print() output ===
// #### Factory Configuration (classes=4, overrides=1) ####
//
// Object Types: ← uvm_object subclasses registered
// base_txn
// err_txn
//
// Component Types: ← uvm_component subclasses registered
// base_drv
// print_test
//
// Type Overrides: ← active type-level substitutions
// base_txn → err_txn
// [when base_txn requested, err_txn is given]
//
// Instance Overrides: ← path-specific substitutions
// (none)
//
// Reading this: "base_txn → err_txn" means ANY call to
// base_txn::type_id::create() will return an err_txn object.
// If this override is unexpected, check your test's build_phase.Example 2 — Intermediate: Confirming Actual Type at Runtime
`include "uvm_macros.svh"
import uvm_pkg::*;
// Three driver types ────────────────────────────────────────────────
class base_drv extends uvm_component;
`uvm_component_utils(base_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
endclass
class slow_drv extends base_drv;
`uvm_component_utils(slow_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
endclass
class type_check_test extends uvm_test;
`uvm_component_utils(type_check_test)
base_drv drv;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
base_drv::type_id::set_type_override(slow_drv::get_type());
super.build_phase(phase);
drv = base_drv::type_id::create("drv", this);
endfunction
task run_phase(uvm_phase phase);
slow_drv sd;
phase.raise_objection(this);
// Method 1: get_type_name() — what type is it REALLY?
$display("[TYPE] get_type_name(): %s", drv.get_type_name());
// Method 2: $cast — does the actual type match our expectation?
if($cast(sd, drv)) begin
$display("[CAST] $cast to slow_drv: SUCCESS");
end else begin
$display("[CAST] $cast to slow_drv: FAIL — wrong type");
end
// Method 3: Factory lookup — what would factory return for this type?
$display("[FACT] Factory override for base_drv:");
uvm_factory::get().debug_pass_one(base_drv::get_type(),
"debug", "uvm_test_top.drv");
phase.drop_objection(this);
endtask
endclass
module type_check_top;
initial run_test("type_check_test");
endmodule
// Expected Output:
// [TYPE] get_type_name(): slow_drv ← confirmed actual type
// [CAST] $cast to slow_drv: SUCCESS ← cast confirms type compatibility
// [FACT] Factory override for base_drv: slow_drv (type override)Example 3 — Verification: Tracing a Three-Level Override Chain
`include "uvm_macros.svh"
import uvm_pkg::*;
// Override chain: base → extended → specialized ────────────────────
class drv_v1 extends uvm_component;
`uvm_component_utils(drv_v1)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string version(); return "v1"; endfunction
endclass
class drv_v2 extends drv_v1;
`uvm_component_utils(drv_v2)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string version(); return "v2"; endfunction
endclass
class drv_v3 extends drv_v2;
`uvm_component_utils(drv_v3)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string version(); return "v3"; endfunction
endclass
class chain_test extends uvm_test;
`uvm_component_utils(chain_test)
drv_v1 drv;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Layer 1: VIP base package registers: drv_v1 → drv_v2
drv_v1::type_id::set_type_override(drv_v2::get_type());
// Layer 2: Your team adds: drv_v2 → drv_v3
drv_v2::type_id::set_type_override(drv_v3::get_type());
super.build_phase(phase);
drv = drv_v1::type_id::create("drv", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("\nOverride chain for drv_v1:");
$display(" drv_v1 requested → factory checks override → drv_v2");
$display(" drv_v2 override? → factory checks override → drv_v3");
$display(" drv_v3 override? → none → FINAL RESULT: drv_v3");
$display("\nActual drv type: %s version=%s",
drv.get_type_name(), drv.version());
$display("\nfactory.print(1) shows the chain:");
uvm_factory::get().print(1);
phase.drop_objection(this);
endtask
endclass
module chain_top;
initial run_test("chain_test");
endmodule
// Expected Output:
// Override chain for drv_v1:
// drv_v1 requested → factory checks override → drv_v2
// drv_v2 override? → factory checks override → drv_v3
// drv_v3 override? → none → FINAL RESULT: drv_v3
//
// Actual drv type: drv_v3 version=v3 ← chain fully resolved!
//
// factory.print(1):
// Type Overrides:
// drv_v1 → drv_v2
// drv_v2 → drv_v3
// ↑ Reading this: drv_v1 requests eventually resolves to drv_v3Example 4 — Tricky: Debug a "Silent Override Failure" (new() bypass)
`include "uvm_macros.svh"
import uvm_pkg::*;
// VIP agent that uses new() instead of type_id::create() ──────────
class bad_agent extends uvm_agent;
`uvm_component_utils(bad_agent)
drv_v1 drv;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase p);
super.build_phase(p);
drv = new("drv", this); // ← VIP bug: uses new() not factory
endfunction
endclass
class bypass_debug_test extends uvm_test;
`uvm_component_utils(bypass_debug_test)
bad_agent agent;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Override registered — but will it work?
drv_v1::type_id::set_type_override(drv_v2::get_type());
super.build_phase(phase);
agent = bad_agent::type_id::create("agent", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("\n=== Diagnostic 1: factory.print() ===");
uvm_factory::get().print(1);
// Shows: drv_v1 → drv_v2 override IS registered
$display("\n=== Diagnostic 2: actual driver type ===");
$display("agent.drv type: %s", agent.drv.get_type_name());
// Shows: drv_v1 ← override DIDN'T apply because new() was used
$display("\n=== Root Cause: bad_agent uses new() instead of factory ===");
$display("Fix: in bad_agent.build_phase, change:");
$display(" drv = new('drv', this); // bypass");
$display("To:");
$display(" drv = drv_v1::type_id::create('drv', this); // factory");
phase.drop_objection(this);
endtask
endclass
module bypass_top;
initial run_test("bypass_debug_test");
endmodule
// Expected Output:
// === Diagnostic 1: factory.print() ===
// Type Overrides:
// drv_v1 → drv_v2 ← override IS registered
//
// === Diagnostic 2: actual driver type ===
// agent.drv type: drv_v1 ← STILL drv_v1! Override didn't apply
//
// === Root Cause: bad_agent uses new() instead of factory ===
// Fix: change drv = new(...) to drv = drv_v1::type_id::create(...)
//
// KEY INSIGHT: factory.print() shows override as registered,
// but get_type_name() shows it didn't apply.
// This signature means: new() bypass.Common Bugs — Factory Debug Scenarios From Real Projects
Factory debug bugs fall into two categories: bugs where the override doesn't apply (silent failure), and bugs where the override applies but to the wrong thing (unintended global effect). Both are hard to find because UVM doesn't raise errors for either. You have to proactively diagnose them.
⚠️ Bug 1 — factory.print() Shows Override, But Type Is Still Wrong
Symptom: factory.print() confirms the override is registered (base_drv → err_drv), but get_type_name() returns base_drv. The override is in the factory but not taking effect.
Diagnostic: This specific combination — override registered in factory but not applied at runtime — has exactly three root causes. Work through them in order.
// ROOT CAUSE A: VIP uses new() instead of factory ─────────────────
// factory.print() shows: base_drv → err_drv
// get_type_name() shows: base_drv ← wrong
// ❌ VIP code:
drv = new("drv", this); // bypass factory
// ✓ Fix:
drv = base_drv::type_id::create("drv", this); // use factory
// ROOT CAUSE B: Override registered AFTER create() ─────────────────
// ❌ Wrong order in test.build_phase:
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ← creates drv here
drv = base_drv::type_id::create("drv", this); // ← using base type
base_drv::type_id::set_type_override(err_drv::get_type()); // ← too late
endfunction
// ✓ Fix: override BEFORE super.build_phase()
function void build_phase(uvm_phase phase);
base_drv::type_id::set_type_override(err_drv::get_type()); // ← FIRST
super.build_phase(phase); // ← cascade uses override
drv = base_drv::type_id::create("drv", this); // ← gets err_drv ✓
endfunction
// ROOT CAUSE C: Instance override path is wrong ────────────────────
// ❌ Wrong path (agent is deeper than expected):
base_drv::type_id::set_inst_override(err_drv::get_type(),
"uvm_test_top.drv"); // ← wrong: actually "uvm_test_top.agent.drv"
// ✓ Fix: use print_topology() to find correct path:
uvm_root::get().print_topology(); // run first to see actual paths
base_drv::type_id::set_inst_override(err_drv::get_type(),
"uvm_test_top.agent.drv"); // ← correct path⚠️ Bug 2 — Type Override Affects Agents You Didn't Intend to Change
Symptom: You add a type override for one specific agent's driver, but two other agents (also using the same driver class) suddenly behave differently. Your targeted change became global.
Root cause: Type override is always global. You used set_type_override() when you needed set_inst_override(). Every instance of base_drv — across all three agents — got err_drv.
// ❌ WRONG: type override changes ALL three agents' drivers
function void build_phase(uvm_phase phase);
base_drv::type_id::set_type_override(err_drv::get_type()); // ← GLOBAL
super.build_phase(phase);
// agent0.drv → err_drv ← intended
// agent1.drv → err_drv ← unintended side effect
// agent2.drv → err_drv ← unintended side effect
endfunction
// Debug: factory.print() clearly shows global scope
// Type Overrides: base_drv → err_drv ← no path constraint = global
// ✓ CORRECT: instance override is path-specific
function void build_phase(uvm_phase phase);
base_drv::type_id::set_inst_override(err_drv::get_type(),
"uvm_test_top.agent0.drv"); // ← ONLY agent0
super.build_phase(phase);
// agent0.drv → err_drv ← intended ✓
// agent1.drv → base_drv ← untouched ✓
// agent2.drv → base_drv ← untouched ✓
endfunction
// Verify with: foreach agent, print get_type_name() of each driver
// Confirm factory.print() now shows:
// Instance Overrides: uvm_test_top.agent0.drv: base_drv → err_drv🔍 Debug Decision Tree — Which Debug Tool to Use First
Problem: Override not working? │ ├─ Is override in factory at all? │ → uvm_factory::get().print(1) │ └─ NO: Check macro, check pre-super registration order │ └─ YES: Continue below ↓ │ ├─ Does actual type match expected? │ → comp.get_type_name() == expected? │ └─ NO (still base type): Check for new() bypass in VIP │ └─ YES (correct type): Override working, check behavior bug │ ├─ For instance override: is path correct? │ → uvm_root::get().print_topology() │ └─ Compare actual path to your set_inst_override() string │ └─ For chain: is final resolution correct? → factory.debug_pass_one(type, context, path) └─ Shows what factory WOULD return for that create() call
Ready-to-Run: Complete Factory Debug Demo
This single-file testbench demonstrates every factory debug technique in one place: print(), get_type_name(), $cast, print_topology(), and the chain resolution pattern. Save as factory_debug_demo.sv and run it — you'll see the complete diagnostic workflow in one simulation.
// ================================================================
// factory_debug_demo.sv — Complete Factory Debug Demonstration
// Shows all debug techniques: print(), get_type_name(), $cast,
// print_topology(), and override chain tracing
//
// Compile (Questa):
// vlog -sv -L uvm_1_2 factory_debug_demo.sv
// vsim -c -L uvm_1_2 factory_debug_top -do "run -all; quit"
//
// Compile (VCS):
// vcs -sverilog -ntb_opts uvm-1.2 factory_debug_demo.sv && ./simv
//
// Compile (Xcelium):
// xrun -sv -uvm factory_debug_demo.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Three-level hierarchy for all demos ───────────────────────────
class comp_a extends uvm_component;
`uvm_component_utils(comp_a)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string label(); return "A"; endfunction
endclass
class comp_b extends comp_a;
`uvm_component_utils(comp_b)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string label(); return "B"; endfunction
endclass
class comp_c extends comp_b;
`uvm_component_utils(comp_c)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string label(); return "C"; endfunction
endclass
class obj_x extends uvm_sequence_item;
`uvm_object_utils(obj_x)
function new(string n="obj_x"); super.new(n); endfunction
endclass
class obj_y extends obj_x;
`uvm_object_utils(obj_y)
function new(string n="obj_y"); super.new(n); endfunction
endclass
// ── Complete debug test ────────────────────────────────────────────
class factory_debug_test extends uvm_test;
`uvm_component_utils(factory_debug_test)
comp_a c1, c2, c3;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Setup: chain comp_a → comp_b → comp_c + instance exception
comp_a::type_id::set_type_override(comp_b::get_type()); // chain step 1
comp_b::type_id::set_type_override(comp_c::get_type()); // chain step 2
comp_a::type_id::set_inst_override( // c2 stays at comp_b
comp_b::get_type(), "uvm_test_top.c2");
obj_x::type_id::set_type_override(obj_y::get_type()); // object override
super.build_phase(phase);
c1 = comp_a::type_id::create("c1", this);
c2 = comp_a::type_id::create("c2", this);
c3 = comp_a::type_id::create("c3", this);
endfunction
task run_phase(uvm_phase phase);
obj_x obj;
comp_b cb;
comp_c cc;
phase.raise_objection(this);
// ── Debug Tool 1: factory.print() ─────────────────────────
`uvm_info("DBG","\n=== TOOL 1: factory.print() ===",UVM_NONE)
uvm_factory::get().print(1);
// ── Debug Tool 2: get_type_name() ─────────────────────────
`uvm_info("DBG","\n=== TOOL 2: get_type_name() ===",UVM_NONE)
$display(" c1: %s (label=%s)", c1.get_type_name(), c1.label());
$display(" c2: %s (label=%s)", c2.get_type_name(), c2.label());
$display(" c3: %s (label=%s)", c3.get_type_name(), c3.label());
// ── Debug Tool 3: $cast ────────────────────────────────────
`uvm_info("DBG","\n=== TOOL 3: $cast verification ===",UVM_NONE)
$display(" c1 castable to comp_c? %0b", $cast(cc, c1));
$display(" c2 castable to comp_c? %0b", $cast(cc, c2));
$display(" c2 castable to comp_b? %0b", $cast(cb, c2));
// ── Debug Tool 4: print_topology() ────────────────────────
`uvm_info("DBG","\n=== TOOL 4: print_topology() ===",UVM_NONE)
uvm_root::get().print_topology();
// ── Debug Tool 5: Object override check ───────────────────
`uvm_info("DBG","\n=== TOOL 5: Object type check ===",UVM_NONE)
obj = obj_x::type_id::create("obj");
$display(" obj type: %s", obj.get_type_name());
phase.drop_objection(this);
endtask
endclass
module factory_debug_top;
initial run_test("factory_debug_test");
endmodule
// ================================================================
// Expected Output:
//
// === TOOL 1: factory.print() ===
// Type Overrides:
// comp_a → comp_b
// comp_b → comp_c
// obj_x → obj_y
// Instance Overrides:
// uvm_test_top.c2: comp_a → comp_b ← stops chain for c2
//
// === TOOL 2: get_type_name() ===
// c1: comp_c (label=C) ← chain: a→b→c, resolved to comp_c
// c2: comp_b (label=B) ← inst override stops at b (doesn't follow type chain)
// c3: comp_c (label=C) ← chain: a→b→c, resolved to comp_c
//
// === TOOL 3: $cast verification ===
// c1 castable to comp_c? 1 ← comp_c IS-A comp_c
// c2 castable to comp_c? 0 ← comp_b is NOT comp_c
// c2 castable to comp_b? 1 ← comp_b IS-A comp_b
//
// === TOOL 4: print_topology() ===
// uvm_test_top [factory_debug_test]
// c1 [comp_c]
// c2 [comp_b] ← instance override stopped the chain here
// c3 [comp_c]
//
// === TOOL 5: Object type check ===
// obj type: obj_y ← object override also works
// ================================================================A Factory Bug the Print Output Cannot Explain
The mechanics of registering an override are covered in
the UVM factory and
type and instance overrides; the failures
below are what happens when those mechanics are correct and the result still is
not. Every diagnostic on this page assumes the override you registered is the
override the environment needs. When that assumption fails, factory::print() becomes
actively misleading: it faithfully reports an override that is real, registered,
and irrelevant.
factory.print showed the override, every component used type_id::create, and the base type was still built
PARAMETERIZED-TYPE-MISMATCHAn error-injection test registered a driver override and the injected errors never
appeared. The standard checks all came back positive: factory.print(1) listed
the override in the Type Overrides table, the override was registered before
super.build_phase(), and grepping the VIP found type_id::create() at every
construction site with no new() anywhere.
get_type_name() on the driver returned the base type. Two engineers spent most
of a day on it, because every diagnostic the runbook offers reported that the
override should be working.
// The VIP driver is parameterised on data width.
class apb_driver #(int DW = 32) extends uvm_driver #(apb_txn);
`uvm_component_param_utils(apb_driver #(DW))
...
endclass
class err_driver #(int DW = 32) extends apb_driver #(DW);
`uvm_component_param_utils(err_driver #(DW))
...
endclass
// The test registered the override for the DEFAULT specialisation.
function void my_test::build_phase(uvm_phase phase);
set_type_override_by_type(apb_driver#(32)::get_type(),
err_driver#(32)::get_type());
super.build_phase(phase);
endfunction
// ...but the environment instantiated a 64-bit agent.
apb_driver #(64) drv;
drv = apb_driver#(64)::type_id::create("drv", this);Printing the type names rather than reading the table resolved it in one line:
UVM_INFO: requested : apb_driver #(32)
UVM_INFO: resolves to: err_driver #(32) <-- the override IS working
UVM_INFO: actual drv : apb_driver #(64) <-- on a type nobody instantiatesThe Type Overrides table had shown apb_driver → err_driver, which is what sent
the investigation the wrong way. Most report formats elide the parameter list, so
two distinct types print with the same name and the mismatch is invisible in
exactly the output people trust most.
The confirming check was find_override_by_type on the type actually built:
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory f = cs.get_factory();
`uvm_info("DBG", $sformatf("64-bit resolves to: %s",
f.find_override_by_type(apb_driver#(64)::get_type(), "*").get_type_name()),
UVM_LOW)
// -> "apb_driver #(64)" : no override registered for this specialisationEach specialisation of a parameterised class is a distinct type to the
factory. apb_driver#(32) and apb_driver#(64) share source code and share
nothing else: separate get_type() handles, separate factory entries, separate
override tables. An override registered for one has no effect on the other.
Two properties of parameterised registration compound this. uvm_component_param_utils
deliberately does not register a string type name, because a name would have to
be unique per specialisation and the macro cannot construct one — so
set_type_override_by_name is unavailable and the by-type form is the only
option. And the printed representation collapses the parameter list in many
report formats, so the table looks like a single unambiguous override.
The result is a bug where every individual diagnostic is accurate and the
conclusion drawn from them is wrong. The override exists. It is registered
correctly. It is registered before creation. Every component uses
type_id::create(). All true, and none of it applies to the type being built.
// Override the specialisation the environment actually instantiates.
function void my_test::build_phase(uvm_phase phase);
set_type_override_by_type(apb_driver#(64)::get_type(),
err_driver#(64)::get_type());
super.build_phase(phase);
endfunction
// Better: take the width from the same parameter the env uses, so the two
// cannot drift apart.
localparam int DW = 64;
set_type_override_by_type(apb_driver#(DW)::get_type(),
err_driver#(DW)::get_type());Verifying the fix means checking the resolved type of the instantiated specialisation rather than reading the override table:
function void my_test::end_of_elaboration_phase(uvm_phase phase);
apb_driver#(64) d;
if (!$cast(d, uvm_top.find("*.drv")))
`uvm_fatal("DBG", "driver not found")
if (d.get_type_name() != "err_driver #(64)")
`uvm_error("DBG", $sformatf("override did not apply: got %s",
d.get_type_name()))
endfunctionChecking at end_of_elaboration_phase rather than in the test body matters: by
then the whole hierarchy is built, so the check sees what was actually
constructed rather than what was requested.
The habit that prevents recurrence is to derive the override's parameters from
the same source the environment uses. A localparam shared between the env
config and the test's override call makes the two impossible to desynchronise,
which is the real defect here — not the wrong number, but two places free to
disagree about it.
Interview Questions — Factory Debug Questions From Real Interviews
factory.print(1) prints every class registered with the factory; print(0) prints only the overrides. The table has four parts: registered object types, registered component types, active type overrides (global substitutions), and active instance overrides (path-specific substitutions).
You reach for it the moment an override does not behave as expected, and its value is that it splits the problem in half. If the override is absent from the table, the registration itself failed — wrong call, wrong phase, or never executed. If the override is present and the component is still the base type, registration succeeded and the problem is downstream: a new() bypass, an override registered after the object was created, or an override registered against a different type than the one being built.
That last case is the one to hold in reserve, because the table looks correct in it. Parameterised classes print with their parameter list elided in many formats, so apb_driver#(32) and apb_driver#(64) appear identically — see the Debug Lab above.
get_type_name() returns what the object is at run time, regardless of the type its handle is declared as. It is the direct answer to "did the override apply", and it is the first thing to print when the factory is suspected.
get_full_name() returns where the component sits in the hierarchy — uvm_test_top.env.agent0.drv. It is what you check when an instance override does not apply, because the path you passed to set_inst_override_by_type has to match this string, and the string is often not what you assumed: an agent instantiated inside a wrapper, or renamed on reuse, changes it without changing any code you wrote.
The reliable sequence is print_topology() first to see the built hierarchy, then get_type_name() on the specific component to see what it became. The hierarchy itself is built during the build phase, whose top-down order decides what a late override can still affect. Together they answer "is it the right type in the right place" before you open the factory tables at all.
Most likely: the component was constructed with new(). The factory can only substitute a type at a type_id::create() call, so a direct constructor call bypasses it entirely and no override applies. This and its relatives are catalogued in common factory bugs. Grep the VIP for = new( at component construction sites. This is far and away the most common cause in third-party code.
Next: the override was registered after the object was created. The factory does not retroactively re-type existing objects. In a test, registering after super.build_phase() is already too late for anything the environment built during that cascade — register overrides before it.
Then: the override targets a different type than the one being built. Two variants: a similarly-named class from another package, and — the harder one — a different specialisation of a parameterised class, where the table's printed names are identical but the types are not.
Rarest: multiple factory instances. Effectively only occurs with unusual core-service manipulation or multiple simulation processes sharing state.
The diagnostic that separates the middle two from the first: print find_override_by_type() for the exact type the component builds. If that returns the base type, the override is registered against something else.
The timing of the registration relative to the specific create() call, not merely relative to super.build_phase().
build_phase runs top-down, so a parent constructs its children during its own build_phase, which executes inside the super.build_phase() cascade of the test. An instance override registered anywhere after that cascade has completed arrives after the child was already built, and the factory does not re-type existing objects. The path being correct is irrelevant if nothing consults it any more.
The fix is to register every override — type and instance — before calling super.build_phase() in the test's build_phase. That places the registration ahead of the entire construction cascade, and it is one of the conventions collected in override best practices.
The second overlooked cause is the wildcard. set_inst_override_by_type matches against get_full_name() using glob semantics, so env.agent.drv does not match uvm_test_top.env.agent.drv, and a component nested one level deeper than assumed silently fails to match. Copy the path from print_topology() output rather than reconstructing it, and prefer a leading * when the top-level name may vary between tests.
Because each specialisation is a distinct type. apb_driver#(32) and apb_driver#(64) share source and share nothing else — separate get_type() handles, separate factory entries, separate override tables. An override registered for one has no effect on the other.
What makes it hard is that every diagnostic reports success. The override is real and registered; factory.print() lists it; the registration happens before super.build_phase(); every construction site uses type_id::create(). All of that is true simultaneously with the override having no effect, because it applies to a specialisation nothing instantiates.
Two details deepen the trap. uvm_component_param_utils deliberately registers no string type name — a name would need to be unique per specialisation and the macro cannot build one — so set_type_override_by_name is unavailable and by-type registration is the only route. And many report formats elide the parameter list when printing type names, so two different types appear identically in the table you are most likely to trust.
The check that cuts through it is to resolve the type the environment actually builds: call find_override_by_type() on apb_driver#(64)::get_type() and print the result. If it returns the base type, the override is registered against a different specialisation. Derive the parameter from the same localparam the environment uses so the two cannot drift.
A type override substitutes globally: every type_id::create() of the requested type anywhere in the testbench produces the replacement. An instance override substitutes only where the component's get_full_name() matches a supplied path pattern.
Type overrides fail loudly and rarely — usually because the class is created with new(), or because the override was registered too late. Their failure mode is uniform: nothing anywhere is substituted, which is easy to spot.
Instance overrides fail quietly and often, because they add a path match that can silently miss. The hierarchy is deeper than assumed, a wrapper component was inserted, the agent was renamed on reuse, or the pattern omits a leading wildcard. The component is built as the base type and everything else looks healthy — no error, because a non-matching instance override is a legitimate state rather than a fault.
The practical consequence: when an instance override does not apply, verify the path against print_topology() before suspecting anything else, and consider whether a type override plus a configuration field would express the intent more robustly. A type override cannot miss a path, because it does not consult one.
Where This Is Specified
- IEEE 1800.2-2020 (UVM) —
uvm_factory.set_type_override_by_type,set_inst_override_by_type,find_override_by_type,create_component_by_type, andprint. Overrides are consulted at creation time only; the factory does not re-type existing objects. - IEEE 1800.2-2020 —
uvm_coreservice_t.uvm_coreservice_t::get().get_factory()is the standard accessor for the factory instance. - IEEE 1800.2-2020 — registration macros.
uvm_component_utilsanduvm_object_utilsregister a type together with a string name;uvm_component_param_utilsanduvm_object_param_utilsregister a parameterised specialisation without a string name, so name-based override APIs are unavailable for them. - IEEE 1800.2-2020 — phasing.
build_phaseexecutes top-down, so a test must register overrides beforesuper.build_phase()for them to affect components built during that cascade. - Accellera UVM User Guide — factory usage. The
type_id::create()requirement, and why a directnew()bypasses substitution entirely.
Best Practices — Factory Debug Habits That Save Hours
| Practice | When to Apply | Engineering Benefit |
|---|---|---|
| Add factory.print(1) in start_of_simulation | Always in development; gate with plusarg in regression | Confirms override state before any transactions run — catches misconfiguration early |
| Verify get_type_name() for every overridden component | After any override registration, in start_of_simulation_phase | Confirms factory actually applied the override — catches timing and bypass bugs |
| Use print_topology() before setting instance override paths | Development phase, when adding new instance overrides | Gets the correct path string — eliminates "silent no-match" bugs |
| $cast to confirm type compatibility | After getting a component with potential override, before using subtype methods | Provides a runtime type safety check that prevents null-dereference in wrong-type scenarios |
| Grep for "= new(" in VIP code before debugging overrides | When factory.print() shows override but get_type_name() shows base type | Immediately identifies new() bypass — the most common override failure cause |
| Gate debug output with plusarg | Production regression environments | Avoids log pollution; enables on-demand debug: +UVM_FACTORY_DEBUG=1 |
💡 Pro Tip — Plusarg-Controlled Factory Debug
In production environments, you never want factory debug output in every run. Gate it behind a plusarg check so it's on-demand: function void start_of_simulation_phase(uvm_phase phase); super.start_of_simulation_phase(phase); if($test$plusargs("FACTORY_DEBUG")) begin $display("\n=== Factory State ==="); uvm_factory::get().print(1); $display("\n=== Topology ==="); uvm_root::get().print_topology(); end endfunction // Run clean: vsim ... (no debug output) // Run debug: vsim ... +FACTORY_DEBUG (full diagnostic)
Summary — Factory Debug Is a Skill, Not a Guess
Engineers who don't know factory debug spend days on problems that take five minutes with the right tools. The difference isn't expertise in UVM theory — it's knowing which diagnostic to apply and reading what the output tells you. Factory debug is a mechanical process: print state, verify type, check path, confirm timing.
| Tool | What It Answers | When to Use First |
|---|---|---|
factory.print(1) | Is the override registered in the factory? | Step 1 of any override debug session |
comp.get_type_name() | Did the override actually apply? | After factory.print() confirms override is registered |
$cast(sub, comp) | Is the actual type compatible with expected subtype? | When using subtype-specific methods after override |
print_topology() | What is the exact component path? What type is shown? | Before setting instance overrides, after build cascade |
| grep "= new(" | Is the VIP bypassing the factory? | When print() shows override but type is still base |
- 1 factory.print() + get_type_name() together. These two tools answer the full diagnostic question: "Is the override registered?" + "Did it apply?" If print() shows it but get_type_name() shows the base type, you have a bypass or timing problem.
- 2 Override chains resolve recursively. If A → B and B → C, requesting A gives C. Instance overrides stop chain resolution at that path — c2 with inst_override to B won't follow the B → C type override.
- 3 Gate debug output with plusargs. Factory debug should be on-demand, not always-on. One plusarg check in start_of_simulation_phase gives you full diagnostic power without cluttering regression logs.
Continue learning
Related tutorials
- Prerequisite
The UVM Factory
How macros register classes, type_id::create() resolution, overrides, and the critical ordering rule.
- Related topic
Common Factory Bugs
The five recurring factory failure modes — created with new, override registered too late, override not a derivative, missing registration, instance-path mismatch — each with its distinct symptom and fix.
- Related topic
build_phase
The top-down construction phase — getting configuration, creating children via the factory (type_id::create not new), setting children's config, and why overrides need the factory.
- Related topic
Why the Factory Exists
The problem the factory solves — substituting component and object types from the top without editing the environment, so one reusable env serves many specialized tests.