How Factory Registration Happens in UVM

services-page-img



Introduction

The `uvm_component_utils is used to register UVM components with the factory right? But what’s there in the macro? And how does registration happen? While it’s not necessary to know every detail of a framework like UVM, delving into the inner workings of UVM source code can greatly enhance our understanding. By exploring how registration occurs in UVM, we can also gain deeper insights into key SystemVerilog concepts such as Singleton class, associative array, stringify macro, inheritance, and polymorphism.

Before that, let’s remember the basics of UVM Factory in brief.

What is a UVM Factory?

It is a lookup table of UVM Components and Transactions. UVM Components and Transactions are registered with the factory during compilation. Contents of the lookup table are called proxy objects.  Actual objects for proxy objects are created at run-time.

Factory is a Singleton class. A class for which only one object can be constructed is called the Singleton class.

What is Factory overriding and why is it needed?

If there is a requirement to change a few things whether in a testbench or a transaction, then, instead of creating a new testbench (with modified components or transactions), a new test can be created by overriding components and transactions.

For example:

If an SoC has pin muxing of UART and I2C, i.e., the same pins are used for both, then during verification only one agent (either UART or I2C ) can be connected. In that case, uvm_agent is instantiated. Based on test requirements either UART or I2C agent is overridden.

 Prerequisites for this Blog: Associative Array, Macros, Static Members, Class as a data type,  Polymorphism, Singleton Class, UVM Factory basics.

The following links will help you understand the necessary prerequisites.

To understand how factory registration happens, I theft some parts of the UVM source code related to factory registration, and added some extra code.

The following code gives a complete understanding of how factory registration happens. Guess the output of the code..….. Could you?

				
					
`define register_type(T) \
   factory_registration #(T, `"T`") type_id;

virtual class my_object;
  
  pure virtual function string get_type_name();
  
endclass

class my_factory;
   
  local function new();
  endfunction
  
  local static my_factory factory;
  
  my_object lookup_table[string];
  
  static function my_factory get();
    if(factory==null) begin
      my_factory f;
      f=new;
      factory=f;
    end 
    return factory;
  endfunction
    
  function register(my_object obj);
    lookup_table[obj.get_type_name] = obj;
  endfunction

endclass

class factory_registration #(type T, string Tname) extends my_object ;
  
  typedef factory_registration #(T, Tname) this_type;
  
  const static string type_name = Tname;
  
  local static this_type me = get();
  
  static function this_type get();
    if (me == null) begin                                                     
  	  my_factory factory = my_factory::get();
      me = new;
      void'(factory.register(me));
    end
    $display("[UVM_Factory] Registering the following type with the factory: %s",me.get_type_name);
    return me;
  endfunction
  
      
  function string get_type_name();
    return type_name;
  endfunction
  
endclass

class sequencer;
  `register_type(sequencer)
endclass
    
class driver;
  `register_type(driver)
endclass    
	
program test;
  
  my_factory factory;
  
  initial
  begin
    factory = my_factory::get();
    $display("%p", factory);
  end
endprogram
 
				
			

Edaplayground Link for the code: https://www.edaplayground.com/x/uMUD 

If you could understand the code, then you don’t need to read the blog further. And if you couldn’t, then check out the following explanation.

The above System Verilog code has 3 classes: factory_registration, my_factory, my_object

It uses a macro: register_type(T).

For example, in this context, the sequencer and driver classes are registered with the factory.

Program block is used to test whether registration is happening by printing the properties of the my_factory.

Let’s start with the sequencer class. What happens when the sequencer class is compiled? 

It has only one macro, that is `register_type(sequencer). Observe the stringify macro: `” used as `”T`”. It converts the argument passed to the macro as a string. This macro looks powerful.

Expanding the macro as defined in the first line of code gives the following

type_id is the member of sequencer class, of type factory_registration class, with parameters sequencer & “sequencer”. Now the factory_registration class gets compiled.

The factory_registration class extends to my_object class which is a virtual class and has a pure function called get_type_name(). 

The parameters of factory_registration class, T which is a type variable would be T = sequencer, and Tname which is a string would be Tname = “sequencer”. ‘ type ‘ is another powerful datatype of System Verilog.

The class factory_registration has a static member this_type where this_type is the typedef for factory_registration class. It has a static variable type_name. The memory allocation for static members occurs at compile time. Hence, type_name = “sequencer”.

It has a static variable “me” of type this_type, and it is assigned a value that is returned by a static function get(). me = get() is a declarative assignment. Hence execution of the get() function takes place.

Now, let’s look at the get() function. Initially, me is null, hence reference for an object of my_factory class which is a singleton class is assigned to the factory variable. Then, an object for me is constructed ( me = new() ).

Figure 1 : Object of factory_registration #(sequencer)

factory.register(me) registers the me with a lookup table available in my_factory.

lookup_table is an associative array of type my_object, and string as index. lookup_table[“sequencer”] = me. Then, get() function returns the reference for the object of type me. That reference is assigned to the variable me in factory_registration class. Refer to diagram 2 below.

Till now, we have seen how the sequencer is registered with the my_factory. Now apply the same for the driver class.

Figure 2 : Registration done with the factory

 

Until now, we haven’t encountered any initial block, but all this registration happened. What does it say? It says that registration happens during the compilation time.

What’s the proof that registration has happened? 

In the initial block, properties of my_factory class are displayed which gives the following output.

In the whole code, the statement local static this_type me = get() i.e line 38, is the core statement for factory registration. Both the me and get() functions are static. This line in the UVM factory registration source code resulted in this blog.

Code with dummy “create()” method: https://www.edaplayground.com/x/F6ja