UVM基础总结——基于《UVM实战》示例

CMU数据库(15-445)Lab1-BufferPoolManager

一、前言

  工作一直在做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:

天天用SpringBoot居然还不知道它的自动装配的原理?

 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

apijson简单使用

相关推荐

发表评论

路人甲

网友评论(0)