Skip to content

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.

SystemVerilog — calling factory::print() in start_of_simulation_phase
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endfunction

Reading the Factory Print Output

The output has three sections. Each tells you something specific about the factory state.

Annotated factory print output
Annotated factory print output

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.

CallOutputUse 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.
SystemVerilog — conditional factory print driven by plusarg
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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_top

Type 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.

SystemVerilog — verifying actual type with get_type_name()
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:

SystemVerilog — using $cast() to verify override type
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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().

SystemVerilog — programmatic override chain inspection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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)
Console — reading the override chain from factory::print(0) output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
## factory::print(0) output when chain ABC is registered:
 
#### UVM Factory Configuration ####
--- Type Overrides ---
  Requested Type       Override Type
  -------------------- -------------------------
  apb_driver           protocol_driver          ←── Step 1: AB
  protocol_driver      error_inject_driver      ←── Step 2: BC
 
## Reading the chain: start from apb_driver
## apb_driverprotocol_drivererror_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.

SimulatorSwitch / PlusargEffect
Questa+UVM_FACTORY_OVERRIDE_TRACEPrints 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_TRACESame as Questa. Supported in UVM 1.2 and later.
Xcelium-uvmfactoryoverridedbgXcelium-specific debug flag. Produces similar per-create() trace output.
All simulators+UVM_CONFIG_DB_TRACENot factory-specific, but useful alongside factory debug — shows all config_db get/set operations which often reveal whether virtual interfaces reached their targets.
Console — UVM_FACTORY_OVERRIDE_TRACE output example
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
## 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 usedno override matched
## "Override: error_inject_driver (type override)" means type override matched
## "Override: ... (instance override)" means instance override matched by path

Systematic 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.

Five-step factory debug workflow
Five-step factory debug workflow

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(...), not new(...). Expected: type_id::create(name, this) — two arguments for components. If using new(): replace with type_id::create(). Factory override has zero effect on objects created with direct new().
  • 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. Move set_type_override() before super.build_phase().
  • Verify override registration order in build_phase Inspect the test's build_phase. The set_type_override() or set_inst_override() call must appear before super.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 in set_inst_override(). Expected: the pattern matches the full path (exact or via wildcard). If mismatch: correct the pattern. Use +UVM_FACTORY_OVERRIDE_TRACE from the command line to see real-time path matching during create() calls.

Quick Reference — All Factory Debug Tools

ToolCall / UsageWhat 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_TRACEPlusarg: +UVM_FACTORY_OVERRIDE_TRACEReal-time trace of every factory create() call — requested type, resolved type, path.
CONFIG_DB_TRACEPlusarg: +UVM_CONFIG_DB_TRACENot factory-specific but useful alongside — shows all config_db get/set to find missing virtual interfaces.
SystemVerilog — reusable factory debug snippet for any component
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

SystemVerilog — Beginner: Basic factory.print() Inspection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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

SystemVerilog — Intermediate: Runtime Type Verification
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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

SystemVerilog — Verification: Multi-Level Override Chain Debug
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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_v3

Example 4 — Tricky: Debug a "Silent Override Failure" (new() bypass)

SystemVerilog — Tricky: Diagnosing Silent Override Failure
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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.

SystemVerilog — Bug 1: Three Root Causes, Three Fixes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

SystemVerilog — Bug 2: Unintended Global vs Intended Instance
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ 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.

SystemVerilog — factory_debug_demo.sv (Complete, Ready-to-Run)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ================================================================
// 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.

1

factory.print showed the override, every component used type_id::create, and the base type was still built

PARAMETERIZED-TYPE-MISMATCH
Symptom

An 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.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
Diagnostic Evidence

Printing the type names rather than reading the table resolved it in one line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 instantiates

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 specialisation
Root Cause

Each 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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()))
endfunction

Checking 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, and print. 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_utils and uvm_object_utils register a type together with a string name; uvm_component_param_utils and uvm_object_param_utils register a parameterised specialisation without a string name, so name-based override APIs are unavailable for them.
  • IEEE 1800.2-2020 — phasing. build_phase executes top-down, so a test must register overrides before super.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 direct new() bypasses substitution entirely.

Best Practices — Factory Debug Habits That Save Hours

PracticeWhen to ApplyEngineering Benefit
Add factory.print(1) in start_of_simulationAlways in development; gate with plusarg in regressionConfirms override state before any transactions run — catches misconfiguration early
Verify get_type_name() for every overridden componentAfter any override registration, in start_of_simulation_phaseConfirms factory actually applied the override — catches timing and bypass bugs
Use print_topology() before setting instance override pathsDevelopment phase, when adding new instance overridesGets the correct path string — eliminates "silent no-match" bugs
$cast to confirm type compatibilityAfter getting a component with potential override, before using subtype methodsProvides a runtime type safety check that prevents null-dereference in wrong-type scenarios
Grep for "= new(" in VIP code before debugging overridesWhen factory.print() shows override but get_type_name() shows base typeImmediately identifies new() bypass — the most common override failure cause
Gate debug output with plusargProduction regression environmentsAvoids 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.

ToolWhat It AnswersWhen 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