factory机制即工厂机制,是uvm中最重要的机制之一。其存在的意义就是为了能够方便的替换环境中的实例或者已注册的类型。一般而言,在搭建完环境后,我们如果需要对环境进行更改配置或者相关的类信息,我们可以通过使用factory机制进行覆盖,达到替换的效果,从而大大提高环境的可重用性和灵活性。
factory机制的实现被集成在了各种宏中。
目录
一、概述
factory机制的主要步骤:
(1)注册
注册就是把component或者object登记在uvm内部的一张表当中,这个factory是整个全局仿真中存在且唯一存在的“机构”,所有被注册过的类才能使用factory机制。
component和object要使用不同的宏进行注册:
① component使用uvm_component_utils宏注册。
② object使用uvm_object_utils宏注册。
③ transaction一般使用uvm_object_utils宏结合field_automation机制注册。
(2)创建
创建即实例化对象,所有注册到factory的类均可通过factory独特的方式实例化对象。
例如在agent中的创建:
function void wr_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info(this.name,"build_phase active.",UVM_LOW)
if(is_active == UVM_ACTIVE)begin
sqr = wr_sequencer::type_id::create("sqr",this);
drv = wr_driver::type_id::create("drv",this);
end
mon = wr_monitor::type_id::create("mon",this);
endfunction
但factory的独特方式,实际上也是调用了new函数,也是先创建句柄再赋予对象。这种factory独特的例化方式,创建了组件的实例并返回句柄,但本质还是调用的new(name, parent) 函数。
(3)重载
重载就是覆盖、替换。对于验证的工作来说,重载可以提高整个环境的重用性,减少验证人员的工作量。
二、factory机制重载概述
(1)virtual类型的function / task
sv作为一种面向对象的语言,同样具有面向对象三大基本特性:封装、继承、多态。其中重载也是sv的一大特征,而重载的实现则离不开factory机制。当在父类中定义一个函数或者任务时,如果将其设置为virtual类型,那么就可以在子类中重载这个函数或者任务。
重载必须要在有继承关系之后才能用,也就是必须有派生关系。对于UVM中的factory机制,可实现factory机制中创建函数的重载,即将父类的创建函数重载成子类的创建函数。也就是说,重载之后,父类的句柄指向的是子类的实例。
uvm中的phase机制,就是对函数或者任务重载的最典型应用。
(2)重载实现前提
对于factory机制来说,重载的实现有自己的独特方式。在实例化时,uvm首先会通过factory机制在自己内部的一张表格中查看是否有相关的重载记录。set_type_override_by_type语句则相当于在factory机制的表格中加入了一条记录,当查到有重载记录时,会使用新的类型来替代旧的类型,最终创建出新的实例。但使用factory机制的重载是有前提的,并不是任意的类都可以互相重载。要想使用重载的功能则必须满足以下4个要求:
① 重载的类和被重载的类都要在定义时进行注册。
② 被重载的类在实例化时要使用factory机制式的实例化方式,不能使用new的方式。③ 重载的类与被重载的类必须有派生关系。也就是说,重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。且此派生关系不可颠倒。
④ component与object之间互相不能重载。他们之间实例化时的参数不同,无法重载。
(3)component源码中的重载种类
在component中,factory机制提供了不同种类的重载实现方式。
查看uvm源码:
① set_type_override_by_type
(uvm_component.svh)
// set_type_override_by_type (static)
// -------------------------
function void uvm_component::set_type_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
factory.set_type_override_by_type(original_type, override_type, replace);
endfunction
set_type_override_by_type函数有三个参数,第三个参数是replace,默认为1。在实际应用中一般只用前两个参数:第一个参数是被重载的类型,第二个参数是重载的类型。该函数会将环境中所有相关类型进行重载。
② set_inst_override_by_type
(uvm_component.svh)
有时候可能并不希望把验证平台中的所有类型全部进行替换,而只是替换其中的某一部分,这种情况就要用到set_inst_override_by_type函数。
// set_inst_override_by_type
// -------------------------
function void uvm_component::set_inst_override_by_type (string relative_inst_path,
uvm_object_wrapper original_type,
uvm_object_wrapper override_type);
string full_inst_path;
if (relative_inst_path == "")
full_inst_path = get_full_name();
else
full_inst_path = {get_full_name(), ".", relative_inst_path};
factory.set_inst_override_by_type(original_type, override_type, full_inst_path);
endfunction
第一个参数是相对路径,第二个参数是被重载的类型,第三个参数是要重载的类型。使用第一个参数就可以指定某一个特定的部件进行重载。
③ set_type_override
(uvm_component.svh)
set_type_override_by_type和set_inst_override_by_type的参数都是一个uvm_object_wrapper型的类型参数,这种参数通过get_type()的形式获得。除此之外,uvm还提供了另外一种简单的方法来替换这种写法:使用字符串。
与set_type_override_by_type相对应的是set_type_override:
// set_type_override (static)
// -----------------
function void uvm_component::set_type_override (string original_type_name,
string override_type_name,
bit replace=1);
factory.set_type_override_by_name(original_type_name,
override_type_name, replace);
endfunction
三个参数是相同的。可以发现set_type_override其实调用了set_type_override_by_name,也就是使用的名称字符串,所有的操作都是基于名称。
④ set_inst_override
(uvm_component.svh)
与set_inst_override_by_type相对的是set_inst_override:
// set_inst_override
// -----------------
function void uvm_component::set_inst_override (string relative_inst_path,
string original_type_name,
string override_type_name);
string full_inst_path;
if (relative_inst_path == "")
full_inst_path = get_full_name();
else
full_inst_path = {get_full_name(), ".", relative_inst_path};
factory.set_inst_override_by_name(
original_type_name,
override_type_name,
full_inst_path);
endfunction
所调用的也是set_inst_override_by_name函数。
(4)factory源码中的重载种类
如果在一个无法使用component的位置,例如在top_tb的initial语句里,那么这个时候就无法使用uvm_component的函数。uvm提供了另外的函数来解决这个问题,这些函数都来自uvm_factory类。其实阅读源码可以发现,uvm_component中的函数所调用的也都是来自uvm_factory中的函数。不过要注意参数的顺序,是不同的。
uvm_component | uvm_factory | 参数 |
set_type_override_by_type | set_type_override_by_type | replace参数 |
set_inst_override_by_type | set_inst_override_by_type | 路径参数 |
set_type_override | set_type_override_by_name | replace参数 |
set_inst_override | set_inst_override_by_name | 路径参数 |
uvm_factory中的四个函数:
extern function
void set_type_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
extern function
void set_inst_override_by_type (uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
string full_inst_path);
extern function
void set_type_override_by_name (string original_type_name,
string override_type_name,
bit replace=1);
extern function
void set_inst_override_by_name (string original_type_name,
string override_type_name,
string full_inst_path);
存在一个uvm_factory类型的全局变量factory,使用时可以直接调用。当然,在一个component内也完全可以直接调用factory机制的重载函数。
三、factory机制重载的实现
(1)transaction重载
例如需要构建一个新的异常测试用例,则可以使用transaction的重载,创建新的数据包,而不需要改动sequence。
以异步FIFO为例,重新约束写入的数据:
class wr_transaction_new #(parameter DSIZE = 8) extends wr_transaction;
`uvm_object_utils(wr_transaction_new)
constraint c_wdata_new{
wdata == 8'h88;
}
endclass:wr_transaction_new
应用重载:
class tc_new_tr extends tc_sanity;
`uvm_component_utils(tc_new_tr)
string name;
function new(string name = "",
uvm_component parent = null);
super.new(name,parent);
this.name = name;
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass:tc_new_tr
function void tc_new_tr::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info(this.name,"build_phase active.",UVM_LOW)
factory.set_type_override_by_type(wr_transaction::get_type(), wr_transaction_new::get_type());
endfunction
function void tc_new_tr::connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info(this.name,"connect_phase active.",UVM_LOW)
uvm_config_db#(uvm_object_wrapper)::set(
uvm_root::get(),
$sformatf("%s.main_phase",this.fifo_env.wr_agt.sqr.get_full_name()),
"default_sequence",
wr_sequence::type_id::get()
);
uvm_config_db#(uvm_object_wrapper)::set(
uvm_root::get(),
$sformatf("%s.main_phase",this.fifo_env.rd_agt.sqr.get_full_name()),
"default_sequence",
rd_sequence::type_id::get()
);
check_config_usage();
print_config(1);
endfunction
(2)sequence的重载
重新约束当然也可以在sequence中完成。不过,sequence其实可以直接完成对transaction的重新约束从而创建新条件。对于重载的情况,就是需要重新启动一个继承之后的新sequence。
当然像嵌套的sequence或者使用virtual_sequence进行调度的情况,也可以针对其中某一个sequence进行重载,是否需要启动新的sequence视情况而定。重载的操作是类似的,不再赘述。
(3)component的重载
如果重载的场景比较复杂,可以直接重载component。一般为了方便,可以直接在新的testcase中对component进行重载。以异步FIFO为例,创建一个新的testcase继承自tc_sanity:
class tc_new extends tc_sanity;
`uvm_component_utils(tc_new)
string name;
function new(string name = "",
uvm_component parent = null);
super.new(name,parent);
this.name = name;
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass:tc_new
function void tc_new::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info(this.name,"build_phase active.",UVM_LOW)
factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type());
endfunction
function void tc_new::connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info(this.name,"connect_phase active.",UVM_LOW)
uvm_config_db#(uvm_object_wrapper)::set(
uvm_root::get(),
$sformatf("%s.main_phase",this.fifo_env.wr_agt.sqr.get_full_name()),
"default_sequence",
wr_sequence::type_id::get()
);
uvm_config_db#(uvm_object_wrapper)::set(
uvm_root::get(),
$sformatf("%s.main_phase",this.fifo_env.rd_agt.sqr.get_full_name()),
"default_sequence",
rd_sequence::type_id::get()
);
check_config_usage();
print_config(1);
endfunction
新创建一个driver继承自wr_driver,所有写入的数据都规定为'h66
class new_driver#(parameter DSIZE = 8) extends wr_driver;
`uvm_component_utils(new_driver)
string name;
function new(string name = "",
uvm_component parent = null);
super.new(name,parent);
this.name = name;
endfunction
extern virtual task main_phase(uvm_phase phase);
extern virtual task drive_tr(input wr_transaction req);
endclass:new_driver
task new_driver::main_phase(uvm_phase phase);
int cnt;
super.main_phase(phase);
repeat(plus::plus_wait_time)@this.wr_drv_if.drv_cb;
fork
while(1)begin
cnt++;
this.seq_item_port.get_next_item(req);
if(req.send_id == 1)begin
@this.wr_drv_if.drv_cb;
end
drive_tr(req);
`uvm_info(this.name,$sformatf("No.%0d driving.",cnt),UVM_LOW)
rsp = new();
rsp.set_id_info(req);
seq_item_port.put_response(rsp);
// driver向sequence返回response
this.seq_item_port.item_done();
req.print();
end
begin
@(negedge this.wr_drv_if.wrst_n);
phase.jump(uvm_reset_phase::get());
`uvm_info(this.name,"2nd reset activated.",UVM_LOW)
end
join
endtask
task new_driver::drive_tr(input wr_transaction req);
this.wr_drv_if.drv_cb.winc <= 1;
this.wr_drv_if.drv_cb.wdata <= 8'h66;
@this.wr_drv_if.drv_cb;
while(this.wr_drv_if.drv_cb.wfull == 1)begin
@this.wr_drv_if.drv_cb;
end
this.wr_drv_if.drv_cb.winc <= 0;
repeat(req.wr_interval)@this.wr_drv_if.drv_cb;
endtask
新的driver并不需要实例化,直接进行重载即可。当然,重载的方法也不止一种,这里只展示了统一重载。
把所有的测试用例都使用driver重载实现,这种方法是可行的,但是不推荐。这样做等于放弃了sequence机制,虽然也可以实现想要的场景,但是违背了uvm最初的设计初衷,其稳定性和可靠性都是未知数,容易造成一些不可控的情况。
四、复杂重载
uvm也支持连续的重载,例如父类派生出子类,子类再派生出一个新的子类,这种线性关系的多次重载之后最终以最新的子类为准。
除了这种连续的重载外,还有一种是替换式的重载。例如树形结构中具有兄弟关系的组件,他们都拥有相同的父类,此时的重载就是替换式的重载。
仍旧拿异步FIFO举例,wr_driver派生出两个子driver,他们是兄弟关系。
new_driver | new_driver_bro |
wdata <= 8'h66; | wdata <= 8'h99; |
进行替换重载:
factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type() ,1 );
factory.set_type_override_by_type(wr_driver::get_type(), new_driver_bro::get_type(),1 );
更改第三个replace的参数可以控制重载的生效。
如果为1则是立即生效;如果为0则不会立即生效。
factory机制能够查询到两条相关的记录。如果为0,它并不会在看完第一条记录后即直接创建一个新的实例,而是等到最终看完第二条记录后才会创建实例。
如果哥哥的参数为1,则无论弟弟的参数是多少,最终结果会以哥哥为准,即立即生效,驱动值为'h66。
如果哥哥的参数为0,即不会立即生效,则无论弟弟的参数是多少,最终结果会以弟弟为准,因为弟弟的重载更加靠后,驱动值为'h99。
当然,这是只有两兄弟的情况。如果有多个兄弟,则需要相应控制replace参数,完成替换重载。
相应地,如果重载的情况改为:
factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type() ,0 );
factory.set_type_override_by_type(new_driver::get_type(), new_driver_bro::get_type(),0 );
这种情况也是可行的。弟弟重载了哥哥,虽然弟弟与哥哥并没有派生关系,但也是可以的。可以总结为:
在有多个重载时,最终重载的类要与最初被重载的类有派生关系。
最终重载的类必须派生自最初被重载的类,最初被重载的类必须是最终重载类的父类。
简单来说就是兄弟关系之间可以进行重载,但前提是必须都派生自相同的父类。
五、factory机制调试
factory机制同样也可以进行调制。uvm提供了print_override_info函数,来输出所有的重载信息。可以选择在connect_phase中进行调用:
this.fifo_env.wr_agt.drv.print_override_info("wr_driver");
可以打印出wr_driver相关的所有重载。结果为:
#### Factory Override Information (*)
Given a request for an object of type 'wr_driver' with an instance
path of 'uvm_test_top.fifo_env.wr_agt.drv', the factory encountered
the following relevant overrides. An 'x' next to a match indicates a
match that was ignored.
Original Type Instance Path Override Type
------------- ------------- --------------
wr_driver * new_driver <type override>
new_driver * new_driver_bro <type override>
Result:
The factory will produce an object of type 'new_driver_bro'
(*) Types with no associated type name will be printed as <unknown>
####
这里的路径是*,因为所用的重载类型是set_type_override_by_type,没有给出路径参数。改为set_inst_override_by_type进行重载:
set_inst_override_by_type("fifo_env.wr_agt.drv",wr_driver::get_type(), new_driver::get_type());
set_inst_override_by_type("fifo_env.wr_agt.drv",new_driver::get_type(), new_driver_bro::get_type());
结果为:
#### Factory Override Information (*)
Given a request for an object of type 'wr_driver' with an instance
path of 'uvm_test_top.fifo_env.wr_agt.drv', the factory encountered
the following relevant overrides. An 'x' next to a match indicates a
match that was ignored.
Original Type Instance Path Override Type
------------- -------------------------------- --------------
wr_driver uvm_test_top.fifo_env.wr_agt.drv new_driver
new_driver uvm_test_top.fifo_env.wr_agt.drv new_driver_bro
Result:
The factory will produce an object of type 'new_driver_bro'
(*) Types with no associated type name will be printed as <unknown>
####
此时会显示出路径。
除了这个函数外,uvm_factory还有debug_create_by_type函数,其输出与使用print_override_info相同。
除了上述两个函数外,uvm_factory还提供print函数。print函数只有一个参数,其取值可为0、1或2。
为0时,仅仅打印被重载的实例和类型。
为1时,打印参数为0时的信息,以及所有用户创建的、注册到factory的类的名称。
为2时,打印参数为1时的信息,以及系统创建的、所有注册到factory的类的名称(例如uvm_reg_item)
调用print函数:
function void tc_new_bro::connect_phase(uvm_phase phase);
super.connect_phase(phase);
`uvm_info(this.name,"connect_phase active.",UVM_LOW)
this.fifo_env.wr_agt.drv.print_override_info("wr_driver");
factory.print(2);
...
打印结果为:
#### Factory Configuration (*)
Instance Overrides:
Requested Type Override Path Override Type
-------------- -------------------------------- --------------
new_driver uvm_test_top.fifo_env.wr_agt.drv new_driver_bro
wr_driver uvm_test_top.fifo_env.wr_agt.drv new_driver
No type overrides are registered with this factory
All types registered with the factory: 68 total
(types without type names will not be printed)
Type Name
---------
env
new_driver
new_driver_bro
rd_agent
rd_alternate_sequence
rd_driver
rd_monitor
rd_sequence
rd_sequencer
rd_transaction
scoreboard
snps_uvm_reg_bank_group
snps_uvm_reg_map
tc_new
tc_new_bro
tc_new_tr
tc_sanity
tc_virtual
tc_wr_alternate
uvm_exhaustive_sequence
uvm_mem_access_seq
uvm_mem_shared_access_seq
uvm_mem_single_access_seq
uvm_mem_single_walk_seq
uvm_mem_walk_seq
uvm_objection
uvm_random_sequence
uvm_recorder
uvm_reg_access_seq
uvm_reg_backdoor
uvm_reg_bit_bash_seq
uvm_reg_field
uvm_reg_hw_reset_seq
uvm_reg_item
uvm_reg_map
uvm_reg_mem_access_seq
uvm_reg_mem_built_in_seq
uvm_reg_mem_hdl_paths_seq
uvm_reg_mem_shared_access_seq
uvm_reg_read_only_cbs
uvm_reg_shared_access_seq
uvm_reg_single_access_seq
uvm_reg_single_bit_bash_seq
uvm_reg_tlm_adapter
uvm_reg_write_only_cbs
uvm_sequence_item
uvm_sequence_library_cfg
uvm_simple_sequence
uvm_test_done
uvm_tlm_generic_payload
uvm_vcs_recorder
uvm_vreg_field
virtual_sequence
virtual_sequencer
wr_agent
wr_alternate_sequence
wr_driver
wr_monitor
wr_sequence
wr_sequencer
wr_transaction
wr_transaction_new
(*) Types with no associated type name will be printed as <unknown>
####
除此之外,还有print_topology()函数,可以显示出整棵UVM树的拓扑结构。uvm树在build_phase执行完成后才完全建立完成,因此该函数应该在build_phase之后调用。
uvm_top.print_topology();
显示出拓扑结构:
UVM_INFO @ 33626.00ns: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------------
uvm_test_top tc_new_bro - @477
fifo_env env - @485
rd_agt rd_agent - @501
drv rd_driver - @627
rsp_port uvm_analysis_port - @644
seq_item_port uvm_seq_item_pull_port - @635
mon rd_monitor - @776
ap uvm_analysis_port - @786
sqr rd_sequencer - @653
rsp_export uvm_analysis_export - @661
seq_item_export uvm_seq_item_pull_imp - @767
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
rdmon_scb_fifo uvm_tlm_analysis_fifo #(T) - @570
analysis_export uvm_analysis_imp - @614
get_ap uvm_analysis_port - @605
get_peek_export uvm_get_peek_imp - @587
put_ap uvm_analysis_port - @596
put_export uvm_put_imp - @578
scb scoreboard - @509
rd_port uvm_blocking_get_port - @809
wr_port uvm_blocking_get_port - @800
wr_agt wr_agent - @493
drv new_driver_bro - @945
rsp_port uvm_analysis_port - @962
seq_item_port uvm_seq_item_pull_port - @953
test_sig integral 32 'h8
mon wr_monitor - @971
ap uvm_analysis_port - @983
sqr wr_sequencer - @822
rsp_export uvm_analysis_export - @830
seq_item_export uvm_seq_item_pull_imp - @936
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
wrmon_scb_fifo uvm_tlm_analysis_fifo #(T) - @517
analysis_export uvm_analysis_imp - @561
get_ap uvm_analysis_port - @552
get_peek_export uvm_get_peek_imp - @534
put_ap uvm_analysis_port - @543
put_export uvm_put_imp - @525
------------------------------------------------------------------
可以很直观地观察整个树形结构。
六、创建实例的接口
factory机制同样提供了一系列接口来创建实例。
(1)create_object_by_name,根据类名字创建一个object,一般只传第一个参数:
wr_transaction tr;
void'($cast(tr, factory.create_object_by_name("wr_transaction")));
(2)create_object_by_type,根据类型创建一个object,一般只传第一个参数:
wr_transaction tr;
void'($cast(tr, factory.create_object_by_type(wr_transaction::get_type())));
(3)create_component_by_name,根据类名创建一个component。有四个参数,第一个参数是字符串类型的类名,第二个参数是父结点的全名,第三个参数是为这个新的component起的名字,
第四个参数是父结点的指针。在调用这个函数时,这四个参数都要使用:
wr_driver wr_drv;
void' ($cast(wr_drv, factory.create_component_by_name("wr_transaction", get_full
_name(), "wr_drv", this)));
这个函数一般只在一个component的new函数或者build_phase中使用。
如果是在一个object中被调用,则很难确认parent参数;如果是在connect_phase之后调用,由于uvm要求component在build_phase及之前需要实例化完毕,所以会调用失败。
总结
本质上来看,factory机制其实是对SystemVerilog中new函数的重载。这个原始的new函数实在是太简单,功能也太少。经过factory机制的改良之后,进行实例化的方法多了很多。这也体现了uvm编写的一个原则,一个好的库应该提供更多方便实用的接口,这种接口一方面是库自己写出来并开放给用户的,另外一方面就是改良语言原始的接口,使得更加方便用户的使用。同时factory机制的强大之处在于我们可以更加灵活地控制整个环境,多种重载功能又可以提高整个环境的重用性,使得我们有更多种方法可以去构建不同的测试场景。