本期將講解 UVM 環(huán)境構(gòu)成和啟動方式。主要參考資料為
[白皮書]: http://bbs.eetop.cn/thread-320165-1-1.html
[紅寶書]: http://rockeric.com/
環(huán)境構(gòu)成
進(jìn)行仿真驗(yàn)證的基本流程是
1. 例化 DUT
2. 產(chǎn)生并發(fā)送激勵
3. 檢測響應(yīng)
4. 檢查響應(yīng)是否正確
在驗(yàn)證環(huán)境中,產(chǎn)生并發(fā)送激勵將會交給兩個不同的類完成,即 uvm_driver 和 uvm_sequence,檢測響應(yīng)通過 uvm_monitor 完成,而檢查響應(yīng)是否正確通過 uvm_scoreboard。除了保證某項(xiàng)功能正確,我們還需要能夠確保 spec 中的每一項(xiàng)功能都通過測試,而衡量驗(yàn)證完備性的指標(biāo)之一就是功能覆蓋率,在我們的驗(yàn)證環(huán)境中收集功能覆蓋率的任務(wù)則交給了 conv_coverage 實(shí)現(xiàn)。
接下來將以數(shù)據(jù)從 uvm_driver 驅(qū)動到 DUT,再從 DUT 到 uvm_monitor,再到 uvm_scoreboard 的順序講解驗(yàn)證環(huán)境的構(gòu)成。
接口定義
當(dāng)我們需要進(jìn)行仿真驗(yàn)證時,與 DUT 的交互是一個必要的內(nèi)容,所我們首先分析 DUT 的接口,較為簡單,一共有四組接口,一組寄存器配置接口,三組數(shù)據(jù)接口用于輸入特征圖、權(quán)重和偏置數(shù)據(jù)的讀取,一組數(shù)據(jù)接口用于輸出特征圖的存儲接口。
interface 的定義在頂層的 tb.sv 中,三組輸入數(shù)據(jù)接口可以使用同一類型的接口實(shí)現(xiàn),下列代碼中的具體內(nèi)容省略了,詳情請自行查看。最后一組接口用于檢測寄存器的內(nèi)容,當(dāng)前版本沒有使用寄存器模型,所以這個接口是必要的。
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 定義中,分別定義了兩個時鐘塊,一組驅(qū)動用的時鐘塊,一組檢測用的時鐘塊,目的就是為了模擬真實(shí)的建立保持時間,時鐘塊的具體用法可以參考綠皮書的第四章內(nèi)容。
環(huán)境組件
有了接口定義以后,通過接口定義,我們便能夠與 DUT 交互,那么進(jìn)行交互我們需要做什么呢?
首先看整體結(jié)構(gòu),如果看不清,后臺回復(fù) UVM 結(jié)構(gòu)圖獲取 VISIO 文件。按照接口進(jìn)行分類,可以分成兩大類,一類通過接口與 DUT 實(shí)現(xiàn)交互,另外一類構(gòu)成了其他的組件,例如 checker,通過其他組件收集到的數(shù)據(jù)進(jìn)行數(shù)據(jù)比對,保證 DUT 的功能正確性。
驗(yàn)證環(huán)境一共由四個 PKG 組成,通過在頂層 import 導(dǎo)入:
import cfg_pkg::*;
import mem_in_pkg::*;
import mem_out_pkg::*;
import conv_pkg::*;
與 DUT 直接聯(lián)系的組件
我們從 DUT 與驗(yàn)證環(huán)境的接口處開始說起,cfg_pkg 包含了對于寄存器進(jìn)行驗(yàn)證的組件。構(gòu)建 UVM 環(huán)境基本的幾個組件包括 uvm_driver,uvm_sequencer,uvm_monitor,uvm_agent。而 uvm_sequence_item 和 uvm_sequence 則不屬于環(huán)境的組件,他們是環(huán)境組件之間傳遞信息的載體。
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 的接口進(jìn)行互動。
uvm_monitor 通過檢測接口上的信號,轉(zhuǎn)化為數(shù)據(jù)包,如實(shí)地發(fā)送給 checker,只實(shí)現(xiàn)這一單純的功能,而對于驅(qū)動 DUT 這一功能則交給 uvm_driver。
uvm_driver 通過從 uvm_sequencer 獲取到的 uvm_sequence_item 解析出驅(qū)動數(shù)據(jù),如實(shí)的將 uvm_sequence_item 的內(nèi)容驅(qū)動到 DUT 的接口上,也只實(shí)現(xiàn)這一單純的功能,至于具體的激勵內(nèi)容,則通過 uvm_sequencer 暴露接口給頂層環(huán)境,讓驗(yàn)證人員通過 uvm_sequencer 發(fā)送激勵。
而 uvm_sequencer 的功能就更加簡單了,只需要實(shí)現(xiàn)傳遞 uvm_sequence_item 即可,不需要關(guān)注其他的工作。
而 uvm_agent 中則通常會例化四個組件,uvm_driver,uvm_sequencer,uvm_monitor 和對應(yīng)的 interface。uvm_agent 的功能也非常單一,僅僅只是將對于一組接口的相關(guān)組件進(jìn)行一個打包,把他們整合起來,這樣在頂層就只需要例化這一個組件即可。
mem_in_pkg 和 mem_out_pkg 的內(nèi)容整體上和 cfg_pkg 基本一致,不再贅述。
與 DUT 沒有直接聯(lián)系的組件
通過 cfg_pkg 的內(nèi)容,我們實(shí)現(xiàn)了對 DUT 的驅(qū)動與檢測,那么驅(qū)動的內(nèi)容從何而來,而檢測的數(shù)據(jù)包又要發(fā)送到哪里去呢?從前面圖中我們可以看到,除了五個 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 的信息,進(jìn)行數(shù)據(jù)對比檢查。在現(xiàn)在的 DUT 中,他所實(shí)現(xiàn)的功能是,在一次卷積運(yùn)算結(jié)束后,使用軟件算法直接進(jìn)行卷積運(yùn)算,然后與 DUT 的計(jì)算結(jié)果進(jìn)行對比,確保 DUT 功能正確。
UVM 并沒有預(yù)置的類用于覆蓋率收集,所以 conv_coverage 繼承自 uvm_component,成為最簡單的 UVM 組件。由于除了寄存器的接口以外,其他都是簡單的 sram 接口,所以只對寄存器進(jìn)行覆蓋率收集。覆蓋率組件通過覆蓋率的收集,量化功能驗(yàn)證的完備性,根據(jù)對應(yīng)的功能點(diǎn),設(shè)定對應(yīng)的覆蓋率,而功能驗(yàn)證的目的就是為了達(dá)到 100%的功能覆蓋率。
conv_virtual_sequencer 繼承自 uvm_sequencer,其本身并沒有什么功能,所以他的名字中帶有 virtual,他只是一個虛擬的 sequencer。其內(nèi)部包含了 cfg_sqr,fmi_sqr,wt_sqr 和 bias_sqr,作用就是將他們整合在一起,sequencer 就是一根數(shù)據(jù)線,uvm_sequence_item 就是傳輸?shù)臄?shù)據(jù),而 virtual_sequencer 就一個集線器或者說一個拓展塢,把很多條數(shù)據(jù)線綁在一起。
conv_env
數(shù)據(jù)的驅(qū)動由 uvm_driver 實(shí)現(xiàn),檢測由 uvm_monitor 實(shí)現(xiàn),激勵由 uvm_sequencer 傳遞,數(shù)據(jù)對比由 conv_checker 實(shí)現(xiàn),覆蓋率收集由 conv_coverage 實(shí)現(xiàn)。那么接下來就需要把這些組件全部整合在一起,成為一個驗(yàn)證環(huán)境,這便是 conv_env。在這里,我們只需要完成各個組件的例化和他們之間的連接,不要關(guān)心其他工作。
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
現(xiàn)在我們已經(jīng)獲得了一個針對卷積模塊的驗(yàn)證環(huán)境,那么如何開始仿真測試?回憶前面所提到的 virtual_sequencer,我們只需要通過 virtual_sequencer 對每個 DUT 的接口進(jìn)行驅(qū)動,就能讓 DUT 運(yùn)轉(zhuǎn)起來。針對每一個測試,我們需要創(chuàng)建對應(yīng)的 uvm_test 類,然后再 uvm_test 內(nèi)通過 virtual_sequencer 進(jìn)行激勵發(fā)送即可。
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 的內(nèi)容就是在構(gòu)建環(huán)境后,整個仿真真正需要進(jìn)行的測試內(nèi)容。可以看到我們定義了一個 run_top_virtual_sequence 方法,用于運(yùn)行 virtual_sequence。
與 virtual_sequencer 對應(yīng)的,virtual_sequence 就是 virtual_sequencer 所需要傳輸?shù)膬?nèi)容,它的內(nèi)部會包括各式各樣的 sequence,針對每一個 agent 發(fā)送不同的激勵。通過修改 virtual_sequence 的內(nèi)容,我們就能夠完成不同的測試用例。
啟動方式
這里先不討論整個環(huán)境的樹狀結(jié)構(gòu)、連接方式和運(yùn)行機(jī)制,這些將在后續(xù)的推送中講解。
在構(gòu)建完整個環(huán)境和測試用例以后,我們就需要在頂層啟動測試。
module tb ();
logic clk;
logic rst_n;
conv i_conv (); // 這里省略了端口連接,具體請參考實(shí)驗(yàn)代碼
// 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 件事:
1. 定義時鐘與復(fù)位 2. 例化 dut3. 例化與連接各個 interface4. 將每個 interface 句柄通過 uvm_config_db 傳遞到環(huán)境中去 5. 通過 run_test()方法啟動測試
uvm_config_db 是 UVM 所提供用于傳遞數(shù)據(jù)的靜態(tài)方法,在后續(xù)的推送中將會展開講解。這里值得注意的是,一定要在 run_test()之前實(shí)現(xiàn) uvm_config_db 傳遞,否則在 run_test()開始后,環(huán)境內(nèi)部將無法獲取句柄,導(dǎo)致報錯。
run_test()是 UVM 提供的測試啟動方法,傳遞參數(shù)是一個字符串變量,該字符串將用于指定默認(rèn)的 testcase。如果在命令選項(xiàng)中,沒有通過+UVM_TESTNAME 指定具體的 TESTNAME,將會運(yùn)行默認(rèn)的 testcase。
總結(jié)
本次講解了驗(yàn)證環(huán)境的基本組件和構(gòu)成,以及在頂層啟動的注意事項(xiàng)。
1. 與 DUT 直接交互的組件為 uvm_driver 和 uvm_monitor,傳遞激勵信息的組件為 uvm_sequencer,uvm_agent 將三者組合起來。
2.uvm_scoreboard 獲取各個 uvm_monitor 傳遞過來的數(shù)據(jù),進(jìn)行比對,保證 DUT 功能的正確性。
3.conv_coverage 用于收集覆蓋率
4.conv_virtual_sequencer 將每個 uvm_agent 中的 uvm_sequencer 集中起來進(jìn)行管理,起到集線器或者說路由器的效果
5.conv_env 將上述所有組件容納起來,并且進(jìn)行連接
6.uvm_test 通過編寫 conv_virtual_sequence,經(jīng)由 conv_virtual_sequencer 發(fā)送激勵實(shí)現(xiàn)不同的 testcase
7. 在頂層實(shí)現(xiàn)各項(xiàng)例化,并且在 run_test()之前傳遞接口句柄
8. 通過 run_test()啟動測試,并且指定默認(rèn) testcase