Type vs Instance Override
When to choose type override vs instance, chain overrides, five dangerous pitfalls.
UVM Fundamentals · Module 6
The Decision Framework — Type vs. Instance
The choice between type override and instance override is not stylistic. It is determined by scope: how many instances do you want to replace? Need to substitute a UVM class?Should ALL instances be replaced?YESTYPE OVERRIDEset_type_override()global — all instances affectedNOSpecific instance by path?(known hierarchy path)YESINSTANCE OVERRIDEset_inst_override()surgical — one path onlyNOOnly configuration differs?(values, not behaviour)YESUSE uvm_config_dboverride is the wrong tool hereNORECONSIDER DESIGNmay be architecture issue Figure 1 — Decision tree for choosing between type override, instance override, and config_db. Follow from top. The answer is almost always one of the first two branches. Use TYPE OVERRIDE when…
- You want to replace every instance of the class across the whole simulation
- You are writing a dedicated test scenario — e.g., an error-injection test that runs the entire testbench with an error-injecting driver
- The replacement is contextual to the test, not the environment structure
- You want the VIP code and environment code completely untouched
- Example: all
apb_driverinstances →slow_apb_driver(adds wait states to every transaction) Use INSTANCE OVERRIDE when… - You have a specific component at a known hierarchy path that needs the replacement
- Other instances of the same class must continue with the original behaviour
- You are in a multi-interface environment and only one interface needs the variant
- You need to inject a debug monitor into one agent without affecting the others
- Example: only
uvm_test_top.env.ahb_agent.drv→error_drv, while all other agents keep their original driver
Type Override Deep Dive — Scope and Subtleties
What "Global" Actually Means
Type override is global with respect to the factory registry — not with respect to time. It replaces every instance created after the override is registered. Instances created before the override was set keep the original type. In practice, because all components are created in build_phase and the override is set at the start of build_phase, this distinction rarely matters — but it matters when you try to override inside a sequence during run_phase.
Multiple Type Overrides for the Same Class
Only one type override can be active for any given source class at a time. Calling set_type_override() a second time for the same source class replaces the previous override — it does not accumulate. The last call before component creation wins.
function void build_phase(uvm_phase phase);
// Override 1 registered first
apb_driver::type_id::set_type_override(slow_apb_driver::get_type());
// Override 2 registered second — REPLACES Override 1
apb_driver::type_id::set_type_override(error_inject_driver::get_type());
super.build_phase(phase);
// Result: apb_driver instances become error_inject_driver, NOT slow_apb_driver
// slow_apb_driver override was silently discarded
endfunction
// If you want BOTH behaviours, create a class that extends both:
class slow_error_driver extends apb_driver; // combines both variants
`uvm_component_utils(slow_error_driver)
// implement both slow and error behaviours here
endclassInstance Override Deep Dive — Path Patterns and Priority
How Path Matching Works
Instance override matches the full path returned by get_full_name() of the component at creation time. The factory evaluates the pattern against the concatenation of the parent's full name and the new component's name argument.
| Pattern | Matches | Does Not Match |
|---|---|---|
"uvm_test_top.env.agent.drv" | Exactly that one component | Any other path |
"*.agent.drv" | Any drv under any agent at any depth | env.drv (no agent in the path) |
"*.agent_0.*" | Every component directly under agent_0 | Components two or more levels under agent_0 |
"uvm_test_top.*" | Every direct child of the test root | Grandchildren and deeper |
"*drv*" | Any component whose name contains "drv" anywhere | Components with names that don't contain "drv" |
Instance Override vs. Type Override Priority
When both a type override and an instance override are registered for the same source type, the instance override always wins for any path it matches. The type override acts as the fallback for all unmatched paths.
function void build_phase(uvm_phase phase);
// Type override: ALL apb_driver instances → slow_driver (default)
apb_driver::type_id::set_type_override(slow_apb_driver::get_type());
// Instance override: ONLY agent_0's driver → error_inject_driver
apb_driver::type_id::set_inst_override(
error_inject_driver::get_type(),
"*.apb_agent_0.drv"
);
super.build_phase(phase);
// apb_agent_0.drv → error_inject_driver (instance override wins)
// apb_agent_1.drv → slow_apb_driver (type override as fallback)
// apb_agent_2.drv → slow_apb_driver (type override as fallback)
endfunctionChain Overrides — Overriding an Override
The factory follows a chain: if a resolved type itself has an override, the factory continues resolving until it reaches a type with no override. This enables layered customisation — a team adds one override, a test adds another on top of it. OVERRIDE REGISTRATIONSFACTORY RESOLUTION (at create time)RESULTVIP base register:base_driver (A)Agent team override:A → Bbase_driver → protocol_driver (B)Test override:B → Cprotocol_driver → error_driver (C)create(base_driver)Factory: requested = base_driverOverride: A → B ∴ resolved = BOverride: B → C ∴ resolved = CC has no override → chain endsObject created:error_driver(type C)Handle returned as:base_driver(base type)Polymorphism routes methodcalls to error_driver Figure 2 — Chain override resolution. Each registered override is followed until no more overrides exist. The base type is used as the return handle; polymorphism does the rest.
Overriding Objects — Transactions and Sequences
Module 5 focused on component overrides. The factory works identically for uvm_object-derived classes — transactions, sequences, configuration objects. The API is the same. The use cases are different.
// ── Use case 1: Override the transaction to add timing fields ────────
class apb_debug_seq_item extends apb_seq_item;
`uvm_object_utils(apb_debug_seq_item) // `uvm_object_utils, not component
bit [63:0] issue_time; // simulation time when transaction was issued
bit [63:0] complete_time; // simulation time when DUT responded
string debug_tag; // user-defined label for the waveform
function new(string name = "apb_debug_seq_item");
super.new(name);
endfunction
endclass
// Override in your debug test — driver gets apb_debug_seq_item from sequencer
class debug_test extends base_test;
function void build_phase(uvm_phase phase);
// Transactions become debug transactions — driver still uses apb_seq_item handle
apb_seq_item::type_id::set_type_override(apb_debug_seq_item::get_type());
super.build_phase(phase);
endfunction
endclass
// ── Use case 2: Override the sequence to inject directed stimulus ─────
class corner_case_seq extends apb_base_seq;
`uvm_object_utils(corner_case_seq)
function new(string name = "corner_case_seq"); super.new(name); endfunction
task body();
// Directed corner-case stimulus — overrides random stimulus
endtask
endclass
// In the test: swap the default random sequence with the corner-case one
apb_base_seq::type_id::set_type_override(corner_case_seq::get_type());Five Dangerous Pitfalls
These are the five scenarios where overrides fail silently or cause unexpected results. Every one of them has been encountered in production environments.
- P1 Component created with new() — override has no effect If the agent uses
drv = new("drv", this)instead ofapb_driver::type_id::create("drv", this), the factory is bypassed entirely. The override is registered but never consulted. Fix: Replace everynew()call in VIP and environment code withtype_id::create(). Search your codebase for= new(in component build phases. - P2 Override class missing
uvm_*_utils — factory cannot find the type The factory resolves types by their registered wrapper (type_id). If the override class has no ``uvm_component_utilsor ``uvm_object_utils, it has notype_id. Callingerror_inject_driver::get_type()` returns null. The override silently fails. Fix: Add the macro to the override class. Both the original and the override must be registered. - P3 Override registered after super.build_phase() — the component already exists The driver is created deep inside the build_phase cascade triggered by
super.build_phase(). Registering the override after that call means the target was created with the original type and the override will never be used in this simulation. Fix: Always register overrides beforesuper.build_phase(). If using base test patterns, register at the very start of build_phase. - P4 Override class does not extend the original — factory fatal The factory enforces a type-safety check: the override type must be a subtype of the requested type. If
error_inject_driverdoes not extendapb_driver, the factory throwsuvm_fatalat runtime: "Override type is not a subtype of requested type." Fix: The override class must extend the original:class error_inject_driver extends apb_driver;. This is mandatory, not optional. - P5 Instance override path doesn't match get_full_name() — no match, original used The path pattern in
set_inst_override()is matched against the component's actual full path at creation time. If the path differs by even one character (wrong agent name, wrong depth, extra prefix), the pattern doesn't match and the original type is created without any warning. Fix: Useuvm_factory::get().print()in start_of_simulation_phase to see registered overrides and which ones matched (covered in Module 7).
Override Doesn't Work — Debug Checklist
Run through this checklist in order when an override has no visible effect. Each check eliminates one category of failure.
- Original class has
uvm_component_utils` oruvm_object_utils`. - Override class has
uvm_component_utils` oruvm_object_utils`. - Override class extends the original class (not just a sibling or unrelated class).
- Both classes are in packages that are compiled and imported.
set_type_override()orset_inst_override()is called beforesuper.build_phase().- The component is created with
type_id::create(), not directnew(). - For instance override: verify the path matches
get_full_name()of the target component exactly. - No second
set_type_override()call later is overwriting the first registration. - Run
uvm_factory::get().print()instart_of_simulation_phaseand confirm the override appears in the output. - Add
+UVM_FACTORY_OVERRIDE_TRACEplusarg (simulator-specific) to see real-time override resolution messages.
// Add this temporarily to any component to see what the factory knows
function void start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
// Print the complete factory state: registry + override table
uvm_factory::get().print(1); // 1 = verbose, shows all registered types and overrides
// You will see output like:
// #### UVM Factory Configuration (#types registered: 47) ####
// No type overrides are registered.
// -- or --
// Type Overrides:
// apb_driver → error_inject_driver
//
// If your override does NOT appear here, one of P1–P4 applies.
// If it DOES appear but the wrong type is created, P5 applies.
endfunction
// ── Confirm what type was actually created ────────────────────────
// In the agent's build_phase after create():
`uvm_info("FACTORY_CHK",
$sformatf("drv is a: %s", drv.get_type_name()),
UVM_LOW)
// If override worked: prints "error_inject_driver"
// If override failed: prints "apb_driver"Quick Reference
| Scenario | Use | Code Pattern |
|---|---|---|
| Replace all instances of a class | Type override | A::type_id::set_type_override(B::get_type()) |
| Replace one instance at a known path | Instance override | A::type_id::set_inst_override(B::get_type(), "path") |
| Replace most instances, one exception | Type + Instance | Type override for all, then instance override for the exception |
| Layered override (team + test) | Chain override | Register A→B at team level, B→C at test level; factory resolves A→C |
| Override a transaction type | Type override on object | apb_seq_item::type_id::set_type_override(debug_item::get_type()) |
| Override a sequence type | Type override on object | base_seq::type_id::set_type_override(corner_seq::get_type()) |
| Debug: check what was created | get_type_name() | drv.get_type_name() — should print override class name |
| Debug: print factory state | factory.print() | uvm_factory::get().print(1) in start_of_simulation |
Code Examples — Override Patterns From Basic to Production
Abstract rules about "type vs instance" click into place the moment you run these patterns and see the output. Each example isolates one concept so the behavior is unambiguous. Work through them in order.
Example 1 — Beginner: Type Override Changes Every Instance
`include "uvm_macros.svh"
import uvm_pkg::*;
// Base driver and extended version ─────────────────────────────────
class base_drv extends uvm_driver#(uvm_sequence_item);
`uvm_component_utils(base_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string mode(); return "NORMAL"; endfunction
function void build_phase(uvm_phase p);
super.build_phase(p);
$display(" [%s] type=%-18s mode=%s", get_full_name(), get_type_name(), mode());
endfunction
endclass
class err_drv extends base_drv;
`uvm_component_utils(err_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string mode(); return "ERROR_INJECT"; endfunction
endclass
class type_ovr_test extends uvm_test;
`uvm_component_utils(type_ovr_test)
base_drv d0, d1, d2;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Register override BEFORE super.build_phase()
base_drv::type_id::set_type_override(err_drv::get_type());
super.build_phase(phase);
// ALL three drivers get err_drv — type override is global
d0 = base_drv::type_id::create("d0", this);
d1 = base_drv::type_id::create("d1", this);
d2 = base_drv::type_id::create("d2", this);
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module type_ovr_top;
initial run_test("type_ovr_test");
endmodule
// Expected Output:
// [uvm_test_top.d0] type=err_drv mode=ERROR_INJECT
// [uvm_test_top.d1] type=err_drv mode=ERROR_INJECT
// [uvm_test_top.d2] type=err_drv mode=ERROR_INJECT
// ↑ All three changed — that is what TYPE override doesExample 2 — Intermediate: Instance Override — One Path, Surgical Precision
`include "uvm_macros.svh"
import uvm_pkg::*;
class inst_ovr_test extends uvm_test;
`uvm_component_utils(inst_ovr_test)
base_drv d0, d1, d2; // same three drivers
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Override ONLY the instance at path "uvm_test_top.d1"
base_drv::type_id::set_inst_override(
err_drv::get_type(),
"uvm_test_top.d1"); // ← exact path
super.build_phase(phase);
d0 = base_drv::type_id::create("d0", this); // → base_drv
d1 = base_drv::type_id::create("d1", this); // → err_drv (override)
d2 = base_drv::type_id::create("d2", this); // → base_drv
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module inst_ovr_top;
initial run_test("inst_ovr_test");
endmodule
// Expected Output:
// [uvm_test_top.d0] type=base_drv mode=NORMAL ← unchanged
// [uvm_test_top.d1] type=err_drv mode=ERROR_INJECT ← ONLY d1 changed
// [uvm_test_top.d2] type=base_drv mode=NORMAL ← unchangedExample 3 — Verification: Combining Both — Global Override + One Exception
`include "uvm_macros.svh"
import uvm_pkg::*;
// Three driver types ────────────────────────────────────────────────
// base_drv and err_drv defined above, plus:
class slow_drv extends base_drv;
`uvm_component_utils(slow_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string mode(); return "SLOW_TIMING"; endfunction
endclass
class combined_test extends uvm_test;
`uvm_component_utils(combined_test)
base_drv d0, d1, d2, d3;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
// Step 1: Global type override — ALL drivers become err_drv
base_drv::type_id::set_type_override(err_drv::get_type());
// Step 2: Instance override — d2 specifically gets slow_drv
// Instance override takes precedence over type override
base_drv::type_id::set_inst_override(
slow_drv::get_type(),
"uvm_test_top.d2");
super.build_phase(phase);
d0 = base_drv::type_id::create("d0", this); // → err_drv (type ovr)
d1 = base_drv::type_id::create("d1", this); // → err_drv (type ovr)
d2 = base_drv::type_id::create("d2", this); // → slow_drv (inst ovr wins)
d3 = base_drv::type_id::create("d3", this); // → err_drv (type ovr)
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module combined_top;
initial run_test("combined_test");
endmodule
// Expected Output:
// d0: type=err_drv mode=ERROR_INJECT ← type override
// d1: type=err_drv mode=ERROR_INJECT ← type override
// d2: type=slow_drv mode=SLOW_TIMING ← instance override WINS
// d3: type=err_drv mode=ERROR_INJECT ← type override
//
// Instance override takes precedence over type override for that specific path.Example 4 — Tricky: Override Object Types in the Sequence Layer
`include "uvm_macros.svh"
import uvm_pkg::*;
// Base transaction and extended version ────────────────────────────
class base_pkt extends uvm_sequence_item;
`uvm_object_utils(base_pkt)
rand bit[7:0] data;
function new(string n="base_pkt"); super.new(n); endfunction
virtual function string kind(); return "BASE"; endfunction
endclass
class crc_err_pkt extends base_pkt;
`uvm_object_utils(crc_err_pkt)
bit[7:0] bad_crc = 8'hFF;
function new(string n="crc_err_pkt"); super.new(n); endfunction
virtual function string kind(); return "CRC_ERROR"; endfunction
endclass
class pkt_ovr_test extends uvm_test;
`uvm_component_utils(pkt_ovr_test)
function new(string n, uvm_component p); super.new(n,p); endfunction
task run_phase(uvm_phase phase);
base_pkt p1, p2;
phase.raise_objection(this);
// Override ALL base_pkt objects → crc_err_pkt
base_pkt::type_id::set_type_override(crc_err_pkt::get_type());
// Create using base type — factory gives crc_err_pkt
p1 = base_pkt::type_id::create("p1");
p2 = base_pkt::type_id::create("p2");
$display("p1: type=%s kind=%s", p1.get_type_name(), p1.kind());
$display("p2: type=%s kind=%s", p2.get_type_name(), p2.kind());
// $cast succeeds because crc_err_pkt IS-A base_pkt
begin
crc_err_pkt crc_p;
$cast(crc_p, p1);
$display("bad_crc = 0x%0h", crc_p.bad_crc);
end
phase.drop_objection(this);
endtask
endclass
module pkt_ovr_top;
initial run_test("pkt_ovr_test");
endmodule
// Expected Output:
// p1: type=crc_err_pkt kind=CRC_ERROR ← factory substituted the type
// p2: type=crc_err_pkt kind=CRC_ERROR
// bad_crc = 0xff ← extended fields accessible via $castCommon Bugs — Override Failures That Are Hard to Diagnose
Override bugs share a common characteristic: they fail silently. No UVM_ERROR. No simulation crash. The wrong type quietly runs, produces wrong behavior, and the first symptom is often a miscompare in the scoreboard three hours into a 500-transaction regression test.
⚠️ Bug 1 — Type Override Applied After the Build Cascade
Symptom: get_type_name() returns "base_drv" even though the test registers an override. The override IS in the factory — factory.print() shows it — but it has no effect.
Root cause: The override was registered AFTER super.build_phase(), which already completed the create() calls. The factory had the base type during those calls and the override arrived too late.
// ❌ WRONG — override after super (too late)
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ← cascade already runs here
d0 = base_drv::type_id::create("d0", this); // gets base_drv
base_drv::type_id::set_type_override(err_drv::get_type()); // ← TOO LATE
endfunction
// factory.print() shows the override IS registered
// but d0 is already created — it's base_drv
// ✓ CORRECT — override before super (in time)
function void build_phase(uvm_phase phase);
base_drv::type_id::set_type_override(err_drv::get_type()); // ← FIRST
super.build_phase(phase); // ← cascade sees the override
d0 = base_drv::type_id::create("d0", this); // gets err_drv ✓
endfunction⚠️ Bug 2 — Instance Path Wrong: Override Silently Doesn't Match
Symptom: Instance override registered, but the target component has the base type. No error from UVM. The path string you provided doesn't match get_full_name() at creation time.
Root cause: Path strings in instance overrides must exactly match the component's get_full_name(). If the component is inside an agent (e.g., uvm_test_top.agent0.drv) but you wrote uvm_test_top.drv, no match is found and the factory silently uses the type-level lookup (which returns base type).
// ❌ WRONG — path doesn't include the agent hierarchy
base_drv::type_id::set_inst_override(
err_drv::get_type(),
"uvm_test_top.drv"); // ← wrong! driver is inside agent0
// Actual path: "uvm_test_top.agent0.drv"
// Factory: no match found → falls back to type lookup → base_drv
// ── Debug: find the correct path ─────────────────────────────────
// In start_of_simulation_phase:
uvm_root::get().print_topology();
// Output shows: uvm_test_top.agent0.drv (agent0)
// ↑ Use this exact string in set_inst_override()
// ✓ CORRECT — use the exact path from print_topology()
base_drv::type_id::set_inst_override(
err_drv::get_type(),
"uvm_test_top.agent0.drv"); // ← exact match
// Alternative: wildcards (works in most UVM implementations)
// set_inst_override(err_drv::get_type(), "uvm_test_top.*.drv");🔍 Debug Insight — Override Not Working? Check These In Order
- Is the override registered before super.build_phase()? Move it to the first line of build_phase.
- Does the VIP use new() instead of type_id::create()?
grep -rn "= new(" src/vip/— any hit bypasses the factory. - Does the override class extend the base class? UVM rejects cross-type overrides.
- Run factory.print():
uvm_factory::get().print(1)in start_of_simulation_phase — shows all registered overrides. - Run print_topology(): Get the exact path of the target component, then verify it matches your inst_override string.
- Check get_type_name() at runtime:
$display("actual type: %s", comp.get_type_name())— if it's the base type, override didn't apply.
Ready-to-Run: Complete Override Comparison Demo
This single-file testbench demonstrates type override, instance override, combined operation, and factory diagnostics side-by-side. Save as override_demo.sv and run it directly.
// ================================================================
// override_demo.sv — Type vs Instance Override Comparison
//
// Compile (Questa):
// vlog -sv -L uvm_1_2 override_demo.sv
// vsim -c -L uvm_1_2 override_demo_top -do "run -all; quit"
//
// Compile (VCS):
// vcs -sverilog -ntb_opts uvm-1.2 override_demo.sv && ./simv
//
// Compile (Xcelium):
// xrun -sv -uvm override_demo.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
// ── Three driver flavors ───────────────────────────────────────────
class drv_a extends uvm_component;
`uvm_component_utils(drv_a)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string flavor(); return "A-BASE"; endfunction
function void build_phase(uvm_phase p);
super.build_phase(p);
$display(" %-30s %-12s %s", get_full_name(), get_type_name(), flavor());
endfunction
endclass
class drv_b extends drv_a;
`uvm_component_utils(drv_b)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string flavor(); return "B-ERROR"; endfunction
endclass
class drv_c extends drv_a;
`uvm_component_utils(drv_c)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string flavor(); return "C-PERF"; endfunction
endclass
// ── Test: shows all three scenarios in sequence ────────────────────
class override_demo_test extends uvm_test;
`uvm_component_utils(override_demo_test)
drv_a d[4];
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
$display("\n%-30s %-12s %s", "PATH", "TYPE", "FLAVOR");
$display({60{"-"}});
// Scenario: type override (all) + instance override (one exception)
drv_a::type_id::set_type_override(drv_b::get_type()); // global: A→B
drv_a::type_id::set_inst_override( // d[2] exception: →C
drv_c::get_type(), "uvm_test_top.d2");
super.build_phase(phase);
foreach(d[i])
d[i] = drv_a::type_id::create($sformatf("d%0d",i), this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
$display("\n=== Factory diagnostic ===");
uvm_factory::get().print(1);
phase.drop_objection(this);
endtask
endclass
module override_demo_top;
initial run_test("override_demo_test");
endmodule
// ================================================================
// Expected Output:
//
// PATH TYPE FLAVOR
// ------------------------------------------------------------
// uvm_test_top.d0 drv_b B-ERROR ← type override
// uvm_test_top.d1 drv_b B-ERROR ← type override
// uvm_test_top.d2 drv_c C-PERF ← instance override wins
// uvm_test_top.d3 drv_b B-ERROR ← type override
//
// === Factory diagnostic ===
// (shows registered types, active type overrides, active instance overrides)
// ================================================================Interview Questions — Override-Specific Questions From Real Interviews
Beginner Level
- Q1 When would you use a type override vs an instance override? Answer: Type override when you want the change to apply globally — every instance of that class gets the new type, regardless of where it is in the hierarchy. Instance override when you need surgical precision — only the component at a specific path gets the new type, everything else remains unchanged. Real-world rule of thumb: type override for "change all drivers to error mode in this test"; instance override for "change only the PCIe agent's driver while leaving all USB agents normal."
- Q2 If both a type override and instance override exist for the same component, which wins? Answer: Instance override wins for the specific path, type override applies to all other instances. The factory checks instance overrides first (path-specific match), then falls back to type override if no instance match is found. This allows you to set a global type override and then selectively revert specific instances back to base or to a third type using instance overrides.
Intermediate Level
- Q3 You register an instance override but the component still gets the base type. List three possible causes. Answer: (1) Wrong path string — the path passed to set_inst_override() doesn't exactly match the component's get_full_name(). Use print_topology() to verify. (2) Late registration — the override was registered after super.build_phase() when create() calls had already executed. Register before super.build_phase(). (3) VIP uses new() directly — the component is created with direct new() rather than type_id::create(), bypassing the factory entirely. Search for = new( in VIP code.
Senior / Architect Level
- Q4 Your project has 12 test variants that each need different combinations of driver types. How do you architect this without creating 12 test classes with 12 sets of override registrations? Answer: Use a base test class with a virtual configure() method. The base test calls configure() in build_phase before the super call. Each derived test overrides configure() to register exactly the overrides it needs. Since configure() runs before super.build_phase(), all overrides are in place before any create() calls. Alternatively, use plusarg-driven override selection: the base test reads +DRV_TYPE from the command line and registers the corresponding override — one test class, 12 command-line configurations. This is the production-scale approach used on large SoC verification programs.
Best Practices — Override Rules From Production Code Review
| Rule | Correct Pattern | Why It Matters |
|---|---|---|
| Override before super.build_phase() | First two lines of build_phase: overrides, then super | Super triggers the cascade — overrides registered after it miss the create() calls |
| Verify path with print_topology() | Add print_topology() to start_of_simulation_phase during debug | Instance override paths must exactly match get_full_name() — wrong paths fail silently |
| Use get_type_name() to confirm | $display("actual: %s", comp.get_type_name()) | Confirms whether factory gave you what you asked for — most reliable diagnostic |
| Override class must extend base class | class err_drv extends base_drv | Factory enforces subtype relationship; unrelated types produce UVM_FATAL |
| Prefer type override for uniform changes | set_type_override for "all X become Y" | Simpler, fewer paths to maintain — one line changes all instances |
| Use instance override for exceptions | set_inst_override for "X at path P becomes Y" | Surgical precision without disturbing other instances |
| Document every override in test comments | // Override: base_drv → err_drv (all instances) | Override effects are invisible from VIP code — tests must be self-documenting |
💡 Pro Tip — Plusarg-Driven Override Selection
For large projects with many test variants, drive override selection from the command line rather than creating separate test classes: function void build_phase(uvm_phase phase); string drv_type; if($value$plusargs("DRV_TYPE=%s", drv_type)) begin case(drv_type) "error": base_drv::type_id::set_type_override(err_drv::get_type()); "slow": base_drv::type_id::set_type_override(slow_drv::get_type()); endcase end super.build_phase(phase); endfunction // Run: vsim ... +DRV_TYPE=error → all drivers become err_drv // Run: vsim ... +DRV_TYPE=slow → all drivers become slow_drv // Run: vsim ... → all drivers are base_drv (no override)
Summary — Choosing the Right Override for the Job
Type override and instance override solve the same fundamental problem — component substitution without touching VIP source — but at different scopes. Picking the wrong one doesn't cause a crash; it causes incorrect behavior that's hard to trace because the factory runs silently.
| Dimension | Type Override | Instance Override |
|---|---|---|
| Scope | Global — ALL instances of the class | Path-specific — ONE component at a given path |
| API | T::type_id::set_type_override(U::get_type()) | T::type_id::set_inst_override(U::get_type(), "path") |
| Priority | Lower — instance override wins for matched paths | Higher — beats type override at its specific path |
| Use when | All instances need the same behavior change | One specific component needs different behavior |
| Common failure | Registered after super.build_phase() | Wrong path string — silent no-match |
- 1 Both fail silently. No UVM_ERROR. No crash. The wrong type quietly runs. Always verify with get_type_name() and factory.print() when override behavior seems off.
- 2 Timing is everything. The override must be registered before super.build_phase() triggers the creation cascade. This is the single most common override bug across all UVM projects.
- 3 Instance override requires an exact path. Use print_topology() to get the correct path string. A near-miss doesn't warn you — the factory silently falls through to type-level lookup.