UVM基礎總結——基於《UVM實戰》示例

1、前言網絡

  工做一直在作SoC驗證,更關注模塊間的鏈接性和匹配性,因此相比於擅長隨機約束激勵的UVM來講,定向測試的概念更容易debug。固然前提是IP已經被充分驗證。所以以爲接觸UVM的機會較少。到如今發現即便在SoC驗證中依然有它的用武之地。好比驗證可獨立於CPU工做的IP、快速對系統性能進行評估、重用IP級別的驗證環境,甚至是一些通用的VIP也有基於UVM編寫的。基於這些考量,也逐漸開始接觸。《UVM實戰》是不少驗證工程師的啓蒙,本文借用書中開頭的示例簡單梳理下UVM的基本知識。app

2、UVM基礎概述dom

  關於UVM的知識網絡上已經鋪天蓋地了,下邊的內容只是本身的一些認識和隨記。UVM其實就是基於SV語言寫的用於驗證的代碼庫和對應的驗證規範。下圖是UVM驗證環境的總體結構(圖來源見參考文獻1)。圖中註釋介紹了每一個組成部分的做用。《UVM實戰》書中給我留下最深入的印象就是用子彈、彈夾和手槍分別類比transaction sequence和sequencer,這也是UVM環境靈活的重要緣由之一。這是激勵的產生機制,至於響應的採集和響應任務會交給monitor和scoreboard。後者中的指望數據或者參考數據的來源比較靈活,函數、文件或是reference model的響應都可。ide

  以上是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)post

  弄明白這三張圖,也就是組件、組件間通訊和phase機制,基本上能夠看懂別人寫的代碼了。性能

3、驗證環境示例測試

  各個UVM object和component:ui

sequence:this

 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解決,可是大大下降了代碼的重用性。

4、測試用例及仿真

 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的發送纔算結束。

5、總結

   咱們不創造知識,咱們只是知識的搬運工。將知識靈活運用,創造出合理高效可重用的VIP,驗證環境乃至整個驗證流程方法是IC驗證的核心技能,這些技能都是爲儘量快速發現潛在問題這一核心任務作的準備。

6、參考文獻

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

相關文章
相關標籤/搜索