System verilog基礎-面向對象編程
類和對象的概述
????????verilog的module+method的方式與SV的class定義有本質上的區(qū)別,即面向對象編程的三要素:封裝(Encapsulation)、繼承(Inheritance)和多態(tài)(Polymorphism)。這一篇章講解SV中最重要的一個類型-類,所以篇幅會很長。
????????類的定義核心即是屬性聲明(property declaration)和方法定義(method definition),所以類是數(shù)據(jù)和方法的自洽體,即可以保存數(shù)據(jù)也可以處理數(shù)據(jù)。這是與struct結構體在數(shù)據(jù)保存方面的重要區(qū)別,因為結構體只是單純的數(shù)據(jù)集合,而類則可以對數(shù)據(jù)做出符合需要的處理。
為什么驗證世界中,需要有類的存在?
※ 激勵生成器(stimulus generator):生成激勵內容。
※?驅動器(driver):將激勵以時序形式發(fā)送至DUT。
※ 檢測器(monitor):檢測信號并記錄數(shù)據(jù)。
※ 比較器(checker):比較數(shù)據(jù)。
驗證環(huán)境的不同組件其功能和所需要處理的數(shù)據(jù)內容是不相同的。
不同環(huán)境的同一類型的組件其所具備的功能和數(shù)據(jù)內容是相似的。
面向對象編程(OOP)術語:
(1)類(class):包含變量和子程序的基本構建塊。Verilog中與之對應的是模塊(module).
(2)對象(object):類的一個實例。在Verilog中,你需要實例化一個模塊才能使用它。
(3)句柄(handke):指向對象的指針。在Verilog中,你通過實例名在模塊外部引用信號和方法。一個OPP句柄就像一個對象的地址,但是它保存在一個只能指向單一數(shù)據(jù)類型的指針中。
(4)屬性(property):存儲數(shù)據(jù)的變量。在Verilog中,就是寄存器(reg)或者線網(wǎng)(wire)類型的信號。
(5)方法(method):任務或者函數(shù)中操作變量的程序性代碼。Verilog模塊除了initial和always塊以外,還含有任務和函數(shù)。
(6)原型(prototype):程序的頭,包括程序名、返回類型和參數(shù)列表。程序體則包含了執(zhí)行代碼。
Verilog和OOP都具有例化的概念,但是在細節(jié)方面卻存在著一些差別,一個Verilog模塊,例如加法器,是在代碼被編譯的時候例化的。而一個System Verilog類,例如一個網(wǎng)絡數(shù)據(jù)包,卻是在運行測試平臺需要的時候才被創(chuàng)建。Verilog的例化是靜態(tài)的,就像硬件一樣在仿真的時候不會變化,只有信號值在變。而System Verilog中,激勵對象不斷地被創(chuàng)建并且用來驅動DUT,檢查結果。最后這些對象所占用的內存可以被釋放,以供新的對象使用。
綜上:verilog的例化時靜態(tài)的,即在編譯連接時完成,而SV class的例化是動態(tài)的,可以再任意時間點發(fā)生,這也使得類的例化方式更加靈活和節(jié)省空間。

new( )和new[ ]的區(qū)別
????????new( )和設置動態(tài)數(shù)組大小的new[ ]看起來非常相似。它們都申請內存并初始化變量。兩者最大的不同在于調用new( )函數(shù)僅創(chuàng)建了一個對象,而new[ ] 操作則建立一個含有多個元素的數(shù)組。new( ) 可以使用參數(shù)設置對象的數(shù)值,而new[ ]只需使用一個數(shù)值來設置數(shù)組的大小。
定義一個class,并給他初始化。

把tr定義為一個對象,然后用new函數(shù)返回給tr一個句柄,在這里做的初始化,a的初值為3,addr默認為‘h10,使用new函數(shù)后,因為傳入了參數(shù)10,默認給第一個a,所以a=10,經(jīng)過foreach函數(shù),給addr賦值10,所以addr = 10。如果使用new()函數(shù)的時候,沒有傳入數(shù)值,那么a就默認為3.
為多個對象分配地址

只要看到一次new函數(shù),那就是例化了一個新的對象。所以看到上面代碼的第四行,就應該知道已經(jīng)例化了兩個對象。

對于上面這個開辟空間和釋放的例子,請問在1ps的時候,總共為word開辟了多少的存儲空間,假設每創(chuàng)建一次,開辟1B空間?1B,開辟了四次個對象,最后wd指向第四個對象,前三個對象都沒有句柄指向,所以前三個對象銷毀了,所以是1B。

靜態(tài)變量
????????與硬件域例如module、interface不同的是,在class中聲明的變量其默認類型是動態(tài)變量,即其生命周期在仿真開始后的某個時間點開始到某時間點結束。具體來講,其聲明周期始于對象創(chuàng)建,終于對象銷毀。
????????如果用static來聲明class內的變量時,則其為靜態(tài)變量。靜態(tài)變量的聲明開始于編譯階段,貫穿于整個仿真階段。
????????如果在類中聲明了靜態(tài)變量,那么可以直接引用該變量class::var,或者通過例化對象引用object.var。類中的靜態(tài)變量聲明后,該靜態(tài)變量有且只有一個,無論例化多少個對象(0...N),只可以共享一個同名的靜態(tài)變量,因此類的靜態(tài)變量在使用時需要注意共享資源的保護。

不管創(chuàng)建了多少Transaction對象,靜態(tài)變量count只存在一個。變量id不是靜態(tài)的,所以每個Transaction都有自己的id變量。這樣你就不需要為count創(chuàng)建一個全局變量了。
靜態(tài)方法
????????靜態(tài)方法內可以聲明并使用動態(tài)變量,但是不能使用類的動態(tài)成員變量,原因是因為在調用靜態(tài)方法時,可能并沒有創(chuàng)建具體的對象,也因此沒有為動態(tài)成員變量開辟空間,因此在靜態(tài)方法中使用類的動態(tài)變量是禁止的。但是在靜態(tài)方法中可以使用類的靜態(tài)變量,因為靜態(tài)方法和靜態(tài)變量一樣在編譯階段就已經(jīng)為其分配好了內存空間。

類中的方法

仿真結果

類的成員
????????類作為載體,具備了天生的閉合屬性,即將其屬性和方法封裝在內部,不會直接將成員變量暴露給外部,通過protected和local關鍵詞來設置成員變量和方法的外部訪問權限。所以封裝屬性在設計模式中稱之為開放封閉原則(OCP open Closed Principle)。

? ? ? ? 用 local 修飾了類中的成員變量之后,外部無法訪問到該類被 local聲明的變量。但是可以通過使用類中的方法來改變類內部的成員變量。但是外部是不能訪問內部的變量的,所以第二條display的訪問方式會報錯。
????????如果沒有指明訪問類型,那么成員的默認類型是public,子類和外部均可以訪問成員。
面試提問:local和protected有什么區(qū)別?
????????如果指明了訪問類型是protected,那么只有該類或者子類可以訪問成員,而外部無法訪問。
????????如果指明了訪問類型是local,那么只有該類可以訪問成員,子類和外部均無法訪問成員。
類與結構體的異同
※ 二者本身都可以定義數(shù)據(jù)成員。
※ 類變量在聲明之后,需要構造(construction)才會構建對象(object)實體,而struct在變量聲明時已經(jīng)開辟內存。
※ 類除了可以聲明數(shù)據(jù)變量成員,還可以聲明方法(function/task),而struct不行。
※ 從根本來講,struct仍然是一種數(shù)據(jù)結構,而class則包含了數(shù)據(jù)成員以及針對這些成員操作的方法。
類和模塊的異同
※ 從數(shù)據(jù)和方法來看,二者均可以作為封閉的容器來定義和存儲。
※ 從例化來看,模塊必須在仿真一開始就確定是否應該被例化,而對于類而言,它的變量在仿真的任何時候都可以構造創(chuàng)建新的對象。
※ 從封裝性(encapsulation)來看,模塊內的變量和方法是對外部公共(public)開放的,而類則可以根據(jù)需要來確定外部訪問的權限是public、protected還是local。
※ 從繼承性(inheritance)來看,模塊沒有任何的繼承性可言,即無法在原有module的基礎上進行新的module功能的擴展,而繼承性是類的一大特點。
????????類的定義可以在module、interface、program和package中,也就是所有的“盒子”。
????????類中也可以再聲明類,如果在類中使用this,即表明this.X所調用的成員是當前類的成員,而非同名的局部變量或者形式參數(shù):

????????類的編譯是具有順序的,應該先編譯基本類,在編譯高級類?;蛘哒f,先編譯將來被引用的類,再編譯引用之前已編譯的類。?
類的繼承
????????類的三要素之一,類的繼承。類的繼承通過extend來使新的類可以擁有其繼承的父類的成員變量和成員方法。

????????子類white_cat 和 black_cat 在new函數(shù)里面,調用了this.color,因為它們本身類里面沒有定義color,所以它們就會去父類里面找父類的color,可以看到父類cat的類型是protected,即不能被外界訪問,但可以被內部及子類訪問。
????????這里要注意幾點,color變量是protected類型的,所以不能通過外部來修改子類的顏色。子類也不能在初始化時,通過this.is_good = 1,因為is_good是local變量,只允許父類內部訪問,不允許子類訪問。外部也不允許訪問is_good屬性來判斷該貓是否是一只好貓,但是外部可以通過function set_good來把類里面的is_good屬性設置為1.(可以set,不能get)
?????????
例子2
?????????在test類中通過繼承basic_test的兩個子類test_wr和test_rd,分別對DUT發(fā)起寫測試和讀測試。

上面為父類basic_test,下面為父類的兩個子類test_wr和test_rd

test_wr和test_rd繼承了basic_test的成員變量int fin,也繼承了它的成員方法test( )。所以就繼承來看,類的繼承包括了繼承父類的成員變量和成員方法。
子類在定義new函數(shù)時,應該調用父類的new函數(shù),即super.new()。如果父類的new函數(shù)沒有參數(shù),子類也可以省略該調用,而系統(tǒng)會在編譯時自動添加super.new()。
在父類和子類里,可以定義相同名稱的成員變量和方法(形式參數(shù)和返回類型也應該相同),而在引用時,也將按照句柄類型來確定作用域。

????????上面子類里調用的new函數(shù)中的def,調用的是子類的def = 200,如果沒有指明,那么變量調用的查找是由近到遠的,先查找子類內部,如果子類內部沒有才會查找父類。如果子類和父類里都有同名變量,且想調用父類而非子類的變量,可以使用super的方式直接訪問父類中的成員變量。

????????父類有個def=100,子類也有個def=200。把子類的句柄賦值給父類的句柄,那么通過父類的句柄,索引到的def的值應該為多少?
????????子類如果出現(xiàn)了和父類同名的變量,通過子類索引到的變量是200,通過父類索引到的變量是100,即wr.def = 200,t.def = 100。如果把子類的句柄賦值給父類,子類可以訪問到父類及子類里面的全部的對象和方法,而父類僅能訪問子類繼承于父類的那部分成員變量和方法。如上面,t 和 wr 均指向同一個對象,但是 t 只能訪問對象中繼承于父類的變量和方法,而 wr 可以訪問父類及子類的全部變量和方法。
????????為什么會有這樣的規(guī)定?因為你把子類的句柄賦值給一個父類,那么父類和子類都指向同一個對象,且子類能訪問該對象中父類和子類的全部變量及方法,父類僅能訪問對象中屬于父類的部分,這是因為對象有很多很多,句柄可以指向很多對象,你不知道一個句柄接下來可能指向的對象是子類的對象還是父類的對象,所以為了數(shù)據(jù)安全,規(guī)定如果是把子類賦值給父類,那么父類僅能訪問子類繼承于父類的那些變量和方法。
句柄的使用
????????可以把子類的句柄賦值給父類的句柄,但是不能把父類的句柄直接賦值給子類的句柄,哪怕你是說他們句柄都指向同一個對象,但是也不能這么操作。因為父類句柄指向對象能訪問的內容僅是父類內的成員變量及方法,而子類句柄指向的對象能訪問子類及繼承父類的全部變量和方法,你可以把范圍大的(子類)賦值個范圍小的(父類),但是不能把范圍小的(父類)賦值給范圍大的(子類)。
????????如果父類的句柄指向的是子類的對象,而且還要把父類的句柄賦值給子類的句柄,有且僅有一個方法,調用cast函數(shù),還拿上面的做例子,假設有一個子類wr2,可以利用$cast(wr2,t) 來把父類句柄賦值給子類。但是如果父類的句柄指向的是父類的對象,那么調用cast函數(shù)也會失敗,因為不能把父類的對象賦值給子類。
????????
????????句柄可以當做形參傳遞,也可以在方法內部完成修改。

????????上面的代碼是有問題的,在聲明函數(shù)的時候,沒有指明句柄tr是input還是output,所以默認為input,句柄傳入并在函數(shù)內部完成初始化賦值是成功的,但是函數(shù)執(zhí)行完后因為句柄是input,所以在退出函數(shù)的時候一切都會被釋放掉,所以退出creat(t)之后,句柄 t 沒有執(zhí)行過new函數(shù),句柄是懸空的,不能給懸空的句柄賦值,t.addr就會報錯。如果要修改,可以在函數(shù)變量中聲明inout,或者指明ref都是可以的。
問:在下面這個task執(zhí)行完之后,t等于多少?

這里是很容易犯錯的一個點,t是指向對象的句柄,for循環(huán)內部執(zhí)行了三次,分別把句柄t壓到了隊列fifo中,要注意的是,fifo中存放的三個位置都是同一個句柄t,也就是指向的都是同一個變量,所以經(jīng)過第一個循環(huán)t=0,第二個循環(huán)t=4,第三個循環(huán)t=8。之后,fifo中的三個句柄指向的都是同一個對象,所以t.addr,都是8。
包的使用(package)
????????SV語言提供了一種在多個module、interface和program之中共享parameter、data、type、task、function、class等的方法,即利用包(package)的方式來實現(xiàn)。
include的功能是文本替換,`include “stimulator.sv”這一行的意思就是把整個.sv文件內的所有內容都在這里展開,比如stimulator.sv里面有十行代碼,include就是把這十行全都展開在當前文件內,但是如果直接把.sv的內容copy過來的話,package文件的代碼行數(shù)就會很多,不好維護,所以采用include的方式把其他文本的內容在這里作展開。

? ? ? ? 當兩個驗證人員寫的類的名稱一樣時,在整合的時候會出現(xiàn)命名沖突,為了避免這一情況,使用包來將一系列相關的類包裝在一起,在引用某個類的時候在前面加上所屬的包,這樣就避免了不同人員編寫類命名相同的問題。

????????也可以直接把兩個package直接import進來,變量選擇*。然后直接寫package里面的類,這樣在module里面如果沒有找到使用的類,那么編譯器就會到package里面找。但是這種方式前提是兩個包里面不要有同名的變量,所以在命名的時候,為了不混淆最好加上包的前綴來保證變量的不二性。

????????在包里面可以定義類、靜態(tài)方法和靜態(tài)變量,不能定義module之類和硬件相關的東西,包內可以導入其它包定義的類。如果把類封裝在某一個包中,那么它就不應該在其它地方編譯,這么做的好處在于之后對類的引用更加方便。一個完整模塊的驗證環(huán)境組件類,應該由一個對應的模塊包來封裝。