一、前言

  工作一直在做SoC验证,更关注模块间的连接性和匹配性,所以相比于擅长随机约束激励的UVM来说,定向测试的概念更容易debug。当然前提是IP已经被充分验证。因此觉得接触UVM的机会较少。到现在发现即使在SoC验证中依然有它的用武之地。比如验证可独立于CPU工作的IP、快速对系统性能进行评估、重用IP级别的验证环境,甚至是一些通用的VIP也有基于UVM编写的。基于这些考量,也逐渐开始接触。《UVM实战》是很多验证工程师的启蒙,本文借用书中开头的示例简单梳理下UVM的基本知识。

二、UVM基础概述

  关于UVM的知识网络上已经铺天盖地了,下边的内容只是自己的一些认识和随记。UVM其实就是基于SV语言写的用于验证的代码库和对应的验证规范。下图是UVM验证环境的整体结构(图来源见参考文献1)。图中注释介绍了每个组成部分的作用。《UVM实战》书中给我留下最深刻的印象就是用子弹、弹夹和手枪分别类比transaction sequence和sequencer,这也是UVM环境灵活的重要原因之一。这是激励的产生机制,至于响应的采集和响应任务会交给monitor和scoreboard。后者中的期望数据或者参考数据的来源比较灵活,函数、文件或是reference model的响应均可。

  以上是UVM的空间维度,这一概念也被抽象成如下的树状结构。各个部分必然存在信息交互。sequence和sequencer之间传递的是transaction,实际上component之间也是transaction级别的通信,叫做TLM (transaction level model)最常见的就是monitor中uvm_analysis_port通过uvm_tlm_analysis_fifo连接到reference model或scoreboard中的uvm_blocking_get_port。这样可以确保transaction能够传递给目的组件。

  另一方面UVM在时间维度上也做了规范。phase机制明确划分了各个阶段所要完成的任务。其中比较重要的是run phase这一消耗仿真时间的task phase以及ojbection的概念。只有每个phase中所有raise的objection都被drop后才会执行下一个phase的任务。搭建空间与时间维度桥梁的也是Phase--build phase是在树状结构中自上而下执行,其他不消耗仿真时间的phase都是自下而上运行。run phase则自上而下启动同事运行。所有phase的顺序见图3(图来源见参考文献2)

  弄明白这三张图,也就是组件、组件间通信和phase机制,基本上可以看懂别人写的代码了。

三、验证环境示例

  各个UVM object和component:

sequence:

 1 class my_sequence extends uvm_sequence #(my_transaction);
 2     my_transaction m_trans;
 3 
 4     function new(string name="my_sequence");
 5         super.new(name);
 6     endfunction
 7 
 8     virtual task body();
 9         if(starting_phase != null)
10             starting_phase.raise_objection(this);
11 
12         repeat(10) begin
13             `uvm_do(m_trans)
14         end
15         #1000;
16 
17         if(starting_phase != null)
18             starting_phase.drop_objection(this);
19     endtask
20 
21     `uvm_object_utils(my_sequence)
22 
23 endclass
my_sequence

transaction:

 1 `ifndef MY_TRANSACTION__SV
 2 `define MY_TRANSACTION__SV
 3 
 4 class my_transaction extends uvm_sequence_item;
 5 
 6    rand bit[47:0] dmac;
 7    rand bit[47:0] smac;
 8    rand bit[15:0] ether_type;
 9    rand byte      pload[];
10    rand bit[31:0] crc;
11 
12    constraint pload_cons{
13       pload.size >= 46;
14       //pload.size <= 1500;
15       pload.size <= 200;
16       dmac == 48'h01_02_03_04_05_06;
17       smac == 48'h60_50_40_30_20_10;
18 
19    }
20 
21    function bit[31:0] calc_crc();
22       return 32'h0;
23    endfunction
24 
25    function void post_randomize();
26       crc = calc_crc;
27    endfunction
28 
29    //`uvm_object_utils(my_transaction)
30    `uvm_object_utils_begin(my_transaction)
31         `uvm_field_int(dmac,UVM_ALL_ON)
32         `uvm_field_int(smac,UVM_ALL_ON)
33         `uvm_field_int(ether_type,UVM_ALL_ON)
34         `uvm_field_array_int(pload,UVM_ALL_ON)
35         `uvm_field_int(crc,UVM_ALL_ON)
36    `uvm_object_utils_end
37 
38    function new(string name = "my_transaction");
39       super.new();
40    endfunction
41 
42    /*function void my_print();
43       $display("dmac = %0h", dmac);
44       $display("smac = %0h", smac);
45       $display("ether_type = %0h", ether_type);
46       for(int i = 0; i < pload.size; i++) begin
47          $display("pload[%0d] = %0h", i, pload[i]);
48       end
49       $display("crc = %0h", crc);
50    endfunction
51 
52    function void my_copy(my_transaction tr);
53       if(tr == null)
54          `uvm_fatal("my_transaction", "tr is null!!!!")
55       dmac = tr.dmac;
56       smac = tr.smac;
57       ether_type = tr.ether_type;
58       pload = new[tr.pload.size()];
59       for(int i = 0; i < pload.size(); i++) begin
60          pload[i] = tr.pload[i];
61       end
62       crc = tr.crc;
63    endfunction
64 
65    function bit my_compare(my_transaction tr);
66       bit result;
67       
68       if(tr == null)
69          `uvm_fatal("my_transaction", "tr is null!!!!")
70       result = ((dmac == tr.dmac) &&
71                 (smac == tr.smac) &&
72                 (ether_type == tr.ether_type) &&
73                 (crc == tr.crc));
74       if(pload.size() != tr.pload.size())
75          result = 0;
76       else 
77          for(int i = 0; i < pload.size(); i++) begin
78             if(pload[i] != tr.pload[i])
79                result = 0;
80          end
81       return result; 
82    endfunction*/
83 
84 endclass
85 `endif
my_transaction

sequencer:

 1 `ifndef MY_SEQUENCER__SV
 2 `define MY_SEQUENCER__SV
 3 
 4 class my_sequencer extends uvm_sequencer #(my_transaction);
 5    
 6    function new(string name, uvm_component parent);
 7       super.new(name, parent);
 8    endfunction 
 9    
10    `uvm_component_utils(my_sequencer)
11 endclass
12 
13 `endif
my_sequencer

driver:

 1 `ifndef MY_DRIVER__SV
 2 `define MY_DRIVER__SV
 3 class my_driver extends uvm_driver#(my_transaction);
 4 
 5    virtual my_if vif;
 6 
 7    `uvm_component_utils(my_driver)
 8    function new(string name = "my_driver", uvm_component parent = null);
 9       super.new(name, parent);
10    endfunction
11 
12    virtual function void build_phase(uvm_phase phase);
13       super.build_phase(phase);
14       if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
15          `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
16    endfunction
17 
18    extern task main_phase(uvm_phase phase);
19    extern task drive_one_pkt(my_transaction tr);
20 endclass
21 
22 /*task my_driver::main_phase(uvm_phase phase);
23    my_transaction tr;
24    phase.raise_objection(this);
25    vif.data <= 8'b0;
26    vif.valid <= 1'b0;
27    while(!vif.rst_n)
28       @(posedge vif.clk);
29    for(int i = 0; i < 2; i++) begin 
30       req = new("req");
31       assert(req.randomize() with {pload.size == 200;
32                                   dmac == 48'h01_02_03_04_05_06;
33                                   smac == 48'h60_50_40_30_20_10;
34                                  }                        
35             );
36       drive_one_pkt(req);
37    end
38    repeat(5) @(posedge vif.clk);
39    phase.drop_objection(this);
40 endtask*/
41 
42 task my_driver::main_phase(uvm_phase phase);
43     vif.data <= 8'b0;
44     vif.valid <= 1'b0;
45    
46     while(!vif.rst_n)
47         @(posedge vif.clk);
48     
49     while(1)begin
50         seq_item_port.get_next_item(req);
51         drive_one_pkt(req);
52         seq_item_port.item_done();
53     end
54 endtask
55 
56 task my_driver::drive_one_pkt(my_transaction tr);
57       
58    byte unsigned data_q[];
59    int unsigned data_size;
60 
61    data_size = tr.pack_bytes(data_q)/8;//fill data_q on the order in uvm_object_utils of transaction
62    `uvm_info("my_driver","begin to drive one pkt",UVM_LOW);
63    repeat(3) @(posedge vif.clk);
64    for(int i=0;i<data_size;i++)begin
65         @(posedge vif.clk);
66         vif.valid <= 1'b1;
67         vif.data <= data_q[i];
68    end
69    @(posedge vif.clk);
70    vif.valid <= 1'b0;
71    repeat(10) @(posedge vif.clk);
72    `uvm_info("my_driver","end drive one pkt",UVM_LOW);
73 endtask
74 
75 
76 `endif
my_driver

monitor:

  1 `ifndef MY_MONITOR__SV
  2 `define MY_MONITOR__SV
  3 class my_monitor extends uvm_monitor;
  4 
  5    virtual my_if vif;
  6 
  7    uvm_analysis_port #(my_transaction)  ap;
  8    
  9    `uvm_component_utils(my_monitor)
 10    function new(string name = "my_monitor", uvm_component parent = null);
 11       super.new(name, parent);
 12    endfunction
 13 
 14    virtual function void build_phase(uvm_phase phase);
 15       super.build_phase(phase);
 16       if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
 17          `uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
 18       ap = new("ap", this);
 19    endfunction
 20 
 21    extern task main_phase(uvm_phase phase);
 22    extern task collect_one_pkt(my_transaction tr);
 23 endclass
 24 
 25 task my_monitor::main_phase(uvm_phase phase);
 26    my_transaction tr;
 27    while(1) begin
 28       tr = new("tr");
 29       collect_one_pkt(tr);
 30       ap.write(tr);
 31    end
 32 endtask
 33 
 34 /*task my_monitor::collect_one_pkt(my_transaction tr);
 35    bit[7:0] data_q[$]; 
 36    int psize;
 37    while(1) begin
 38       @(posedge vif.clk);
 39       if(vif.valid) break;
 40    end
 41 
 42    `uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
 43    while(vif.valid) begin
 44       data_q.push_back(vif.data);
 45       @(posedge vif.clk);
 46    end
 47    //pop dmac
 48    for(int i = 0; i < 6; i++) begin
 49       tr.dmac = {tr.dmac[39:0], data_q.pop_front()};
 50    end
 51    //pop smac
 52    for(int i = 0; i < 6; i++) begin
 53       tr.smac = {tr.smac[39:0], data_q.pop_front()};
 54    end
 55    //pop ether_type
 56    for(int i = 0; i < 2; i++) begin
 57       tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
 58    end
 59 
 60    psize = data_q.size() - 4;
 61    tr.pload = new[psize];
 62    //pop payload
 63    for(int i = 0; i < psize; i++) begin
 64       tr.pload[i] = data_q.pop_front();
 65    end
 66    //pop crc
 67    for(int i = 0; i < 4; i++) begin
 68       tr.crc = {tr.crc[23:0], data_q.pop_front()};
 69    end
 70    `uvm_info("my_monitor", "end collect one pkt", UVM_LOW);
 71 endtask*/
 72 
 73 task my_monitor::collect_one_pkt(my_transaction tr);
 74     byte unsigned data_q[$];
 75     byte unsigned data_array[];
 76 
 77     logic [8-1:0] data;
 78     logic valid=0;
 79     int data_size;
 80 
 81     while(1) begin
 82       @(posedge vif.clk);
 83       if(vif.valid) break;
 84     end
 85 
 86 
 87     `uvm_info("my_monitor","begin to collect one pkt",UVM_LOW);
 88     while(vif.valid)begin
 89         data_q.push_back(vif.data);
 90         @(posedge vif.clk);
 91     end
 92 
 93     data_size = data_q.size();
 94     data_array = new[data_size];
 95     for(int i=0;i<data_size;i++)begin
 96         data_array[i] = data_q[i];
 97     end
 98 
 99     tr.pload = new[data_size-18];
100     data_size = tr.unpack_bytes(data_array)/8;
101     `uvm_info("my_monitor","end collect one pkt",UVM_LOW);
102 endtask
103 
104 
105 `endif
my_monitor

agent:

 1 `ifndef MY_AGENT__SV
 2 `define MY_AGENT__SV
 3 
 4 class my_agent extends uvm_agent ;
 5    my_sequencer sqr;
 6    my_driver     drv;
 7    my_monitor    mon;
 8    
 9    uvm_analysis_port #(my_transaction)  ap;
10    
11    function new(string name, uvm_component parent);
12       super.new(name, parent);
13    endfunction 
14    
15    extern virtual function void build_phase(uvm_phase phase);
16    extern virtual function void connect_phase(uvm_phase phase);
17 
18    `uvm_component_utils(my_agent)
19 endclass 
20 
21 
22 function void my_agent::build_phase(uvm_phase phase);
23    super.build_phase(phase);
24    if (is_active == UVM_ACTIVE) begin
25        sqr = my_sequencer::type_id::create("sqr",this);
26        drv = my_driver::type_id::create("drv", this);
27    end
28    mon = my_monitor::type_id::create("mon", this);
29 endfunction 
30 
31 function void my_agent::connect_phase(uvm_phase phase);
32    super.connect_phase(phase);
33    if(is_active == UVM_ACTIVE)begin
34         drv.seq_item_port.connect(sqr.seq_item_export);   
35    end
36    ap = mon.ap;
37 endfunction
38 
39 `endif
my_agent

reference model:

 1 `ifndef MY_MODEL__SV
 2 `define MY_MODEL__SV
 3 
 4 class my_model extends uvm_component;
 5    
 6    uvm_blocking_get_port #(my_transaction)  port;
 7    uvm_analysis_port #(my_transaction)  ap;
 8 
 9    extern function new(string name, uvm_component parent);
10    extern function void build_phase(uvm_phase phase);
11    extern virtual  task main_phase(uvm_phase phase);
12 
13    `uvm_component_utils(my_model)
14 endclass 
15 
16 function my_model::new(string name, uvm_component parent);
17    super.new(name, parent);
18 endfunction 
19 
20 function void my_model::build_phase(uvm_phase phase);
21    super.build_phase(phase);
22    port = new("port", this);
23    ap = new("ap", this);
24 endfunction
25 
26 task my_model::main_phase(uvm_phase phase);
27    my_transaction tr;
28    my_transaction new_tr;
29    super.main_phase(phase);
30    while(1) begin
31       port.get(tr);
32       new_tr = new("new_tr");
33       //new_tr.my_copy(tr);
34       new_tr.copy(tr);
35       `uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
36       //new_tr.my_print();
37       new_tr.print();
38       ap.write(new_tr);
39    end
40 endtask
41 `endif
my_model

scoreboard:

 1 `ifndef MY_SCOREBOARD__SV
 2 `define MY_SCOREBOARD__SV
 3 class my_scoreboard extends uvm_scoreboard;
 4    my_transaction  expect_queue[$];
 5    uvm_blocking_get_port #(my_transaction)  exp_port;
 6    uvm_blocking_get_port #(my_transaction)  act_port;
 7    `uvm_component_utils(my_scoreboard)
 8 
 9    extern function new(string name, uvm_component parent = null);
10    extern virtual function void build_phase(uvm_phase phase);
11    extern virtual task main_phase(uvm_phase phase);
12 endclass 
13 
14 function my_scoreboard::new(string name, uvm_component parent = null);
15    super.new(name, parent);
16 endfunction 
17 
18 function void my_scoreboard::build_phase(uvm_phase phase);
19    super.build_phase(phase);
20    exp_port = new("exp_port", this);
21    act_port = new("act_port", this);
22 endfunction 
23 
24 task my_scoreboard::main_phase(uvm_phase phase);
25    my_transaction  get_expect,  get_actual, tmp_tran;
26    bit result;
27  
28    super.main_phase(phase);
29    fork 
30       while (1) begin
31          exp_port.get(get_expect);
32          expect_queue.push_back(get_expect);
33       end
34       while (1) begin
35          act_port.get(get_actual);
36          if(expect_queue.size() > 0) begin
37             tmp_tran = expect_queue.pop_front();
38             //result = get_actual.my_compare(tmp_tran);
39             result = get_actual.compare(tmp_tran);
40             if(result) begin 
41                `uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);
42             end
43             else begin
44                `uvm_error("my_scoreboard", "Compare FAILED");
45                $display("the expect pkt is");
46                //tmp_tran.my_print();
47                tmp_tran.print();
48                $display("the actual pkt is");
49                //get_actual.my_print();
50                get_actual.print();
51             end
52          end
53          else begin
54             `uvm_error("my_scoreboard", "Received from DUT, while Expect Queue is empty");
55             $display("the unexpected pkt is");
56             //get_actual.my_print();
57             get_actual.print();
58          end 
59       end
60    join
61 endtask
62 `endif
my_scoreboard

  base test:

 1 `ifndef BASE_TEST__SV
 2 `define BASE_TEST__SV
 3 
 4 class base_test extends uvm_test;
 5 
 6    my_env         env;
 7    
 8    function new(string name = "base_test", uvm_component parent = null);
 9       super.new(name,parent);
10    endfunction
11    
12    extern virtual function void build_phase(uvm_phase phase);
13    extern virtual function void report_phase(uvm_phase phase);
14    `uvm_component_utils(base_test)
15 endclass
16 
17 
18 function void base_test::build_phase(uvm_phase phase);
19    super.build_phase(phase);
20    env  =  my_env::type_id::create("env", this); 
21    //uvm_config_db#(uvm_object_wrapper)::set(this,
22                                            //"env.i_agt.sqr.main_phase",
23                                            //"default_sequence",
24                                            // my_sequence::type_id::get());
25 endfunction
26 
27 function void base_test::report_phase(uvm_phase phase);
28    uvm_report_server server;
29    int err_num;
30    super.report_phase(phase);
31 
32    server = get_report_server();
33    err_num = server.get_severity_count(UVM_ERROR);
34 
35    if (err_num != 0) begin
36       $display("TEST CASE FAILED");
37    end
38    else begin
39       $display("TEST CASE PASSED");
40    end
41 endfunction
42 
43 `endif
base_test

  Interface:

 1 `ifndef MY_IF__SV
 2 `define MY_IF__SV
 3 
 4 interface my_if(input clk, input rst_n);
 5 
 6    logic [7:0] data;
 7    logic valid;
 8 endinterface
 9 
10 `endif
my_if

  testbench top:

 1 `timescale 1ns/1ps
 2 `include "uvm_macros.svh"
 3 
 4 import uvm_pkg::*;
 5 `include "my_if.sv"
 6 `include "my_transaction.sv"
 7 //`include "my_sequence.sv"
 8 `include "my_driver.sv"
 9 `include "my_monitor.sv"
10 `include "my_sequencer.sv"
11 `include "my_agent.sv"
12 `include "my_model.sv"
13 `include "my_scoreboard.sv"
14 `include "my_env.sv"
15 `include "base_test.sv"
16 `include "my_case0.sv"
17 `include "my_case1.sv"
18 
19 module top_tb;
20 
21 reg clk;
22 reg rst_n;
23 reg[7:0] rxd;
24 reg rx_dv;
25 wire[7:0] txd;
26 wire tx_en;
27 
28 my_if input_if(clk, rst_n);
29 my_if output_if(clk, rst_n);
30 
31 dut my_dut(.clk(clk),
32            .rst_n(rst_n),
33            .rxd(input_if.data),
34            .rx_dv(input_if.valid),
35            .txd(output_if.data),
36            .tx_en(output_if.valid));
37 
38 initial begin
39    clk = 0;
40    forever begin
41       #100 clk = ~clk;
42    end
43 end
44 
45 initial begin
46    rst_n = 1'b0;
47    #1000;
48    rst_n = 1'b1;
49 end
50 
51 initial begin
52    //run_test("my_env");
53    run_test();
54 end
55 
56 initial begin
57    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);
58    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", input_if);
59    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif", output_if);
60 end
61 
62     //Enable dump waveform
63     initial
64     begin
65       $shm_open("test.shm");
66       $shm_probe(top_tb,"ACTFM");
67       #1ms;
68       $shm_close;
69     end
70 
71 endmodule
top_tb

   需要注意是在定义class前,如果这个class会使用到其他class,最好在前面加type class <class name>。例如在class my_sequence extends uvm_sequence前一行加上type class my_transaction。否则如果my_sequence在my_transaction之前编译,就会报错。虽然可以通过在testbench top中先include my_transaction.sv解决,但是大大降低了代码的重用性。

四、测试用例及仿真

 1 `ifndef MY_CASE0__SV
 2 `define MY_CASE0__SV
 3 class case0_sequence extends uvm_sequence #(my_transaction);
 4    my_transaction m_trans;
 5 
 6    function  new(string name= "case0_sequence");
 7       super.new(name);
 8    endfunction 
 9    
10    virtual task body();
11       if(starting_phase != null) 
12          starting_phase.raise_objection(this);
13       repeat (10) begin
14          `uvm_do(m_trans)
15       end
16       #100;
17       if(starting_phase != null) 
18          starting_phase.drop_objection(this);
19    endtask
20 
21    `uvm_object_utils(case0_sequence)
22 endclass
23 
24 
25 class my_case0 extends base_test;
26 
27    function new(string name = "my_case0", uvm_component parent = null);
28       super.new(name,parent);
29    endfunction 
30    extern virtual function void build_phase(uvm_phase phase); 
31    `uvm_component_utils(my_case0)
32 endclass
33 
34 
35 function void my_case0::build_phase(uvm_phase phase);
36    super.build_phase(phase);
37 
38    uvm_config_db#(uvm_object_wrapper)::set(this, 
39                                            "env.i_agt.sqr.main_phase", 
40                                            "default_sequence", 
41                                            case0_sequence::type_id::get());
42 endfunction
43 
44 `endif
my_case0

  所用的测试用例都扩展自自定义的base_test,后者又来自uvm_test。base_test例化整个UVM environment,用例中主要要做的事情就是启动sequence, 包括调用start任务手动启动和自动启动方式,具体见参考文献3.这里是最常见的自动启动方式:用uvm_config_db将要启动的sequence设置为sequencer main_phase的default_sequence.

  每个sequence中都有个叫body的task,当sequence被启动时会自动调用这个task。通过`uvm_do宏来产生transaction。更灵活的方式是先后使用`uvm_create()和`uvm_send()实现这一功能,并在两者间控制transaction的各个field。只有当消耗仿真时间的driver调用了item_done()后一次transaction的发送才算结束。

五、总结

   我们不创造知识,我们只是知识的搬运工。将知识灵活运用,创造出合理高效可重用的VIP,验证环境乃至整个验证流程方法是IC验证的核心技能,这些技能都是为尽可能快速发现潜在问题这一核心任务做的准备。

六、参考文献

1 UVM——基础类结构图(uvm树、常用继承关系结构)https://blog.csdn.net/weixin_46022434/article/details/105838815

2 UVM phase机制 https://blog.csdn.net/qq_41394155/article/details/81914826

3 UVM中启动sequence的方法 https://aijishu.com/a/1060000000132327

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/moluoqishi/p/14289288.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!