UVM Factory

The magic behind UVM polymorphism. It allows replacing components/objects with derived types without changing the code.

Foundation: Registration & The Create Method

Why the Factory Pattern?

The Problem: If you use new to create components, you can't swap them out later. Your testbench becomes rigid and hard to customize.

The Solution: The Factory lets you swap component types without changing code. Want to use a faster driver for one test? Just override it—no code changes needed.

In SystemVerilog, new() is static—once you call it, you get an instance of exactly that class. The UVM Factory replaces this with a dynamic lookup. By using type_id::create(), you are asking UVM: "I want an object of Type A, but check if the Testwriter has requested a replacement (Override) first."

1. Registration Macros

To use the factory, every class must register itself at the top of the file:

  • `uvm_component_utils(my_driver): For persistent components (Driver, Monitor, Agent). These have a set place in the hierarchy.
  • `uvm_object_utils(my_transaction): For transient objects (Sequences, Items, Configs). These are created and destroyed frequently.
Creating via Factory

// WRONG: Static allocation. Polymorphism won't work!
my_drv = new("my_drv", this); 
// RIGHT: Dynamic allocation via Factory.
my_drv = my_driver::type_id::create("my_drv", this); 
                            

Type vs Instance Overrides

Overrides are the most powerful feature of UVM. They allow you to swap out a standard driver with an "Error-Injection Driver" for a specific test, without touching the environment or agent code.

Type A: The Type Override (Global)

Replaces every single instance of a class in the entire testbench.


// In your test:
my_driver::type_id::set_type_override(error_driver::get_type());
                                

Type B: The Instance Override (Targeted)

Replaces only the class at a specific hierarchical path.


// Only replace the driver in the 'USB_AGENT'
set_inst_override_by_type("env.usb_agt.drv", 
                          usb_driver::get_type(), 
                          fixed_usb_driver::get_type());
                                

Override Precedence:

  1. Instance Overrides always win over Type Overrides.
  2. Last Override wins if multiple overrides are applied to the same type.

$cast vs Factory Overrides: When to Use Which?

Both $cast and Factory Overrides enable polymorphism in UVM, but they work at different times and serve different purposes. Understanding when to use each is crucial for writing flexible, maintainable testbenches.

What is $cast?

$cast is a SystemVerilog feature for run-time type checking and conversion. It allows you to safely convert a base class handle to a derived class handle.

$cast Example

// Base class handle
uvm_sequence_item item;
// Derived class handle
my_packet pkt;
// Get an item from the sequencer (returns base class)
seq_item_port.get_next_item(item);
// Cast it to the derived type to access specific fields
if (!$cast(pkt, item)) begin
    `uvm_fatal("CAST", "Failed to cast item to my_packet")
end
// Now you can access derived class fields
pkt.payload_data = 8'hFF;
                            

What are Factory Overrides?

Factory Overrides are a UVM feature for build-time component substitution. They allow you to replace one class with another before objects are created.

Factory Override Example

// In your test's build_phase
function void build_phase(uvm_phase phase);
    // Replace all instances of standard_driver with error_driver
    standard_driver::type_id::set_type_override(error_driver::get_type());
    super.build_phase(phase);
endfunction
// Now when the agent creates the driver:
driver = standard_driver::type_id::create("driver", this);
// UVM actually creates an error_driver instead!
                            

Key Differences

Aspect $cast Factory Override
When it happens Run-time (during simulation) Build-time (before simulation starts)
What it does Converts an existing handle from base to derived type Replaces which class gets created
Scope Local (affects only the specific handle) Global or instance-specific (affects all or targeted creations)
Typical use case Accessing derived class fields from a base class handle Swapping components for different test scenarios

When to Use $cast

  • Receiving base class handles: When a function returns uvm_sequence_item but you need to access fields in your derived my_packet class.
  • Callback implementations: When implementing do_copy() or do_compare() methods that receive uvm_object handles.
  • TLM ports: When pulling items from a FIFO that stores base class types.

When to Use Factory Overrides

  • Test-specific behavior: Replacing a standard driver with an error-injection driver for negative testing.
  • Configuration variations: Swapping a simple scoreboard with an advanced one for specific tests.
  • Debugging: Replacing a component with a debug version that has extra logging.
  • Reusability: Using the same environment code with different component implementations.

Rule of Thumb

Use $cast when you're working with handles that already exist and need to access derived class features. Use Factory Overrides when you want to change what type of object gets created in the first place.

Think of it this way: $cast is like putting on different glasses to see an object differently. Factory Override is like ordering a different product from the factory.

Expert: Checking the Factory State

Sometimes an override doesn't seem to work. This usually happens because the component was created using new() instead of create(), or the path string in the instance override was wrong.

Audit Command

function void end_of_elaboration_phase(uvm_phase phase);
    uvm_factory::get().print(); // Prints all registered types and active overrides
endfunction
                            

Look for the "Override Queue" in the log output to verify that your Test successfully applied the replacement.

new() vs create(): Interview Deep Dive

Frequently Asked: "What's the difference between new() and create() in UVM?"

Aspect new() type_id::create()
Factory Aware ❌ No Yes
Supports Override ❌ No Yes (type or instance)
Polymorphism ❌ Fixed type Dynamic type substitution
Registration Needed ❌ No Yes (`uvm_*_utils)
Use Case Local objects, non-reusable Components, testbench reuse
Comparison: Same Class, Different Outcomes

// Using new() - STATIC, no override possible
class my_agent extends uvm_agent;
    my_driver drv;
    function void build_phase(uvm_phase phase);
        drv = new("drv", this);  // ALWAYS creates my_driver
        // Even if test does: my_driver::type_id::set_type_override(...)
        // This will NOT be affected!
    endfunction
endclass
// Using create() - DYNAMIC, override works
class my_agent extends uvm_agent;
    my_driver drv;
    function void build_phase(uvm_phase phase);
        drv = my_driver::type_id::create("drv", this);
        // If test overrides my_driver with error_driver,
        // drv will actually be an error_driver!
    endfunction
endclass
                            

The Golden Rule

Always use create() for any class you might want to override later. The only exception is purely local, temporary objects that will never be replaced (like a configuration struct used only inside one function).

Interview Answer Template:

"new() is a SystemVerilog construct that creates an object of exactly the specified type. create() is a UVM factory method that first checks if any overrides are registered before creating the object. This enables polymorphism and testbench reuse without code changes."