The UVM factory is a mechanism that improves testbench flexibility by allowing you to substitute any class object with an object of its derived type - without modifying existing instantiation code. This is especially valuable in large testbenches where the same component or transaction type is used across dozens of files.
For the factory to support substitution, every class that participates must be registered with the factory using the appropriate UVM utility macro. Once registered, the factory can intercept create() calls and return a different (but compatible) type in place of the originally requested one.
5 min read | Intermediate Level
What You'll Learn
- What the UVM factory is and why it's needed in scalable testbenches
- How to register classes and use
create()instead ofnew() - How to apply type overrides and instance overrides using factory methods
- When to use type override vs. instance override
Why Not Just Use new()?
SystemVerilog's new() is perfectly valid for object creation. The problem arises when a testbench grows large and a particular class - say, a protocol packet - is instantiated in dozens of places across drivers, monitors, scoreboards, and sequences. If you need to swap that packet class for an updated version, you'd need to find and replace every single new() call by hand.
The UVM factory solves this. All object creation goes through a central create() method, and override rules can be applied once at the test level to affect every instantiation site automatically.
Always use type_id::create() instead of new() for UVM components and objects. Using new() bypasses the factory entirely and makes your testbench impossible to override.
Here is the contrast between the two approaches:
// Using new() - factory cannot intercept this
wb_pkt m_pkt = new("m_pkt");
// Using create() - factory can substitute type transparently
wb_pkt m_pkt = wb_pkt::type_id::create("m_pkt", this);
In the create() call, the first argument is the instance name and the second is the parent component handle (this). For objects (non-components), omit the second argument.
Factory Registration
Before a class can participate in factory overrides, it must be registered using one of two UVM macros:
// For classes that extend uvm_component (agent, driver, monitor, env, test)
`uvm_component_utils(my_driver)
// For classes that extend uvm_object (sequence_item, sequence, transaction)
`uvm_object_utils(my_transaction)
Place the registration macro immediately after the class declaration line, before the constructor. Forgetting to register a class is the most common reason factory overrides silently have no effect.
Factory Override Methods
The UVM factory provides four override methods, split across two dimensions: type vs. instance, and by type handle vs. by string name.
// Replace ALL objects of original_type with override_type
set_type_override_by_type(uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace = 1);
set_type_override_by_name(string original_type_name,
string override_type_name,
bit replace = 1);
// Replace objects of original_type only at a specific instance path
set_inst_override_by_type(uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
string full_inst_path);
set_inst_override_by_name(string original_type_name,
string override_type_name,
string full_inst_path);
These methods are called inside build_phase(), before any create() calls for the components you want to override. The factory processes overrides top-down during the build phase.
The _by_type variants are preferred over _by_name because they are checked at compile time. A typo in a string name will not be caught until simulation.
The two set_inst_override_by_type signatures shown above are the uvm_factory class's own methods, called as factory.set_inst_override_by_type(...) - the instance path goes last, and must already be a full, absolute hierarchy path. uvm_component also provides a convenience wrapper of the same name with a different argument order: set_inst_override_by_type(relative_inst_path, original_type, override_type) - the path goes first, is relative, and the component automatically prepends its own get_full_name() to it before forwarding the call to the factory. Both forms appear later in this article - a bare call (no factory. prefix) resolves through the convenience method, while a factory.-qualified call uses the global one.
Type Override
A type override globally replaces every instance of a given type with the override type. Use this when you want to swap a class everywhere in the testbench in one shot.
Consider a simple testbench with a base_agent and a derived child_agent. The environment creates a base_agent, but we want to transparently substitute it with child_agent from the test.
// In base_env's build_phase - this code does NOT change
m_agent = base_agent::type_id::create("m_agent", this);
// In the test's build_phase - override declared BEFORE env is created
class my_test extends uvm_test;
`uvm_component_utils(my_test)
base_env m_env;
virtual function void build_phase(uvm_phase phase);
uvm_factory factory = uvm_factory::get();
super.build_phase(phase);
// Override: everywhere base_agent is requested, return child_agent instead
set_type_override_by_type(base_agent::get_type(), child_agent::get_type());
factory.print(); // Confirm override is registered
m_env = base_env::type_id::create("m_env", this);
endfunction
endclass
After running, factory.print() confirms the override is active:
Type Overrides: Requested Type Override Type -------------- ------------- base_agent child_agent
And the info message from the environment confirms the right object was created:
UVM_INFO testbench.sv @ 0: uvm_test_top.m_env [AGENT] Factory returned agent of type=child_agent, path=uvm_test_top.m_env.m_agent
Quick Check
Q: If you call set_type_override_by_type for base_agent after calling base_env::type_id::create(), will the override take effect?
Show Answer
No. Type overrides must be declared before the create() call for the component hierarchy that contains the overridden type. The factory applies overrides at construction time, not retroactively.
Instance Override
An instance override replaces a type only at a specific path in the testbench hierarchy, leaving all other instances of that type untouched. This is ideal when you want different behavior in one part of the testbench while keeping the rest unchanged.
// In the test's build_phase
virtual function void build_phase(uvm_phase phase);
uvm_factory factory = uvm_factory::get();
super.build_phase(phase);
// Override base_agent ONLY under m_env - other agents are unaffected
set_inst_override_by_type("m_env.*",
base_agent::get_type(),
child_agent::get_type());
factory.print();
m_env = base_env::type_id::create("m_env", this);
endfunction
The path string "m_env.*" is relative to the calling component. Wildcards (*) are supported to match any child hierarchy under the given path.
Instance Overrides: Requested Type Override Path Override Type -------------- -------------------- ------------- base_agent uvm_test_top.m_env.* child_agent
Instance override paths are resolved relative to the calling component's full path. If you call set_inst_override_by_type from the test, the path "m_env.*" resolves to uvm_test_top.m_env.*. Getting this path wrong means the override silently has no effect.
Quick Check
Q: You have three agents in your environment: m_agnt0, m_agnt1, m_agnt2. You want to replace the driver only inside m_agnt1. Should you use a type override or an instance override?
Show Answer
Instance override. A type override would replace the driver in all three agents. Use set_inst_override_by_type("m_env.m_agnt1.*", base_driver::get_type(), new_driver::get_type()) to target only that one agent.
Type Override vs Instance Override
| Aspect | Type Override | Instance Override |
|---|---|---|
| Scope | All instances of the type | Only instances at the specified path |
| Use case | Protocol upgrade, global swap | Per-agent or per-region customization |
| Requires path string? | No | Yes |
| Risk of path typo? | No | Yes - silent failure if path is wrong |
| Priority (if both set) | Lower | Higher - instance overrides take precedence |
Practical Example: Overriding Drivers and Sequences
The following example shows a realistic test that applies multiple factory overrides simultaneously - a packet type override, a driver instance override for one agent, and a sequence instance override for another. The test uses preprocessor defines to select which override combination to apply.
The testbench structure looks like this:
- Environment:
my_envcontainingm_agnt0,m_agnt1(bothmy_agent), andm_agnt2(my_agent_v2) - Drivers:
base_driver(base),eth_driver,spi_driver(children) - Transactions:
eth_packet(base),eth_v2_packet(child) - Sequences:
seq1,seq2,seq3
class feature_test extends base_test;
`uvm_component_utils(feature_test)
function new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// TYPE OVERRIDE: replace all eth_packet instances with eth_v2_packet
set_type_override_by_type(eth_packet::get_type(), eth_v2_packet::get_type());
// INSTANCE OVERRIDE: replace base_driver only inside m_agnt0
factory.set_inst_override_by_type(base_driver::get_type(),
eth_driver::get_type(),
{get_full_name(), ".m_top_env.m_agnt0.*"});
// INSTANCE OVERRIDE: replace seq1 with seq2 only for m_agnt1's sequencer
set_inst_override_by_type("m_top_env.m_agnt1.m_seqr0.*",
seq1::get_type(),
seq2::get_type());
factory.print(); // Always print to verify override configuration
endfunction
endclass
Running this test produces the following factory configuration in the log:
#### Factory Configuration (*) Instance Overrides: Requested Type Override Path Override Type ---------------- ---------------------------------------- --------------- base_driver #(T) uvm_test_top.m_top_env.m_agnt0.* eth_driver #(T) seq1 uvm_test_top.m_top_env.m_agnt1.m_seqr0.* seq2 Type Overrides: Requested Type Override Type ---------------- ---------------------------------------- eth_packet eth_v2_packet
And the runtime log confirms that the correct types were created at each location:
UVM_INFO @ 0: uvm_test_top.m_top_env.m_agnt0.m_drv0 [eth_driver #(T)] Driver running ...with packet of type : eth_v2_packet UVM_INFO @ 0: uvm_test_top.m_top_env.m_agnt1.m_drv0 [base_driver #(T)] Driver running ...with packet of type : eth_v2_packet UVM_INFO @ 0: uvm_test_top.m_top_env.m_agnt2.m_drv0 [eth_driver #(T)] Driver running ...with packet of type : eth_v2_packet
Notice that m_agnt0's driver was overridden to eth_driver, while m_agnt1 retained base_driver (only its sequence was overridden). All three agents use eth_v2_packet due to the global type override.
Always call factory.print() in your test's build_phase() during development. It displays a complete table of active overrides and all registered types, making it easy to verify that your override declarations are correct before chasing down unexpected runtime behavior.
Try this example live:
Common Mistakes
Mistake #1: Forgetting `uvm_component_utils or `uvm_object_utils
Wrong: Declaring a class without a registration macro, then attempting an override.
Right: Always add the appropriate macro immediately after the class declaration.
Why? Without registration, the factory has no knowledge of the type and cannot process any override that references it.
Mistake #2: Calling override after create()
Wrong: Setting set_type_override_by_type after the environment has already been created in build_phase.
Right: Always declare all overrides at the top of build_phase(), before any create() calls for the affected hierarchy.
Why? The factory evaluates overrides at the moment create() is called. A late override has no effect on already-created objects.
Mistake #3: Mixing up the two set_inst_override_by_type overloads
Wrong: factory.set_inst_override_by_type(base_agent::get_type(), child_agent::get_type(), "m_agnt0.*") - calling the global uvm_factory method with a bare relative path.
Right: Either qualify the path yourself - factory.set_inst_override_by_type(base_agent::get_type(), child_agent::get_type(), {get_full_name(), ".m_agnt0.*"}) - or drop the factory. prefix and call the uvm_component convenience method instead, which takes the path first and prepends get_full_name() for you: set_inst_override_by_type("m_agnt0.*", base_agent::get_type(), child_agent::get_type()).
Why? The global factory method takes an already-absolute path and does no prepending, while the uvm_component convenience method takes a relative path and builds the absolute one for you. Passing a bare relative path to the global method silently matches nothing.
Mistake #4: Overriding with a non-derived type
The override type must be a subclass of the original type. The factory performs a compatibility check, but attempting to override with an unrelated type will cause a runtime error.
Why? UVM uses polymorphism - the handle is still typed as the base class, so the object assigned to it must be assignment-compatible.
Summary
In this tutorial, you learned:
- The UVM factory enables transparent class substitution by intercepting
type_id::create()calls - Classes must be registered with
`uvm_component_utilsor`uvm_object_utilsbefore they can participate in factory overrides - Type overrides (
set_type_override_by_type) replace all instances of a given type globally - Instance overrides (
set_inst_override_by_type) target specific hierarchy paths, leaving other instances unchanged - All override declarations must be placed in
build_phase()before anycreate()calls for the affected components - Use
factory.print()to verify override configuration and debug silent failures