在UVM中,我们经常使用`uvm_object_utils或者`uvm_component_utils来将uvm_object/uvm_component注册,但很多同学并不清楚注册是怎么实现的,因此本文主要分析下`uvm_object_utils的注册究竟做了什么。
`uvm_object_utils本质上是一个宏,这个宏由另外两个宏`xxx_begin、`xxx_end组成,而`xxx_begin又是由几段功能宏组成:
`define uvm_object_utils(T) \
`uvm_object_utils_begin(T) \
`uvm_object_utils_end
`define uvm_object_utils_begin(T) \
`m_uvm_object_registry_internal(T,T) \
`m_uvm_object_create_func(T) \
`m_uvm_get_type_name_func(T) \
`uvm_field_utils_begin(T)
`define uvm_object_utils_end \
end \
endfunction \
先看代码:
//This is needed due to an issue in of passing down strings
//created by args to lower level macros.
`define m_uvm_object_registry_internal(T,S) \
typedef uvm_object_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
这个宏里面只做了两件事情:
粗略一看,这两件事情好像和uvm_object的注册没有什么关系,其实不然。对于参数化的类type_id,我们可以把它抽象成一个简单的哈希(key-value,type_id的名字也是由此而来)。由于这些宏的参数实际上就是类名T。所以它的key-value就是uvm_object的类名(class name)和类的名称(string)。随着这个宏的展开,我们相当于给class额外定义了两个函数:get_type()和get_object_type()。由于uvm_object_registry #(T,“S”)是一个singletone的class,所以get_type()和get_object_type()的函数内容一样,区别在于返回值的句柄不同。对于get_type(),返回的是type_id的句柄;对于get_object_type(),返回uvm_object_wrapper的句柄。
这里面还有一个细节,我们在上一层传递的宏参数是(T, T),但实际使用时第二个参数是被当作字符串来进行使用的。因此使用了`"S`"这样的语法。
上文中的type_id实际上是参数化的类,两个参数可以看成是哈希的key-value。在这个参数化的类的get()函数中,通过uvm_factory的register函数将它自己注册,或者说将这个哈希放到一个全局的登记表格中去了。
顺带一提另一个经常看见的函数create()。它的作用也很简单,即根据给定名字(string),在全局的登记表格中查找匹配的句柄,并利用该句柄来创建一个新的实例。
下面是摘自UVM的部分源代码:
class uvm_object_registry #(type T=uvm_object, string Tname="" )
extends uvm_object_wrapper;
typedef uvm_object_registry #(T,Tname) this_type;
...
local static this_type me = get();
// Function: get
//
// Returns the singleton instance of this type. Type-based factory operation
// depends on there being a single proxy instance for each registered type.
static function this_type get();
if (me == null) begin
uvm_factory f = uvm_factory::get();
me = new;
f.register(me);
end
return me;
endfunction
...
// Function: create
//
// Returns an instance of the object type, ~T~, represented by this proxy,
// subject to any factory overrides based on the context provided by the
// ~parent~'s full name. The ~contxt~ argument, if supplied, supercedes the
// ~parent~'s context. The new instance will have the given leaf ~name~,
// if provided.
static function T create (string name="", uvm_component parent=null,
string contxt="");
uvm_object obj;
uvm_factory f = uvm_factory::get();
if (contxt == "" && parent != null)
contxt = parent.get_full_name();
obj = f.create_object_by_type(get(),contxt,name);
if (!$cast(create, obj)) begin
string msg;
msg = {"Factory did not return an object of type '",type_name,
"'. A component of type '",obj == null ? "null" : obj.get_type_name(),
"' was returned instead. Name=",name," Parent=",
parent==null?"null":parent.get_type_name()," contxt=",contxt};
uvm_report_fatal("FCTTYP", msg, UVM_NONE);
end
endfunction
...
uvm_object_registry比较有意思的是它利用了static method和get()函数结合,实现了singleton的效果。
这个宏里面完成的事情很简单,只是给class定义了create()函数。
`define m_uvm_object_create_func(T) \
`ifdef UVM_CREATE_OPT \
virtual function void call_new(); \
endfunction \
`else \
function uvm_object create (string name=""); \
T tmp; \
`ifdef UVM_OBJECT_MUST_HAVE_CONSTRUCTOR \
if (name=="") tmp = new(); \
else tmp = new(name); \
`else \
tmp = new(); \
if (name!="") \
tmp.set_name(name); \
`endif \
return tmp; \
endfunction \
`endif
至此,我们有了两种方式来创建uvm_object:
- user_object.create("user_object");
- user_object::type_id::create("user_object");
注意第一种方式实际上只是new()的另一种写法而已。
这个宏里面完成的事情同样也很简单,只是给class定义了type_name变量和get_type_name()函数。
`define m_uvm_get_type_name_func(T) \
const static string type_name = `"T`"; \
virtual function string get_type_name (); \
return type_name; \
endfunction
这里面是和field automation相关的一些代码,这里就不展开进行说明了。