In short: The UVM factory lets you swap the class a component or transaction is built from — globally by type or surgically by instance path — without editing the testbench, so one reusable environment drives dozens of tests through polymorphism instead of copy-paste.
Ask any verification engineer what separates a throwaway testbench from one that survives a three-year chip program, and the honest answer is usually reuse. The UVM factory is the single mechanism that makes that reuse mechanical rather than heroic. Instead of hard-coding which driver, sequence, or packet class gets instantiated, you register classes with a central registry and ask that registry to build objects on your behalf. Later, a test can tell the factory "wherever you were about to build class A, build class B instead." The environment code never changes. This article explains how the factory is wired, the precise difference between type and instance overrides, and the real-world patterns where it earns its keep.
Why not just use new()?
If a monitor calls my_driver::new() directly, you have welded that exact class into the hierarchy. The only way to inject an error-injecting driver for one test is to edit the monitor — which breaks every other test that shared it. The factory breaks this coupling. Components and objects are created through type_id::create(), which routes the request through a lookup table. Because the actual class is resolved at build time, an SoC-level test can substitute a derived class purely from the test file. This is classic object-oriented polymorphism applied to testbench construction, and it is the reason a verification IP vendor can ship a VIP you customize without touching their source.
Rule of thumb: if a class may ever be extended or swapped — which is nearly every uvm_component and uvm_sequence_item — it must be built with create(), never new(). A single stray new() silently defeats every override aimed at that object.
Registering a class with the factory
Registration is a one-line macro inside the class body. Two macros cover almost everything:
class eth_packet extends uvm_sequence_item;
`uvm_object_utils(eth_packet) // objects / transactions
endclass
class eth_driver extends uvm_driver #(eth_packet);
`uvm_component_utils(eth_driver) // components
endclass
These macros expand into a nested type_id proxy and auto-register the class with the global uvm_factory singleton. From then on you build instances like this:
// inside a parent component's build_phase
drv = eth_driver::type_id::create("drv", this);
// inside a sequence
eth_packet p = eth_packet::type_id::create("p");
create() call and returns the overridden class when one is registered — the calling code is unaware a swap happened.Type override vs instance override
This is the distinction interviewers probe, because engineers conflate the two. A type override replaces every occurrence of a requested type across the whole testbench. An instance override replaces only the object(s) matching a specific hierarchical path, using UVM's dotted instance names and wildcards.
| Aspect | Type override | Instance override |
|---|---|---|
| API | set_type_override_by_type | set_inst_override_by_type |
| Scope | All instances of the type | Only paths matching a string |
| Path arg | None | e.g. "env.agent0.*" |
| Typical use | Swap all drivers for an error-injecting variant | Make one agent of four misbehave |
| Precedence | Lower | Higher — wins when both match |
// in a test's build_phase
// EVERY eth_driver becomes err_eth_driver:
set_type_override_by_type(eth_driver::get_type(),
err_eth_driver::get_type());
// ONLY the driver under agent0 is swapped:
set_inst_override_by_type("env.agent0.drv",
eth_driver::get_type(),
err_eth_driver::get_type());
Precedence order to memorize: an instance override beats a type override, and both override the class named in create(). Among competing instance overrides, the first one registered wins — so register the most specific path first.
There are also the convenience methods on the proxy itself — eth_driver::type_id::set_type_override(err_eth_driver::get_type()) — which many teams prefer because they read more naturally and avoid a direct handle to the factory singleton. The string-based variants (set_type_override_by_name) exist too, but they fail silently if you typo the class name, so most shops standardize on the by-type forms. Simulators from Synopsys (VCS), Cadence (Xcelium), and Siemens (Questa) all implement the same Accellera UVM 1.2 / IEEE 1800.2 semantics here, so override behavior is portable across the big three.
A worked example: error injection without forking the env
Imagine a 4-port Ethernet switch DUT. Your base environment has four agents, each with a driver built via create(). For the "bad CRC on port 0" test, you derive crc_err_packet extends eth_packet that corrupts the FCS in post_randomize(), then register an instance override on "env.agent0.*.req" so only port 0's transactions are faulted. The other three ports still send clean traffic, the scoreboard still works, and the base env file has zero test-specific edits. Multiply this across 60–80 directed tests and you have eliminated the single largest source of testbench maintenance bugs.
Debugging overrides that "didn't take"
The most common failure mode is an override that appears to be ignored. Three causes dominate:
- A hidden
new(). If any code path builds the target withnew()the factory never sees the request. Grep for it. - Override set too late. Overrides must be registered before the object is built — practically, in
build_phaseof a parent higher in the tree, or in the test beforesuper.build_phase()triggers construction. - Wrong instance path. The path string is the full hierarchical name as UVM sees it, not the variable name. Print it to be sure.
Your best friend here is factory.print() (or the automatic UVM_INFO factory dump at UVM_HIGH verbosity), which lists every registered type and active override. Pair it with print_override_info() for a specific requested type to see exactly which rule fired. For config-driven stimulus that complements overrides, see our companion piece on the UVM config_db, and for structuring reusable stimulus layers read UVM sequence layering.
When to reach for the factory — and when not to
Use overrides for polymorphic substitution: error variants, protocol-specific drivers, alternate scoreboards, or vendor-VIP customization. Do not abuse it as a general configuration channel — numeric knobs, mode flags, and interface handles belong in the config database, not in a class swap. A useful heuristic: if you are changing behavior by subclassing, use the factory; if you are changing parameters of existing behavior, use config_db. Teams that keep this boundary clean report dramatically fewer "why is this test building the wrong class" debugging sessions.
If you are building verification IP commercially or want a methodology-reviewed reusable environment, the design-services team at Avecas applies exactly these factory patterns in production SoC flows, and our full UVM track walks through them hands-on.
Ready to build testbenches that scale across a whole chip program? CourseTron's industry-aligned UVM and functional-verification track takes you from factory registration to constraint-random closure with live simulator labs. Explore the CourseTron course catalog and start writing reusable TBs this week.
Frequently asked questions
What is the difference between get_type() and type_id::create()?
get_type() returns the factory proxy (a type handle) used to name a class in an override call. type_id::create() actually builds an instance through the factory. You pass get_type() handles to override methods; you call create() everywhere you would otherwise have called new().
Does an instance override beat a type override?
Yes. When both a type override and a matching instance override apply to the same object, the instance override wins because it is more specific. Among multiple instance overrides whose paths match, the earliest registered one takes effect.
Why is my factory override being ignored?
Almost always one of three things: the object was built with new() instead of create(), the override was set after the object was already constructed, or the instance path string does not match the real hierarchical name. Run factory.print() to confirm the rule is registered and the path is correct.
Can I override a sequence item, not just a component?
Absolutely. Overriding uvm_sequence_item subclasses is the standard way to inject error or corner-case transactions. As long as the item is created with type_id::create() inside the sequence body, a type or instance override redirects it transparently.