An interviewer at a VIP company asks: "Your AXI-Stream agent is parameterized on data width. A test wants to replace the 64-bit driver with an error-injecting one, from the command line. How?" The honest first answer is "you can't, not directly", and the interesting part is why. This lesson covers the factory corners that separate a user from an owner: parameterized and abstract classes, by-name overrides, and a trap that silently builds the wrong class.
Every registered class gives the factory a card with a type handle and, usually, a name string. Two kinds of lookup use those cards: by type (set_type_override_by_type, type_id::create) and by name (set_type_override_by_name, +UVM_TESTNAME, +uvm_set_type_override). A parameterized class registers each specialization by type, but with no usable name, because the macro cannot turn axis_driver_p #(64) into a string. A class without its own macro has no card at all and silently borrows its parent's. Most advanced factory behaviour follows from those two facts.
dma_truncate_test overrides axis_driver with axis_truncating_driver. A derived test that also wants logging does not need to fight that override; it extends the truncating driver and overrides it. The factory resolves the winner again, so the chain is followed (the rules):
// snippet: a derived test refining the base test's override
class axis_trunc_log_driver extends axis_truncating_driver;
`uvm_component_utils(axis_trunc_log_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function axis_item pre_drive(axis_item it);
axis_item b = super.pre_drive(it);
if (b == null) `uvm_info("TRUNC", "beat dropped", UVM_HIGH)
return b;
endfunction
endclass
class dma_truncate_log_test extends dma_truncate_test;
`uvm_component_utils(dma_truncate_log_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // axis_driver -> axis_truncating_driver
axis_truncating_driver::type_id::set_type_override(axis_trunc_log_driver::get_type());
endfunction
endclassThe order of the two type overrides does not matter here, because they are for different original types, and the drivers are created later, in the agent's build_phase.
A parameterized class registers with ` uvm_component_param_utils ` (or uvm_object_param_utils `). Each specialization is a distinct type with its own proxy, and none has a name string. Three consequences: overrides must name the exact specialization; by-name overrides and +uvm_set_type_override cannot find it; and get_type_name()` does not return a useful name unless you supply one.
// snippet: a width-parameterized driver and a by-type override
class axis_driver_p #(int W = 32) extends uvm_driver #(axis_item_p #(W));
`uvm_component_param_utils(axis_driver_p #(W))
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
class axis_err_driver_64 extends axis_driver_p #(64);
`uvm_component_utils(axis_err_driver_64) // not parameterized: has a name
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
// In the test: only the 64-bit specialization is replaced; #(32) is untouched.
axis_driver_p #(64)::type_id::set_type_override(axis_err_driver_64::get_type());That is the answer to the opening question. The usual workaround for command-line control is a non-parameterized, registered subclass per specialization you care about (class axis_driver_64 extends axis_driver_p #(64) with ` uvm_component_utils `), which the env instantiates. Then by-name overrides work on axis_driver_64`.
A virtual class cannot be constructed, so the normal registration macro does not compile on it in UVM 1.2: the generated proxy calls new. In UVM 1.2 you either leave the abstract base unregistered and register only concrete subclasses, or make the base concrete with methods that report an error if not overridden.
Every by-type call has a by-name twin that takes strings, which is what the command line and data-driven test configs use. You reach the factory through the core service, or through the uvm_factory::get() shortcut:
// snippet: by-name overrides through the factory handle
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory f = cs.get_factory(); // same object as uvm_factory::get()
f.set_type_override_by_name("axis_driver", "axis_truncating_driver");
f.set_inst_override_by_name("axis_driver", "axis_truncating_driver",
"uvm_test_top.env.in_agt.drv");By-name calls are checked at run time, not compile time. A typo in a type name does not fail the build; the override simply never matches (or the factory warns that the type is not registered). Prefer by-type calls in code and keep by-name for the command line.
A class that extends a registered class but has no macro of its own still has a type_id: the parent's, inherited like any static member. So child::type_id::create(...) compiles, and builds the parent. Nothing fails until behaviour is missing.
// bad-example: axis_err_item forgot its registration macro
class axis_err_item extends axis_item; // no uvm_object_utils here
function new(string name = "axis_err_item");
super.new(name);
endfunction
endclass
axis_item it = axis_err_item::type_id::create("it"); // compiles; builds an axis_item
// it.get_type_name() returns "axis_item", and an override to axis_err_item
// cannot work: axis_err_item::get_type() is axis_item's proxy.The compile error you might hope for appears only if you assign the result to an axis_err_item handle (the returned type is axis_item). The fix is the macro. A lint rule "every class extending a UVM class has a utils macro" catches it.
Senior interviews often end the factory discussion with "when would you not use an override?" A short comparison helps:
Mechanism | Good for | Cost |
|---|---|---|
Factory override | Replacing a whole behaviour for a test: a truncating driver, an end-of-test scoreboard. | One class per variation; the base class must expose virtual methods or hooks. |
Callback | Adding small, stackable behaviours to VIP you cannot edit, several at once. | The VIP must provide callback hooks; ordering between callbacks needs care. |
Config knob | Tuning a value the class already supports: idle cap, error rate, | Every knob is code in the base class; too many knobs make it unreadable. |
The bench uses an override for truncation because cutting packets is a different behaviour, not a different number. Callbacks versus factory overrides goes deeper.
The only advanced case the bench runs as written is the plain type override; its result is the familiar truncation line. Chaining adds a second hop with the same path and the same log ID:
UVM_INFO dma_tb.sv(520) @ 275000: uvm_test_top.env.in_agt.drv [TRUNC] TLAST forced on beat 3When you add the chain, read the topology: the drv row must show axis_trunc_log_driver. If it shows axis_truncating_driver, the second override was not registered (or was registered for the wrong original type).
A team built an error-injection campaign: axis_err_item with a constraint that occasionally flips a parity bit, swapped in by a type override from axis_item. Coverage on the error bit stayed at zero for three weeks. Everyone suspected the constraint. The real cause was the missing macro: axis_err_item::get_type() returned axis_item's proxy, so the override said "replace axis_item with axis_item", and UVM printed a TYPDUP warning ("original and override type arguments are identical") that nobody read in a log of thousands of lines. One macro line fixed it. The team now fails the regression on any TYPDUP warning.
axis_driver_p #(32) with an error driver. The bench also has 64-bit agents. Which drivers change?Only the 32-bit ones. axis_driver_p #(32) and axis_driver_p #(64) are distinct types with separate proxies; an override on one does not touch the other.
f.set_type_override_by_name("axis_drvier", "axis_truncating_driver") (note the typo). What happens?It compiles, because the names are just strings. At run time no requested type is ever called axis_drvier, so the override never matches and the normal driver is built. Depending on the version the factory may warn that the type is not registered, which is easy to miss. That is why code uses by-type overrides.
TYPDUP: Original and override type arguments are identical: axis_item, from a line that overrides axis_item with axis_err_item. What is the bug?axis_err_item has no registration macro, so axis_err_item::get_type() is inherited and returns axis_item's proxy. The override maps a type to itself. Add ` uvm_object_utils(axis_err_item) ``.
Next: most of these overrides are more useful when a regression can apply them without recompiling. Command-line overrides shows how, and the ordering surprise between command line and test code.