Concept of UVM Factory

Factory stimulates everyone’s imagination to fall down on industrial settings and mechanical setup of machineries & processing units with products getting made and packaged before running on the transport rail & to be delivered to the warehouses. Hence the word “Factory” makes us curious to know about this jargon in Verification Methodology Context i.e. UVM.

Since UVM primarily deals with the high level of data abstraction e.g. Transactions represented by OOPs based Classes, hence it looks like Factory has to do something with OOPs based components & objects, their generation and/or replacements in the UVM based Verification Environment. Lets dig deep into “Factory” and know more about it.

First of all, it’s important to understand that as per the recommended UVM methodology, we should never construct new components and/or transactions using new() class constructor. Instead it is recommended that – we should make calls to a look-up table to create the requested components and transactions. This special look-up table is called “Factory” in UVM. Entries to this look-up table is made by registering the components and/or transactions while defining them. To create a component/transaction using Factory, create() method is used.

From the application point of view, UVM Factory facilitates an object of one type to be substituted with an object of derived type without having to change the structure of the Testbench or modify the Testbench code. This behavior is called “overriding” and there are following types of overriding is possible with UVM Factory::

  1. Type Overriding(set_type_override)
  2. Instance Overriding(set_inst_override)

Overriding helps to replace one transaction/sequence with another to generate new scenarios or conditions without making any change in the Testbench structure/code. Similarly, new version of the components can be brought into the Testbench without any change in the structure of the Testbench & beauty of all this is that – it happens all the fly at the run time.

To achieve & enable this capability of the UVM Testbench, it is required to follow certain steps while defining & creating our components/sequences/transactions.  

Let’s go through required steps with example:

  1. Factory Registration:

Every UVM component or sequence/transactions must have Factory registration & this registration can be done by using Factory registration macros, as shown below in the code

// Registration of a UVM Component
   class axi_driver extends uvm_driver;
     `uvm_component_utils(axi_driver)
     ...
     ...
     ...
   endclass: axi_driver

It is recommended to extend your component class from UVM base classes available e.g uvm_env, uvm_agent, uvm_driver, uvm_monitor, uvm_sequencer etc. for the corresponding physical component

// Registration of a sequence
class axi_wr_seq extends uvm_sequence #(axi_txn);
  `uvm_object_utils(axi_wr_seq)
     ...
     ...
     ...
 endclass: axi_wr_seq
// Registration of a transaction
class axi_txn extends uvm_sequence_item;
  `uvm_object_utils(axi_txn)
  ...
  ...
  ...
endclass: axi_txn

2. Default Constructor

As we know that uvm_component and uvm_object constructors are virtual methods hence user have to follow their prototype template. As we know that in UVM components/objects are constructed during build phase but Factory constructor should contain default arguments in the definition of the components/objects. This allows Factory registered component/object to be created inside Factory initially & later to be re-assigned to the class properties passed via the create() command as arguments. The default arguments are different for components and objects.

Let’s see this by following example:

class axi_driver extends uvm_driver;
  `uvm_component_utils(axi_driver)
  
  //Default Constructor
  function new (string name = "axi_driver", uvm_component parent = null);
    super.new(name, parent);
  endfunction
 
  endclass: axi_driver

class axi_wr_seq extends uvm_seqeuence #(axi_txn);
  `uvm_object_utils(axi_wr_seq)
  
  //Default Constructor
  function new (string name = "axi_wr_seq");
    super.new(name);
  endfunction
 
endclass: axi_wr_seq
 
class axi_txn extends uvm_sequence_item;
  `uvm_object_utils(axi_txn)
  
  //Default Constructor
  function new (string name = "axi_txn");
    super.new(name);
  endfunction
 
  endclass: axi_txn

3.  Component & Object Creation:

Using Factory, hierarchically lower components/objects are created by the immediate higher components/objects. Hence, the next goal would be the Factory supported component and object creation code entry for the child components inside the parent components and objects.

Let’s go through via example:

class axi_agent extends uvm_agent;
  `uvm_component_utils(axi_agent)
  
  axi_driver drv;
  axi_monitor mon;
  
  function new (string name = "axi_agent", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  
  function void build_phase (uvm_phase phase);
    super.build_phase(phase);
    drv = axi_driver::type_id::create("drv", this);
    mon = axi_monitor::type_id::create("mon", this);
  endfunction: build_phase
endclass: axi_agent
 
class axi_driver extends uvm_driver #(axi_txn);
  `uvm_component_utils(axi_driver)
  
  function new (string name = "axi_driver", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  
  task run_phase (uvm_phase phase);
    axi_txn txn;
    txn = axi_txn::type_id::create("txn", this);
    .......;
  endtask: run_phase
 
  endclass: axi_driver

As we discussed it before, The UVM Factory can be thought of as a look-up table, so when component construction takes place using <type>::type_id::create(“<name>”, <parent>); approach, what happens is that the type_id is used to pick up the Factory component wrapper for the class, construct its contents & pass the resultant handle back again to the LHS.

The Factory override changes the way in which lookup happens so that looking up the original type_id results in a different type_id being used. Consequently, a handle to a different type of constructed object is returned. This technique is primarily based on Polymorphism which is the ability to be able to refer to the derived types using a base type handle. In practice, an override will only work if the parent class is being overridden by one of its derived classes.

Type Overriding(set_type_override)

A type overriding means that every time a component class type is created in the Testbench hierarchy, a substitute type i.e. derived class of the original component class, is created in its place. It applies to all the instances of that component type

Syntax:
<original_type>::type_id::set_type_override(<substitute_type>::get_type(), replace);

where “replace” is a bit which is when set equals to 1, enables the overriding of an existing override else existing override is honoured.

Let us understand Type Overriding with example mentioned below:

class axi_driver extends uvm_driver #(axi_txn);
  `uvm_component_utils(axi_driver)
 
     ...
     ...
 
endclass: axi_driver
 
class axi_new_driver extends axi_driver #(axi_txn);
  `uvm_component_utils(axi_new_driver)
 
     ...
     ...
 
endclass: axi_new_driver
 
class axi_agent extends uvm_agent;
  `uvm_component_utils(axi_agent)
  
  axi_driver drv;
  
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  function void build_phase (uvm_phase phase);
    super.build_phase(phase);
    drv = axi_driver::type_id::create("drv", this);
  endfunction: build_phase
 
endclass: axi_agent
 
class axi_base_test extends uvm_test;
  `uvm_component_utils(axi_base_test)
  
  axi_env env;
  
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
     axi_driver::type_id::set_type_override(axi_new_driver::get_type(),1);
     env = axi_env::type_id::create("env", this);
  endfunction: build_phase
 
  task run_phase (uvm_phase phase);
       ...
       ...
       ...
  endtask: run_phase
 
endclass: axi_base_test

An important point to note here is the order of 2 methods i.e. set_type_override()  to be placed before the create() method inside the build_phase() of the axi_base_test class. Only with this order of methods the substitution will get into effect. In case the order is reversed, the original driver in the code i.e. axi_driver will be constructed instead of the intended driver i.e. axi_new_driver.

Instance Overriding(set_inst_override)

In Instance Overriding, as the name suggests it substitutes ONLY a particular instance of the component OR a set of instances with the intended component. The instance to be substituted is specified using the UVM component hierarchy.

Syntax:
<original_type>::type_id::set_inst_override(<substitute_type>::get_type(), <path_string>);

Where “path_string” is the hierarchical path of the component instance to be replaced.

Let us check out this concept by following example:

class axi_driver extends uvm_driver #(axi_txn);
  `uvm_component_utils(axi_driver)
 
     ...
     ...
 
endclass: axi_driver
 
class axi_new_driver extends axi_driver #(axi_txn);
  `uvm_component_utils(axi_new_driver)
 
     ...
     ...
 
endclass: axi_new_driver
 
class axi_agent extends uvm_agent;
  `uvm_component_utils(axi_agent)
  
  axi_driver drv;
  
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  function void build_phase (uvm_phase phase);
    super.build_phase(phase);
    drv = axi_driver::type_id::create("drv", this);
  endfunction: build_phase
 
endclass: axi_agent
 
class axi_base_test extends uvm_test;
  `uvm_component_utils(axi_base_test)
  
  axi_env env;
  
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    axi_driver::type_id::set_inst_override(axi_new_driver::get_type(), "top.env.agent.drv");
    env = axi_env::type_id::create("env", this);
  endfunction: build_phase
 
  task run_phase (uvm_phase phase);
       ...
       ...
       ...
  endtask: run_phase
 
endclass: axi_base_test

Objects or sequence related objects are generally only used with type overrides since the instance override approach relates to a position in the UVM Testbench component hierarchy which objects do not take part in.