本篇为验证平台的最后一个环节,step by step 的最后一步,给平台添加测试用例。
项目地址:https://github.com/shirainbown/UVM-Step-by-Step/tree/master
我们理论上已经将平台完整搭建出来了,不过还存在一个问题,虽然平台能够发送激励并且对比数据,但是我们需要平台发送什么样的数据在哪里体现呢?难道每次换一种测试数据都需要重新大改一次现有平台?或者说,我希望一段时间发数据a,一段时间发数据b怎么实现呢?这个时候就需要使用测试用例了,每个用例可以决定我们当前测试的是DUT的什么功能,最大程度重用平台。
1. 编写base_case
正如名字所示,这个base_case是所有testcase的基础,它应该包括所有测试用例的公共部分,所有测试用例都继承自base_case。测试用例会是我们在前面的top_tb中的run_test(xxx)里面的xxx,也是我们整个验证平台的顶层。
至于base_case里面具体要有哪些内容,和个人习惯相关,你可以在里面写一个report_phase用于打印本次仿真的结果数据,也可以完成一些DUT测试的初始化检查,通过之后才开始仿真。
我们可以在base_case 中定义一个deliver函数,这样能够根据需要控制sequence发送:
class base_test extends uvm_test;
my_env env;
my_sequence seq;
function new(string name = "base_test", uvm_component parent = null);
super.new(name, parent);
env = my_env::type_id::create("env", this);
seq = my_sequence::type_id::create("seq");
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
virtual task deliver();
if (env == null || env.i_agt.sqr == null) begin
`uvm_fatal("DELIVER", "Environment or sequencer not initialized!");
end
if (seq == null) begin
`uvm_fatal("DELIVER", "Sequence not initialized!");
end
`uvm_info(get_name(), "Starting sequence via deliver method", UVM_LOW);
seq.start(env.i_agt.sqr);
`uvm_info(get_name(), "Sequence execution completed", UVM_LOW);
endtask
`uvm_component_utils(base_test)
endclass
2. 修改 my_env.sv
我们已经将seq.start 放在了base_test中,之前是放在了env 的main_phase中,这样会导致只要平台运行就一定会启动sequencer,我们想要的结果是可以人为控制信号的发送。
`ifndef MY_ENV__SV
`define MY_ENV__SV
class my_env extends uvm_env;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
my_scoreboard scb;
uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
function new(string name = "my_env", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);
o_agt = my_agent::type_id::create("o_agt", this);
i_agt.is_active = UVM_ACTIVE;
o_agt.is_active = UVM_PASSIVE;
mdl = my_model::type_id::create("mdl", this);
scb = my_scoreboard::type_id::create("scb", this);
agt_scb_fifo = new("agt_scb_fifo", this);
agt_mdl_fifo = new("agt_mdl_fifo", this);
mdl_scb_fifo = new("mdl_scb_fifo", this);
endfunction
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_env)
endclass
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
o_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction
`endif
相对于原来的平台,我们去掉了main_pahse中的内容:
3. 编写自定义的test_case
有了base_test,我们就可以在这个基础上来定制自己想要的测试用例,针对DUT 的某些功能进行测试,设置sequence发送的包长,或者控制两个包之间的发送间隙等等。
class svf_sanity extends base_test;
//my_sequence seq;
`uvm_component_utils(svf_sanity)
function new(string name = "svf_sanity", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task main_phase(uvm_phase phase);
phase.raise_objection(this);
#100; // 等待 100 时间单位
deliver(); // 第二次启动 sequence
#100;
deliver();
phase.drop_objection(this);
endtask
endclass
由于所有的用例都继承与base_test,因此可以直接在用例里面调用deliver函数,通过在不同的测试用例里面对sequence的参数进行设置可以得到更加丰富的功能。
4.修改top_tb中的设置
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "my_if.sv"
`include "my_transaction.sv"
`include "my_sequencer.sv"
`include "my_driver.sv"
`include "my_monitor.sv"
`include "my_agent.sv"
`include "my_model.sv"
`include "my_scoreboard.sv"
`include "my_sequence.sv"
`include "my_env.sv"
module top_tb;
reg clk;
reg rst_n;
reg[7:0] rxd;
reg rx_dv;
wire[7:0] txd;
wire tx_en;
my_if input_if(clk, rst_n);
my_if output_if(clk, rst_n);
dut my_dut(.clk(clk),
.rst_n(rst_n),
.rxd(input_if.data),
.rx_dv(input_if.valid),
.txd(output_if.data),
.tx_en(output_if.valid));
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
initial begin
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
end
initial begin
// run_test("base_test");
run_test("svf_sanity"); // 修改点1
end
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if); //修改点2
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif", output_if);
end
endmodule
此处是因为整个验证平台的结构变了,顶层组件不再是env而是svf_sanity了。需要注意的是,base_test在这里并不是层级结构的一部分,svf_sanity继承于base_test,base_test可以看成svf_sanity的一部分。