UVM
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?
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 is active for any given source class at a time — but which one depends on an argument most engineers never pass.
set_type_override() takes an optional second argument, replace, which defaults to 1. With the default, a second call for the same source class replaces the first. Pass replace = 0 and the second call is ignored, leaving the original override in place.
That default is why the rule is usually stated as "the last call wins" — it is the behaviour you get unless you deliberately ask for the opposite. On a project where a base test and a derived test both register an override for the same type, replace = 0 in the derived test is how you let the base test's choice stand.
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)
endfunctionWhen Two Instance Overrides Both Match — First Registered Wins
This is the rule that surprises people, and it is the opposite of what most engineers assume.
Instance overrides are held in an ordered list. At creation time the factory walks that list and takes the first entry whose path pattern matches — not the most specific one. Specificity is never computed. Registration order is the priority.
// ❌ The general pattern is registered FIRST, so it matches everything
// and the specific pattern below it is unreachable.
base_drv::type_id::set_inst_override(slow_drv::get_type(), "uvm_test_top.env.*");
base_drv::type_id::set_inst_override(err_drv::get_type(), "uvm_test_top.env.agent0.drv");
// agent0.drv → slow_drv ← the "more specific" rule NEVER applies
// ✓ Specific first, general second — the order the factory actually honours
base_drv::type_id::set_inst_override(err_drv::get_type(), "uvm_test_top.env.agent0.drv");
base_drv::type_id::set_inst_override(slow_drv::get_type(), "uvm_test_top.env.*");
// agent0.drv → err_drv, every other env component → slow_drvThis inverts the habit built by every other tool an engineer uses. CSS, routing tables, and constraint solvers all resolve by specificity; the UVM factory resolves by insertion order. Write the exceptions before the defaults.
The practical consequence for a base-test / derived-test structure is sharp: if the base test registers a broad wildcard in its build_phase and the derived test calls super.build_phase() first, the derived test's precise override is registered second and will never fire.
The Full Override API Surface
The type_id proxy shown so far is a convenience layer. Underneath, the factory exposes four registration methods, and knowing all four matters because two of them accept a type that has not been compiled into scope yet.
| Method | Identifies types by | Use when |
|---|---|---|
set_type_override_by_type(orig, ovrd, replace = 1) | type handles (::get_type()) | Normal case — both classes visible; compile-time checked |
set_type_override_by_name("orig", "ovrd", replace = 1) | string names | The override class is not imported in this scope, or the name comes from a plusarg |
set_inst_override_by_type(orig, ovrd, "path") | type handles | Normal path-specific case |
set_inst_override_by_name("orig", "ovrd", "path") | string names | Same reason as the by-name type override |
// Layer 1 — the type_id proxy (what you normally write)
base_drv::type_id::set_type_override(err_drv::get_type());
// Layer 2 — the factory handle directly (identical effect)
uvm_factory f = uvm_factory::get();
f.set_type_override_by_type(base_drv::get_type(), err_drv::get_type());
// By-name form — no handle to err_drv required in this scope
f.set_type_override_by_name("base_drv", "err_drv");Chain 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.
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. - Print the factory state rather than reaching for a trace plusarg. There is no standard UVM plusarg that traces override resolution —
+UVM_CONFIG_DB_TRACE,+UVM_RESOURCE_DB_TRACE,+UVM_OBJECTION_TRACEand+UVM_PHASE_TRACEexist, but the factory has no equivalent.uvm_factory::get().print()plus aget_type_name()check is the supported route.
// 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)
// ================================================================Self-Checking Proof — Make the Simulation Fail When the Override Fails
Every example so far prints the resulting type and asks you to read it. That is fine in a tutorial and useless in a regression: a printed line nobody reads is not a check. The version below fails the test when the factory does not deliver what was asked for.
// ================================================================
// override_check.sv — asserts the override actually took effect
// Questa : vlog -sv -L uvm_1_2 override_check.sv
// vsim -c -L uvm_1_2 override_check_top -do "run -all; quit"
// VCS : vcs -sverilog -ntb_opts uvm-1.2 override_check.sv && ./simv
// Xcelium: xrun -sv -uvm override_check.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
class base_drv extends uvm_component;
`uvm_component_utils(base_drv)
function new(string n, uvm_component p); super.new(n,p); endfunction
virtual function string flavor(); return "BASE"; 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 flavor(); return "ERROR"; endfunction
endclass
class ovr_check_test extends uvm_test;
`uvm_component_utils(ovr_check_test)
base_drv d0, d1;
function new(string n, uvm_component p); super.new(n,p); endfunction
// The reusable guard: assert the CONCRETE type, not just that a handle exists.
// $cast to the expected override type is the only check that cannot be
// satisfied by the base type — get_type_name() string compares are brittle
// because a further override down the chain legitimately changes the name.
function void expect_type(base_drv h, string want);
err_drv e;
if (h == null)
`uvm_fatal("OVR", "handle is null — create() never ran")
if (want == "err_drv" && !$cast(e, h))
`uvm_fatal("OVR", $sformatf(
"%s: expected err_drv, factory delivered %s. Override did not apply.",
h.get_full_name(), h.get_type_name()))
`uvm_info("OVR", $sformatf("%s → %s (%s) OK",
h.get_full_name(), h.get_type_name(), h.flavor()), UVM_LOW)
endfunction
function void build_phase(uvm_phase phase);
// Exception BEFORE default — registration order is priority.
base_drv::type_id::set_inst_override(err_drv::get_type(), "uvm_test_top.d1");
super.build_phase(phase);
d0 = base_drv::type_id::create("d0", this);
d1 = base_drv::type_id::create("d1", this);
endfunction
// check_phase runs after the whole build/connect/run sequence, so a
// component replaced anywhere in the hierarchy is still caught here.
function void check_phase(uvm_phase phase);
expect_type(d0, "base_drv");
expect_type(d1, "err_drv"); // ← uvm_fatal if the override silently failed
endfunction
task run_phase(uvm_phase p);
p.raise_objection(this); p.drop_objection(this);
endtask
endclass
module override_check_top;
initial run_test("ovr_check_test");
endmodule
// Passing run:
// UVM_INFO ... [OVR] uvm_test_top.d0 → base_drv (BASE) OK
// UVM_INFO ... [OVR] uvm_test_top.d1 → err_drv (ERROR) OK
//
// Delete the set_inst_override line and re-run:
// UVM_FATAL ... [OVR] uvm_test_top.d1: expected err_drv, factory
// delivered base_drv. Override did not apply.The $cast is deliberate. Comparing get_type_name() against a literal breaks the moment somebody adds a further override on top of err_drv — the name changes, the type relationship does not. $cast asks the question you actually mean: is what I got at least the thing I asked for?
An error-injection test passed for three weeks without injecting a single error
OVERRIDE-ORDER-SHADOWEDA regression suite contained one error-injection test whose job was to drive protocol violations through every agent and confirm the scoreboard flagged them. It had been green since it was written.
During a review someone noticed the test had never failed — not once, not even when the DUT's error handling was rewritten. A test that only ever passes is either verifying nothing or verifying something else.
class err_base_test extends uvm_test;
function void build_phase(uvm_phase phase);
// Base test: "every driver in the env becomes an error driver"
apb_driver::type_id::set_inst_override(err_apb_driver::get_type(),
"uvm_test_top.env.*");
super.build_phase(phase);
endfunction
endclass
class err_focused_test extends err_base_test;
function void build_phase(uvm_phase phase);
super.build_phase(phase); // ← base registers its wildcard HERE
// Intended: agent0 gets the *aggressive* error driver
apb_driver::type_id::set_inst_override(nasty_apb_driver::get_type(),
"uvm_test_top.env.agent0.drv");
endfunction
endclassuvm_factory::get().print() showed both instance overrides registered — which is why the first three engineers to look at it concluded the factory was working and moved on.
The decisive evidence came from the components themselves:
UVM_INFO ... uvm_test_top.env.agent0.drv type=err_apb_driver
UVM_INFO ... uvm_test_top.env.agent1.drv type=err_apb_drivernasty_apb_driver appeared in the factory's override table and in no component in the hierarchy. It was registered and unreachable.
Two instance overrides matched uvm_test_top.env.agent0.drv, and the factory takes the first one registered, not the most specific.
super.build_phase(phase) was called at the top of the derived test's build phase, so the base test's "uvm_test_top.env.*" wildcard entered the list first. The derived test's precise path was appended behind a pattern that already matched it.
The test still injected ordinary errors — which is why it passed. It never injected the aggressive ones it existed to test, so the scoreboard checks it was supposed to stress were never exercised.
Register the exception before the default. In a base/derived structure that means moving the super call after the derived test's own registrations:
class err_focused_test extends err_base_test;
function void build_phase(uvm_phase phase);
// Specific first — it now precedes the base test's wildcard.
apb_driver::type_id::set_inst_override(nasty_apb_driver::get_type(),
"uvm_test_top.env.agent0.drv");
super.build_phase(phase);
endfunction
endclassThe structural guard is better than the ordering fix: give the base test a virtual configure_overrides() hook that it calls at the start of its own build_phase, and have derived tests override that rather than reordering super calls. Ordering then stops being a thing anyone has to remember.
Whichever route, add the check_phase type assertion from the section above. It converts this entire class of bug from "noticed during a review three weeks later" into a fatal on the first run.
An override worked in the unit testbench and vanished in the SoC environment
OVERRIDE-PATH-DEPTHAn instance override that had worked for a year in the standalone agent testbench had no effect once the agent was integrated into the full SoC environment. The same test file, the same override line, no code change.
// Written when the agent sat directly under the test
apb_driver::type_id::set_inst_override(err_apb_driver::get_type(),
"uvm_test_top.agent.drv");uvm_root::get().print_topology() in the SoC environment printed the real path:
uvm_test_top.env.subsys_a.apb_env.agent.drvThe registered pattern "uvm_test_top.agent.drv" matched nothing. No warning was issued, because a non-matching instance override is not an error — it is simply a pattern that did not apply.
Instance override paths are matched against get_full_name(), which encodes the component's absolute position in the hierarchy. Reusing an agent by nesting it deeper changes every one of its descendants' full names, so any absolute path written for the old depth stops matching.
This is the reuse tax on absolute paths: the override is coupled not to the component but to where the component happened to sit when the line was written.
Anchor the pattern to the part of the path that survives re-nesting:
// Depth-independent — matches the agent's driver wherever the agent is instantiated
apb_driver::type_id::set_inst_override(err_apb_driver::get_type(), "*.agent.drv");Two cautions come with it. A leading * matches broadly, so confirm with print_topology() that exactly the intended components match — see the wildcard row in the path-pattern table above. And because breadth increases the chance of another pattern also matching, apply the ordering rule from the section above and register narrow patterns first.
The alternative, which scales better in large environments, is to stop using absolute paths at all: pass a uvm_component as the parent argument to set_inst_override, and the path is interpreted relative to that parent. The override then travels with the sub-environment no matter how deeply it is instantiated.
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());
default: `uvm_fatal("CFG", $sformatf("unknown +DRV_TYPE=%s", drv_type))
endcase
end
super.build_phase(phase);
endfunction
// vsim ... +DRV_TYPE=error → all drivers become err_drv
// vsim ... +DRV_TYPE=slow → all drivers become slow_drv
// vsim ... → all drivers stay base_drv (no override)The default arm matters: without it a typo in the plusarg silently runs the unoverridden test while the log line says the variant was selected.
Questions Engineers Actually Ask
The factory print proves registration. It says nothing about resolution, and those fail for different reasons.
Once the override appears in the table, only three things can stop it applying. The component was created with new() instead of type_id::create(), so the factory was never consulted. The component was created before the override was registered — the classic super.build_phase() ordering bug. Or, for an instance override, the path pattern did not match get_full_name().
Confirm which by printing comp.get_type_name() at the point of use and uvm_root::get().print_topology() for the real path. Registration and resolution are separate questions and the print-out only answers the first.
The first one registered, not the most specific.
Instance overrides live in an ordered list, and the factory takes the first entry whose pattern matches. Specificity is never computed. This is the reverse of CSS, routing tables, and most other pattern systems, which is exactly why it catches people.
So register exceptions before defaults. In a base/derived test structure the practical trap is super.build_phase() — call it first and the base test's broad wildcard is registered ahead of your precise path, permanently shadowing it. DebugLab 1 is that failure in full.
Where This Is Specified
- IEEE 1800.2-2020 (UVM) —
uvm_factory.set_type_override_by_type,set_type_override_by_name,set_inst_override_by_typeandset_inst_override_by_name, thereplaceargument on the type-override forms, and theparentargument that makes an instance path relative. - IEEE 1800.2-2020 —
uvm_component_registry/uvm_object_registry. Thetype_idproxy:create(),get_type(),set_type_override()andset_inst_override(), and the requirement that the override type be derived from the requested type. - IEEE 1800.2-2020 — factory override resolution. Instance overrides considered before type overrides, and the ordered traversal of registered instance overrides in which the first matching pattern is selected.
- IEEE 1800.2-2020 —
uvm_factory::print. The registry and override-table dump used to distinguish a registration failure from a resolution failure. - IEEE 1800.2-2020 —
uvm_object::get_type_name. The concrete type name of the created object, and its relationship to the chain of applied overrides. - IEEE 1800-2023 §8.16 —
$cast. Dynamic-cast semantics, and why a successful cast to the expected type is a stronger check than a type-name string comparison.
Related Reading
- The UVM Factory — what the factory is, why creation is decoupled from concrete type, and what the registration macros build. Start there if the mechanism is unclear rather than the choice.
- Factory Debug — the systematic procedure for an override that will not take effect, including reading the factory dump and the topology print.
- Common Factory Bugs — the recurring failure catalogue, of which the ordering bug in DebugLab 1 is one member.
- Override Best Practices — team-scale conventions for who is allowed to register what, and where.
- Factory Pattern — the design-pattern background, for readers who want the general form before the UVM specialisation.
- config_db Architecture — the other half of the customisation story. If the two candidate classes differ only by a value, configuration is the right tool and override is not.
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.
Part of UVM Fundamentals·The UVM Factory·Lesson 27 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.