前言
作者在学习了一段时间的UVM factory源码之后写下此文,旨在记录自己的学习成果,毕竟好记性不如烂笔头嘛,当然如果能帮助到对这部分有疑惑的同仁就更好了。作者是在笔记本电脑上的windows环境下使用source insight软件分析UVM 源码的。感兴趣的同仁可以试试用source insight分析源码,个人认为还是比在纯编辑器(比如vim)方便很多。source insight支持systemverilog的配置文件可以通过 sourceinsightsystemverilog语言CLF配置文件_SystemVerilogCLF-其它文档类资源-优快云下载 下载。接下来,本文将尝试循序渐进地介绍UVM factory的实现。
1 什么是工厂?
工厂是软件工程中的一种常见的设计模式,工厂模式(factory pattern)。其核心理念是创建不同但相似的类型时,使用统一的工厂类接口。工厂模式的好处是,客户创建对象时无需知道内部细节,只需要传入参数即可。提供创建对象接口的就是工厂,创建的对象就是产品。
比如,有一家汽车公司叫Benz,它可以生产c200,e300,s400等不同类型的汽车。在没有工厂模式的情况下,创建对象的方法就是:benz_c200.new(), benz_e300.new(), benz_s400.new(). 使用工厂模式时,创建对象的接口就是:Benz_factory.creat_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();
$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 benz_c200.new();
else if ("e300" == type_name) return benz_e300.new();
else if ("s400" == type_name) return benz_s400.new();
else return null;
endfunction
endclass
上述这种实现方式的可扩展性实在太差,当产品类型变多之后,那段if else的代码长度可以绕办公室两圈。
3 改进的工厂
进一步地思考,为了便于扩展,我们必须建立一种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_type_names.exists(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_type_names.exists(type_name)) begin
benz_car_wrapper wrapper = m_type_names[type_namen];
return wrapper.create_car();
end else
return null;
endfunction
protected static benz_car_wrapper m_type_names[string];
endclass
其实,UVM的factory机制的核心就是类似于上面的方式,只是UVM巧妙的使用了一些宏来实现,而且加上了override的功能,所以看起来要比这复杂很多。但其本质就是用上面的理念和方法去实现的。
5 言归正传,UVM factory机制的源码解析
有了前文生动形象的例子,我们再来看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还是s400...
我们来看看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 (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") begin
if (m_type_names.exists(obj.get_type_name()))
uvm_report_warning ...
else
m_type_names[obj.get_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_type_names.exists(requested_type_name)) begin
uvm_report_warning ...
return null;
end
wrapper = m_type_names[requested_type_name];
end
return wrapper.create_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函数。