Factory Pattern in UVM: From Theory to Implementation
The Factory pattern is the backbone of UVM's flexibility. Every type_id::create() call you write relies on it. But what's actually happening under the hood? This post explores the theory behind factory patterns, how UVM implements them, and why it matters for your testbench.
- The Problem: Why Factories Exist
- Gang of Four: Factory Patterns
- UVM's Factory Architecture
- Override Mechanisms
- Advanced: Factory Internals
- Quick Reference
The Problem: Why Factories Exist
Consider a simple UVM environment. You have a driver, and you instantiate it directly:
Example: Hardcoded Instantiation (The Problem)
class apb_env extends uvm_env;
apb_driver drv;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = new("drv", this); // Hardcoded to apb_driver
endfunction
endclass
This works, but creates tight coupling. The environment is permanently bound to apb_driver. Now consider these scenarios:
- Debug mode: You need
apb_debug_driverwith extra logging - Error injection: You need
apb_error_driverthat corrupts transactions - Performance: You need
apb_fast_driverthat skips protocol delays
With hardcoded new(), you must edit apb_env for each variant. This violates two SOLID principles:
- Open-Closed Principle: Classes should be open for extension, closed for modification
- Dependency Inversion: Depend on abstractions, not concrete implementations
The Factory pattern solves this by decoupling object creation from object usage.
Gang of Four: Factory Patterns
The "Gang of Four" (GoF) book describes three factory-related patterns. Understanding these helps you appreciate UVM's design choices.
1. Simple Factory
A single class with a method that creates objects based on input parameters. Not a formal GoF pattern, but commonly used.
classDiagram
class SimpleFactory {
+create(type) : Product
}
class Product
class ConcreteProductA
class ConcreteProductB
Product <|-- ConcreteProductA
Product <|-- ConcreteProductB
SimpleFactory ..> Product : creates
Example: Simple Factory in SystemVerilog
class driver_factory;
static function uvm_driver create(string driver_type);
case (driver_type)
"apb": return new apb_driver();
"axi": return new axi_driver();
"ahb": return new ahb_driver();
default: return null;
endcase
endfunction
endclass
// Usage
drv = driver_factory::create("apb");
Limitation: Adding new types requires modifying the factory's case statement.
2. Factory Method
Define an interface for creating objects, but let subclasses decide which class to instantiate. The creation is deferred to subclasses.
classDiagram
class Creator {
+factoryMethod()* : Product
+operation()
}
class ConcreteCreatorA {
+factoryMethod() : Product
}
class ConcreteCreatorB {
+factoryMethod() : Product
}
class Product
class ConcreteProductA
class ConcreteProductB
Creator <|-- ConcreteCreatorA
Creator <|-- ConcreteCreatorB
Product <|-- ConcreteProductA
Product <|-- ConcreteProductB
ConcreteCreatorA ..> ConcreteProductA : creates
ConcreteCreatorB ..> ConcreteProductB : creates
Example: Factory Method in SystemVerilog
virtual class base_env extends uvm_env;
// Factory method - subclasses override this
pure virtual function uvm_driver create_driver();
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = create_driver(); // Delegates to subclass
endfunction
endclass
class apb_env extends base_env;
virtual function uvm_driver create_driver();
return apb_driver::type_id::create("drv", this);
endfunction
endclass
class axi_env extends base_env;
virtual function uvm_driver create_driver();
return axi_driver::type_id::create("drv", this);
endfunction
endclass
Benefit: New products don't require modifying existing code—just add new subclasses.
3. Abstract Factory
Provide an interface for creating families of related objects without specifying concrete classes.
classDiagram
class AbstractFactory {
+createDriver()* : Driver
+createMonitor()* : Monitor
+createSequencer()* : Sequencer
}
class APBFactory {
+createDriver() : APBDriver
+createMonitor() : APBMonitor
+createSequencer() : APBSequencer
}
class AXIFactory {
+createDriver() : AXIDriver
+createMonitor() : AXIMonitor
+createSequencer() : AXISequencer
}
AbstractFactory <|-- APBFactory
AbstractFactory <|-- AXIFactory
Example: Abstract Factory for VIP Components
virtual class vip_factory;
pure virtual function uvm_driver create_driver(string name, uvm_component parent);
pure virtual function uvm_monitor create_monitor(string name, uvm_component parent);
pure virtual function uvm_sequencer create_sequencer(string name, uvm_component parent);
endclass
class apb_vip_factory extends vip_factory;
virtual function uvm_driver create_driver(string name, uvm_component parent);
return apb_driver::type_id::create(name, parent);
endfunction
virtual function uvm_monitor create_monitor(string name, uvm_component parent);
return apb_monitor::type_id::create(name, parent);
endfunction
virtual function uvm_sequencer create_sequencer(string name, uvm_component parent);
return apb_sequencer::type_id::create(name, parent);
endfunction
endclass
// Usage: Swap entire VIP family by changing factory
class generic_env extends uvm_env;
vip_factory factory;
function void build_phase(uvm_phase phase);
drv = factory.create_driver("drv", this);
mon = factory.create_monitor("mon", this);
sqr = factory.create_sequencer("sqr", this);
endfunction
endclass
Use case: When you have related components that must be used together (driver + monitor + sequencer for a specific protocol).
UVM's Factory Architecture
UVM implements a centralized, type-based factory that combines aspects of all three patterns. Let's break it down.
The Registration Mechanism
Every UVM class registers itself with the factory using macros:
class my_driver extends uvm_driver #(my_txn);
`uvm_component_utils(my_driver) // Registers with factory
// ...
endclass
class my_txn extends uvm_sequence_item;
`uvm_object_utils(my_txn) // For objects (no parent)
// ...
endclass
But what do these macros actually do?
Macro Expansion: What uvm_component_utils Actually Does
// `uvm_component_utils(my_driver) expands to approximately:
// 1. Typedef for the proxy type
typedef uvm_component_registry #(my_driver, "my_driver") type_id;
// 2. Static method to get the type
static function type_id get_type();
return type_id::get();
endfunction
// 3. Virtual method to get type name
virtual function string get_type_name();
return "my_driver";
endfunction
The key is uvm_component_registry—a proxy class that knows how to create instances of your class.
The Proxy Pattern Inside UVM Factory
UVM uses the Proxy pattern to enable type-based creation. Each registered class gets a proxy that acts as its factory representative.
sequenceDiagram
participant Test
participant Factory as uvm_factory
participant Proxy as type_id (proxy)
participant Driver as my_driver
Test->>Proxy: my_driver::type_id::create("drv", parent)
Proxy->>Factory: create_component_by_type(proxy, name, parent)
Factory->>Factory: Check for overrides
Factory->>Proxy: Call proxy.create_component()
Proxy->>Driver: new(name, parent)
Driver-->>Test: Return instance
Simplified Proxy Implementation
class uvm_component_registry #(type T=uvm_component, string Tname="")
extends uvm_object_wrapper;
typedef uvm_component_registry #(T, Tname) this_type;
// Singleton proxy instance
static this_type me = get();
// Get the singleton proxy
static function this_type get();
if (me == null) begin
me = new();
// Register with global factory
uvm_factory::get().register(me);
end
return me;
endfunction
// Create method - called by factory
virtual function uvm_component create_component(
string name, uvm_component parent);
T obj = new(name, parent); // Actual instantiation
return obj;
endfunction
// Convenience method for users
static function T create(string name, uvm_component parent);
uvm_component obj;
obj = uvm_factory::get().create_component_by_type(
get(), "", name, parent);
$cast(create, obj);
endfunction
endclass
The Global Factory Singleton
UVM maintains a single global factory instance (uvm_factory) that:
- Stores all registered type proxies
- Maintains override tables (type and instance)
- Resolves creation requests to the correct proxy
Factory's Internal Data Structures
class uvm_default_factory extends uvm_factory;
// Registry: type_name -> proxy
protected uvm_object_wrapper m_type_names[string];
// Type overrides: original_type -> override_type
protected uvm_factory_override m_type_overrides[$];
// Instance overrides: {path, original_type} -> override_type
protected uvm_factory_override m_inst_overrides[$];
// Creation flow
virtual function uvm_component create_component_by_type(
uvm_object_wrapper requested_type,
string parent_inst_path,
string name,
uvm_component parent);
uvm_object_wrapper override_type;
// 1. Check instance overrides first (higher priority)
override_type = find_inst_override(requested_type, parent_inst_path);
// 2. If no instance override, check type overrides
if (override_type == null)
override_type = find_type_override(requested_type);
// 3. Use original if no override found
if (override_type == null)
override_type = requested_type;
// 4. Create via the proxy
return override_type.create_component(name, parent);
endfunction
endclass
Override Mechanisms
The real power of UVM's factory is the ability to swap implementations without modifying source code. There are two override types:
Type Override
Replace all instances of a type globally:
Type Override Examples
class error_injection_test extends base_test;
function void build_phase(uvm_phase phase);
// Method 1: Using type_id
apb_driver::type_id::set_type_override(
apb_error_driver::get_type());
// Method 2: Using factory directly
uvm_factory::get().set_type_override_by_type(
apb_driver::get_type(),
apb_error_driver::get_type());
// Method 3: Using type names (strings)
uvm_factory::get().set_type_override_by_name(
"apb_driver",
"apb_error_driver");
super.build_phase(phase);
endfunction
endclass
Instance Override
Replace a specific instance based on its hierarchical path:
Instance Override Examples
class targeted_error_test extends base_test;
function void build_phase(uvm_phase phase);
// Only override driver in agent[0], leave agent[1] normal
apb_driver::type_id::set_inst_override(
apb_error_driver::get_type(),
"uvm_test_top.env.agent[0].driver");
// Wildcards supported
apb_driver::type_id::set_inst_override(
apb_debug_driver::get_type(),
"uvm_test_top.*.debug_agent.driver");
super.build_phase(phase);
endfunction
endclass
Override Priority
When multiple overrides apply, UVM resolves them in this order:
1. Instance override — Highest priority. Matches the most specific hierarchical path.
2. Type override — Applied globally. If multiple exist, last registered wins.
3. Original type — Default when no overrides match.
Instance overrides always win because they target a specific location in your testbench hierarchy, while type overrides are a blanket replacement everywhere.
Override Chaining
Overrides can chain: if A is overridden by B, and B is overridden by C, creating A gives you C.
Override Chaining Example
// In base_test
apb_driver::type_id::set_type_override(apb_debug_driver::get_type());
// In derived_test (extends base_test)
apb_debug_driver::type_id::set_type_override(apb_full_debug_driver::get_type());
// Result: apb_driver::type_id::create() returns apb_full_debug_driver
Advanced: Factory Internals
Debugging Factory Issues
When overrides don't work as expected, use factory debug methods:
Factory Debug Commands
// Print all registered types
uvm_factory::get().print();
// Print with verbosity (0=types, 1=+overrides, 2=+all instances)
uvm_factory::get().print(1);
// Debug specific creation
uvm_factory::get().debug_create_by_type(
apb_driver::get_type(),
"uvm_test_top.env.agent.driver");
Sample print() output:
#### Factory Configuration (*)
No instance overrides are registered with this factory
Type Overrides:
Requested Type Override Type
-------------- -------------
apb_driver apb_error_driver
All types registered with the factory: 42 total
apb_driver
apb_error_driver
apb_monitor
...
Parameterized Classes
Parameterized classes require special registration:
Registering Parameterized Classes
class generic_driver #(type T=uvm_sequence_item) extends uvm_driver #(T);
typedef generic_driver #(T) this_type;
// Use uvm_component_param_utils for parameterized classes
`uvm_component_param_utils(generic_driver #(T))
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
// Each parameterization is a separate factory registration
generic_driver #(apb_txn)::type_id::set_type_override(
custom_apb_driver::get_type());
Factory with Field Automation
When using field macros, ensure consistent registration:
Field Automation with Factory
class my_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
`uvm_object_utils_begin(my_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(write, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name="my_txn");
super.new(name);
endfunction
endclass
// Override works with field automation
my_txn::type_id::set_type_override(my_extended_txn::get_type());
Quick Reference
| Operation | Code |
|---|---|
| Register component | `uvm_component_utils(class_name) |
| Register object | `uvm_object_utils(class_name) |
| Register parameterized | `uvm_component_param_utils(class#(T)) |
| Create component | class::type_id::create("name", parent) |
| Create object | class::type_id::create("name") |
| Type override | orig::type_id::set_type_override(new::get_type()) |
| Instance override | orig::type_id::set_inst_override(new::get_type(), "path") |
| Print factory | uvm_factory::get().print() |
| Debug creation | uvm_factory::get().debug_create_by_type(...) |
Common Mistakes
| Mistake | Fix |
|---|---|
Using new() directly | Use type_id::create() |
Override after super.build_phase() | Override before super.build_phase() |
| Wrong path in instance override | Use get_full_name() to verify paths |
Forgetting `uvm_*_utils | Always register classes with factory |
Next: Singleton Pattern - uvm_root, uvm_config_db, and global resources
Comments (0)
Leave a Comment