需求說明: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: 基礎知識 如圖所示 通用 讀者 管家
文章來源: