歡迎關注個人公眾號摸魚范式
本期將講解UVM環境構成和啟動方式。主要參考資料為
http://bbs.eetop.cn/thread-320165-1-1.html
http://rockeric.com/
環境構成
進行仿真驗證的基本流程是
- 例化DUT
- 產生並發送激勵
- 檢測響應
- 檢查響應是否正確
在驗證環境中,產生並發送激勵將會交給兩個不同的類完成,即uvm_driver和uvm_sequence,檢測響應通過uvm_monitor完成,而檢查響應是否正確通過uvm_scoreboard。除了保證某項功能正確,我們還需要能夠確保spec中的每一項功能都通過測試,而衡量驗證完備性的指標之一就是功能覆蓋率,在我們的驗證環境中收集功能覆蓋率的任務則交給了conv_coverage實現。
接下來將以數據從uvm_driver驅動到DUT,再從DUT到uvm_monitor,再到uvm_scoreboard的順序講解驗證環境的構成。
接口定義
當我們需要進行仿真驗證時,與DUT的交互是一個必要的內容,所我們首先分析DUT的接口,較為簡單,一共有四組接口,一組寄存器配置接口,三組數據接口用於輸入特征圖、權重和偏置數據的讀取,一組數據接口用於輸出特征圖的存儲接口。
interface的定義在頂層的tb.sv中,三組輸入數據幾口可以使用同一類型的接口實現,下列代碼中的具體內容省略了,詳情請自行查看。最后一組接口用於檢測寄存器的內容,當前版本沒有使用寄存器模型,所以這個接口是必要的。
interface cfg_intf (input clk , input rst_n);
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
endinterface
interface mem_in_intf (input clk , input rst_n);
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
endinterface
interface mem_out_intf (input clk , input rst_n);
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
endinterface
interface conv_intf (input clk , input rst_n);
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
endclocking
endinterface
注意在interface定義中,分別定義了兩個時鍾塊,一組驅動用的時鍾塊,一組檢測用的時鍾塊,目的就是為了模擬真實的建立保持時間,時鍾塊的具體用法可以參考綠皮書的第四章內容。
環境組件
有了接口定義以后,通過接口定義,我們便能夠與DUT交互,那么進行交互我們需要做什么呢?
首先看整體結構,如果看不清,后台回復UVM結構圖獲取VISIO文件。按照接口進行分類,可以分成兩大類,一類通過接口與DUT實現交互,另外一類構成了其他的組件,例如checker,通過其他組件收集到的數據進行數據比對,保證DUT的功能正確性。
驗證環境一共由四個PKG組成,通過在頂層import導入:
import cfg_pkg::*;
import mem_in_pkg::*;
import mem_out_pkg::*;
import conv_pkg::*;
與DUT直接聯系的組件
我們從DUT與驗證環境的接口處開始說起,cfg_pkg包含了對於寄存器進行驗證的組件。構建UVM環境基本的幾個組件包括uvm_driver,uvm_sequencer,uvm_monitor,uvm_agent。而uvm_sequence_item和uvm_sequence則不屬於環境的組件,他們是環境組件之間傳遞信息的載體。
package cfg_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
typedef enum {WR,RD,IDLE}cmd_t;
class cfg_item extends uvm_sequence_item;
endclass : cfg_item
class cfg_driver extends uvm_driver #(cfg_item);
endclass :cfg_driver
class cfg_sequencer extends uvm_sequencer #(cfg_item);
endclass: cfg_sequencer
class cfg_base_sequence extends uvm_sequence #(cfg_item);
endclass: cfg_conv_sequence
class cfg_monitor extends uvm_monitor;
endclass: cfg_monitor
class cfg_agent extends uvm_agent;
cfg_driver driver;
cfg_monitor monitor;
cfg_sequencer sequencer;
local virtual cfg_intf vif;
endclass:cfg_agent
endpackage
UVM的思想之一就是要降低組件之間的耦合度,讓組件的功能更加單純。
uvm_driver和uvm_monitor是距離DUT最近的兩個組件,可以直接與DUT的接口進行互動。
uvm_monitor通過檢測接口上的信號,轉化為數據包,如實地發送給checker,只實現這一單純的功能,而對於驅動DUT這一功能則交給uvm_driver。
uvm_driver通過從uvm_sequencer獲取到的uvm_sequence_item解析出驅動數據,如實的將uvm_sequence_item的內容驅動到DUT的接口上,也只實現這一單純的功能,至於具體的激勵內容,則通過uvm_sequencer暴露接口給頂層環境,讓驗證人員通過uvm_sequencer發送激勵。
而uvm_sequencer的功能就更加簡單了,只需要實現傳遞uvm_sequence_item即可,不需要關注其他的工作。
而uvm_agent中則通常會例化四個組件,uvm_driver,uvm_sequencer,uvm_monitor和對應的interface。uvm_agent的功能也非常單一,僅僅只是將對於一組接口的相關組件進行一個打包,把他們整合起來,這樣在頂層就只需要例化這一個組件即可。
mem_in_pkg和mem_out_pkg的內容整體上和cfg_pkg基本一致,不再贅述。
與DUT沒有直接聯系的組件
通過cfg_pkg的內容,我們實現了對DUT的驅動與檢測,那么驅動的內容從何而來,而檢測的數據包又要發送到哪里去呢?從前面圖中我們可以看到,除了五個agent以外,我們還有其他的組件,包括conv_checker,conv_coverage和conv_virtual_sequencer。
class conv_checker extends uvm_scoreboard;
endclass:conv_checker
class conv_coverage extends uvm_component;
endclass:conv_coverage
class conv_virtual_sequencer extends uvm_sequencer;
cfg_sequencer cfg_sqr;
mem_in_sequencer fmi_sqr;
mem_in_sequencer wt_sqr;
mem_in_sequencer bias_sqr;
endclass:conv_virtual_sequencer
conv_checker繼承自uvm_scoreboard,他通過前面所述的五個agent中的monitor,獲取DUT的信息,進行數據對比檢查。在現在的DUT中,他所實現的功能是,在一次卷積運算結束后,使用軟件算法直接進行卷積運算,然后與DUT的計算結果進行對比,確保DUT功能正確。
UVM並沒有預置的類用於覆蓋率收集,所以conv_coverage繼承自uvm_component,成為最簡單的UVM組件。由於除了寄存器的接口以外,其他都是簡單得sram接口,所以只對寄存器進行覆蓋率收集。覆蓋率組件通過覆蓋率的收集,量化功能驗證的完備性,根據對應的功能點,設定對應的覆蓋率,而功能驗證的目的就是為了達到100%的功能覆蓋率。
conv_virtual_sequencer繼承自uvm_sequencer,其本身並沒有什么功能,所以他的名字中帶有virtual,他只是一個虛擬的sequencer。其內部包含了cfg_sqr,fmi_sqr,wt_sqr和bias_sqr,作用就是將他們整合在一起,sequencer就是一根數據線,uvm_sequence_item就是傳輸的數據,而virtual_sequencer就一個集線器或者說一個拓展塢,把很多條數據線綁在一起。
conv_env
數據的驅動由uvm_driver實現,檢測由uvm_monitor實現,激勵由uvm_sequencer傳遞,數據對比由conv_checker實現,覆蓋率收集由conv_coverage實現。那么接下來就需要把這些組件全部整合在一起,成為一個驗證環境,這便是conv_env。在這里,我們只需要完成各個組件的例化和他們之間的連接,不要關心其他工作。
class conv_env extends uvm_env;
cfg_agent cfg_agt;
mem_in_agent fmi_agt;
mem_in_agent wt_agt;
mem_in_agent bias_agt;
mem_out_agent fmo_agt;
conv_checker chker;
conv_coverage cvrg;
conv_virtual_sequencer virt_sqr;
endclass: conv_env
uvm_test
現在我們已經獲得了一個針對卷積模塊的DUT,那么如何開始仿真測試?回憶前面所提到的virtual_sequencer,我們只需要通過virtual_sequencer對每個DUT的接口進行驅動,就能讓DUT運轉起來。針對每一個測試,我們需要創建對應的uvm_test類,然后再uvm_test內通過virtual_sequencer進行激勵發送即可。
class conv_base_test extends uvm_test;
conv_env env;
task run_phase(uvm_phase phase);
phase.raise_objection(this);
this.run_top_virtual_sequence();
phase.drop_objection(this);
endtask
virtual task run_top_virtual_sequence();
endtask
endclass: conv_base_test
上述代碼中的run_phase的內容就是在構建環境后,整個仿真真正需要進行的測試內容。可以看到我們定義了一個run_top_virtual_sequence方法,用於運行virtual_sequence。
與virtual_sequencer對應的,virtual_sequence就是virtual_sequencer所需要傳輸的內容,它的內部會包括各式各樣的sequence,針對每一個agent發送不同的激勵。通過修改virtual_sequence的內容,我們就能夠完成不同的測試用例。
啟動方式
這里先不討論整個環境的樹狀結構、連接方式和運行機制,這些將在后續的推送中實現。
在構建完整個環境和測試用例以后,我們就需要在頂層啟動測試。
module tb ();
logic clk;
logic rst_n;
conv i_conv ();//這里省略了端口連接,具體請參考實驗代碼
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rst_n <= 0;
repeat(10) @(posedge clk);
rst_n <= 1;
end
import uvm_pkg::*;
`include "uvm_macros.svh"
import cfg_pkg::*;
import mem_in_pkg::*;
import mem_out_pkg::*;
import conv_pkg::*;
cfg_intf cfg_if(.*);
mem_in_intf fmi_if(.*);
mem_in_intf wt_if(.*);
mem_in_intf bias_if(.*);
mem_out_intf fmo_if(.*);
conv_intf conv_if(.*);
assign conv_if.start =i_conv.i_regfile.start ;
assign conv_if.done =i_conv.i_regfile.done ;
assign conv_if.fmap_in_w =i_conv.i_regfile.fmap_in_w ;
assign conv_if.fmap_in_h =i_conv.i_regfile.fmap_in_h ;
assign conv_if.fmap_in_ch_div_32 =i_conv.i_regfile.fmap_in_ch_div_32 ;
assign conv_if.k_w =i_conv.i_regfile.k_w ;
assign conv_if.k_h =i_conv.i_regfile.k_h ;
assign conv_if.fmap_out_w =i_conv.i_regfile.fmap_out_w ;
assign conv_if.fmap_out_h =i_conv.i_regfile.fmap_out_h ;
assign conv_if.fmap_out_w_div_32 =i_conv.i_regfile.fmap_out_w_div_32 ;
assign conv_if.fmap_out_ch_div_32 =i_conv.i_regfile.fmap_out_ch_div_32 ;
assign conv_if.pooling_bypass =i_conv.i_regfile.pooling_bypass ;
assign conv_if.act_bypass =i_conv.i_regfile.act_bypass ;
assign conv_if.padding_cnt =i_conv.i_regfile.padding_cnt ;
assign conv_if.stripe =i_conv.i_regfile.stripe ;
assign conv_if.last_pixel =i_conv.i_regfile.last_pixel ;
assign conv_if.last_pixel_div_32 =i_conv.i_regfile.last_pixel_div_32 ;
assign conv_if.fmap_out_ch =i_conv.i_regfile.fmap_out_ch ;
assign bias_if.addr[15:8]='0;
initial begin
uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "fmi_in_vif", fmi_if);
uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "wt_vif", wt_if);
uvm_config_db#(virtual mem_in_intf)::set(uvm_root::get(), "uvm_test_top", "bias_vif", bias_if);
uvm_config_db#(virtual cfg_intf)::set(uvm_root::get(), "uvm_test_top", "cfg_vif", cfg_if);
uvm_config_db#(virtual mem_out_intf)::set(uvm_root::get(), "uvm_test_top", "fmo_vif", fmo_if);
uvm_config_db#(virtual conv_intf)::set(uvm_root::get(), "uvm_test_top", "conv_vif", conv_if);
// If no external configured via +UVM_TESTNAME=my_test, the default test is
// std_test
run_test("std_test");
end
endmodule : tb
在tb的頂層模塊中,我們要做5件事:
- 定義時鍾與復位
- 例化dut
- 例化與連接各個interface
- 將每個interface句柄通過uvm_config_db傳遞到環境中去
- 通過run_test()方法啟動測試
uvm_config_db是UVM所提供用於傳遞數據的靜態方法,在后續的推送中將會展開講解。這里值得注意的是,一定要在run_test()之前實現uvm_config_db傳遞,否則在run_test()開始后,環境內部將無法獲取句柄,導致報錯。
run_test()是UVM提供的測試啟動方法,傳遞參數是一個字符串變量,該字符串將用於指定默認的testcase。如果在命令選項中,沒有通過+UVM_TESTNAME指定具體的TESTNAME,將會運行默認的testcase。
總結
本次講解了驗證環境的基本組件和構成,以及在頂層啟動的注意事項。
- 與DUT直接交互的組件為uvm_driver和uvm_monitor,傳遞激勵信息的組件為uvm_sequencer,uvm_agent將三者組合起來。
- uvm_scoreboard獲取各個uvm_monitor傳遞過來的數據,進行比對,保證DUT功能的正確性。
- conv_coverage用於收集覆蓋率
- conv_virtual_sequencer將每個uvm_agent中的uvm_sequencer集中起來進行管理,起到集線器或者說路由器的效果
- conv_env將上述所有組件容納起來,並且進行連接
- uvm_test通過編寫conv_virtual_sequence,經由conv_virtual_sequencer發送激勵實現不同的testcase
- 在頂層實現各項例化,並且在run_test()之前傳遞接口句柄
- 通過run_test()啟動測試,並且指定默認testcase
更多內容請期待后續推送,實驗代碼在后台回復“UVM實驗”即可獲得資源。