一、验证组件和层次构建
首先将各个package中的SV组件替换为UVM组件
实现组件对应原则:
- SV的
transaction
类对应uvm_sequence_item
- SV的
driver
类对应uvm_driver
- SV的
generator
类对应uvm_sequence
+uvm_sequencer
- SV的
monitor
对应uvm_monitor
- SV的
agent
对应uvm_agent
- SV的
env
对应uvm_env
- SV的
checker
对应uvm_scoreboard
- SV的
reference model
和coverage model
均对应uvm_component
- SV的
test
对应uvm_test
在遵循上面的对应原则的过程中,在进行类的转换时,需要注意:
- SV的上述类均需要继承于其对应的UVM类
- 在类定义过程中,一定需要使用
'uvm_component_utils()
或者'uvm_object_utils()
完成类的注册。 - 在使用上述工厂注册宏的时候,会伴随着“域声明自动化”,一般而言,将
sequence item类
定义时,应当伴随着域声明,即利用'uvm_object_utils_begin
和'uvm_object_utils_end
完成。这是由于对于sequence item
对象的拷贝、比较和打印等操作比较多,因此在定义sequence item
类时,最好需要完成域的自动化声明。 - 一定要注意构建函数new()的声明方式,
uvm_component
的构建函数有两个参数new(string name, uvm_component parent)
,而uvm_object
的构建函数只有一个参数new(string name)
。 - 在组件之间的层次关系构建中,依然按照之前SV组件的层次关系,只需要在不同的phase阶段完成组件的例化和连接。
UVM代码实现
chnl.pkg.sv
chnl_trans
类
相比于SV验证模块代码,成员变量没有发生什么变化,但是省略掉了clone()
和sprint()
这两个方法,因为UVM做了类的注册以及域的自动化的声明,可以使用UVM核心基类的克隆、打印、比较等一些常见方法。
// channel sequence item
class chnl_trans extends uvm_sequence_item;
rand bit[31:0] data[];
rand int ch_id;
rand int pkt_id;
rand int data_nidles;
rand int pkt_nidles;
bit rsp;
constraint cstr{
soft data.size inside {[4:32]};
foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
soft ch_id == 0;
soft pkt_id == 0;
soft data_nidles inside {[0:2]};
soft pkt_nidles inside {[1:10]};
};
`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
endclass: chnl_trans
chnl_driver
类
-
继承于
uvm_driver
是一个参数类,所以得uvm_driver #(chnl_trans)
。而SV中的run()
转换成了UVM中run_phase(uvm_phase phase)
。 -
do_driver()
方法里面void'($cast(rsp, req.clone()))
,是因为UVM核心基类的克隆方法返回的是uvm_object
类型,所以需要把父类的句柄转换为子类的句柄,而req.clone()
这个父类句柄指向的是一个子类的对象,所以能转换成功。 -
chnl_write()
方法中把SV中的$display
替换成了UVM中的'uvm_info()
。
// channel driver
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
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask
task do_reset();
forever begin
@(negedge intf.rstn);
intf.ch_valid <= 0;
intf.ch_data <= 0;
end
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
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
`uvm_info(get_type_name(), $sformatf("sent data 'h%8x", t.data[i]), UVM_HIGH)
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_idle();
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass: chnl_driver
chnl_generator
类
- 相比于SV使用构建函数
new()
来创建对象,send_trans()
方法中使用req = chnl_trans::type_id::create("req")
来创建对象。 - 打印消息使用了UVM中的
'uvm_info()
。
// channel generator and to be replaced by sequence + sequencer later
class chnl_generator extends uvm_component;
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;
constraint cstr{
soft ch_id == -1;
soft pkt_id == 0;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans == 10;
}
`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
task start();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
req = chnl_trans::type_id::create("req");;
assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
else $fatal("[RNDFAIL] channel packet randomization failure!");
this.pkt_id++;
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
this.req_mb.put(req);
this.rsp_mb.get(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, super.sprint()};
// NOTE:: field automation already implemented clone() method
// s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
// s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
// s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
// s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
// s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
// s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: chnl_generator
chnl_monitor
类
将SV中的run()
替换成了UVM中的
run_phase(uvm_phase phase)
以及使用UVM中的'uvm_info()
打印消息。
// channel monitor
class chnl_monitor extends uvm_monitor;
local virtual chnl_intf intf;
mailbox #(mon_data_t) mon_mb;
`uvm_component_utils(chnl_monitor)
function new(string name="chnl_monitor", uvm_component parent);
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
this.mon_trans();
endtask
task mon_trans();
mon_data_t m;
forever begin
@(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1));
m.data = intf.mon_ck.ch_data;
mon_mb.put(m);
`uvm_info(get_type_name(), $sformatf("monitored channel data 'h%8x", m.data), UVM_HIGH)
end
endtask
endclass: chnl_monitor
chnl_agent
类
- SV验证结构中例化和连接都发生在构建函数
new()
里面,而UVM中例化是在build_phase()
方法中,并且通过create()
来例化创建对象。 - SV验证结构中
run()
需要调用子一级的run()
方法,而在UVM中不需要手动去调用子一级的run_phase()
,因为run_phase
是按照层次来执行的,是由uvm_root
来安排的,会自动调用。
// channel agent
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
function void set_interface(virtual chnl_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
task run_phase(uvm_phase phase);
// NOTE:: No more needed to call run manually
// fork
// driver.run();
// monitor.run();
// join
endtask
endclass: chnl_agent
reg_pkg.sv文件、fmt_pkg.sv文件、mcdf_pkg.sv文件修改的地方相似。
二、测试的开始和结束
UVM验证环境测试的开始、环境构建的过程、连接以及结束的控制。
tb.sv
通过uvm_config_db
完成了各个接口从TB
(硬件一侧)到验证环境mcdf_env
(软件一侧)的传递。实现了以往SV函数的剥离,即UVM不需要深入到目标组件一侧,调用其set_interface()
即可完成传递。这种传递方式有赖于config_db
的数据存储和层次传递特性。而在mcdf_env
中,暂时保留了mcdf_env
的set_interface()
以及各个子组件的set_interface()
函数。仅修改了TB
与mcdf_env
之间的接口传递,其实可以移除所有的set_interface()
函数,完全使用uvm_config_db
的set
和get
方法,从而使得mcdf_env
与其各个子组件之间也实现“层次剥离”,这样也就进一步促进了组件之间的独立性。
// 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);
通过调用run_test()
函数即完成了test
的选择、例化和开始测试。可以在代码中指定UVM test,或者通过 +UVM_TESTNAME=mytest
在仿真选项中灵活传递test名。在run_test()
执行中,它会初始化objection机制,即查看objection有没有挂起的地方,因此在test或者generator中必须至少有一处地方使用phase.raise_objection()
来挂起仿真,避免仿真退出,而在仿真需要结束时,使用phase.drop_objection()
来允许仿真可以退出。同时run_test()
可以创建uvm_test
组件,及其以下的各层组件群,并且可以调用phase控制方法,按照所有phase顺序执行。在UVM中,将对象的例化放置在build_phase
中,而将对象的连接放置在connect_phase
中。
module tb;
logic clk;
logic rstn;
mcdf dut(
.clk_i (clk )
,.rstn_i (rstn )
,.cmd_i (reg_if.cmd )
,.cmd_addr_i (reg_if.cmd_addr )
,.cmd_data_i (reg_if.cmd_data_m2s)
,.cmd_data_o (reg_if.cmd_data_s2m)
,.ch0_data_i (chnl0_if.ch_data )
,.ch0_vld_i (chnl0_if.ch_valid )
,.ch0_ready_o (chnl0_if.ch_ready )
,.ch1_data_i (chnl1_if.ch_data )
,.ch1_vld_i (chnl1_if.ch_valid )
,.ch1_ready_o (chnl1_if.ch_ready )
,.ch2_data_i (chnl2_if.ch_data )
,.ch2_vld_i (chnl2_if.ch_valid )
,.ch2_ready_o (chnl2_if.ch_ready )
,.fmt_grant_i (fmt_if.fmt_grant )
,.fmt_chid_o (fmt_if.fmt_chid )
,.fmt_req_o (fmt_if.fmt_req )
,.fmt_length_o(fmt_if.fmt_length )
,.fmt_data_o (fmt_if.fmt_data )
,.fmt_start_o (fmt_if.fmt_start )
,.fmt_end_o (fmt_if.fmt_end )
);
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
import uvm_pkg::*;
`include "uvm_macros.svh"
import chnl_pkg::*;
import reg_pkg::*;
import arb_pkg::*;
import fmt_pkg::*;
import mcdf_pkg::*;
reg_intf reg_if(.*);
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
arb_intf arb_if(.*);
fmt_intf fmt_if(.*);
mcdf_intf mcdf_if(.*);
// mcdf interface monitoring MCDF ports and signals
assign mcdf_if.chnl_en[0] = tb.dut.ctrl_regs_inst.slv0_en_o;
assign mcdf_if.chnl_en[1] = tb.dut.ctrl_regs_inst.slv1_en_o;
assign mcdf_if.chnl_en[2] = tb.dut.ctrl_regs_inst.slv2_en_o;
// arbiter interface monitoring arbiter ports
assign arb_if.slv_prios[0] = tb.dut.arbiter_inst.slv0_prio_i;
assign arb_if.slv_prios[1] = tb.dut.arbiter_inst.slv1_prio_i;
assign arb_if.slv_prios[2] = tb.dut.arbiter_inst.slv2_prio_i;
assign arb_if.slv_reqs[0] = tb.dut.arbiter_inst.slv0_req_i;
assign arb_if.slv_reqs[1] = tb.dut.arbiter_inst.slv1_req_i;
assign arb_if.slv_reqs[2] = tb.dut.arbiter_inst.slv2_req_i;
assign arb_if.a2s_acks[0] = tb.dut.arbiter_inst.a2s0_ack_o;
assign arb_if.a2s_acks[1] = tb.dut.arbiter_inst.a2s1_ack_o;
assign arb_if.a2s_acks[2] = tb.dut.arbiter_inst.a2s2_ack_o;
assign arb_if.f2a_id_req = tb.dut.arbiter_inst.f2a_id_req_i;
// mcdf_data_consistence_basic_test t1;
// mcdf_full_random_test t2;
// mcdf_base_test tests[string];
// string name;
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
// mcdf_data_consistence_basic_test
run_test("mcdf_data_consistence_basic_test");
// NOTE:: SV RUN test method is replaced by UVM run_test()
// + UVM_TESTNAME=[my_test] solution
// t1 = new();
// t2 = new();
// tests["mcdf_data_consistence_basic_test"] = t1;
// tests["mcdf_full_random_test"] = t2;
// if($value$plusargs("TESTNAME=%s", name)) begin
// if(tests.exists(name)) begin
// tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if, reg_if, arb_if, fmt_if, mcdf_if);
// tests[name].run();
// end
// else begin
// $fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
// end
// end
// else begin
// $display("NO runtime optiont +TESTNAME=xxx is configured, and run default test mcdf_data_consistence_basic_test");
// tests["mcdf_data_consistence_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if, reg_if, arb_if, fmt_if, mcdf_if);
// tests["mcdf_data_consistence_basic_test"].run();
// end
end
endmodule
运行仿真: