UVM源码分析之factory机制详解
前⾔
作者在学习了⼀段时间的UVM factory源码之后写下此⽂,旨在记录⾃⼰的学习成果,毕竟好记性不如烂笔头嘛,当然如果能帮助到对这部分有疑惑的同仁就更好了。作者是在笔记本电脑上的windows环境下使⽤source insight软件分析UVM 源码的。感兴趣的同仁可以试试⽤source insight分析源码,个⼈认为还是⽐在纯编辑器(⽐如vim)⽅便很多。source insight⽀持systemverilog的配置⽂件可以通过  下载。接下来,本⽂将尝试循序渐进地介绍UVM factory的实现
1 什么是⼯⼚?
⼯⼚是软件⼯程中的⼀种常见的设计模式,⼯⼚模式(factory pattern)。其核⼼理念是创建不同但相似的类型时,使⽤统⼀的⼯⼚类接⼝。⼯⼚模式的好处是,客户创建对象时⽆需知道内部细节,只需要传⼊参数即可。提供创建对象接⼝的就是⼯⼚,创建的对象就是产品
⽐如,有⼀家汽车公司叫Benz,它可以⽣产c200,e300,s400等不同类型的汽车。在没有⼯⼚模式的情况下,创建对象的⽅法就是:w(), w(), w(). 使⽤⼯⼚模式时,创建对象的接⼝就是:at_car(string type_name).
2 ⼯⼚最简单的实现⽅式
⽐如上⾯那个奔驰汽车的例⼦,如果有⼈要你实现这个⼯⼚系统,你会怎么实现呢?这3种汽车类型,他们具有很多相似的属性,因此给他们搞个基类是理所当然的。那么在⼯⼚类中如何根据不同的汽车类型⽣产不同的产品呢?最简单的就是if,else if 判断type_name, 针对不同的type_name创建不同的对象。如下:
class benz_car;
endclass
class benz_c200 extends benz_car;
function new();
$display("I am benz c200.\n");
endfunction
endclass
class benz_e300 extends benz_car;
function new();
$display("I am benz e300.\n");
endfunction
endclass
class benz_s400 extends benz_car;
function new();
12308网上订票$display("I am benz s400.\n");
endfunction
endclass
class benz_factory;
static function benz_car create_car(string type_name);
if ("c200" == type_name) return w();
else if ("e300" == type_name) return w();
else if ("s400" == type_name) return w();海马丘比特论坛
else return null;
endfunction
endclass
上述这种实现⽅式的可扩展性实在太差,当产品类型变多之后,那段if else的代码长度可以绕办公室两圈。
3 改进的⼯⼚
奥德赛2020款进⼀步地思考,为了便于扩展,我们必须建⽴⼀种type_name与类型的联系。那么,我们能否在factory中设置⼀个⽤string类型索引的关联数组呢?关联数组就存放产品类型的对象。在⽤户实现⼀种
新的产品后,就把产品名字和类型注册到factory中。试试看:
class benz_factory;
// 注册产品的接⼝
static function void registry(benz_car obj, string type_name);
m_type_names[type_name] = obj;
endfunction
// 创建产品的接⼝
static function benz_car create_car(string type_name);
if (m_ists(type_name))
return m_type_names[type_namen];
else
return null;
endfunction
protected static benz_car  m_type_names[string];
endclass
这种实现⽅式看起来要好很多,最起码的新增产品类型时不⽤再写⼀串else if。我们再来看看⽤户如何注册⾃⼰的产品?
benz_c200 obj = new();
benz_factory::registry(obj, "benz_c200");
在注册之前,先要通过new的⽅式创建⼀个对象,这有点多此⼀举,⽤户都已经通过new的⽅式创建对象了,那还要⼯⼚⼲嘛?况且对于这种实现⽅式,⽤户想要创建多个同⼀产品时,得到的是多个指向同⼀对象的handle, 这违背了初衷。因此,这种⽅式也不是我们想要的实现⽅式。
4 进⼀步改进
我们在factory中不能直接存储产品类型的对象,可以存储"产品加⼯器",这个产品加⼯器可以作为产品类型的⼀个静态成员,⽽且这个加⼯器必须具有create car的功能。⽤户在实现了⼀种产品后,可以将产品的加⼯器注册到factory⾥⾯。factory在创建特定产品时,可以通过字符串索引,先到对应的加⼯器,然后调⽤加⼯器的create_car函数,完美。如下:
// 奔驰加⼯器的基类
virtual class benz_car_wrapper;
pure virtual function benz_car create_car();
return null;
endfunction
endcalss
// 特定车型的加⼯器
class benz_c200_wrapper extends benz_car_wrapper;
function benz_car create_car();
benz_c200 obj;
obj = new(); // 这⾥new的是benz_c200类型的对象
return obj;
endfunction
endclass
// 产品类型benz_c200的实现
class benz_c200 extends benz_car;
function new();
$display("I am benz c200.\n");
endfunction
local static benz_c200_wrapper my_wrapper = get_wrapper();
static function benz_c200_wrapper get_wrapper();
if (my_wrapper == null) begin
my_wrapper = new();
benz_factory::registry(my_wrapper); // 将c200加⼯器注册到⼯⼚
end
return my_wrapper
endfunction
车震专区
endclass
class benz_factory;
// 注册产品的接⼝
static function void registry(benz_car_wrapper obj, string type_name);
m_type_names[type_name] = obj;
endfunction
// 创建产品的接⼝
static function benz_car create_car(string type_name);
if (m_ists(type_name)) begin
benz_car_wrapper wrapper = m_type_names[type_namen];
ate_car();
end else
return null;
endfunction
protected static benz_car_wrapper  m_type_names[string];
endclass
宝马事件
其实,UVM的factory机制的核⼼就是类似于上⾯的⽅式,只是UVM巧妙的使⽤了⼀些宏来实现,⽽且加上了override的功能,所以看起来要⽐这复杂很多。但其本质就是⽤上⾯的理念和⽅法去实现的。
5 ⾔归正传,UVM factory机制的源码解析
奥迪a5敞篷价格有了前⽂⽣动形象的例⼦,我们再来看UVM的源码可能就要轻松⼀些了……
熟悉UVM的读者⼀定知道,我们在实现⼀个class的时候,如果想使⽤⼯⼚机制,就必须要⽤UVM提供的宏对你实现的类进⾏注册。对于uvm_object家族的,我们要使⽤uvm_object_utils进⾏注册,对于uvm_component家族的,我们要使⽤uvm_component_utils进⾏注册。接下来,我们选择uvm_object_utils进⾏解析,看看这个宏到底⼲了啥?通过前⽂奔驰的例⼦,可以⼤胆猜测⼀下,这个宏可能包含加⼯器类的实现,以及将加⼯器注册到factory的内容。事实也的确是这样的…...
uvm_object_utils的实现在uvm_object_defines.svh中,⽤source insight应该很容易看这部分代码。如上图,勤学好思的作者已经将这个宏的内容通过⼀个简洁明了的图展现出来了,⽅便读者了解这个宏的内容。这个⼤宏⼀共由4个⼩宏组成,这⾥我们先重点关注第⼀个uvm_object_registry(T,S),看名字就知道它是⽤来注册产品的。在uvm_object_registry(T,S)中,typedef了⼀个type_id类型,type_id 类型实质上是⼀个模板类uvm_object_registry#(T,S),它的功能和地位就有点类似于前⽂奔驰例⼦中的benz_c200_wrapper, 只是这⾥UVM⽤模板类来实现了,⽤模板参数T来区分到底是c200,e300还是
我们来看看uvm_object_registry(T,S)这个模板类的实现,对此作者⼜画了个简图,⽅便⼤家理解。源
码在⽂件uvm_registry.svh中。(源码太长,况且跳来跳去不⽅便展⽰,简图中在不影响源码灵魂的情况下进⾏了精简)
请再次回忆奔驰车的例⼦,如上,这个模板类的实现与benz_c200_wrapper的实现基本是⼀致的,就是加上了模板参数⽽已。该模板类继承⾃uvm_object_wrapper,这⾥也可以⼤胆猜测下,factory中可能就有个string类型为索引,存储类型为uvm_object_wrapper的关联数组,⽤来记录各个产品类型的
加⼯器(也就是不同类型的wrapper)。⼤家注意下这个static成员变量me,它的初始化是通过调⽤static函数get()来实现的,也就是说,当我们使⽤这个模板定义⼀个新的wrapper的时候会⾃动调⽤这个get函数。⽽在这个get函数中,me作为具体产品的加⼯器被注册到了factory中. 下⾯是uvm_object_wrapper的实现,uvm_object_wrapper是⼀个抽象类,定义了⼀些虚函数,源码在uvm_factory.svh中,此处只展⽰纲要:
接下来,我们要看看uvm_factory⾥做了些什么,其实,⼤家去翻翻源码就知道,uvm_factory⾥基本啥都没做,因为它是⼀个抽象类,只定义了⼀些虚函数,让它的⼦类去真正实现内容。起作⽤的是uvm_factory的⼦类,uvm_default_factory。我们只关注两个核⼼点,⼀个是类型注册,⼀个是创建对象,⾄于override部分下期再详解。
// 代码⽚段摘⾃uvm_factory.svh,进⾏了⼤量精简,删去了override功能相关代码
protected uvm_object_wrapper  m_type_names[string];
function void uvm_default_factory::register(uvm_object_wrapper obj);
if (obj == null) begin
uvm_report_fatal ...
end
if (_type_name() != "" && _type_name() != "<unknown>") begin
if (m__type_name()))
uvm_report_warning ...
else
m_type__type_name()] = obj; // 这样是注册的重点
end
endfunction
function uvm_object uvm_default_factory::create_object_by_name (string requested_type_name,  string parent_inst_path="",  string name="");
uvm_object_wrapper wrapper;
// if no override exists, try to use requested_type_name directly
if (wrapper==null) begin
if(!m_ists(requested_type_name)) begin
uvm_report_warning ...
return null;
end
wrapper = m_type_names[requested_type_name];
end
ate_object(name);
endfunction
如上所⽰,vum_default_factory中有⼀个索引为string的关联数组m_type_names,关联数组存储类型为
uvm_object_wrapper,uvm_object_wrapper为uvm_object_registry(T,S)的基类。uvm_default_factory::register()的实现就是将wrapper放到了这个关联数组中,uvm_default_factory::create_object_by_name()的实现就是先从关联数组中取出wrapper,然后调⽤wrapper的create_object函数。