一、factory机制
1.factory机制的作用
为了更方便地替代验证环境中的实例或者已注册的类型,同时带来配置的灵活性。
2.使用factory机制进行重载
原理
在实例化时,UVM会通过factory机制在自己内部的一张表格中查看是否有相关的重载记录。当查到有重载记录时,会使用新的类型来替代旧的类型。
前提条件
- 原类型和新类型在定义时必须要注册到factory中。
- 类在实例化时,要使用factory机制的实例化方式,而不能使用new()。
- 原类型要与新类型要有派生关系,原类型必须是新类型的父类(component和object之间不能相互覆盖),并且被覆盖的方法要声明virtual。
步骤
- 注册:使用 uvm_component_utils() 或 uvm_object_utils() 注册。
- 创建:使用factory机制的实例化方式创建 xxx::type_id::create() 。
- 重载
重载方式
- set_type_override_by_type:属于uvm_factory中的函数,component类和object类都能用。
- 第一个参数是被重载的类型,第二个参数是重载的类型,需要使用 xx::get_type() 获取类型。
set_type_override_by_type(trans::get_type(), bad_trans::get_type());
- set_type_override:属于uvm_component中的函数,只有component类能用。
- 第一个参数是被重载的类型,第二个参数是重载的类型,只需写类名。
set_type_override("trans", "bad_trans");
3.常用的重载
重载transaction
采用重载transaction的方法构建异常的测试用例。
- 假设有如下的正常sequence,此sequence被作为某个测试用例的default_sequence:
class normal_sequence extends uvm_sequence #(my_transaction);
...
virtual task body();
repeat (10) begin
`uvm_do(m_trans)
end
#100;
endtask
`uvm_object_utils(normal_sequence)
endclass
- 从正常的transaction派生一个新的transaction,构建一个新的异常测试用例,测试CRC错误的情况:
class my_transaction extends uvm_sequence_item;
...
constraint crc_err_cons{
crc_err == 1'b0;
}
constraint sfd_err_cons{
sfd_err == 1'b0;
}
constraint pre_err_cons{
pre_err == 1'b0;
}
...
endclass
class crc_err_tr extends my_transaction;
...
constraint crc_err_cons{
crc_err == 1;
}
endclass
- 仍使用normal_sequence作为新的测试用例的default_sequence,只是需要将my_transaction使用crc_err_tr重载:
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
set_type_override_by_type(my_transaction::get_type(), crc_err_tr::get_type());
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
normal_sequence::type_id::get());
endfunction
重载sequence
采用重载sequence的方法构建异常的测试用例。
- 当已经定义了如下的两个sequence时:这里使用了嵌套的sequence,case_sequence被作为default_sequence:
class normal_sequence extends uvm_sequence #(my_transaction);
...
virtual task body();
`uvm_do(m_trans)
m_trans.print();
endtask
`uvm_object_utils(normal_sequence)
endclass
class case_sequence extends uvm_sequence #(my_transaction);
...
virtual task body();
normal_sequence nseq;
repeat(10) begin
`uvm_do(nseq)
end
endtask
endclass
- 如果要新建异常测试用例,依然可以将case_sequence作为default_sequence,只需要从normal_sequence派生一个异常的sequence:
class abnormal_sequence extends normal_sequence;
...
virtual task body();
m_trans = new("m_trans");
m_trans.crc_err_cons.constraint_mode(0);
`uvm_rand_send_with(m_trans, {crc_err == 1;})
m_trans.print();
endtask
endclass
- 并且在build_phase中将normal_sequence使用abnormal_sequence重载掉:
function void my_case0::build_phase(uvm_phase phase);
...
set_type_override_by_type(normal_sequence::get_type(), abnormal_sequence::get_type());
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
case_sequence::type_id::get());
endfunction
重载component
采用重载component的方法构建异常的测试用例,通过重载driver的方式实现。
- 假设测试用例使用上述的normal_sequence作为其default_sequence。这是一个只产生正常transaction的sequence,使用它构造的测试用例是一个正常的用例。
- 产生CRC错误的测试用例,可以依然使用这个sequence作为default_sequence,只是需要定义如下的driver:
class crc_driver extends my_driver;
...
virtual function void inject_crc_err(my_transaction tr);
tr.crc = $urandom_range(10000000, 0);
endfunction
virtual task main_phase(uvm_phase phase);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
inject_crc_err(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
endclass
- 并在build phase中将my_driver使用crc_driver重载:
function void my_case0::build_phase(uvm_phase phase);
...
set_type_override_by_type(my_driver::get_type(), crc_driver::get_type());
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
normal_sequence::type_id::get());
endfunction
二、field automation机制
通过域的自动化(field automation),在注册UVM类的同时可以使用宏声明今后会参与到对象拷贝、克隆、打印等操作的成员变量。
tips:在工厂注册时,进行域的自动化声明。
1.常用的uvm_field宏
ARG表示成员变量;FLAG表示标志位,可选择数据操作方式,UVM_ALL_ON表示打开所有数据操作方式,可使用按位或关闭某些操作方式而保留其他,UVM_NOCOPY | UVM_NOCOMPARE。
`uvm_object_utils_begin(trans)
`uvm_field_int(ARG, FLAG)//整形
`uvm_field_enum(T, ARG, FLAG) //枚举
`uvm_field_string(ARG, FLAG)//字符串
`uvm_field_array_int(ARG,FLAG)//动态数组
`uvm_object_utils_end
2.常用的field automation函数
- copy:用于实例的复制,在使用copy函数前,目标实例必须已经完成了实例化创建。
注:在uvm_object中有clone函数,无需实现实例化,clone = new + copy。 - compare:用于比较两实例是否相同,相同返回1,否则返回0。
注:比较器uvm_package::uvm_default_comparer最大输出的错误比较信息默认是1,即发生了一个错误比较之后,就不再进行后续比较。可以通过修改全局的uvm_comparer对象的show_max变量在出现一个错误后继续比较:uvm_default_comparer.show_max = 10。 - print:打印所有的字段。
- pack_bytes:将所有的字段打包成byte流。
- unpack_bytes:将一个byte流逐一恢复到某个类的实例中。
- pack:将所有的字段打包成bit流。
- unpack:将一个bit流逐一恢复到某个类的实例中。
- pack_ints:将所有的字段打包成int(4个byte,或者dword)流。
- unpack_ints:将一个int流逐一恢复到某个类的实例中。
三、config机制
1.config机制的作用
- config机制用于在UVM验证平台间传递参数,通常都是成对出现的,set函数是寄信,get函数是收信。
2.set()与get()函数的参数
- 在某个测试用例test的build_phase中可以使用以下方式寄信:
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100);
其中,第一个和第二个参数联合起来组成目标路径,与此路径符合的目标才能收信。
第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。第三个参数表示这个值是传给目标中的哪个成员变量的,第四个参数是要设置的值。
tips:set函数时其第一个参数应该尽量使用this,在无法得到this指针的情况下(如在top_tb中),使用null或uvm_root::get()。
- 在driver中的build_phase使用如下方式收信:
uvm_config_db#(int)::get(this, "", "pre_num", pre_num);
其中,第一个和第二个参数联合起来组成目标路径。
第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。一般的,如果第一个参数设置为this,那么第二个参数可以是一个空的字符串。第三个参数就是set函数中的第三个参数,这两个参数必须严格匹配,第四个参数则是要设置的成员变量。
- 某些情况下可以只有set()而没有get()语句(与field automation机制的结合),前提是
1.my_driver必须用uvm_component_utils宏注册。
2.my_driver中有成员变量pre_num,并使用uvm_field_int宏注册(完成域的自动化)。
3.set函数的第三个参数必须与get函数中成员变量的名字(第四个参数)一致。
3.config_db的多重设置
- 在设置多次,而只获取一次的情况下,UVM采用层次优先级高,时间优先级低的方式。
- 跨层次的多重设置时,高层次的set()设置优先。
- 同一层次的多重设置时,按时间优先原则。
4.config_db的检查(check_config_usage)
- UVM提供了check_config_usage函数,可以显示截止到此函数调用时,有哪些参数是被设置过但是没有被获取过。
- 由于config_db的set()及get()语句一般都用于build_phase阶段,所以此函数一般都在connect_phase及connect_phase之后的phase被调用。
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
check_config_usage();
endfunction
5.config_db的常见使用方式
传递interface
接口定义:
//MCDF中chnl接口的传递
//tb.sv
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_valid;
logic ch_ready;
endinterface
1. 在顶层tb中set():类型为virtual chnl_intf
//tb.sv
module tb;
...
initial begin
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch0_vif", chnl0_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch1_vif", chnl1_if);
uvm_config_db#(virtual chnl_intf)::set(uvm_root::get(), "uvm_test_top", "ch2_vif", chnl2_if);
···
run_test("mcdf_data_consistence_basic_test");
end
endmodule
2. 在uvm_base_test的build_phase中get():
//mcdf_pkg.sv
class mcdf_base_test extends uvm_test;
mcdf_env env;
virtual chnl_intf ch0_vif ;
virtual chnl_intf ch1_vif ;
virtual chnl_intf ch2_vif ;
...
`uvm_component_utils(mcdf_base_test)
function new(string name = "mcdf_base_test", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get virtual interface from top TB
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch0_vif", ch0_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch1_vif", ch1_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","ch2_vif", ch2_vif)) begin
`uvm_fatal("GETVIF","cannot get vif handle from config DB")
end
...
this.env = mcdf_env::type_id::create("env", this);
endfunction
...
endclass: mcdf_base_test
tips:
1. config_db要在组件例化之前进行配置(set在run_test之前,get在例化之前)。
2. 只有tb中声明接口时用interface,在传递过程中的类型应当为virtual interface,即实际接口的句柄。
设置成员变量值(int、string、enum等)
- 在各个test中,可以在build_phase里对底层组件的成员变量加以配置set(),在底层组件例化前get()配置信息,既实现了变量值的修改,又无需重新编译。
传递配置对象(config object)
- 如果在test配置里,需要配置的参数多,而且还分属于不同的组件,那么可以将每个组件中的变量加以整合,放置到一个uvm_object中,再把这个对象进行传递,会更有利于整理环境的维护。
- 具体实现:把每个组件里需要配置的参数都放在一个对象里,然后把这个对象传递到需要的组件中去。组件get到对象之后,只选择自己需要的参数即可。
get():
class config1 extends uvm_object
`uvm_component_utils(config1)
int val1 = 1;
int str1 = "null";
...
endclass:config1
class comp1 extends uvm_component;
`uvm_component_utils(comp1)
config1 cfg;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_object tmp;
uvm_config_db#(uvm_object)::get(this,"","cfg",tmp);
void'($cast(cfg,tmp));
`uvm_info("SETVAL", $sformatf("cfg.val1 is %d after get", cfg.var1), UVM_LOW)
`uvm_info("SETVAL", $sformatf("cfg.str1 is %d after get", cfg.str1), UVM_LOW)
endfunction
endclass:comp1
set():
class test1 extends uvm_test;
`uvm_component_utils(uvm_config_test)
config1 cfg1,cfg2;
comp1 c1,c2;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg1 = config1::type_id::create("cfg1");
cfg2 = config1::type_id::create("cfg2");
cfg1.var1 = 30;
cfg1.str1 = "c1";
cfg2.var1 = 50;
cfg2.str1 = "c2";
uvm_config_db#(uvm_object)::set(this,"c1","cfg",cfg1);
uvm_config_db#(uvm_object)::set(this,"c2","cfg",cfg2);
c1 = comp1::type_id::create("c1", this);
c2 = comp1::type_id::create("c2", this);
endfunction
task run_phase(uvm_phase phase);
...
endtask
endclass:test1
输出结果:
UVM_INFO @ 0:uvm_test_top.c1 [SETVAL] cfg.val1 is 30 after get
UVM_INFO @ 0:uvm_test_top.c1 [SETVAL] cfg.str1 is c1 after get
UVM_INFO @ 0:uvm_test_top.c2 [SETVAL] cfg.val1 is 50 after get
UVM_INFO @ 0:uvm_test_top.c2 [SETVAL] cfg.str1 is c2 after get
四、phase机制
UVM中的phase机制是component独有的。
1.phase机制的作用
- 在验证环境实现层次化时保证例化的先后关系以及各组件之间的连接。
- 将UVM仿真阶段层次化(各个phase之间有先后顺序,同一phase中的层次化组件之间的phase也有先后关系),很大程度上解决了因代码顺序杂乱可能会引发的问题。
- 可以在底层组件例化之前完成对镀层的配置(区别与SV)。
2.phase的执行顺序
- 所有phase会按照图中顺序自上而下自动执行。
- UVM中的phase,按照其是否消耗仿真时间,可以分为function phase与task phase,上图中灰色部分的是task phase,其他为function phase。
- 对于function_phase来说,build_phase按自上而下的顺序执行,而其它的function_phase都是按自下而上的顺序执行。
- 对于task_phase来说,也是按照自下而上的顺序执行,但由于其耗费时间,并不是等下面的phase运行完再运行上面的phase,而是将这些run phase通过fork…join_none全部启动,所以更准确的说法是自上而下的启动,同时在运行。
3.end_of_elaboration_phase中微调测试环境
超时退出
- 在验证平台运行时,有时测试用例会出现挂起(hang up)的情况,仿真时间一直往前走,driver或monitor并没有发出或收到transcation,也没有UVM_ERROR出现。
- 通过uvm_root的set_timeout函数可以设置超时时间。
设置打印信息的冗余度
- 在打印消息之前,UVM会比较要显示信息的冗余度与默认的冗余度阈值,如果小于等于阈值,才会显示该信息。
- 通过set_report_verbosity_level_hier()设置冗余度阈值。
UVM_ERROR到达一定数量结束仿真
- 当UVM_FATAL出现时,仿真会马上停止,UVM也支持UVM_ERROR到达一定数量结束仿真。
- 通过set_report_max_quit_count()设置结束仿真的UVM_ERROR阈值。
代码
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH); //打印消息冗余度
uvm_root::get().set_report_max_quit_count(1); //结束仿真的UVM_ERROR阈值
uvm_root::get().set_timeout(10ms); //超时时间
endfunction
4.objection机制
作用与原理
- UVM中通过objection机制来控制验证平台的关闭。
- 在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase。
objection机制的应用
- raise_objection和drop_objection总是成对出现,在drop_objection之前一定要raise_objection。
- raise_objection语句必须要在main_phase中第一个消耗仿真时间的语句之前。
控制objection的最佳选择
- 在实际验证平台中,objection一般伴随着sequence,通常只在sequence出现的地方才提起和撤销objection。
- 《UVM实战》中的比喻:“sequence就像一个弹夹,里面的子弹是transaction,而sequencer是一把枪,当弹夹里的子弹用光之后,就可以结束仿真了。”
MCDF中的raise_objection和drop_objection:
class mcdf_base_test extends uvm_test;
...
task run_phase(uvm_phase phase);
// NOTE:: raise objection to prevent simulation stopping
phase.raise_objection(this);
this.run_top_virtual_sequence();
// NOTE:: drop objection to request simulation stopping
phase.drop_objection(this);
endtask
virtual task run_top_virtual_sequence();
// User to implement this task in the child tests
endtask
class mcdf_data_consistence_basic_test extends mcdf_base_test;
`uvm_component_utils(mcdf_data_consistence_basic_test)
function new(string name = "mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction
task run_top_virtual_sequence();
mcdf_data_consistence_basic_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_data_consistence_basic_test
set_drain_time
- 无论任何功能的模块,都有其处理延时,DUT的输出相对于输入总会有一定的延时:
- 可以通过设置objection的drain_time属性,用来定义在phase结束后延时大小,保证DUT的输出完毕后再停止UVM验证平台。
task base_test::main_phase(uvm_phase phase);
phase.phase_done.set_drain_time(this, 200);
endtask
- 一个phase对应一个drain_time,并不是所有的phase共享一个drain_time。在没有设置的情况下,drain_time的默认值为0。
- 当UVM在某个task phase中检测到所有的objection被撤销后,接下来会检查有没有设置drain_time。如果没有设置,则马上进入下一个phase,否则延迟drain_time后再进入下一个phase。