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
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
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
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
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
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
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
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
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
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
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
須要注意是在定義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
所用的測試用例都擴展自自定義的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