本篇为验证平台的最后一个环节,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的一部分。