factory机制源代码解析
文章目录
0、写在前面
uvm源代码的第二篇,工厂机制是整个uvm的基石,用到的所有组件和sequence都需要在工厂中注册。前面第一篇的component源代码也使用到了工厂机制注册uvm_test
1、根据类的名字来创建实例
这个话听起来是不是很摸不着头脑,为什么要根据名字创建实例,直接要用什么创建什么不可以嘛,大不了在new的时候传一个名字
1.1、为什么要根据名字创建实例
试想一个这样的场景:
class A ;
...
endclass
class B;
string type_string;+
type_string = $value$plusarg("type_string=%s",type_string);
function new();
//to create A;
endfunction
endclass
怎么样这个场景是不是很熟悉,我们上一节在讲run_test的时候,是不是就是这样的, v a l u e value valueplusagr传进来一个名字,然后工厂直接根据名字就创建这个类型。现在是不是有点明白工厂机制的作用了。工厂在根据名字创建类型的时候,有一个前提,这个名字必须要在工厂里面注册,否则工厂怎么知道要创建什么样的实例呢,下面我们讲怎么实现工厂里面的注册。
1.2、工厂如何实现注册
工厂的注册流程比较固定,其实我们能预想到的就是向factory里面的关联数组添加一条记录,因此uvm用宏帮我们简化了一系列流程。
工厂机制里面主要有两大类的宏,分别是:uvm_component_utils和uvm_object_utils
(1) uvm_object_utils宏展开
`define uvm_object_utils
`uvm_object_utils_begin(T)
`uvm_object_utils_end
//重点看uvm_object_utils_begin
`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)
1)m_uvm_object_registry_internal
`define m_uvm_object_registry_internal //看名字就知道 要向工厂中注册
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_registry这个类
class uvm_object_registry #(type T=uvm_object,string Tname="<unknown>")
typedef uvm_object_registry #(T,Tname) this_type;
...
loacal staic this_type me = get();
...
static function this_type get();
if(me == null)begin
uvm_factory f =uvm_factory::get();
me =new;
f.registry(me);
end
endfunction
uvm_object_wrapper是一个代理类,我们在使用uvm_config_db#(uvm_object_warpper)::set或者::get的时候会讲这个类作为类型。
uvm_object_registry 通过参数化的方式,根据传入的class_name名字不同会定义出不同的类型,例如
typedef uvm_object_registry#(T1,“S1”) this_type1;
typedef uvm_object_registry#(T!,“S2”) this_type2;
这是两个不同的类型,因此在使用过程中,uvm_object_utils(classname)根据clas name的不同注册的是不同的类型,虽然真正注册进工厂的是me,也就是这个type_id 的类型,但是也相当于间接把T注册了。
我们来梳理一下,工厂如何实现注册,
1、通过宏,类的类型和名字用于参数化。这样uvm_object_registry类获得了类的类型和类的名字;
2、通过一个typedef uvm_object_registry#(T,Tname) type_id; 给该类留下了一个接口(class_name::type_id:);
3、type_id 干了什么事情,type_id 里面声明了一个staic 的uvm_object_registry 类型的me ,并自动调用get完成向工厂里面的注册。
2、uvm_factory 类
上面用到了f.registry();那么factory是什么样的呢。仅仅只能注册嘛?
class uvm_factory;
extern `_protected function new();
extren static function uvm_factory get();
endclass
function uvm_factory uvm_factory::get();
if(m_inst == null)begin
m_inst =new();
end
return m_inst;
endfunction
//从这里可以看出来factory也是单实例的
function void uvm_factory::register(uvm_object_warpper obj);
if(obj == null)begin
//report error
end
if(obj.get_type_name() || obj.get_type_name() == "<unknown>" )begin
//report warning
end
else begin
if(m_type_name.exist(obj.get_type_name()))begin
//report error
end
else begin
m_type_name[obj.get_type_name()] = obj;
end
end
endfunction
这里最重要的就是,向联合数组里面插入一条记录,联合数组定义如下:uvm_object_wrapper m_type_names[sting];
同时也会向另一个联合数组 bit m_types[uvm_object_wrapper] 用于指示,该类型是否已经被注册过了。
3、factory机制的应用
3.1 根据类名创建类的一个实例
class AAA extends uvm_object;
`uvm_object_utils(AAA)
...
endclass
class test;
uvm_object aaa;
function new();
uvm_factory f =uvm_factory::get();
aaa = f.create_object_by_name("AAA")
这里我们分析一下 create_object_by_name是如何工作的:
function uvm_object uvm_factory::create_object_by_name(string requested_type_name,
string parent_inst_path = "",
string name = "",);
uvm_object_wrapper wrapper;
string inst_path;
...
if (wrpper == null)begin
wrapper = m_type_names[requestew_type_name];
end
return wrapper.create_object(name);
endfunction
function uvm_object_registry#(type T=uvm_object ,string Tname="<unkonwn>")::create_object(name);
T obj;
obj =new();
if(name !="")
obj.set_name(name);
return obj;
endfucntion
因此工厂机制核心就是参数化的类,类T被当作类型参数传入uvm_object_registry类,这样将这个uvm_object_registry类注册进工厂,间接也就是将T注册进工厂,最后调用 create_object完成类型创建。
3.2白皮书对于factory机制的反思
factory 机制的核心就是一个联合数组,m_type_names。这个联合数组的索引是string 类型的,其存储的内容是 uvm_object_wrapper 类型的。想像一下,在 systemverilog 中,我们要往任何的数组或者 queue 中存放东西,存放的永远是值,而不可能是一个类型。我们只能说其中存放了某个类型的值,而不能说存放了一个类型。形象点说,假如我们定义好了一个 A 类,这个类的名字就是 A,我们要把这个类存放在联合数组中,那么应该存什么?第一,我们可以存放"A"这个字符串,但是这只是字符串,而不是类。第二,我们可以声明如下的一个数组:
A testarray[3];
这样,testarray 中就可以存放 A 类型的实例指针了,注意,存放的是 A 类型的 实例的指针,而不可能是 A这个类。类是一个抽象的概念,是不可能存放在一个数 组里面的!我们只有先把这个类实例化了,然后才能把实例的指针放入数组里面。基于这样的一种考虑,那么我们可以把一个 A 类的指针直接放入 m_type_names 中,干吗非要引入uvm_object_registry#(A,“A”)这样的一个类呢?
class A extends uvm_object;
`uvm_object_utils(A)
…
endclass
我们要把一个 A 实例的指针放入 m_type_names 数组中,其索引是字符串"A",create_object_by_name(“A”)时,可以直接到 m_type_names 中搜索"A",找出的是一个 A类型的实例,要想创建一个新的实例, 那么就 m_type_names[“A”].copy()可以了。这样就可以直接省去 uvm_object_registry 这个类的,节省了存储空间,而且变的非常简单了。 看上去确实是挺不错的主意,但是关键是,怎么样在 A定义的时候把 A 的一个 实例的指针放m_type_names 中呢?这是很难的事情,可以用静态成员变量来实 现。
class A extends uvm_object;
static A m_a = A:;get();
static function A get();
if (m_a == null) begin
uvm_factory f=uvm_factory::get();
m_a = new();
f.register(m_a);
end
return m_a;
endfunction
…
endclass
如上就可以实现我们要的功能。但是上述过程比较复杂,如果作为 UVM 的用 户,每从 uvm_object 类派生一个类的时候,都要这么做,那么我相信每个用户都会 很快放弃 UVM 的。UVM 的伟大之处就在于,把上述过程给标准化了,通过调用一 个uvm_object_utils 的宏来完成上述事情。通过使用 uvm_object_registry#(A,“A”)这样一个中间类,在这个类中做与上面类似的事情,即使用一些静态变量和静态函数, 通过静态变量的方法产生 uvm_object_registry#(A,“A”)的一个实例,把其加入到 factory 的 m_type_names 中去。仔细回味,我们才觉 uvm_object_registry 类的巧妙。
总结:其实注册功能我们可以手撸出来,无非就是声明一个静态的变量自己 staic A ma =A::get(); 然后把ma放到f.registry(ma)就行了,但是这样很麻烦,每次要我uvm_info(get_type_name(),"info",UVM_HIGH)
这样打印一个信息我都觉得麻烦,如果每次让我手搓这样的代码,我可能会疯掉。
4、override
这里提一个点,不仅工厂里面提供了override的功能,我们的component组件也提供了override功能。
不过component组件的override功能也是调用了工厂机制。
set_type_override_by_name();
set_type_override();
set_inst_override();
set_inst_override_by_name();
//组件在type_id 内部还定义了两种override方法
uvm_component_registry#(T,Tname)::set_type_override();
uvm_component_registry#(T,Tname)::set_inst_override();
工厂有如下四种override方法
uvm_factory::set_type_override_by_type();
uvm_factory::set_type_override_by_name();
uvm_factory::set_inst_override_by_name();
uvm_factory::set_inst_override_by_type();
4.1如何实现override
前面的component源代码和factory源代码存在大量的关联数组,override功能使用了大量的队列来实现
uvm_factory_override ,_type_overrides[$];
class uvm_facrory_override;
string full_inst_path;
string orig_type_name;
string ovrd_type_name;
bit selected;
uvm_object_wrapper orig_type;
uvm_object_wrapper ovrd_type;
endclass
略,等待后面补充 ,不是特别影响使用,创建之前先去override队列里面找一下有没有override,有的话就用override。核心就是这个点
5、总结
这篇文章在component源码剖析的基础上,对factory机制进行分析,经过对源码的解析,我们可以总结出来以下核心内容:
factory机制主要的两个内容:
1、根据名字创建实例,我们感受最深的就是,这个在component组件的run_tesr里面被用到了,我们给验证环境传递了一个test_name ,工厂机制就自动帮我们完成了test_case的创建。
2、override,想复用别人的代码,但是又不能去修改别人的代码,就使用override功能去实现组件的替换,注意哦不仅component类型可以override,object类型也可以override。
这可以用来回答介绍一下factory机制这个问题。
感谢耐心观看,下期再见!