天天看點

《UVM實戰》——2.2節隻有driver的驗證平台

本節書摘來自華章社群《uvm實戰》一書中的第2章,第2.2節隻有driver的驗證平台,作者 張 強,更多章節内容可以通路雲栖社群“華章社群”公衆号檢視

2.2 隻有driver的驗證平台

driver是驗證平台最基本的元件,是整個驗證平台資料流的源泉。本節以一個簡單的dut為例,說明一個隻有driver的uvm驗證平台是如何搭建的。

2.2.1 最簡單的驗證平台

在本章中,假設有如下的dut定義:

代碼清單 2-1

這個dut的功能非常簡單,通過rxd接收資料,再通過txd發送出去。其中rx_dv是接收的資料有效訓示,tx_en是發送的資料有效訓示。本章中所有例子都是基于這個dut。

uvm中的driver應該如何搭建?uvm是一個庫,在這個庫中,幾乎所有的東西都是使用類(class)來實作的。driver、monitor、reference model、scoreboard等組成部分都是類。類是像systemverilog這些面向對象程式設計語言中最偉大的發明之一,是面向對象的精髓所在。類有函數(function),另外還可以有任務(task),通過這些函數和任務可以完成driver的輸出激勵功能,完成monitor的監測功能,完成參考模型的計算功能,完成scoreboard的比較功能。類中可以有成員變量,這些成員變量可以控制類的行為,如控制driver的行為等。當要實作一個功能時,首先應該想到的是從uvm的某個類派生出一個新的類,在這個新的類中實作所期望的功能。是以,使用uvm的第一條原則是:驗證平台中所有的元件應該派生自uvm中的類。

uvm驗證平台中的driver應該派生自uvm_driver,一個簡單的driver如下例所示:

代碼清單 2-2

這個driver的功能非常簡單,隻是向rxd上發送256個随機資料,并将rx_dv信号置為高電平。當資料發送完畢後,将rx_dv信号置為低電平。在這個driver中,有兩點應該引起注意:

所有派生自uvm_driver的類的new函數有兩個參數,一個是string類型的name,一個是uvm_component類型的parent。關于name參數,比較好了解,就是名字而已;至于parent則比較難以了解,讀者可暫且放在一邊,下文會有介紹。事實上,這兩個參數是由uvm_component要求的,每一個派生自uvm_component或其派生類的類在其new函數中要指明兩個參數:name和parent,這是uvm_component類的一大特征。而uvm_driver是一個派生自uvm_component的類,是以也會有這兩個參數。

driver所做的事情幾乎都在main_phase中完成。uvm由phase來管理驗證平台的運作,這些phase統一以xxxx_phase來命名,且都有一個類型為uvm_phase、名字為phase的參數。main_phase是uvm_driver中預先定義好的一個任務。是以幾乎可以簡單地認為,實作一個driver等于實作其main_phase。

上述代碼中還出現了uvm_info宏。這個宏的功能與verilog中display語句的功能類似,但是它比display語句更加強大。它有三個參數,第一個參數是字元串,用于把列印的資訊歸類;第二個參數也是字元串,是具體需要列印的資訊;第三個參數則是備援級别。在驗證平台中,某些資訊是非常關鍵的,這樣的資訊可以設定為uvm_low,而有些資訊可有可無,就可以設定為uvm_high,介于兩者之間的就是uvm_medium。uvm預設隻顯示uvm_medium或者uvm_low的資訊,本書3.4.1節會講述如何顯示uvm_high的資訊。本節中uvm_info宏列印的結果如下:

uvm_info my_driver.sv(20) @ 48500000: drv [my_driver] data is drived

在uvm_info宏列印的結果中有如下幾項:

uvm_info關鍵字:表明這是一個uvm_info宏列印的結果。除了uvm_info宏外,還有uvm_error宏、uvm_warning宏,後文中将會介紹。

my_driver.sv(20):指明此條列印資訊的來源,其中括号裡的數字表示原始的uvm_info列印語句在my_driver.sv中的行号。

48500000:表明此條資訊的列印時間。

drv:這是driver在uvm樹中的路徑索引。uvm采用樹形結構,對于樹中任何一個結點,都有一個與其相應的字元串類型的路徑索引。路徑索引可以通過get_full_name函數來擷取,把下列代碼加入任何uvm樹的結點中就可以得知目前結點的路徑索引:

代碼清單 2-3

類的定義類似于在紙上寫下一紙條文,然後把這些條文通知給systemverilog的仿真器:驗證平台可能會用到這樣的一個類,請做好準備工作。而類的執行個體化在于通過new()來通知systemverilog的仿真器:請建立一個a的執行個體。仿真器接到new的指令後,就會在記憶體中劃分一塊空間,在劃分前,會首先檢查是否已經預先定義過這個類,在已經定義過的情況下,按照定義中所指定的“條文”配置設定空間,并且把這塊空間的指針傳回給a_inst,之後就可以通過a_inst來檢視類中的各個成員變量,調用成員函數/任務等。對大部分的類來說,如果隻定義而不執行個體化,是沒有任何意義的;而如果不定義就直接執行個體化,仿真器将會報錯。

對my_driver執行個體化并且最終搭建的驗證平台如下:

代碼清單 2-6

第2行把uvm_macros.svh檔案通過include語句包含進來。這是uvm中的一個檔案,裡面包含了衆多的宏定義,隻需要包含一次。

第4行通過import語句将整個uvm_pkg導入驗證平台中。隻有導入了這個庫,編譯器在編譯my_driver.sv檔案時才會認識其中的uvm_driver等類名。

第24和25行定義一個my_driver的執行個體并将其執行個體化。注意這裡調用new函數時,其傳入的名字參數為drv,前文介紹uvm_info宏的列印資訊時出現的代表路徑索引的drv就是在這裡傳入的參數drv。另外傳入的parent參數為null,在真正的驗證平台中,這個參數一般不是null,這裡暫且使用null。

第26行顯式地調用my_driver的main_phase。在main_phase的聲明中,有一個uvm_phase類型的參數phase,在真正的驗證平台中,這個參數是不需要使用者理會的。本節的驗證平台還算不上一個完整的uvm驗證平台,是以暫且傳入null。

第27行調用finish函數結束整個仿真,這是一個verilog中提供的函數。

運作這個例子,可以看到“data is drived”被輸出了256次。

2.2.2 加入factory機制

上一節給出了一個隻有driver、使用uvm搭建的驗證平台。嚴格來說這根本就不算是uvm驗證平台,因為uvm的特性幾乎一點都沒有用到。像上節中my_driver的執行個體化及drv.main_phase的顯式調用,即使不使用uvm,隻使用簡單的systemverilog也可以完成。本節将會為讀者展示在初學者看來感覺最神奇的一點:自動建立一個類的執行個體并調用其中的函數(function)和任務(task)。

要使用這個功能,需要引入uvm的factory機制:

代碼清單 2-7

factory機制的實作被內建在了一個宏中:uvm_component_utils。這個宏所做的事情非常多,其中之一就是将my_driver登記在uvm内部的一張表中,這張表是factory功能實作的基礎。隻要在定義一個新的類時使用這個宏,就相當于把這個類注冊到了這張表中。那麼factory機制到底是什麼?這個宏還做了哪些事情呢?這些屬于uvm中的進階問題,本書會在後文一一展開。

在給driver中加入factory機制後,還需要對top_tb做一些改動:

代碼清單 2-8

一個run_test語句會建立一個my_driver的執行個體,并且會自動調用my_driver的main_phase。仔細觀察run_test語句,會發現傳遞給它的是一個字元串。uvm根據這個字元串建立了其所代表類的一個執行個體。如果沒有uvm,讀者自己能夠實作同樣的功能嗎?

根據類名建立一個類的執行個體,這是uvm_component_utils宏所帶來的效果,同時也是factory機制給讀者的最初印象。隻有在類定義時聲明了這個宏,才能使用這個功能。是以從某種程度上來說,這個宏起到了注冊的作用。隻有經過注冊的類,才能使用這個功能,否則根本不能使用。請記住一點:所有派生自uvm_component及其派生類的類都應該使用uvm_component_utils宏注冊。

除了根據一個字元串建立類的執行個體外,上述代碼中另外一個神奇的地方是main_phase被自動調用了。在uvm驗證平台中,隻要一個類使用uvm_component_utils注冊且此類被執行個體化了,那麼這個類的main_phase就會自動被調用。這也就是為什麼上一節中會強調實作一個driver等于實作其main_phase。是以,在driver中,最重要的就是實作main_phase。

上面的例子中,隻輸出到“main_phase is called”。令人沮喪的是,根本沒有輸出“data is drived”,而按照預期,它應該輸出256次。關于這個問題,牽涉uvm的objection機制。

2.2.3 加入objection機制

在上一節中,雖然輸出了“main_phase is called”,但是“data is drived”并沒有輸出。而main_phase是一個完整的任務,沒有理由隻執行第一句,而後面的代碼不執行。看上去似乎main_phase在執行的過程中被外力“殺死”了,事實上也确實如此。

uvm中通過objection機制來控制驗證平台的關閉。細心的讀者可能發現,在上節的例子中,并沒有如2.2.1節所示顯式地調用finish語句來結束仿真。但是在運作上節例子時,仿真平台确實關閉了。在每個phase中,uvm會檢查是否有objection被提起(raise_objection),如果有,那麼等待這個objection被撤銷(drop_objection)後停止仿真;如果沒有,則馬上結束目前phase。

加入了objection機制的driver如下所示:

代碼清單 2-9

2.2.4 加入virtual interface

在前幾節的例子中,driver中等待時鐘事件(@posedge top.clk)、給dut中輸入端口指派(top.rx_dv <= 1' b1)都是使用絕對路徑,絕對路徑的使用大大減弱了驗證平台的可移植性。一個最簡單的例子就是假如clk信号的層次從top.clk變成了top.clk_inst.clk,那麼就需要對driver中的相關代碼做大量修改。是以,從根本上來說,應該盡量杜絕在驗證平台中使用絕對路徑。

避免絕對路徑的一個方法是使用宏:

代碼清單 2-11

可以清楚看到,代碼中的絕對路徑已經消除了,大大提高了代碼的可移植性和可重用性。

剩下的最後一個問題就是,如何把top_tb中的input_if和my_driver中的vif對應起來呢?最簡單的方法莫過于直接指派。此時一個新的問題又擺在了面前:在top_tb中,通過run_test語句建立了一個my_driver的執行個體,但是應該如何引用這個執行個體呢?不可能像引用my_dut那樣直接引用my_driver中的變量:top_tb.my_dut.xxx是可以的,但是top_tb.my_driver.xxx是不可以的。這個問題的終極原因在于uvm通過run_test語句執行個體化了一個脫離了top_tb層次結構的執行個體,建立了一個新的層次結構。

對于這種脫離了top_tb層次結構,同時又期望在top_tb中對其進行某些操作的執行個體,uvm引進了config_db機制。在config_db機制中,分為set和get兩步操作。所謂set操作,讀者可以簡單地了解成是“寄信”,而get則相當于是“收信”。在top_tb中執行set操作:

代碼清單 2-17

這裡引入了build_phase。與main_phase一樣,build_phase也是uvm中内建的一個phase。當uvm啟動後,會自動執行build_phase。build_phase在new函數之後main_phase之前執行。在build_phase中主要通過config_db的set和get操作來傳遞一些資料,以及執行個體化成員變量等。需要注意的是,這裡需要加入super.build_phase語句,因為在其父類的build_phase中執行了一些必要的操作,這裡必須顯式地調用并執行它。build_phase與main_phase不同的一點在于,build_phase是一個函數phase,而main_phase是一個任務phase,build_phase是不消耗仿真時間的。build_phase總是在仿真時間($time函數列印出的時間)為0時執行。

在build_phase中出現了uvm_fatal宏,uvm_fatal宏是一個類似于uvm_info的宏,但是它隻有兩個參數,這兩個參數與uvm_info宏的前兩個參數的意義完全一樣。與uvm_info宏不同的是,當它列印第二個參數所示的資訊後,會直接調用verilog的finish函數來結束仿真。uvm_fatal的出現表示驗證平台出現了重大問題而無法繼續下去,必須停止仿真并做相應的檢查。是以對于uvm_fatal來說,uvm_info中出現的第三個參數的備援度級别是完全沒有意義的,隻要是uvm_fatal列印的資訊,就一定是非常關鍵的,是以無需設定第三個參數。

config_db的set和get函數都有四個參數,這兩個函數的第三個參數必須完全一緻。set函數的第四個參數表示要将哪個interface通過config_db傳遞給my_driver,get函數的第四個參數表示把得到的interface傳遞給哪個my_driver的成員變量。set函數的第二個參數表示的是路徑索引,即在2.2.1節介紹uvm_info宏時提及的路徑索引。在top_tb中通過run_test建立了一個my_driver的執行個體,那麼這個執行個體的名字是什麼呢?答案是uvm_test_top:uvm通過run_test語句建立一個名字為uvm_test_top的執行個體。讀者可以通過把代碼清單2-3中的語句插入my_driver(build_phase或者main_phase)中來驗證。

無論傳遞給run_test的參數是什麼,建立的執行個體的名字都為uvm_test_top。由于set操作的目标是my_driver,是以set函數的第二個參數就是uvm_test_top。set函數的第一個參數null以及get函數的第一和第二個參數可以暫時放在一邊,後文會詳細說明。

set函數與get函數讓人疑惑的另外一點是其古怪的寫法。使用雙冒号是因為這兩個函數都是靜态函數,而uvm_config_db#(virtual my_if)則是一個參數化的類,其參數就是要寄信的類型,這裡是virtual my_if。假如要向my_driver的var變量傳遞一個int類型的資料,那麼可以使用如下方式:

代碼清單 2-19