How uvm factory actually works
This post will go into the detailed implementation of the uvm factory. If you have not known what uvm factory is yet, check this one: How to use uvm factory.
Things to know before diving in
- Class scope resolution operator
::
.- We use the class scope resolution operator to gain access to an element inside a class.
- A class scope resolution operator can be used for all static element: static class variables, static function/task, parameters, local parameters, enums, unions, constraints, nested class declaritions and typedefs.
- Abstract class
- Abstract class is like a template of other classes, cannot be instantiated directly. Virtual class and pure virtual methods
- Parameterized class
- Known as generic class in other programming languages.
- The important point when using parameterized class is that the class type is only valid when parameters are provided. And each set of parameters will create a different class type.
- For example, given the parameterized class
class bus_monitor #(int ADDR_BUS_WIDTH);
, thebus_monitor
is not a valid class type.bus_monitor#(32)
andbus_monitor#(64)
are valid, and these are two different class type.
- Singleton class
- This is a type of class that can only has one single instance.
- Check this post for detailed informations Singleton class in Systemverilog
Registering the uvm obj/component to uvm factory
Firstly, when defining a class, we must register that class to the uvm factory. This step is simpified by using the uvm macro: uvm_object_utils
or uvm_component_utils
.
Object/Component registry class
The macro will actually create a typedef of type_id as below:
The uvm_object_registry#(l2_layer, "l2_layer")
class will be constructed and register it’s instance handle to uvm factory by itself.
We can see that the
uvm_object_registry#(l2_layer, "l2_layer")
is actually a singleton class. Also, thanks to the initialization of the static variablestatic this_type me = get()
, this class will be constructed automatically at the very beginning, before any execution. This is actually called eager initialization, where the instance of singleton class is created thanks to the initialization of static variable.Also, in the
get()
function, this singleton classuvm_object_registry#(l2_layer, "l2_layer")
will be registered to uvm factory using this statementfactory.register(me)
So, when define a class
l2_layer
, by usinguvm_object_utils(l2_layer)
macro, we actually will create a singleton object. Inl2_layer
class, this singleton objectuvm_object_registry#(l2_layer, "l2_layer") has a type
type_id. This
type_idobject is registered to the uvm factory automatically. Also, this singleton object will contain the class type
l2_layer` and the class name string as its parameters. Those are the information will be used by the uvm factory for searching and constructing class instance.
Constructing the new object
The other code convention when using uvm factory is that to construct the obj, instead of using the new()
constructor directly, we must use the create()
method inside the type_id
singleton object:
l2_layer m_l2_obj = l2_layer::type_id::create("l2_layer");
Constructing object
Still in the uvm_object_registry
class, we will have this create()
method.
- The
create()
is a static function, so we can access this function using class scope resolution operatorl2_layer::type_id::create()
- In this method, we actually ask the uvm factory to construct the object using this statement
obj = factory.create_object_by_type(get(), contxt, name)
. In uvm factory, there is an array to stored the original class type and it’s overriden class type. If thel2_layer
is overriden by it’s child class, this child class will be constructed instead.
Example of creating l2_layer
object in a sequence class:
- In the above example, the
l2_layer
is overriden by its child classl2_layer_mac
. - So after constructing object using uvm factory, the
m_l2_obj
variable will point to an object ofl2_layer_mac
.
Inside the uvm_factory
So, when defining the class with uvm factory we need:
- Firstly, we will register the class into the
uvm_factory
using the uvm macro. - Then when constructing the obj, we will ask the
uvm_factory
to construct and return the expected obj. If the class has been overriden, then the return obj will be the object of the overriden child class instead of the original one.
Let see how the uvm_factory
can do that
uvm_factory class
In uvm1.2, The uvm_factory
is actually a abstract class. The uvm_default_factory
is the extended class of the uvm_factory
and it’s instance can be retrieved through a uvm_coreservice_t
singleton class as below:
uvm_factory factory = uvm_core_service_t::get().get_factory()
Object/Component wrapper class
The uvm_object_registry
actually has another function called create_object()
. This function will be called by the uvm_factory
to create an object of the registered class.
Also the uvm_object_registry
is extended from the uvm_object_wrapper
class, which is simply an abstract class.
- The
create()
method will be called by user. As explained in the section above, this function will return the handle of the overriden class object. - The
create_object()
method will be called byuvm_factory
. This function will construct and return the object of the register class.
Example:
- In the above example, inside the
uvm_object_registry#(l2_layer, "l2_layer")
, thecreate()
method will construct thel2_layer_mac
object thanks to uvm factory, and thecreate_object()
will construct thel2_layer
object.
Factory register() function
As above, to register a class to the factory, we call factory.register(me)
inside the get()
function of the uvm_object_registry
. Where me
has the handle to the object of singleton class uvm_object_registry#(l2_layer, "l2_layer")
Let’s take a look at the register()
function
- In uvm factory, we have options for overriding the original class, either by type or by name. The two above associative arrays
m_types
andm_type_names
serve that purpose. They will store the class registered to factory using either class type or class name string as lookup key. - Before perform override function or class contructor function on a class, the factory will check these 2 arrays to make sure that class has already been registered in the factory.
Factory override function
Let just cover the override by type here. The override by inst just operates in the same fashion as the by type override.
We has a queue to store the overriden information named m_type_overrides
. This queue store the information of original class and it’s overriden one. The uvm_factory_override
class object is an object that store those information when we call the set_type_override_by_type()
function.
Factory create object function
Finaly, when create and object using the create()
function as below:
l2_layer m_l2_obj = l2_layer::type_id::create("l2_layer");
We will call the function create_object_by_type()
in uvm factory
- The
find_override_by_type()
will search the latest overriden class type in them_type_overrides[$]
, then return the class type that needed to construct. - Then we will construct the object using the
create_object()
function inside theuvm_object_registry#()
class. - For example, if the
l2_layer
is an original class type, and thel2_layer_mac
is the overriden class type. Thecreate_object()
ofuvm_object_registry#(l2_layer_mac, "l2_layer_mac")
will be call, and thel2_layer_mac
object will be constructed eventually.
Finding more information
[Tags
uvm
]