UVM系統驗證基礎知識1(基於UVM的verilog驗證 )

分類:IT技術 時間:2016-10-11

需求說明:UVM系統驗證

內容       :IC設計驗證

來自       :時間的詩

原文:http://www.cnblogs.com/bettty/p/5285785.html

Abstract

本文介紹UVM框架,並以crc7為例進行UVM的驗證,最後指出常見的UVM驗證開發有哪些坑,以及怎麽避免。


Introduction

本例使用環境:ModelSim 10.2c,UVM-1.1d,Quartus II 13.1(64 bit),器件庫MAX V


1. UVM介紹

對UVM結構熟悉的讀者可跳過本節。

叫UVM“框架”可能並不確切(只是便於理解,可類比軟件界的“框架”)。UVM全稱為通用驗證方法論。在硬件開發過程中,驗證是十分重要的環節。可以說,左手開發,右手驗證。在歷史上,為了實現通用化的驗證,前人摸爬滾打,創造出了UVM這一套框架。UVM前身是OVM,兩者都是Accellera提出,UVM在OVM的基礎上有所改進。

本文旨在用一種簡單的方式介紹UVM的結構。期望讀者能夠讀完本文後,成功搭建一個完整的UVM驗證系統。


Part 1:UVM的功能

請看下圖,一個典型的testbench驗證過程如圖所示。即,我們寫testbench,將激勵信號傳入DUT(待驗證模塊),然後觀察輸出波形,或者查看輸出結果,是否和預期的一致。通過這樣的過程,我們判斷我們編寫的Verilog是否正確。

請看下圖,UVM如同一個管家,將“輸入激勵”和“觀察波形”的動作管理了起來。基於UVM進行開發,UVM提供了很多機制,也能夠快速的產生我們想要輸入的激勵。

問題是,我們完全可以使用testbench解決問題,為什麽還要使用UVM呢?

UVM是一個通用驗證平臺,基於它,我們可以產生復雜、大量、可定制化的隨機激勵,並可以提高大型驗證工程的協作性和擴展性。舉個例子,UVM框架就像軟件開發的分層結構,定義好了統一的接口,那麽,各個層次就可以交給各個團隊來開發。驗證項目也是如此,產生激勵的工程如果有改動,並不會影響“觀察波形”(實質是觀察結果)的團隊。實際上,UVM分的更細,它將各個流程都拆分開來,包括transaction、driver、sequence、sequencer、monitor、agent、test、env、top等部分。此外,UVM提供了優秀的factory機制、objection機制、reg機制,為我們簡化開發過程。比如,reg機制就封裝了我們在硬件開發中讀寫寄存器的一些操作。我們調用UVM的函數,就能夠迅速的開發讀寫reg的過程。

 

Part 2:UVM的結構

如前所述,UVM包括transaction、interface、driver、sequence、sequencer、monitor、reference model、agent、test、env、top等部分,其相互關系極其復雜。不如說,UVM犧牲簡潔性換來“通用”性。

借用Pedro Araujo的結構圖。

 

1) 正如這個圖片所展示的,UVM是除了DUT(待驗證模塊)的其他所有部分。其中,sequencer產生sequence(圖上沒畫),sequence產生transaction。

transaction,類似於軟件中的一個package。在硬件中,以一個transaction為單位進行傳輸,一個完整的transaction傳輸結束,才拉高或拉低電平。

2)通過UVM的專門的類型——port把數據給driver。driver通過interface把產生的激勵(也就是transaction)輸入DUT。同時,DUT的輸出也是和interface相連接的。一個monitor(monitor after)監測driver吐給DUT的輸入,一個monitor(monitor before)監測DUT吐出來的輸出。

3) 這裏,看到一個agent把整個monitor、sequencer、driver都裝起來了。這個agent實現的功能是轉換。因為整個UVM都是systemverilog的,並且理論上是仿真的,都不是“硬件”。DUT在這裏是真正的“硬件”。兩者之間不能直接通信,只能通過一個agent,來對協議進行轉換。不過,我們可以不用管agent怎麽實現的。在開發的時候,只要把相關的模塊連接好就行了。

4)這個圖還少了一個reference model。因為reference model的工作在這個例子中,實際是在monitor after裏面實現的。- -不過沒關系。reference model完成的工作是,把DUT做的事完全的再做一遍。reference model接入monitor采到的輸入激勵,按照DUT的邏輯,產生一個結果。

5)同樣通過port,把reference model產生的結果同monitor before采到的數據都丟到scoreboard上。在scoreboard上,我們會對兩個結果進行比較。如果兩個結果一致,則為正確。如果不一致,則為錯誤。

 UVM本身用700頁來寫也完全可以。因為UVM極其復雜。本文不在此贅述。


2. 以crc7為例進行UVM的驗證

Part 1:搭建環境。

本文使用的Quartus II 13.1(64 bit),器件庫MAX V。寫了一個Verilog的簡單的crc7。

仿真環境是ModelSim 10.2c。雖說自帶UVM庫。但是,沒找到Modelsim自帶的uvm_dpi.dll,於是,還重新編譯了一番。

本文在win 10下。下載uvm-1.1d(現在最新版本有1.2d了),放好。安裝好ModelSim 10.2c後,在命令提示符>後,輸入

編輯環境變量

1 2 set UVM_HOME c:/tool/uvm-1.1d set MODEL_TECH c:/modeltech_10.2c/win32

編譯UVM_DPI動態鏈接庫。編好一次就不用再編了。

1 c:/modeltech_10.2c/gcc-4.2.1-mingw32vc9/bin/g++.exe -g -DQUESTA -W -shared -Bsymbolic -I $MODEL_TECH/../include $UVM_HOME/src/dpi/uvm_dpi.cc -o $UVM_HOME/lib/uvm_dpi.dll $MODEL_TECH/mtipli.dll -lregex

 

 Part 2:編寫待驗證模塊。

module crc7(clk,
    rst,
    data,
    crc);
input wire clk;
input wire rst;
input wire data;
output reg[6:0] crc;

reg g0;
assign g0 = data ^ crc[6];

always @(posedge rst or negedge clk)
if (rst)
begin
    crc <= 7'b0000000;
end
else
begin
    crc[6] <= crc[5];
    crc[5] <= crc[4];
    crc[4] <= crc[3];
    crc[3] <= crc[2] ^ g0;
    crc[2] <= crc[1];
    crc[1] <= crc[0];
    crc[0] <= g0;
end

endmodule

在Quartus中編譯通過。


Part 3:編寫驗證代碼。

在Modelsim中新建一個項目,新建如圖所示多個.sv文件。

crc7_tb_top.sv如下所示

 `include "uvm_pkg.sv"
 `include "crc7_pkg.sv"
 `include "crc7.v"
 `include "crc7_if.sv"
 
 module crc7_tb_top;
   import uvm_pkg::*;
   import crc7_pkg::*;
   
   //interface declaration
   crc7_if vif();
   
   //connect the interface to the DUT
   crc7 dut(vif.sig_clk,
   vif.sig_rst,
   vif.sig_data,
   vif.sig_crc);
   
   initial begin
     uvm_resource_db#(virtual crc7_if)::set
       (.scope("ifs"), .name("crc7_if"), .val(vif));
       
     run_test("crc7_test");
   end
   
   initial begin
     vif.sig_clk <= 1'b1;
   end
   
   always
     #5 vif.sig_clk =~ vif.sig_clk;
 endmodule


crc7_monitor.sv如下所示 

   class crc7_monitor_before extends uvm_monitor;
   `uvm_component_utils(crc7_monitor_before)
   
   uvm_analysis_port#(crc7_transaction) mon_ap_before;
   
   virtual crc7_if vif;
   
   function new(string name, uvm_component parent);
     super.new(name, parent);
   endfunction: new
   
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
     
     void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
     mon_ap_before = new(.name("mon_ap_before"), .parent(this));
   endfunction: build_phase
   
   task run_phase(uvm_phase phase);
     crc7_transaction c7_tx;
     c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
     
     forever begin
       @(negedge vif.sig_clk)
       begin
         c7_tx.crc = vif.sig_crc;
         `uvm_info("monitor_before",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);
         mon_ap_before.write(c7_tx);
       end
     end
   endtask: run_phase
 endclass: crc7_monitor_before
 
 class crc7_monitor_after extends uvm_monitor;
   `uvm_component_utils(crc7_monitor_after)
   
   uvm_analysis_port#(crc7_transaction) mon_ap_after;
   
   virtual crc7_if vif;
   
   crc7_transaction c7_tx;
       //For coverage
     crc7_transaction c7_tx_cg;
 
     //Define coverpoints
     covergroup crc7_cg;
     endgroup: crc7_cg
    
   function new(string name, uvm_component parent);
     super.new(name, parent);
     crc7_cg = new;
   endfunction: new
   
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
     
     void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
     mon_ap_after = new(.name("mon_ap_after"), .parent(this));
   endfunction: build_phase
   
   task run_phase(uvm_phase phase);
     integer count = 42, rst = 0;
     
     c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
     
     forever begin
       @(negedge vif.sig_clk)
       begin
         rst = 0;
         count = count - 1;
         if(count == 0)
           begin
             rst = 1;
             count = 42;
             predictor();
             `uvm_info("monitor_after",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);
             c7_tx_cg = c7_tx;
           
             crc7_cg.sample();
           
             mon_ap_after.write(c7_tx);
           end
         end
       end
 
 endtask: run_phase
 
 virtual function void predictor();
   c7_tx.crc = 7'b0101010;
 endfunction: predictor
 endclass: crc7_monitor_after


crc7_sequencer.sv如下所示

 class crc7_transaction extends uvm_sequence_item;
   bit[6:0] crc;
   
   function new(string name = "");
     super.new(name);
   endfunction: new
   
   `uvm_object_utils_begin(crc7_transaction)
     `uvm_field_int(crc, UVM_ALL_ON)
   `uvm_object_utils_end
 endclass: crc7_transaction
 
 class crc7_sequence extends uvm_sequence#(crc7_transaction);
   `uvm_object_utils(crc7_sequence)
   
   function new(string name = "");
     super.new(name);
   endfunction: new
   
   task body();
     crc7_transaction c7_tx;
     
     repeat (1) begin
       c7_tx = crc7_transaction::type_id::create(.name("c7_tx"), .contxt(get_full_name()));
 
       start_item(c7_tx);
       assert(c7_tx.randomize());
       finish_item(c7_tx);
       //c7_tx.end_event.wait_on();
     end
   endtask: body
 endclass: crc7_sequence
 
 typedef uvm_sequencer#(crc7_transaction) crc7_sequencer;
   


crc7_driver.sv如下所示

 class crc7_driver extends uvm_driver#(crc7_transaction);
   `uvm_component_utils(crc7_driver)
   
   virtual crc7_if vif;
   
   function new(string name, uvm_component parent);
     super.new(name, parent);
   endfunction: new
   
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
     
     void'(uvm_resource_db#(virtual crc7_if)::read_by_name(.scope("ifs"), .name("crc7_if"), .val(vif)));
   endfunction: build_phase
   
   task run_phase(uvm_phase phase);
     drive();
   endtask: run_phase
   
   virtual task drive();
     crc7_transaction c7_tx;
     integer counter = 40;
     bit[39:0] data;
     data = http://blog.csdn.net/times_poem/article/details/40'b0101000100000000000000000000000000000000; 
     vif.sig_data = 1'b0;
     vif.sig_rst = 1'b0;
     
     
     forever begin
       
       //seq_item_port.get_next_item(c7_tx);
       
       @(negedge vif.sig_clk)
         begin
           vif.sig_rst = 1'b0;
           vif.sig_data = data[counter - 1];
           counter = counter - 1;
           if(counter == 0) begin
             #28 counter = 40;
             vif.sig_rst = 1'b1;
             end
         end         
           //seq_item_port.item_done();
       end
   endtask: drive
 endclass: crc7_driver
   


(其余代碼請在文章末尾下載。 )

其中,crc7_tb_top.sv第31行:

#5 vif.sig_clk =~ vif.sig_clk;

 表示每5個時鐘單位反向一次。即時鐘周期為10ns。

crc7_monitor.sv中第1行和第34行,分別新建一個名為crc7_monitor_before和crc7_monitor_after的類。第26行,

c7_tx.crc = vif.sig_crc;

crc7_monitor_before類采集DUT輸出的信號。本測試用例中只有一個輸出信號,即計算出的crc。vif是interface類的實例。前文中說過,monitor是從interface上采集的。

第27行,

`uvm_info("monitor_before",$sformatf("c7_tx.crc is '%b'", c7_tx.crc), UVM_LOW);

使用UVM提供的宏打印格式化的數據。UVM提供的宏可類比軟件中的庫函數。就是無需聲明,只管調用。

第28行,

mon_ap_before.write(c7_tx);

將采集到的數據寫入ap中。ap是什麽?ap是UVM中的port類型之一,叫analysis_port。簡單說就像硬件的一個接口,協議UVM已經搞好了。我們只管讀寫就好了。

從第66行到第84行,都是本來應該在reference model裏面實現的。就是說,從結構上來說,monitor采集到了driver發到interface上的信號,會丟給reference model來計算出一個結果(這是比較標準的結構)。本例中,由於reference model要做的事情太簡單,就直接放在monitor裏面做了。當monitor_after采集到了輸入激勵後,就在自己內部算了一把,然後把結果寫出去。這就是66-84行所起的作用。由於crc7的運算結果可以查表。本例就直接查了表。也就是說,UVM完全可以實現輸入激勵的隨機化。但是本例沒用。本例使用了一對輸入/輸出數據。並且在第88行,直接把輸出數據返回了。這對輸入/輸出數據為:40'b0101000100000000000000000000000000000000;7'b0101010;

同樣,在第81行,

mon_ap_after.write(c7_tx);

把正確的結果寫入ap。註意,ap是成對的。有寫就有讀。

crc7_sequencer.sv中,第26-28行,

26       start_item(c7_tx);
27       assert(c7_tx.randomize());
28       finish_item(c7_tx)

是規定動作。有開始就有結束。隨機化也是必須寫的,缺一不可。

crc7_driver.sv中第33行至42行,

復制代碼
33       @(negedge vif.sig_clk)
34         begin
35           vif.sig_rst = 1'b0;
36           vif.sig_data = http://blog.csdn.net/times_poem/article/details/data[counter - 1];
37           counter = counter - 1;
38           if(counter == 0) begin
39             #28 counter = 40;
40             vif.sig_rst = 1'b1;
41             end
42         end      
復制代碼

完成的工作是,每一個時鐘節拍打出data的1位。data見第24行。本來應該由sequence產生隨機激勵的,由於本例使用的是查表驗證,因此,並沒有使用UVM的隨機功能。因此,要隨機的數據沒有在sequence裏面寫進去,要打入的數據在driver模塊中寫入了vif。這樣完成了設定數據的輸入。


Part 4:編譯仿真。

編譯crc7_tb_top.sv,

vlog +incdir+C:/modeltech_10.2c/verilog_src/uvm-1.1d/src -L mtiAvm -L mtiOvm -L mtiUvm -L mtiUPF crc7_tb_top.sv

crc7_tb_top是整個工程的入口。

仿真crc7_tb_top,

vsim -ldflags "-lregex" -t 1ns -c -sv_lib c:/modeltech_10.2c/uvm-1.1d/win32/uvm_dpi work.crc7_tb_top

vsim的-ldflags參數是將後面的字符串“-lregex”作為參數傳入到mingw裏面。mingw會原封不動的傳入給其調用的g++。至於為什麽要這麽做?因為按照命令,

vsim -c -sv_lib $UVM_HOME/lib/uvm_dpi work.hello_world_example  

編譯的時候報錯了,提示找不到uvm_dpi。明明編譯了放在對的地方,可是就是找不到。於是,將路徑寫死在命令裏。成功仿真。

添加波形,得到波形如圖(截取部分):

 查看打印結果。仿真結果和正確值相比,如圖所示:

其中,每一個時鐘周期打印的是,當前時刻移位後的crc7值。在40個bit結束後,移位結果為0101010,正確結果也是0101010,因此,compare OK。


3. UVM驗證開發常見有哪些坑?怎樣避免。


Q1:為什麽狀態機走入了未預料的分支?

在時鐘邊沿采集與時鐘同樣跳變的信號時,這一瞬間采集到的信號可能是高,也可能是低。此時,若做邏輯判斷,可能出現無法預料的情況。

解決的方法是,加入其它邏輯判斷條件,確保程序邏輯正確。


Q2:為什麽reference model和DUT的結果計算時序不一致?

一般來說,一組數據經過DUT的處理,會有一定的時延。而reference model沒有時延。我們希望在同一時刻,對reference model和DUT計算的結果在scoreboard中比較,則須考慮兩者運行的時間差。

有兩種方法解決:一是手動添加時延,比較簡單,但是有可能出錯;二是使用UVM的FIFO機制,把先到達的reference model輸出的數據放入到一個隊列中。確保仿真時間結束後,用來比較的兩個結果都是基於同樣的激勵輸入。

 

本文用例完整代碼下載:uvm-crc-test.zip



Tags: 基礎知識 如圖所示 通用 讀者 管家

文章來源:


ads
ads

相關文章
ads

相關文章

ad