UVM Factory

The UVM factory is a powerful mechanism for object creation in UVM, providing flexibility and configurability in your verification environment. It allows you to override the default creation of objects, enabling polymorphism and customization without modifying existing code.

Why Use the UVM Factory?

  • Polymorphism: You can replace a base class object with a derived class object without changing the code that creates it. This is crucial for extending and adapting verification components.
  • Configuration: You can configure which objects are created at runtime, based on simulation settings or command-line arguments.
  • Centralized Object Creation: The factory provides a single point for object creation, making it easier to manage and maintain your verification environment.

How the UVM Factory Works:

  1. Registration: Every UVM component and transaction class that you want to be able to create using the factory must be registered with it. This is typically done using the \uvm_object_utilsor`uvm_component_utils` macros within the class definition.

  2. Creation: Instead of using the new() constructor directly, you use the create() method of the factory. The factory then looks up the registered type and creates an instance of the appropriate class.

Screenshot-2024-12-17-at-6.33.58 PM-243x300 UVM Factory

Diagram 1: Basic Factory Operation

+-----------------+     +-----------------+     +-----------------+
| Request to      |---->| UVM Factory     |---->| Created Object  |
| create object   |     | (Lookup &      |     | (Base or       |
| (e.g., by type) |     |  Creation)     |     | Derived)       |
+-----------------+     +-----------------+     +-----------------+

Using UVM Factory

1. Registering Classes with the Factory

To use the UVM factory, classes need to be registered using the uvm_object_utils or uvm_component_utils macros.

class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)

  // Class definition
endclass

2. Creating Objects with the Factory

To create objects using the factory, use the create method.

my_driver drv = my_driver::type_id::create("drv", this);

3. Overriding Objects

You can override objects to replace them with custom implementations.

class my_custom_driver extends my_driver;
  `uvm_component_utils(my_custom_driver)
  
  // Custom implementation
endclass

initial begin
  my_driver::type_id::set_type_override(my_custom_driver::get_type());
end

Advanced UVM Factory Concepts

Type Overrides

Type overrides allow you to specify that any time an object of a certain type is created, an object of a different type should be created instead.

my_driver::type_id::set_type_override(my_custom_driver::get_type());

Instance Overrides

Instance overrides allow you to override a specific instance of a component with another component.

my_driver::type_id::set_inst_override("driver", get_full_name(), my_custom_driver::get_type());

Factory Methods:

  • create(): Creates an object of the specified type.
  • set_type_override(): Overrides the creation of one type with another (by string name).
  • set_type_override_by_type(): Overrides the creation of one type with another (by type handle – preferred).
  • get_type_override(): Retrieves the currently set override for a type.
  • print(): Prints the factory’s contents, showing current overrides.

 

Example Code (Simplified):

// Base transaction class
class base_transaction extends uvm_sequence_item;
  `uvm_object_utils(base_transaction)
  function new(string name = "base_transaction", uvm_component parent = null);
    super.new(name, parent);
  endfunction
endclass

// Derived transaction class
class derived_transaction extends base_transaction;
  int extra_data;
  `uvm_object_utils(derived_transaction)
  function new(string name = "derived_transaction", uvm_component parent = null);
    super.new(name, parent);
  endfunction
endclass

// In a component:
class my_component extends uvm_component;
  `uvm_component_utils(my_component)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  task run_phase(uvm_phase phase);
    base_transaction trans;

    // Default creation (creates base_transaction)
    trans = base_transaction::type_id::create("my_transaction", this);
    trans.print();

    // Override the factory to create derived_transaction
    uvm_factory::get().set_type_override_by_type(base_transaction::get_type(), derived_transaction::get_type());

    // Now creates derived_transaction
    trans = base_transaction::type_id::create("my_overridden_transaction", this);
    trans.print();
  endtask
endclass

Diagram 2: Type Override

+-----------------+   Override   +-----------------+     +-----------------+
| Request to      |------------>| UVM Factory     |---->| Created Object  |
| create          |             | (Override Map   |     | (Derived)       |
| base_transaction|             |  Lookup &      |     |                |
|                 |             |  Creation)     |     |                |
+-----------------+             +-----------------+     +-----------------+
                                     ^
                                     |
                                     | set_type_override
                                     |
                                     +-----------------+
                                     | Override Config |
                                     | (e.g.,          |
                                     | base -> derived)|
                                     +-----------------+

Key Considerations:

  • Type Handle vs. String Name: Using type handles (::get_type()) for overrides is strongly recommended over string names for better performance and type safety.
  • Phase Awareness: Factory overrides often need to be set early in the simulation (e.g., in the build_phase) to ensure they are in effect when objects are created.
  • Hierarchical Overrides: Overrides can be applied at different levels of the testbench hierarchy, allowing for fine-grained control.

Example of hierarchical override:

//In the top env:
uvm_factory::get().set_type_override_by_type(base_transaction::get_type(), derived_transaction::get_type());

//In a sub-env:
uvm_factory::get().set_type_override_by_type(base_transaction::get_type(), yet_another_derived_transaction::get_type());

In this example, the top env will create objects of type derived_transaction and the sub-env will create objects of type yet_another_derived_transaction.

The UVM factory is a powerful tool that promotes code reusability, flexibility, and maintainability in your verification environments. Mastering its use is essential for building robust and scalable testbenches.