验证组件和层次构建
实现从SV组件到UVM组件的替换:
- transaction -> uvm_sequence_item
- driver -> uvm_driver
- generator -> uvm_sequence + uvm_sequencer
- monitor -> uvm_monitor
- agent ->uvm_agent
- env -> uvm_env
- checker -> uvm_scoreboard
- refmod/coveragee model -> uvm_component
- test -> uvm_test
进行类的转换时需注意:
- 上述类均需派生自对应的UVM类
- 定义类时使用 'uvm_component_utils() 或 'uvm_object_utils() 宏实现factory的注册。
- 使用factory注册宏的时候,引入field automation机制,一般定义uvm_sequence_item类时,使用 'uvm_object_utils_begin() 和 'uvm_object_utils_end实现factory注册,并使用 'uvm_field注册所有字段,即可调用copy、compare、print等函数。
- 注意构建函数new()的声明方式,uvm_component相关类的new函数有string name(实例名字)和 uvm_component parent两个参数,而uvm_object相关类的的new函数只有string name参数。
- 在组件之间的层次关系构建中,依然按照之前SV组件的层次关系,在不同的phase完成组件的例化和连接。
以chnl_pkg为例:
package chnl_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
// channel sequence item
class chnl_trans extends uvm_sequence_item;
...
`uvm_object_utils_begin(chnl_trans)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(rsp, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name = "chnl_trans");
super.new(name);
endfunction
// field automation机制已实现clone()和sprint()方法
// function chnl_trans clone();
// function string sprint();
endclass: chnl_trans
class chnl_driver extends uvm_driver #(chnl_trans); // ①派生
local virtual chnl_intf intf;
mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;
`uvm_component_utils(chnl_driver) // ②注册
function new (string name = "chnl_driver", uvm_component parent); // ③创建
super.new(name, parent);
endfunction
...
task run_phase(uvm_phase phase); // 执行SV中的run()
fork
this.do_drive();
this.do_reset();
join
endtask
...
task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.chnl_write(req);
void'($cast(rsp, req.clone())); // 将父类句柄转换为子类句柄
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask
...
endclass: chnl_driver
// channel generator and to be replaced by sequence + sequencer later
class chnl_generator extends uvm_component;
...
`uvm_component_utils_begin(chnl_generator)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_component_utils_end
function new (string name = "chnl_generator", uvm_component parent);
super.new(name, parent);
this.req_mb = new();
this.rsp_mb = new();
endfunction
...
function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, super.sprint()}; // 调用父类,field automation机制已实现
return s;
endfunction
...
endclass: chnl_generator
class chnl_monitor extends uvm_monitor;
...
class chnl_agent extends uvm_agent;
chnl_driver driver;
chnl_monitor monitor;
local virtual chnl_intf vif;
`uvm_component_utils(chnl_agent)
function new(string name = "chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = chnl_driver::type_id::create("driver", this);
monitor = chnl_monitor::type_id::create("monitor", this);
endfunction
...
task run_phase(uvm_phase phase);
// 不需要再手动调用子一级的run_phase(),交给uvm_root自动调用。
endtask
endclass: chnl_agent
endpackage
测试的开始和结束
在tb.sv中已注释了关于SV各test声明、例化、外部参数传递、执行选择过程及开始测试的过程,并用UVM实现测试的开始、环境构建的过程、连接以及结束的控制。
- 通过uvm_config_db完成了各个接口从TB(硬件一侧)到验证环境mcdf_env(软件一侧)的传递。理论上可以移除所有的set_interface()函数,完全使用uvm_config_db的set和get方法,从而使mcdf_env与其各个子组件之间相互独立。
- 调用run_test() 函数完成test的选择、例化和开始测试。
- 可在代码中指定UVM test,或通过 +UVM_TESTNAME=mytest在仿真中传参指定。
- 在run_test()执行中会初始化objection机制,使用phase.raise_objection() 来挂起仿真,避免仿真退出;而在仿真需要结束时,使用phase.drop_objection() 来允许仿真退出。
- 创建uvm_test组件,及其以下的各层组件群
- 调用phase控制方法,按照所有phase顺序执行。
- 在UVM中,将对象的例化放置在build_phase中,而将对象的连接放置在connect_phase中。
initial begin
// do interface configuration from top tb (HW) to verification env (SW)
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);
uvm_config_db#(virtual reg_intf)::set(uvm_root::get(), "uvm_test_top", "reg_vif", reg_if);
uvm_config_db#(virtual arb_intf)::set(uvm_root::get(), "uvm_test_top", "arb_vif", arb_if);
uvm_config_db#(virtual fmt_intf)::set(uvm_root::get(), "uvm_test_top", "fmt_vif", fmt_if);
uvm_config_db#(virtual mcdf_intf)::set(uvm_root::get(), "uvm_test_top", "mcdf_vif", mcdf_if);
// If no external configured via +UVM_TESTNAME=my_test, the default test is
run_test("mcdf_data_consistence_basic_test");
end
TLM通信
TLM单向通信和多向通信
在之前monitor与checker,和checker与refmod之间的通信,都是通过mailbox以及在上层进行句柄传递实现的。这次使用TLM端口进行通信,做逐步的通信元素和方法的替换。相较于mailbox句柄连接,更加容易定制,同时提高组件独立性。
- TLM通信结构更新方案
- 将monitor中用来与checker中mailbox通信的mon_mb句柄替换为对应的uvm_blocking_put_port类型。
uvm_blocking_put_port #(mon_data_t) mon_bp_port;
- 在checker中声明与monitor通信的import端口类型,以及reference model通信的import端口类型,由于checker与多个monitor以及reference model通信,是典型的多方向通信类型,因此需要使用多端口通信的宏声明方法(防止通信方法名冲突)。在使用了宏声明端口类型之后,再在checker中声明其句柄,并且完成例化。
- 根据声明的import端口类型,实现其对应的方法。
`uvm_blocking_put_imp_decl(_chnl0) // 宏定义端口类型
...
uvm_blocking_put_imp_chnl0 #(mon_data_t, mcdf_checker) chnl0_bp_imp; // 声明句柄
...
chnl0_bp_imp = new("chnl0_bp_imp", this); // 例化
...
task put_chnl0(mon_data_t t); // 实现定义的方法
chnl_mbs[0].put(t);
endtask
- 在mcdf_refmod中声明用来与mcdf_checker中的import连接的端口,并且完成例化。完成声明和例化后,使用TLM端口呼叫方法。
- 在env的connect_phase()阶段,完成monitor、refmod的TLM port与checker的TLM import的连接。
refmod.in_bgpk_ports[0].connect(chnl0_bgpk_imp);
...
refmod.reg_bg_port.connect(reg_bg_imp);
TLM通信管道
TLM通信的优点:
- 通信函数可以定制化,例如可定制put()/get()/peek()的内容和参数,比mailbox的通信更加灵活。
- 将组件实现了完全的隔离,通过层次化的TLM端口连接,可以很好地避免直接将不同层次的数据缓存对象的句柄进行“空中传递”。
使用uvm_tlm_fifo类可以不自己实现put()/get()/peek()等方法,同时享受到TLM的好处。
- 将原本在mcdf_refmod中的out_mb替换成uvm_tlm_fifo类型,并且完成例化以及对应的变量名替换。
uvm_tlm_fifo #(fmt_trans) out_tlm_fifos[3];
...
foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]", i), this);
...
this.out_tlm_fifos[id].put(ot);
- 将原本在mcdf_checker中的exp_mbs[3]的mailbox句柄数组,替换为uvm_blocking_get_port类型句柄数组,并做相应例化及变量名替换。
- 在mcdf_checker中,完成mcdf_checker中的TLM port端口到mcdf_refmod中的uvm_tlm_fifo自带的blocking_get_export端口的连接。
foreach(exp_bg_ports[i]) begin
exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
end
UVM回调类
类的复用除了可以使用继承,还可以使用回调函数。将原有的mcdf_data_consistence_basic_test和mcdf_full_random_test的类库实现方式(即类继承方式)改为回调函数的实现方式。(以实现do_formatter() 为例)
- 在uvm_callback类中,预先定义需要的虚方法。
class cb_mcdf_base extends uvm_callback;
`uvm_object_utils(cb_mcdf_base)
...
virtual task cb_do_formatter(); // 1.定义
endtask
- 使用callback对应的宏,完成目标uvm_test类型与目标uvm_callback类型的关联。
class mcdf_base_test extends uvm_test;
...
`uvm_register_cb(mcdf_base_test, cb_mcdf_base) // 2.绑定
- 在目标uvm_test类型指定的方法中,完成uvm_callback的方法回调指定。
virtual task do_formatter();
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_formatter()) // 3.插入
endtask
- 实现uvm_callback和对应test类的定义。
class cb_mcdf_data_consistence_basic extends cb_mcdf_base;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cb = cb_mcdf_data_consistence_basic::type_id::create("cb"); // 创建
uvm_callbacks#(mcdf_base_test)::add(this, cb); // 4.添加 (关联父类)
endfunction
...
class cb_mcdf_data_consistence_basic extends cb_mcdf_base;
...
task cb_do_formatter(); // uvm_callback可直接继承使用
super.cb_do_formatter();
void'(test.fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
test.fmt_gen.start();
endtask
UVM仿真控制方法
在test类中添加end_of_elaboration_phase() 函数,并添加一些uvm_root类的方法。
- 利用uvm_root类来将信息冗余度设置为UVM_HIGH,以允许更多低级别的信息打印出来。
- 使用uvm_root::get().set_report_max_quit_count()函数设置当uvm_error数量超过设定值时仿真退出。默认数值为-1,表示仿真不会随uvm_error退出。
- 使用uvm_root::get().set_timeout()设置仿真的最大时间长度,替代原定义的do_watchdog()方法。
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_root::get().set_timeout(10ms);
endfunction