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:
class l2_layer extends uvm_object;
`uvm_object_utils(l2_layer)
//-->
//generated code from macros
typedef uvm_object_registry #(l2_layer, "l2_layer") type_id;
...
endclass
The uvm_object_registry#(l2_layer, "l2_layer")
class will be constructed and register it’s instance handle to uvm factory by itself.
class uvm_object_registry #(type T=uvm_object, string Tname="") extends uvm_object_wrapper;
//1. factory register code
typedef uvm_object_registry #(T,Tname) this_type;
local static this_type me = get();
static function this_type get();
if (me == null) begin
uvm_factory factory = uvm_coreservice_t::get().get_factory();
me = new;
factory.register(me); //register to uvm factory
end
return me;
endfunction
endclass
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.
class uvm_object_registry #(type T=uvm_object, string Tname="") extends uvm_object_wrapper;
...
//1. singleton object construction and factory register code
...
//2. create() function, called by user
static function T create (string name="", uvm_component parent=null, string contxt="");
uvm_object obj;
uvm_factory factory = uvm_coreservice_t::get().get_factory();
//uvm factory will construct and return the obj
//the uvm factory actually will construct the override child class of T
obj = factory.create_object_by_type(get(),contxt,name);
...
endfunction
endclass
- 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:
class sequence_a extends uvm_sequence;
...
l2_layer::type_id::set_type_override(l2_layer_mac::get_type()); // where the l2_layer_mac is extended from l2_layer
...
l2_layer m_l2_obj = l2_layer::type_id::create("l2_layer"); // the l2_layer_mac object will be constructed
...
endclass
- 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.
virtual class uvm_object_wrapper;
virtual function uvm_object create_object (string name="");
return null;
endfunction
pure virtual function string get_type_name();
endclass
class uvm_object_registry #(type T=uvm_object, string Tname="") extends uvm_object_wrapper;
//1. factory register code
...
factory.register(me); //register to uvm factory
...
//2. create() function, called by user
//3. create_object() function, called by uvm_factory
virtual function uvm_object create_object(string name="");
T obj;
obj = new(name);
return obj;
endfunction
const static string type_name = Tname;
virtual function string get_type_name();
return type_name;
endfunction
endclass
- 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:
...
l2_layer::type_id::set_type_override(l2_layer_mac::get_type()); // where the l2_layer_mac is extended from l2_layer
...
l2_layer m_l2_obj = l2_layer::type_id::create("l2_layer"); // the l2_layer_mac object will be constructed
...
- 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
class uvm_default_factory extends uvm_factory;
protected bit m_types[uvm_object_wrapper];
protected uvm_object_wrapper m_type_names[string];
function void register(uvm_object_wrapper obj);
m_type_names[obj.get_type_name()] = obj;
...
m_types[obj] = 1;
...
endfunction
endclass
- 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.
class uvm_default_factory extends uvm_factory;
protected uvm_factory_override m_type_overrides[$];
function void set_type_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
uvm_factory_override override;
override = new(.orig_type(original_type),
.orig_type_name(original_type.get_type_name()),
.full_inst_path("*"),
.ovrd_type(override_type));
m_type_overrides.push_back(override);
endfunction
endclass
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
function uvm_object uvm_default_factory::create_object_by_type (uvm_object_wrapper requested_type,
string parent_inst_path="",
string name="");
requested_type = find_override_by_type(requested_type, full_inst_path);
return requested_type.create_object(name);
endfunction
- 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
]