Thursday, July 14, 2016

Where Does The ::type_id::create Command Come From?

Below explanation have been extracted from (http://www.sunburst-design.com/papers/CummingsSNUG2012SV_UVM_Factories.pdf) and I found it useful to know :-).

Trying to understand the inner workings of UVM by examining the source code can be a difficult task. The myriad of intertwined macro definitions coupled with frequent polymorphic replacement and indirection of base classes with extended classes, and repeated use of similar or the same method names in different classes makes it difficult to understand how some of the UVM features actually work. There is no greater example of this indirection and confusion than the ::type_id::create command.
To understand this command, first recognize that the use of the :: operators in this command indicate that you are probably using one or more static function calls, which is indeed the case.

To better understand this command, consider the following lines of code from the creation of the tb_agent in the simple example used in this paper:
tb_driver drv; // declaration of a tb_driver handle

function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = tb_driver::type_id::create("drv", this);
...
endfunction 

This command is going to call the ::type_id::create command from the tb_driver, which happens to be code largely inherited from other macros and classes. The source of this command can be traced to the following: 
(1) tb_driver is an extension of uvm_driver, which is an extension of uvm_component, which is a derivative of uvm_object, which defines a virtual method called create() with a single input argument. So how can the tb_driver::type_id::create("drv", this) command pass two arguments to this virtual method? 
The create() method defined in the uvm_object base class and passed down to the tb_driver class IS NOT THE create() COMMAND USED IN THIS FACTORY CONSTRUCTOR! (This is a point of confusion!) 

(2) At the top of the tb_driver class definition is the macro-invocation:
`uvm_component_utils(tb_driver) 


(3) The `uvm_component_utils macro is defined in the
<uvm_src_dir>/src/macros/uvm_object_defines.svh file 


(4) In this file, `uvm_component_utils(T) is defined to be the macros:
`define uvm_component_utils(T) \
`m_uvm_component_registry_internal(T,T) \
`m_uvm_get_type_name_func(T) 


(5) In this same file, `m_uvm_component_registry_internal(T,S) is defined to be:
`define m_uvm_component_registry_internal(T,S) \
typedef uvm_component_registry #(T,`"S`") type_id; \
static function type_id get_type(); \
return type_id::get(); \
endfunction \
virtual function uvm_object_wrapper get_object_type(); \
return type_id::get(); \
endfunction


(6) And in this same file, `m_uvm_get_type_name_func(T) is defined to be:
`define m_uvm_get_type_name_func(T) \
const static string type_name = `"T`"; \
virtual function string get_type_name (); \
return type_name; \
endfunction


Doing the macro expansion, the top of the tb_driver class now includes the code:
class tb_driver extends uvm_driver #(trans);
//`uvm_component_utils(tb_driver)
//`define uvm_component_utils(T) \
// `m_uvm_component_registry_internal(T,T) \
// `m_uvm_get_type_name_func(T)
//`define m_uvm_component_registry_internal(T,S) \
typedef uvm_component_registry #(tb_driver,"tb_driver") type_id;
static function type_id get_type();
return type_id::get();
endfunction
virtual function uvm_object_wrapper get_object_type();
return type_id::get();
endfunction
//`define m_uvm_get_type_name_func(T) \
const static string type_name = "tb_driver";
virtual function string get_type_name ();
return type_name;
endfunction 


This macro added the type_id type definition, get_type() method, get_object_type() method, static type_name string, and get_type_name() method, to the tb_driver class code. The type_id type definition is part of the ::type_id::create command, and type_id is just a type definition for the class type:
uvm_component_registry #(tb_driver,"tb_driver")


(7) The uvm_component_registry parameterized class is defined in the
<uvm_src_dir>/src/base/uvm_registry.svh file 


(8) In this file, is the definition for the uvm_component_registry class. An abbreviated section of this class definition is shown below:
1 class uvm_component_registry #(type T=uvm_component, string Tname="")
2 extends uvm_object_wrapper;
3
4 typedef uvm_component_registry #(T,Tname) this_type;
5
6 virtual function uvm_component
7 create_component (string name, uvm_component parent);
8 T obj;
9 obj = new(name, parent);
10 return obj;
11 endfunction
12
13 const static string type_name = Tname;
14
15 virtual function string get_type_name();
16 return type_name;
17 endfunction
18
19 local static this_type me = get();
20
21 static function this_type get();
22 if (me == null) begin
23 uvm_factory f = uvm_factory::get();
24 me = new;
25 f.register(me);
26 end
27 return me;
28 endfunction
29
30 static function T create(string name, uvm_component parent, ...);
31 uvm_object obj;
32 uvm_factory f = uvm_factory::get();
33 ...
34 obj = f.create_component_by_type(get(),contxt,name,parent);
35 if (!$cast(create, obj)) begin
36 string msg;
37 msg = {<"... error message ...">};
38 uvm_report_fatal("FCTTYP", msg, UVM_NONE);
39 end
40 endfunction
41 ...
42 endclass 


On line 4, this_type is set to
uvm_component_registry #(tb_driver,"tb_driver") 


On line 21 is the static get() function that, if the this_type (tb_driver registry class) is null, will call the uvm_factory static get() method to create a handle for this tb_driver registry and copy that handle to the this_type handle, register the tb_driver registry with the factory and then return the handle to the caller of the get() function. The same get() function just returns the handle if it already exists. 

On line 19 is the static handle declaration for the local static this_type me declaration, which calls the static get() function (described in the preceding paragraph) to register this tb_driver registry class with the factory and assign the corresponding handle. Since this handle and the get() function are both static, they will both happen automatically when the testbench is compiled without any required user invocation. In this way, when the `uvm_component_utils() macro is called from each component, it literally registers the corresponding registry class with the factory, which makes it possible to
::type_id::create any registered component from anywhere in the testbench class components. 


Line 30 - When the uvm_component_registry #(tb_driver,"tb_driver") is compiled, the static create() command is also made statically available. The most confusing piece of the static create() command code is on line 35. It took me 2 hours to figure out this command happens to be a rather simple command, once you understand the code. Most of the function methods in the UVM base classes use the SystemVerilog return command to return the correct value from the function, but line 35 is using the old Verilog way to return a function value. 

On line 34, the factory is asked to create the requested component by type and return the component handle to a uvm_object handle (obj - declared on line 31). This handle is then cast to the correct component type, which happens to be the type of the create() function. By casting back to the function name, the cast is actually assigned back to type of the function, and the caller of this function is then given a handle to the created component. Assigning (casting) to the function name is the old Verilog way to return a function value. The new SystemVerilog way to return a value would have been to declare a handle of the function type (T), cast the uvm_object handle to declared T-type function handle, and then call return to give back the T-type function handle. Hopefully this description just saved you 2 hours! 

In any uvm_*_registry class, is the static create() function. This is the last piece of the ::type_id::create() command. This explains where this command comes from. For components, the <component_type>::type_id::create(<component_handle>, this) command comes from the uvm_component_registry class parameterized to the component type. Similarly, for transactions, the <object_type>::type_id::create(<object_handle>this) command comes from the uvm_object_registry class parameterized to the object type.

uvm_object_wrappers are the proxy (substitute, place holder) types that are actually stored in the factory. When you create a component, you have actually created a uvm_component_registry class that is an extension of the uvm_object_wrapper. When you create a transaction, you have actually created a uvm_object_registry class that is an 
extension of the uvm_object_wrapper. It is handles of these component and object extensions of the wrapper class that are actually stored in the type-based factory.


1 comment: