UVM
The UVM Factory
How macros register classes, type_id::create() resolution, overrides, and the critical ordering rule.
UVM Fundamentals · Module 5
The Problem the Factory Solves
Imagine your team maintains a shared APB VIP used by ten different projects. One project needs a driver that injects errors. Another needs a driver that adds protocol-level delays. A third needs a driver that records detailed timing logs for debug. Each project needs a different apb_driver.
Without a factory, you have two choices — both bad:
Option A — Edit the VIP
Edit apb_agent.sv to instantiate the variant driver. Now the shared VIP is different for every project. Version control becomes a nightmare. Other projects break.
Option B — Fork the VIP
Copy the entire VIP into each project. Now you maintain ten copies. A bug fix in the original must be applied to all ten — manually. Teams drift out of sync.
Option C — Use the Factory
The shared VIP creates drivers through the factory. Each project's test registers an override before the VIP builds. The VIP code is never touched. All ten projects use the same VIP source.
The factory makes Option C possible. It decouples "what type to create" from "the code that does the creating." The VIP asks the factory for an apb_driver. The test tells the factory to give an error_inject_driver instead. The VIP never knows the difference.
What the Factory Actually Is
The UVM factory is a singleton — one instance, accessible everywhere, that holds two tables:
The Type Registry
Maps each registered class name ("apb_driver") to a creator wrapper (type_id). Every call to uvm_component_utils` or uvm_object_utils` adds an entry to this table.
The Override Table
Maps requested types to override types. When create() is called, the factory checks this table first. If an override is registered, it creates the override type. If not, it creates the original.
Figure 1 — Factory mechanism. Registration happens once at static init time. Every create() call queries both the type registry and the override table. The caller gets whichever type the factory resolves to.
Registration — What the Macros Do Inside
The uvm_component_utils` and uvm_object_utils` macros do far more than a comment. They generate several declarations inside your class and register it with the factory at simulation startup. If you skip the macro, the factory cannot create or override your class.
// When you write this:
class apb_driver extends uvm_driver #(apb_seq_item);
`uvm_component_utils(apb_driver)
// ...
endclass
// The macro expands to approximately:
class apb_driver extends uvm_driver #(apb_seq_item);
// 1. Defines type_id — the factory wrapper for this class
typedef uvm_component_registry #(apb_driver, "apb_driver") type_id;
// 2. Defines get_type() — used by override calls
static function type_id get_type();
return type_id::get();
endfunction
// 3. Defines get_type_name() — used by reporting and debug
virtual function string get_type_name();
return "apb_driver";
endfunction
// 4. Registration happens automatically at package import time via
// uvm_component_registry's static constructor — no code needed from you
// ...
endclass
// This is why type_id::create() and type_id::set_type_override() work:
// type_id is a concrete class that knows how to create apb_driver.
// The factory holds a reference to type_id — not to apb_driver directly.Factory Creation — What type_id::create() Does
When you call apb_driver::type_id::create("drv", this), the following sequence executes inside the factory. Understanding this sequence explains every override behaviour you will ever encounter.
- Call is routed to the factory singleton
type_id::create()callsuvm_factory::get().create_component_by_type(type_id::get(), context_path, name, parent). The factory singleton is the same object throughout the entire simulation. - Factory checks the override table — instance overrides first The factory looks up the requested type (
apb_driver) and the full path of the component being created. Registered instance overrides are held in an ordered list and walked in order; the first pattern that matches is the one applied — the factory does not rank patterns by specificity. Instance overrides are considered before type overrides, so a matching instance override wins. - Factory checks for a type override If no instance override matched, the factory checks whether a type override was registered for
apb_driver. If yes, the override type is used. If no, the original registered type is used. - The resolved type's creator is called The factory calls the creator function for the resolved type. This is essentially
new(name, parent)on the resolved class — which may beapb_driverorerror_inject_driverdepending on the override table. - The handle is returned as the requested base type The factory returns the created object as an
apb_driverhandle, even if anerror_inject_driverwas created. This works because the override class must extend the base class — so the assignment is type-safe. Polymorphism does the rest.
Type Override — Global Substitution
A type override tells the factory: "Every time anyone asks for apb_driver, give them error_inject_driver instead." It applies globally — every instance of that type in the entire simulation hierarchy is substituted.
// ── Step 1: Create the override class — it MUST extend the original ──
class error_inject_driver extends apb_driver;
`uvm_component_utils(error_inject_driver) // must also be registered
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// Override only the method that differs
task run_phase(uvm_phase phase);
`uvm_info("ERR_DRV", "Error injection enabled", UVM_LOW)
// inject errors on select transactions
super.run_phase(phase);
endtask
endclass
// ── Step 2: Register the override in your test ────────────────────────
class error_inject_test extends base_test;
`uvm_component_utils(error_inject_test)
function void build_phase(uvm_phase phase);
// ⚠️ CRITICAL: register override BEFORE super.build_phase()
// super.build_phase() triggers env.build_phase() → agent.build_phase()
// which calls apb_driver::type_id::create() — override must be set by then
apb_driver::type_id::set_type_override(error_inject_driver::get_type());
super.build_phase(phase); // env creates drivers — now gets error_inject_driver
endfunction
endclass
// Result: every apb_driver instance in the entire sim is now error_inject_driver
// The agent, env, VIP — none of them were edited.Three Rules for Type Override
| # | Rule | Consequence if Violated |
|---|---|---|
| 1 | The override class must extend the original class | Factory throws a fatal error: "Override type is not a subtype of the requested type." |
| 2 | The override class must have uvm_component_utils` or uvm_object_utils` | The factory cannot find the override type. The override silently has no effect. |
| 3 | The override must be registered before the component is created (before super.build_phase()) | The component was already created with the original type. The override is registered but never used. |
Instance Override — Path-Specific Substitution
An instance override substitutes one specific component identified by its full hierarchy path. It is the surgical alternative to the blunt-force type override.
Figure 2 — Type override replaces every instance globally (left). Instance override uses a path pattern to replace exactly one specific instance (right). Wildcards (*) are supported in the path pattern.
// ── Instance override — exact path ────────────────────────────────
apb_driver::type_id::set_inst_override(
error_inject_driver::get_type(),
"uvm_test_top.env.apb_agent_1.drv" // exact full path
);
// ── Instance override — wildcard path ─────────────────────────────
apb_driver::type_id::set_inst_override(
error_inject_driver::get_type(),
"*.apb_agent_1.drv" // * matches any number of path segments
);
// ── Overriding all drivers in one specific agent (wildcard suffix) ─
apb_driver::type_id::set_inst_override(
error_inject_driver::get_type(),
"*.apb_agent_1.*" // all components under apb_agent_1
);
// ── Alternative: use the factory singleton directly ──────────────
uvm_factory factory = uvm_factory::get();
factory.set_inst_override_by_type(
apb_driver::get_type(),
error_inject_driver::get_type(),
"uvm_test_top.env.apb_agent_1.drv"
);The Critical Ordering Rule — Override Before Creation
This is the single most common factory mistake in production UVM code. The override must be registered before the component is created. In practice, this means: register overrides at the start of your test's build_phase(), before calling super.build_phase().
// ── Why the order matters — the build_phase cascade ─────────────────
//
// error_inject_test.build_phase()
// → base_test.build_phase()
// → env.build_phase() ← creates components HERE
// → apb_agent.build_phase()
// → apb_driver::type_id::create("drv", this) ← point of creation
//
// The override must be registered BEFORE the cascade reaches the create() call.
// ❌ WRONG — override registered after super.build_phase() ────────────
class error_inject_test extends base_test;
`uvm_component_utils(error_inject_test)
function void build_phase(uvm_phase phase);
super.build_phase(phase); // driver already created!
apb_driver::type_id::set_type_override( // too late — ignored
error_inject_driver::get_type());
endfunction
endclass
// ✓ CORRECT — override registered before super.build_phase() ──────────
class error_inject_test extends base_test;
`uvm_component_utils(error_inject_test)
function void build_phase(uvm_phase phase);
apb_driver::type_id::set_type_override( // registered first
error_inject_driver::get_type());
super.build_phase(phase); // cascade creates driver
// apb_driver::type_id::create() now returns error_inject_driver ✓
endfunction
endclass
// ── Alternative: use new() callback in new() — also correct ──────────
// Some teams register overrides in the test constructor (new()) which
// runs before build_phase(). Both approaches work — pick one and be consistent.Quick Reference — All Factory Methods
| Method | Where Called | What It Does |
|---|---|---|
type_id::create("name", parent) | build_phase | Creates a component through the factory. Respects all registered overrides. |
type_id::create("name") | Anywhere | Creates an object (no parent) through the factory. |
T::type_id::set_type_override(U::get_type()) | Before super.build_phase() | Globally replaces every instance of T with U. U must extend T. |
T::type_id::set_inst_override(U::get_type(), "path") | Before super.build_phase() | Replaces T with U only at the given hierarchy path. Wildcards allowed. |
T::get_type() | In override calls | Returns the type handle for T. Used as the argument to override methods. |
uvm_factory::get().print() | start_of_simulation_phase | Prints the full factory registry and override table. Essential for debugging. Covered in Module 7. |
// ── Registration (in every class — never skip) ────────────────────
class my_component extends uvm_component;
`uvm_component_utils(my_component) // structural elements
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class my_object extends uvm_sequence_item;
`uvm_object_utils(my_object) // data / transactions
function new(string name = "my_object");
super.new(name);
endfunction
endclass
// ── Creation (always through the factory) ─────────────────────────
my_component comp = my_component::type_id::create("comp", this);
my_object obj = my_object::type_id::create("obj");
// ── Type override (BEFORE super.build_phase) ──────────────────────
my_component::type_id::set_type_override(my_override_component::get_type());
// ── Instance override (BEFORE super.build_phase) ──────────────────
my_component::type_id::set_inst_override(
my_override_component::get_type(),
"uvm_test_top.env.agent.comp" // or use wildcards: "*.agent.comp"
);
// ── Override priority (highest to lowest) ─────────────────────────
// 1. Instance override ← FIRST REGISTERED match wins, NOT the most
// specific one. Register exceptions before
// defaults. See type-instance-override.
// 2. Type override
// 3. Original registered type (default)Code Examples — Factory in Action From Simple to Production
The factory is one of those mechanisms that sounds complicated until you see it working. These four examples build progressively — from the minimal registration pattern to the kind of multi-level override chain that production VIP teams use to swap components between regression tests.
Example 1 — Beginner: Registration and Basic Creation
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Step 1: Register classes with the factory via macros ──────────
class base_txn extends uvm_sequence_item;
`uvm_object_utils(base_txn) // ← registers "base_txn" in factory
rand bit[7:0] data;
function new(string n="base_txn"); super.new(n); endfunction
virtual function string describe();
return $sformatf("base_txn: data=%0h", data);
endfunction
endclass
class reg_test extends uvm_test;
`uvm_component_utils(reg_test) // ← registers "reg_test" in factory
function new(string n, uvm_component p); super.new(n,p); endfunction
task run_phase(uvm_phase phase);
base_txn txn;
phase.raise_objection(this);
// ── Step 2: Create via factory — ALWAYS this way ──────────
txn = base_txn::type_id::create("txn");
void'(txn.randomize());
$display("[FACTORY] Created: %s", txn.describe());
$display("[FACTORY] Actual type: %s", txn.get_type_name());
// ── Step 3: Verify factory lookup ─────────────────────────
$display("[FACTORY] Factory knows base_txn: %0b",
(base_txn::type_id::get() != null));
phase.drop_objection(this);
endtask
endclass
module factory_basic_top;
initial run_test("reg_test");
endmodule
// Expected Output:
// [FACTORY] Created: base_txn: data=a3
// [FACTORY] Actual type: base_txn
// [FACTORY] Factory knows base_txn: 1Example 2 — Intermediate: Type Override Swaps Driver Transparently
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Production driver — normal operation ──────────────────────────
class apb_driver extends uvm_driver#(base_txn);
`uvm_component_utils(apb_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string get_mode(); return "NORMAL"; endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
$display("[DRV] Built: %s mode=%s", get_type_name(), get_mode());
endfunction
endclass
// ── Error injection driver — extends production driver ─────────────
class apb_err_driver extends apb_driver;
`uvm_component_utils(apb_err_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string get_mode(); return "ERROR_INJECT"; endfunction
endclass
// ── Test: overrides driver type before build cascade ───────────────
class error_test extends uvm_test;
`uvm_component_utils(error_test)
apb_driver drv; // declared as base type
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Register override BEFORE calling super (which creates the driver)
apb_driver::type_id::set_type_override(apb_err_driver::get_type());
super.build_phase(phase);
// Factory intercepts create() and returns apb_err_driver instead
drv = apb_driver::type_id::create("drv", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("[TEST] drv actual type: %s", drv.get_type_name());
$display("[TEST] drv mode: %s", drv.get_mode());
// Polymorphism: apb_err_driver.get_mode() called even though handle is apb_driver
phase.drop_objection(this);
endtask
endclass
module factory_override_top;
initial run_test("error_test");
endmodule
// Expected Output:
// [DRV] Built: apb_err_driver mode=ERROR_INJECT ← factory gave us the subtype!
// [TEST] drv actual type: apb_err_driver
// [TEST] drv mode: ERROR_INJECT
//
// Key: the VIP code (apb_driver::type_id::create) was NOT modified.
// The test registered the override → factory did the substitution.Example 3 — Verification: Instance Override for One Specific Agent
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Two agents in the testbench — only agent_0 gets overridden ────
class multi_agent_test extends uvm_test;
`uvm_component_utils(multi_agent_test)
apb_driver drv_0, drv_1; // two drivers
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Override ONLY the driver at path "...agent_0.drv"
apb_driver::type_id::set_inst_override(
apb_err_driver::get_type(),
"uvm_test_top.drv_0"); // ← path must match get_full_name()
// Both created the same way — factory decides what each gets
drv_0 = apb_driver::type_id::create("drv_0", this);
drv_1 = apb_driver::type_id::create("drv_1", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("drv_0 type: %s mode: %s", drv_0.get_type_name(), drv_0.get_mode());
$display("drv_1 type: %s mode: %s", drv_1.get_type_name(), drv_1.get_mode());
phase.drop_objection(this);
endtask
endclass
module instance_override_top;
initial run_test("multi_agent_test");
endmodule
// Expected Output:
// drv_0 type: apb_err_driver mode: ERROR_INJECT ← overridden
// drv_1 type: apb_driver mode: NORMAL ← unchanged
//
// Instance override is path-specific — surgical precision
// Type override would have changed BOTH driversExample 4 — Tricky: Override Registered After build_phase Cascade
`include "uvm_macros.svh"
import uvm_pkg::*;
class late_override_test extends uvm_test;
`uvm_component_utils(late_override_test)
apb_driver drv;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ← build cascade starts here
drv = apb_driver::type_id::create("drv", this);
// ❌ WRONG: override registered AFTER create() already happened
apb_driver::type_id::set_type_override(apb_err_driver::get_type());
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("[LATE] drv type: %s", drv.get_type_name());
// Output: "apb_driver" — override arrived too late
// Override IS registered in factory, but the object was already created
phase.drop_objection(this);
endtask
endclass
// Expected Output (bug version):
// [LATE] drv type: apb_driver ← NOT apb_err_driver — too late!
//
// ✓ CORRECT — register override BEFORE super.build_phase():
// function void build_phase(uvm_phase phase);
// apb_driver::type_id::set_type_override(apb_err_driver::get_type()); // ← FIRST
// super.build_phase(phase); // ← cascade creates using the override
// drv = apb_driver::type_id::create("drv", this);
// endfunctionCommon Bugs — Factory Failures That Are Hard to Diagnose
Factory bugs are uniquely frustrating because they usually don't crash the simulation. The wrong type silently runs. Overrides silently don't apply. You end up debugging behavior differences without realizing the root cause is that the factory gave you the wrong component two build_phases ago.
⚠️ Bug 1 — Using new() Instead of type_id::create() — Override Never Works
Symptom: You register a type override in the test, but the driver still runs with the base behavior. get_type_name() returns the base class name. No errors. The override is simply ignored.
Root cause: new() bypasses the factory entirely. Factory overrides only apply when objects are created via type_id::create(). If VIP code uses direct new(), factory is never consulted and the override is invisible.
// ❌ WRONG — direct new() bypasses factory completely
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver drv;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = new("drv", this); // ← bypasses factory — WRONG
// Type override in test will NEVER take effect here
endfunction
endclass
// ✓ CORRECT — always use factory create()
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver drv;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = apb_driver::type_id::create("drv", this); // ← factory consulted
// Now type overrides registered in any test WILL take effect
endfunction
endclass
// Debug command to find all new() usage in VIP code:
// grep -rn "= new(" src/vip/ | grep -v "\.md"
// Any hit = potential factory bypass⚠️ Bug 2 — Instance Override Path Doesn't Match get_full_name()
Symptom: Instance override registered, but get_type_name() still returns the base class. No error from the factory. Simulation runs normally with the wrong type.
Root cause: The path string in set_inst_override() must exactly match the component's get_full_name() at the time of creation. If the hierarchy changed (agent renamed, nesting level different), the path doesn't match and the factory silently falls back to the base type.
// ❌ WRONG — path doesn't match actual component hierarchy
apb_driver::type_id::set_inst_override(
apb_err_driver::get_type(),
"uvm_test_top.agent.drv"); // ← assumes "agent" layer exists
// But actual hierarchy is: uvm_test_top.drv (no agent wrapper)
// Result: no match → falls back to base type silently
// ── Debug: print actual path BEFORE registering override ──────────
// Add this in start_of_simulation_phase (BEFORE build_phase completes)
// Actually, print_topology() shows all paths:
uvm_root::get().print_topology();
// Then use the EXACT path shown in the topology output
// ✓ CORRECT — verify path with get_full_name() first
// Option 1: Use wildcards (supported in most UVM implementations)
apb_driver::type_id::set_inst_override(
apb_err_driver::get_type(),
"uvm_test_top*.drv"); // ← wildcard matches any middle path
// Option 2: Hard-code the exact path from topology output
apb_driver::type_id::set_inst_override(
apb_err_driver::get_type(),
"uvm_test_top.drv"); // ← exactly what print_topology shows🔍 Debug Insight — Factory Diagnostic Toolkit
- Print factory state:
factory.print()oruvm_default_factory.print()— shows all registered types and active overrides - Verify actual type at runtime:
$display("type: %s", comp.get_type_name())— if it's the base class name, override didn't apply - Find hierarchy paths:
uvm_root::get().print_topology()instart_of_simulation_phase— shows all component full paths - Find new() bypasses:
grep -rn "= new(" src/vip/— any direct construction bypasses factory - Override too late: If override is registered inside or after
super.build_phase(), the components are already created
Ready-to-Run: Complete Factory Demo
This single-file testbench demonstrates type override, instance override, factory diagnostics, and the ordering rule — everything from the article in one compilable file. Save as factory_demo.sv and run it.
// ================================================================
// factory_demo.sv — UVM Factory Complete Demonstration
// Shows: registration, type override, instance override, diagnostics
//
// Compile (Questa):
// vlog -sv -L uvm_1_2 factory_demo.sv
// vsim -c -L uvm_1_2 factory_demo_top -do "run -all; quit"
//
// Compile (VCS):
// vcs -sverilog -ntb_opts uvm-1.2 factory_demo.sv && ./simv
//
// Compile (Xcelium):
// xrun -sv -uvm factory_demo.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Base transaction ───────────────────────────────────────────────
class pkt extends uvm_sequence_item;
`uvm_object_utils(pkt)
rand bit[7:0] data;
function new(string n="pkt"); super.new(n); endfunction
virtual function string kind(); return "NORMAL"; endfunction
endclass
// ── Error packet — extends base ────────────────────────────────────
class err_pkt extends pkt;
`uvm_object_utils(err_pkt)
function new(string n="err_pkt"); super.new(n); endfunction
virtual function string kind(); return "ERROR"; endfunction
endclass
// ── Base component ─────────────────────────────────────────────────
class base_comp extends uvm_component;
`uvm_component_utils(base_comp)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string mode(); return "BASE"; endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
$display(" Built: %-20s type=%-15s mode=%s",
get_full_name(), get_type_name(), mode());
endfunction
endclass
// ── Extended component ─────────────────────────────────────────────
class adv_comp extends base_comp;
`uvm_component_utils(adv_comp)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string mode(); return "ADVANCED"; endfunction
endclass
// ── Full-featured test ─────────────────────────────────────────────
class factory_test extends uvm_test;
`uvm_component_utils(factory_test)
base_comp comp_a, comp_b, comp_c;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
uvm_factory factory = uvm_factory::get();
`uvm_info("TEST", "\n=== 1. Type Override (all instances) ===", UVM_NONE)
base_comp::type_id::set_type_override(adv_comp::get_type());
`uvm_info("TEST", "\n=== 2. Instance Override (comp_b only) ===", UVM_NONE)
base_comp::type_id::set_inst_override(
base_comp::get_type(), // revert comp_b back to base
"uvm_test_top.comp_b");
super.build_phase(phase);
comp_a = base_comp::type_id::create("comp_a", this); // → adv_comp
comp_b = base_comp::type_id::create("comp_b", this); // → base_comp (inst override)
comp_c = base_comp::type_id::create("comp_c", this); // → adv_comp
endfunction
task run_phase(uvm_phase phase);
pkt p1, p2;
phase.raise_objection(this);
`uvm_info("TEST", "\n=== 3. Object Type Override ===", UVM_NONE)
pkt::type_id::set_type_override(err_pkt::get_type());
p1 = pkt::type_id::create("p1");
$display(" p1 type=%s kind=%s", p1.get_type_name(), p1.kind());
`uvm_info("TEST", "\n=== 4. Factory Print ===", UVM_NONE)
uvm_factory::get().print(1);
`uvm_info("TEST", "\n=== 5. Component Summary ===", UVM_NONE)
$display(" comp_a: %s", comp_a.get_type_name());
$display(" comp_b: %s", comp_b.get_type_name());
$display(" comp_c: %s", comp_c.get_type_name());
phase.drop_objection(this);
endtask
endclass
module factory_demo_top;
initial run_test("factory_test");
endmodule
// ================================================================
// Expected Output:
//
// === 1. Type Override (all instances) ===
// === 2. Instance Override (comp_b only) ===
// Built: uvm_test_top.comp_a type=adv_comp mode=ADVANCED
// Built: uvm_test_top.comp_b type=base_comp mode=BASE ← inst override
// Built: uvm_test_top.comp_c type=adv_comp mode=ADVANCED
//
// === 3. Object Type Override ===
// p1 type=err_pkt kind=ERROR ← pkt factory gave err_pkt
//
// === 4. Factory Print ===
// (shows registered types and active overrides)
//
// === 5. Component Summary ===
// comp_a: adv_comp ← type override applied
// comp_b: base_comp ← instance override reverted it
// comp_c: adv_comp ← type override applied
// ================================================================The Whole Point, In One Runnable File
Everything above describes a mechanism. This demonstrates the payoff: swapping a component's implementation without editing the code that constructs it. That is the entire reason the factory exists, and it is worth seeing as a single before/after.
// ================================================================
// factory_payoff.sv — the env is written ONCE and never edited.
// Questa : vlog -sv -L uvm_1_2 factory_payoff.sv
// vsim -c -L uvm_1_2 factory_payoff_top -do "run -all; quit"
// VCS : vcs -sverilog -ntb_opts uvm-1.2 factory_payoff.sv && ./simv
// Xcelium: xrun -sv -uvm factory_payoff.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
class apb_driver extends uvm_component;
`uvm_component_utils(apb_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual task send(); `uvm_info("DRV", "clean transfer", UVM_LOW) endtask
endclass
class err_apb_driver extends apb_driver;
`uvm_component_utils(err_apb_driver)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual task send(); `uvm_info("DRV", "CORRUPTED transfer", UVM_LOW) endtask
endclass
// ─────────────────────────────────────────────────────────────────
// This class is the reusable VIP. It is written once. No test ever
// edits it, and it has no knowledge that err_apb_driver exists.
// ─────────────────────────────────────────────────────────────────
class apb_env extends uvm_env;
`uvm_component_utils(apb_env)
apb_driver drv; // BASE-type handle
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Direct `new("drv", this)` here would hard-wire apb_driver forever
// and make every override in every test silently ineffective.
drv = apb_driver::type_id::create("drv", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
drv.send(); // virtual dispatch
phase.drop_objection(this);
endtask
endclass
class base_test extends uvm_test;
`uvm_component_utils(base_test)
apb_env env;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = apb_env::type_id::create("env", this);
endfunction
// Proves the swap really happened, rather than trusting the log line.
function void check_phase(uvm_phase phase);
`uvm_info("CHK", $sformatf("env.drv concrete type = %s",
env.drv.get_type_name()), UVM_LOW)
endfunction
endclass
// The ONLY thing an error test adds is one registration line.
class error_test extends base_test;
`uvm_component_utils(error_test)
err_apb_driver probe; // for the $cast check
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
apb_driver::type_id::set_type_override(err_apb_driver::get_type());
super.build_phase(phase); // ← cascade sees it
endfunction
function void check_phase(uvm_phase phase);
super.check_phase(phase);
if (!$cast(probe, env.drv))
`uvm_fatal("CHK", $sformatf(
"override did not apply — env.drv is %s", env.drv.get_type_name()))
endfunction
endclass
module factory_payoff_top;
initial run_test(); // +UVM_TESTNAME selects the test
endmodule
// vsim ... +UVM_TESTNAME=base_test
// UVM_INFO [DRV] clean transfer
// UVM_INFO [CHK] env.drv concrete type = apb_driver
//
// vsim ... +UVM_TESTNAME=error_test ← apb_env source is UNCHANGED
// UVM_INFO [DRV] CORRUPTED transfer
// UVM_INFO [CHK] env.drv concrete type = err_apb_driverTwo details carry the whole argument. apb_env declares drv as the base type and constructs it through type_id::create — that is what leaves the decision open. And error_test contributes exactly one line. If apb_env had used new("drv", this), that line would register successfully, print in the factory dump, and change nothing.
Every test in the suite ignored its factory overrides, and the factory dump looked perfect
FACTORY-BYPASSED-BY-NEWA team adopted the factory across an existing environment. Overrides were added to four tests. None of them changed behaviour.
The factory dump showed all four overrides registered against the right source and target types. The registration code was demonstrably running. Yet every component in the hierarchy reported its original type.
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver drv;
apb_monitor mon;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = new("drv", this); // ← factory never consulted
mon = apb_monitor::type_id::create("mon", this);
endfunction
endclassThe decisive comparison was between two sibling components in the same agent:
UVM_INFO ... uvm_test_top.env.agent.drv type=apb_driver ← override ignored
UVM_INFO ... uvm_test_top.env.agent.mon type=dbg_apb_monitor ← override appliedBoth overrides were registered identically and at the same moment. One took effect and one did not, which ruled out registration timing, path matching and the override table entirely — and pointed at the construction site.
grep -rn "= new(" src/vip/ returned eleven hits across the VIP, all of them components.
new() constructs a specific class directly. The factory is not a hook into SystemVerilog's new — it is a separate creation path that only runs when something calls type_id::create().
An override is therefore not a property of a class. It is an instruction to the factory, consulted only at create() time. Every new() call in a component's construction path is a place where that instruction is silently skipped, and nothing warns you: registering an override that is never consulted is not an error.
The monitor worked because somebody had converted that one line during an earlier cleanup.
Replace direct construction of factory-registered components with type_id::create:
drv = apb_driver::type_id::create("drv", this);Then prevent the regression, because this reappears every time someone adds a component in a hurry. Two guards are worth having. A CI grep over VIP and environment sources for = new( on uvm_component subclasses catches it at review time. And a check_phase assertion in the base test that $casts each swappable handle to its expected type — as in the runnable example above — catches it on the first run of any test that relies on an override.
Note the asymmetry worth remembering: new() is correct and normal for uvm_object subclasses in throwaway contexts, and wrong for anything you might ever want to override. When in doubt, create() costs nothing.
Questions Engineers Actually Ask
One thing: the decision about which concrete class to construct moves out of the code that does the constructing.
With new(), apb_env names apb_driver and that choice is fixed at compile time in a file owned by the VIP team. With type_id::create(), apb_env says "give me whatever is currently registered for apb_driver", and a test — a different file, owned by a different person, written later — can answer differently.
Everything else the factory does exists to support that. The registration macros build the lookup table, the override methods write to it, and create() reads it.
The dump proves registration. It does not prove the factory was ever asked.
The most common cause is a new() call somewhere on the construction path, which bypasses the factory entirely — DebugLab 1 is exactly this, including the diagnostic that isolates it. The other two causes are registering the override after the build cascade already created the component, and, for instance overrides, a path pattern that does not match.
A fast discriminator: if two sibling components in the same parent behave differently under identically-registered overrides, the difference is at the construction site, not in the factory.
Where This Is Specified
- IEEE 1800.2-2020 (UVM) —
uvm_factory. The factory singleton,create_component_by_typeandcreate_object_by_type, the registry of registered types, and the override table consulted at creation. - IEEE 1800.2-2020 —
uvm_component_registryanduvm_object_registry. Thetype_idproxy generated by the utility macros, itscreate()andget_type()methods, and the separate component and object creation signatures. - IEEE 1800.2-2020 —
uvm_component_utils/uvm_object_utils. What registration adds to a class, and the consequence that an unregistered class has notype_idand cannot participate in override. - IEEE 1800.2-2020 — factory override resolution. Instance overrides considered before type overrides, and the ordered traversal in which the first matching instance pattern is selected.
- IEEE 1800.2-2020 —
uvm_factory::print. The registry and override-table dump, and its scope: it reports what is registered, not what was resolved. - IEEE 1800-2023 §8.20 — Virtual methods. Dynamic dispatch through a base-class handle, which is what makes a substituted subtype behave differently at an unchanged call site.
Related Reading
- Type vs Instance Override — the override mechanics: scope, path patterns, registration order, the
replaceargument, and the full API surface. - Factory Debug — the procedure for an override that will not apply, and how to read the factory dump against the topology print.
- Common Factory Bugs — the recurring failure catalogue.
- Why the Factory Exists — the reuse problem that motivates the mechanism, if you want the argument before the API.
- Factory Pattern — the general design pattern, independent of UVM.
- UVM Object and UVM Component — the two base classes whose registries the factory maintains, and why their
create()signatures differ.
Interview Questions — Factory Questions From Real Interviews
Beginner Level
- Q1 **What does the
uvm_component_utils macro do internally?** **Answer:** It registers the class with the UVM factory so it can be created by name, overridden, and looked up at runtime. Specifically, it definestype_id(the factory registration wrapper), providesget_type()(returns the registered type object), and definescreate()(the factory creation method). Without this macro, the class exists in SystemVerilog but is invisible to UVM's factory — you can't override it, can't create it by name string, andget_type_name()` returns a generic base class name. - Q2 What is the difference between type override and instance override? Answer: Type override replaces every instance of a class across the entire testbench — when any code calls
base_type::type_id::create()anywhere in the hierarchy, the factory returns the override type. Instance override is path-specific: only the component at the exact path string (matching itsget_full_name()) gets the override. Other instances of the same type are unaffected. Use type override for "change all drivers to error-inject mode." Use instance override for "change only agent_0's driver, leave agent_1 alone."
Intermediate Level
- Q3 Why must factory overrides be registered before super.build_phase() in the test's build_phase? Answer:
super.build_phase()triggers the build_phase cascade — it calls build_phase on all child components, which in turn call it on their children. Anytype_id::create()calls in those child build_phases consult the factory at that moment. If the override hasn't been registered yet (because it's in code after the super call), those create() calls get the base type. The objects are already created before the override arrives. Registering overrides before super.build_phase() ensures the factory has the override in place before any creation happens. - Q4 Does the override class need to extend the overridden class? What happens if it doesn't? Answer: Yes — the override class must be a subtype (direct or indirect) of the overridden class. The factory creates the override type but returns it through the base type's handle (via SystemVerilog polymorphism). If the override class doesn't extend the base, UVM will issue a
UVM_WARNINGorUVM_FATALfrom the factory saying "override type is not a subtype of the requested type," and the creation may fail. This is enforced because the calling code holds a handle of the base type — $cast between unrelated types would fail at runtime.
Senior / Architect Level
- Q5 A test registers both a type override and an instance override for the same component. Which takes precedence? Answer: Instance override takes precedence over type override for the specific instance path. The factory's priority order is: (1) Instance override for the exact path — highest priority; (2) Type override — applies to all instances not covered by an instance override; (3) Default registered type — if no override exists. This allows you to set a global type override ("all drivers → error driver") and then selectively revert specific instances back to base ("agent_2's driver → base driver") using an instance override that points back to the original type.
Best Practices — Factory Rules That Production Teams Enforce
| Rule | Correct Pattern | Why It Matters |
|---|---|---|
| Always use type_id::create() | T::type_id::create("name", parent) | Direct new() bypasses factory — overrides never work on that component |
| Register override BEFORE super.build_phase() | Override first line of build_phase, then super | Super triggers the cascade — components created before override arrives get base type |
| Override class must extend base class | class override_drv extends base_drv | Factory enforces subtype relationship; unrelated types cause fatal errors |
| Verify path before instance override | Use print_topology() to get exact path strings | Wrong path → override silently doesn't apply → wrong type runs |
| Use factory.print() for diagnostics | uvm_factory::get().print(1) in start_of_simulation | Shows registered types and active overrides — confirms override took effect |
| Never override without a macro | Both base and override class need ``uvm_*_utils` | Un-registered classes can't be overridden or looked up by the factory |
| Don't override in connect_phase or run_phase | Override only in build_phase (before super) | Overrides in later phases are too late — all components already created |
💡 Pro Tip — Factory-Based Test Architecture
In production environments, the factory enables a "configuration by substitution" pattern. A base test class builds a standard testbench. Derived tests override specific components before super.build_phase() to inject error behavior, logging, or alternate implementations — all without touching VIP source files. This is how teams run the same VIP against 50 different test scenarios: one set of VIP source files, 50 different test classes that swap components via factory overrides. No code duplication, no ifdef forests.
Summary — The Mechanism That Makes UVM Reuse Possible
The UVM factory is the reason VIPs are worth buying. Without it, every test that needs different behavior requires editing the VIP source — which means version control chaos, merge conflicts, and VIPs that drift between projects. With the factory, you change test behavior by changing the test, not the VIP.
| Concept | What It Does | When to Use It |
|---|---|---|
| Registration | Makes a class known to the factory via `uvm_*_utils macro | Every class that needs to be created, overridden, or looked up by name |
| type_id::create() | Creates objects through the factory (not new()) | Every component and object creation in VIP and testbench code |
| Type Override | Replaces all instances of a class globally | Swap all drivers to error mode, all sequences to stress mode |
| Instance Override | Replaces one specific component by path | Error injection on one specific agent while leaving others normal |
| factory.print() | Shows registered types and active overrides | Debugging when override doesn't seem to take effect |
- 1 Registration is invisible but mandatory. Without ``uvm_*_utils`, the class is opaque to the factory. You can still create it with new() — but you can never override it and type_id::create() returns the wrong type.
- 2 Override before super.build_phase() — non-negotiable. The cascade creates all components. If the override isn't registered before the cascade starts, it arrives after all objects are already created. The override is stored but never used.
- 3 The factory is what makes VIPs reusable. The whole point of UVM's factory isn't convenience — it's that test configurations can vary independently of VIP source code. One set of files, infinite configurations. That's the engineering value.
Part of UVM Fundamentals·The UVM Factory·Lesson 26 of 33
View programContinue learning
Related tutorials
- 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.