攝影師個(gè)人網(wǎng)站制作自動(dòng)點(diǎn)擊器免費(fèi)下載
5.1概述
????????對(duì)結(jié)構(gòu)化編程語言,例如 Verilog和C語言來講,它們的數(shù)據(jù)結(jié)構(gòu)和使用這些數(shù)據(jù)結(jié)構(gòu)的代碼之間存在很大的溝壑。數(shù)據(jù)聲明、數(shù)據(jù)類型與操作這些數(shù)據(jù)的算法經(jīng)常放在不同的文件里,因此造成了對(duì)程序理解的困難。
????????Verilog程序員的境遇比C程序員更加棘手,因?yàn)棰?erilog語言中沒有結(jié)構(gòu)( structures),只有位向量和數(shù)組。如果你想要存儲(chǔ)一個(gè)總線事務(wù)( bus transaction)的信息,你就需要多個(gè)數(shù)組:一個(gè)用于保存地址,一個(gè)用于保存數(shù)據(jù),一個(gè)用于保存指令等等。事務(wù)( transaction)N的信息分布在這些所有的數(shù)組中。用來創(chuàng)建、發(fā)送和接收事務(wù)的代碼位于模塊( module)中,但這個(gè)模塊可能連接到總線上,也可能根本沒有連接到總線上。最糟糕的是,這些數(shù)組都是靜態(tài)的,所以如果測(cè)試平臺(tái)( testbench)只配置了100個(gè)數(shù)組項(xiàng),而當(dāng)前測(cè)試需要101個(gè)時(shí),就需要修改源代碼來改變數(shù)組的大小,并且重新編譯。結(jié)果,數(shù)組的大小被配置成可以容納最大數(shù)目的事務(wù),但是在一個(gè)普通的測(cè)試中,大多數(shù)的存儲(chǔ)空間卻浪費(fèi)了。
????????面向?qū)ο缶幊?OOP)使用戶能夠創(chuàng)建復(fù)雜的數(shù)據(jù)類型,并且將它們跟使用這些數(shù)據(jù)類型的程序緊密地結(jié)合在一起。用戶可以在更加抽象的層次建立測(cè)試平臺(tái)和系統(tǒng)級(jí)模型,通過調(diào)用函數(shù)來執(zhí)行一個(gè)動(dòng)作而不是改變信號(hào)的電平。當(dāng)使用事務(wù)來代替信號(hào)翻轉(zhuǎn)的時(shí)候,你就會(huì)變得更加高效。這樣做的附加好處是,測(cè)試平臺(tái)跟設(shè)計(jì)細(xì)節(jié)分開了,它們變得更加可靠,更加易于維護(hù),在將來的項(xiàng)目中可以重復(fù)使用。
????????如果用戶已經(jīng)熟悉了面向?qū)ο缶幊?OP),則可以跳過這一章,因?yàn)?SystemVerilog相當(dāng)嚴(yán)格地遵守OOP的規(guī)則。但你還是需要讀一下5.18節(jié),以便了解如何搭建一個(gè)測(cè)試平臺(tái)。第8章給出了一些諸如繼承等OOP的高級(jí)概念,以及更多的測(cè)試平臺(tái)搭建技巧。
5.2考慮名詞,而非動(dòng)詞
????????將數(shù)據(jù)和代碼組合在一起可以有效地幫助你編寫和維護(hù)大型測(cè)試平臺(tái)。如何把數(shù)據(jù)和代碼組合到一起?你可以先想想測(cè)試平臺(tái)是怎么工作的。測(cè)試平臺(tái)的目標(biāo)是給一個(gè)設(shè)計(jì)施加激勵(lì),然后檢查其結(jié)果是否正確。如果把流入和流出設(shè)計(jì)的數(shù)據(jù)組合到一個(gè)事務(wù)里,那么圍繞事務(wù)及其操作實(shí)施測(cè)試平臺(tái)就是最好的辦法。在OOP中,事務(wù)就是測(cè)試平臺(tái)的焦點(diǎn)。
????????傳統(tǒng)的測(cè)試平臺(tái)強(qiáng)調(diào)的是要做的操作:創(chuàng)建個(gè)事務(wù)、發(fā)送、接收、檢查結(jié)果、然后產(chǎn)生報(bào)告。而在OOP中,你需要重新考慮測(cè)試平臺(tái)的結(jié)構(gòu),以及每部分的功能。發(fā)生器( generator)創(chuàng)建事務(wù)并且將它們傳給下一級(jí),驅(qū)動(dòng)器( driver)和設(shè)計(jì)進(jìn)行會(huì)話,設(shè)計(jì)返回的事務(wù)將被監(jiān)視器( monitor)捕獲,記分板( scoreboard)會(huì)將捕獲的結(jié)果跟預(yù)期的結(jié)果進(jìn)行比對(duì)。因此,測(cè)試平臺(tái)應(yīng)該分成若干個(gè)塊( block),然后定義它們相互之間如何通信。
5.3編寫第一個(gè)類(Clas)
????????類封裝了數(shù)據(jù)和操作這些數(shù)據(jù)的子程序。例5.1是一個(gè)通用數(shù)據(jù)包類。這個(gè)數(shù)據(jù)包包含了地址、CRC和一個(gè)存儲(chǔ)數(shù)值的數(shù)組。在 Transaction類中有兩個(gè)子程序:一個(gè)輸出數(shù)據(jù)包地址的函數(shù)和一個(gè)計(jì)算循環(huán)冗余校驗(yàn)碼〔CRC: cyclic redundancy check)的函數(shù)。
????????為了更加方便地對(duì)齊一個(gè)塊的開始和結(jié)東部分,你可以在該塊的最后放上一個(gè)標(biāo)記(label)。在例5.1中,這些結(jié)束標(biāo)記可能看起來是多余的,但是在具有很多嵌套塊的真實(shí)代碼中,標(biāo)記可以很好地幫助你配對(duì)簡(jiǎn)單的結(jié)束或 endtask, endfunction和endaclass。
例5.1簡(jiǎn)單的 Transaction類
class Transaction;bit[31:0] addr ,crc, data[8];function void display;$display("Transaction :%h",addr);endfunction :displayfunction void calc_crc;crc=addr^data.xor;endfunction:calc_crcendclass :Transaction
5.4在哪里定義類
????????在 System Verilog中,你可以把類定義在 program、 module、 package中,或者在這些塊之外的任何地方。類可以在程序和模塊中使用。本書僅給出了類在程序塊中使用的情況,如第4章所示。在此之前,可以將程序塊當(dāng)作一個(gè)包含了測(cè)試代碼的模塊,它含有一條測(cè)試、組成測(cè)試平臺(tái)的對(duì)象及創(chuàng)建、初始化并運(yùn)行測(cè)試的初始化塊。當(dāng)你創(chuàng)建一個(gè)項(xiàng)目的時(shí)候,可能需要將每個(gè)類保存在獨(dú)立的文件中。當(dāng)文件的數(shù)目變得太大的時(shí)候,可以使用 System Verilog的包( package)將一組相關(guān)的類和類型定義捆綁在一起。例如,可以將所有的SCSI/ATA事務(wù)組合到一個(gè)包中。這個(gè)包與系統(tǒng)的其他部分獨(dú)立,可以單獨(dú)地編譯。其他不相關(guān)的類,例如事務(wù)、記分板或者不同協(xié)議( protocol)的類應(yīng)該放在不同的文件中。
????????關(guān)于包的更詳細(xì)的信息可以參見 System Verilog LRM。
5.5OOP術(shù)語
? ? ? 下面是一些OOP的術(shù)語定義以及它們與?Verilog-2001的大致的對(duì)應(yīng)關(guān)系。
????????(1)類( class):包含變量和子程序的基本構(gòu)建塊。 Verilog中與之對(duì)應(yīng)的是模塊(mocule)
????????(2)對(duì)象( object):類的一個(gè)實(shí)例。在 Verilog中,你需要實(shí)例化一個(gè)模塊才能使用它
????????(3)句柄( handle):指向?qū)ο蟮闹羔?/span>。在 verilog中,你通過實(shí)例名在模塊外部引用信號(hào)和方法。一個(gè)OOP句柄就像一個(gè)對(duì)象的地址,但是它保存在一個(gè)只能指向單一數(shù)據(jù)類型的指針中。
????????(4)屬性( property):存儲(chǔ)數(shù)據(jù)的變量。在 Verilog中,就是寄存器(reg)或者線網(wǎng)(wire)類型的信號(hào)????????
????????(5)方法( method):任務(wù)或者函數(shù)中操作變量的程序性代碼。 Verilog模塊除了 initial和 always塊以外,還含有任務(wù)和函數(shù)
????????(6)原型( prototype):程序的頭,包括程序名返回類型和參數(shù)列表。程序體則包含了執(zhí)行代碼。
????????本書使用了更加傳統(tǒng)的 Verilog術(shù)語:變量( variable)和程序( routine),而沒有使用OOP中的屬性( property)和方法( method)(本書實(shí)際上始終將 routine翻譯為“程序或子程序—一譯者)。
????????在 Verilog中,通過創(chuàng)建模塊并且逐層例化就可以得到一個(gè)復(fù)雜的設(shè)計(jì)。在OOP中創(chuàng)建類并且例化它們(創(chuàng)建對(duì)象),你可以得到一個(gè)相似的層次結(jié)構(gòu)下面是一個(gè)對(duì)這些OQP術(shù)語的比喻。將類視為一個(gè)房子的藍(lán)圖( blueprint),該設(shè)計(jì)圖描述了房子的結(jié)構(gòu),但是你不能住在一個(gè)藍(lán)圖里,你需要建造一幢實(shí)際的房子。一個(gè)對(duì)象就是一個(gè)實(shí)際的房子。如同一組藍(lán)圖可以用來建造每個(gè)房子的各個(gè)部分,一個(gè)類也可以創(chuàng)建很多的對(duì)象。房子的地址就像一個(gè)句柄,它唯一地標(biāo)志了你的房子。在你的房子里面,你有很多東西,例如帶有開關(guān)的燈(開或者關(guān))。類中的變量用來保存數(shù)值,而子程序用來控制這些數(shù)值。一個(gè)房子類可能具有很多盞燈,對(duì) turn_on_porch_ light()的一個(gè)簡(jiǎn)單調(diào)用就可以將一個(gè)房子的走廊燈變量值置為ON。
5.6創(chuàng)建新對(duì)象
????????Verilog和OOP都具有例化的概念,但是在細(xì)節(jié)方面卻存在著一些區(qū)別。一個(gè) Verilog模塊,例如一個(gè)計(jì)數(shù)器,是在代碼被編譯的時(shí)候例化的。而一個(gè) System verilog類,例如個(gè)網(wǎng)絡(luò)數(shù)據(jù)包,卻是在運(yùn)行中測(cè)試平臺(tái)需要的時(shí)候才被創(chuàng)建。Ⅴerilog的例化是靜態(tài)的,就像硬件一樣在仿真的時(shí)候不會(huì)變化,只有信號(hào)值在改變。而 System Verilog中,激勵(lì)對(duì)象不斷地被創(chuàng)建并且用來驅(qū)動(dòng)DUT,檢查結(jié)果。最后這些對(duì)象所占用的內(nèi)存可以被釋放以供新的對(duì)象使用。O0P和Ⅴerilog之間的相似性也有一些例外。 Verilog的頂層模塊是不會(huì)被顯式地例化的。但是 System Verilog類在使用前必須先例化。另外, Verilog的實(shí)例名只可以指向一個(gè)實(shí)例,而 System verilog句柄可以指向很多對(duì)象,當(dāng)然一次只能指向一個(gè)。
5.6.1實(shí)例化對(duì)象
????????在例5.2中,tr是一個(gè)指向 Transaction類型對(duì)象的句柄,因此tr可以簡(jiǎn)稱為個(gè) Transaction句柄。????????
例52聲明和使用一個(gè)句柄
Transaction tr;//聲明一個(gè)句柄
Tr=new();//為一個(gè) Transaction對(duì)象分配空間
? ? ? ? 在聲明句柄tr的時(shí)候,它被初始化為特殊值nu11。接下來,你調(diào)用new()函數(shù)來創(chuàng)建 Transaction對(duì)象。new函數(shù)為 Transaction分配空間,將變量初始化為默認(rèn)值(二值變量為0,四值變量為X),并返回保存對(duì)象的地址。對(duì)于每一個(gè)類來講, System Verilog創(chuàng)建一個(gè)默認(rèn)的new函數(shù)來分配并初始化對(duì)象。有關(guān)new函數(shù)的更多細(xì)節(jié),請(qǐng)參見5.6.2節(jié)。
5.6.2定制構(gòu)造函數(shù)( Constructor)
????????有時(shí)候OOP術(shù)語會(huì)使一個(gè)簡(jiǎn)單的概念看起來復(fù)雜化。例化是什么意思?當(dāng)你調(diào)用new函數(shù)例化一個(gè)對(duì)象的時(shí)候,你是在為該對(duì)象申請(qǐng)一個(gè)新的內(nèi)存塊來保存對(duì)象的變量。例如, Transaction類有兩個(gè)32位的寄存器(addx和crc)以及一個(gè)具有八個(gè)元素(數(shù)據(jù))的數(shù)組,總計(jì)包含10個(gè)長(zhǎng)字( longword),或者說40個(gè)字節(jié)。所以當(dāng)你調(diào)用new函數(shù)的時(shí)候, System verilog就會(huì)分配40字節(jié)的存儲(chǔ)空間。如果你使用過C語言,你可以發(fā)現(xiàn)這個(gè)步驟跟mal1loc函數(shù)非常相似。(應(yīng)當(dāng)指出的是, System Verilog為四值變量使用更多的內(nèi)存,并且會(huì)保存一些內(nèi)部信息,例如對(duì)象的類型等。)
????????構(gòu)造函數(shù)除了分配內(nèi)存之外,它還初始化變量,在默認(rèn)情況下,它將變量設(shè)置成默認(rèn)數(shù)值——二值變量為0,四值變量為X,等等。你可以通過自定義new函數(shù)將默認(rèn)值設(shè)成你想要的數(shù)值。這就是為什么new函數(shù)也稱為“構(gòu)造函數(shù)”,因?yàn)樗鼊?chuàng)建對(duì)象,一如用木頭和釘子建造你的房子。但是new函數(shù)不能有返回值,因?yàn)闃?gòu)造函數(shù)總是返回一個(gè)指向類對(duì)象的句柄,其類型就是類本身。
例5.3簡(jiǎn)單的用戶定義的new()函數(shù)
class Transaction ;logic [31:0] addr ,crc, data[8];function new;addr=3;foreach (data[i])data[i]=5;endfunctionendclass
????????例5.3將addr和data設(shè)為固定數(shù)值,但是crc仍將被初始化位默認(rèn)值X( Systemverilog自動(dòng)為對(duì)象分配存儲(chǔ)空間)。你可以使用具有默認(rèn)值的函數(shù)參數(shù)來創(chuàng)建更加靈活的構(gòu)造函數(shù),如例5.4所示。這樣你就可以在調(diào)用構(gòu)造函數(shù)的時(shí)候給addr和data指定值或者使用默認(rèn)值。
例54一個(gè)帶有參數(shù)的new()函數(shù)
class Transaction ;logic [31:0] addr ,crc, data[8];function new(logic [31:0] a=3,d=5);addr=a;foreach (data[i])data[i]=d;endfunctionendclassinitial beginTransaction tr;tr=new(10); //data使用默認(rèn)值end
????????System verilog怎么知道該調(diào)用哪個(gè)new()函數(shù)呢?這取決于賦值操作符左邊的句柄類型。在例5.5中,調(diào)用 Driver構(gòu)造函數(shù)內(nèi)部的new()函數(shù),會(huì)調(diào)用 Transaction的new()函數(shù),即使 Driver的new函數(shù)的定義離它更近。這是因?yàn)閠r是Transaction句柄, System verilog會(huì)做出正確的選擇,創(chuàng)建一個(gè) Transaction類的對(duì)象。
例5.5調(diào)用正確的new()函數(shù)
class Transaction ;...
endclass :Transactionclass Driver;Transaction tr;function new(); //Driver的new 函數(shù)tr=new();//調(diào)用Transaction 的New函數(shù)endfunctionendclass;Driver
5.6.3將聲明和創(chuàng)建分開
????????你應(yīng)該避免在聲明一個(gè)句柄的時(shí)候調(diào)用構(gòu)造函數(shù),即new函數(shù)。雖然這樣在語法上是合法的,但是這會(huì)引起順序問題,因?yàn)樵谶@時(shí)構(gòu)造函數(shù)在第一條過程語句前就被調(diào)用了。你可能希望按照一定的順序初始化對(duì)象,但是如果在聲明的時(shí)候調(diào)用了new函數(shù),你就不能控制這個(gè)順序了。此外,如果你忘記了使用 automatic存儲(chǔ)空間,構(gòu)造函數(shù)將在仿真開始時(shí),而非進(jìn)入塊的時(shí)候被調(diào)用。
5.6.4new()和new[ ]的區(qū)別
????????你可能已經(jīng)注意到new()函數(shù)跟2.3節(jié)中用來設(shè)置動(dòng)態(tài)數(shù)組大小的new[]操作看起來非常相似。它們都申請(qǐng)內(nèi)存并初始化變量。兩者最大的不同在于調(diào)用new()函數(shù)僅創(chuàng)建了一個(gè)對(duì)象,而new[ ]操作則建立一個(gè)含有多個(gè)元素的數(shù)組。new( )可以使用參數(shù)設(shè)置對(duì)象的數(shù)值,而new[ ]只需使用一個(gè)數(shù)值來設(shè)置數(shù)組的大小。
5.6.5為對(duì)象創(chuàng)建一個(gè)句柄
????????OOP的新手經(jīng)常會(huì)混淆對(duì)象和對(duì)象的句柄。其實(shí)兩者之間的區(qū)別是非常明顯的。你通過聲明一個(gè)句柄來創(chuàng)建一個(gè)對(duì)象。在一次仿真中,一個(gè)句柄可以指向很多對(duì)象。這就是OOP和 System verilog的動(dòng)態(tài)特性。
????????在例5.6中,t1首先指向一個(gè)對(duì)象,然后指向另一個(gè)對(duì)象。圖5.1給出了對(duì)象和指針最后的結(jié)果。
例5.6為多個(gè)對(duì)象分配地址
Transaction t1,t2;//聲明兩個(gè)句柄t1=new();//為第一個(gè)Transaction對(duì)象分配地址t2=t1; //t1 和t2 都指向該對(duì)象t1=new();//為第二個(gè)Transaction對(duì)象分配地址
????????為什么我們希望動(dòng)態(tài)地創(chuàng)建對(duì)象?在一次仿真過程中,你可能需要?jiǎng)?chuàng)建成百上千個(gè)事務(wù)。 System Verilog使你能夠在需要的時(shí)候自動(dòng)創(chuàng)建對(duì)象。在 verilog中,你只能使用固定大小的數(shù)組,而且這個(gè)數(shù)組必須要大到能夠容納最大數(shù)量的事務(wù)。應(yīng)當(dāng)指出的是,這種動(dòng)態(tài)的對(duì)象創(chuàng)建不同于 Verilog語言之前所提供的任何特性。Verilog模塊的實(shí)例和它的名字是在編譯的過程中靜態(tài)地捆綁在一起的。即使是在仿真過程中產(chǎn)生和自動(dòng)注銷的 automatic變量,名字和內(nèi)存也總是捆在一起的。
5.7對(duì)象的解除分配( deallocation)
????????你已經(jīng)知道了如何創(chuàng)建一個(gè)對(duì)象,但是你知道怎么回收它嗎?例如,你的測(cè)試平臺(tái)創(chuàng)建并且發(fā)起了上千次的事務(wù),例如發(fā)到DUT的事務(wù)。一旦你得知事務(wù)已經(jīng)成功完成,并且也得到了統(tǒng)計(jì)結(jié)果,你就不需要再保留這些對(duì)象了。這時(shí)候,你需要回收內(nèi)存。否則,長(zhǎng)時(shí)間的仿真會(huì)將內(nèi)存耗盡,或者運(yùn)行得越來越慢。垃圾回收是一種自動(dòng)釋放不再被引用的對(duì)象的過程。 System verilog分辨對(duì)象不再被引用的辦法就是記住指向它的句柄的數(shù)量,當(dāng)最后一個(gè)句柄不再引用某個(gè)對(duì)象了System verilog就釋放該對(duì)象的空間。
例5.7創(chuàng)建多個(gè)對(duì)象
Transaction t; //創(chuàng)建一個(gè)句柄
t=new(); //分配一個(gè)新的Transaction
t=new(); //分誒第二個(gè),并釋放第一個(gè)
t=null; //解除分配第二個(gè)
????????例5.7的第二行調(diào)用new()創(chuàng)建了一個(gè)對(duì)象,并且將其地址保存在句柄t中。下個(gè)new()函數(shù)的調(diào)用創(chuàng)建了一個(gè)新的對(duì)象,并將其地址放在t中,覆蓋了句柄t先前的值。因?yàn)檫@時(shí)候已經(jīng)沒有任何句柄指向第一個(gè)對(duì)象, System verilog就可以將其解除分配了。對(duì)象可以立刻被刪除,或者等上一小段時(shí)間再刪除。最后一行明確地清除句柄,所以至此第二個(gè)對(duì)象也可以被解除分配了。
????????如果你熟悉C++,這些對(duì)象和句柄的概念可能看起來不陌生,但是這兩者存在著些非常重要的區(qū)別。 System verilog的句柄只能指向一種類型,即所謂的“安全類型”。在C中,一個(gè)典型的無類型指針只是內(nèi)存中的一個(gè)地址,你可以將它設(shè)為任何數(shù)值,還可以通過預(yù)增量(pre- Increment)操作來改變它。這時(shí)候你無法保證指針一定是合法的。C++的指針相對(duì)安全些但和C有類似的問題。 System Verilog不允許對(duì)句柄作和C類似的改變,也不允許將一種類型的句柄指向另一種類型的對(duì)象。( System Verilog的OOP規(guī)范比起C++來更加接近Java)。
????????其次,因?yàn)?System verilog在沒有任何句柄指向一個(gè)對(duì)象的時(shí)候自動(dòng)回收垃圾,這就保證了代碼中所使用的任何句柄都是合法的。而在C/C++中,指針可以指向一個(gè)不再存在的對(duì)象。在這些語言中,垃圾回收是手動(dòng)的,所以當(dāng)你忘了手動(dòng)釋放對(duì)象的時(shí)候,代碼就可能會(huì)存在內(nèi)存泄露。
????????System verilog不能回收一個(gè)被句柄引用的對(duì)象。如果你創(chuàng)建了一個(gè)鏈接表,除非手工設(shè)置所有的句柄為null,清除所有句柄,否則 SystemVerilog不會(huì)釋放對(duì)象的空間。如果對(duì)象包含有從一個(gè)線程派生出來的程序,那么只要該線程仍在運(yùn)行,這個(gè)對(duì)象的空間就不會(huì)被釋放。同樣的,任何被一個(gè)子線程所使用對(duì)象在該線程沒有結(jié)東之前不會(huì)被解除分配。關(guān)于線程的更多信息請(qǐng)參見第7章。
5.8使用對(duì)象
????????現(xiàn)在你已經(jīng)分配了一個(gè)對(duì)象,那么如何來使用它呢?回到 Verilog模塊的對(duì)比,可以對(duì)對(duì)象使用“.”符號(hào)來引用變量和子程序,如例5.8所示。
例5.8使用對(duì)象的變量和子程序
Transaction t; //聲明一個(gè)Transaction句柄
t=new(); //創(chuàng)建衣蛾Transaction對(duì)象
t.addr=32'h44; //設(shè)置變量的值
t.display();//調(diào)用一個(gè)子程序
????????嚴(yán)格的OOP規(guī)定,只能通過對(duì)象的公有方法訪問對(duì)象的變量,例如get()和put(),這是因?yàn)橹苯釉L問變量會(huì)限制以后對(duì)代碼的修改。如果將來出現(xiàn)一個(gè)更好的(或者另種)算法,你可能因?yàn)樾枰淖兯心切┲苯右米兞康拇a,而導(dǎo)致你不能采用這種新的算法。
5.9靜態(tài)變量和全局變量
????????每個(gè)對(duì)象都有自己的局部變量,這些變量不和任何其他對(duì)象共享。如果有兩個(gè)Transaction對(duì)象,則每個(gè)對(duì)象都有自己的addr、crc和data變量。但有時(shí)候你需要個(gè)某種類型的變量,被所有的對(duì)象所共享。例如,可能需要一個(gè)變量來保存已創(chuàng)建事務(wù)的數(shù)目。如果沒有OOP,可能需要?jiǎng)?chuàng)建一個(gè)全局變量。然后你就有了一個(gè)只被一小段代碼所使用,但是整個(gè)測(cè)試平臺(tái)都可以訪問的全局變量。這會(huì)“污染”全局名字空間(namespace),導(dǎo)致即使你想定義局部變量,但是變量對(duì)每個(gè)人都是可見的。
5.9.1簡(jiǎn)單的靜態(tài)變量
????????在 System Verilog中,可以在類中創(chuàng)建一個(gè)靜態(tài)變量。該變量將被這個(gè)類的所有實(shí)例所共享,并且它的使用范圍僅限于這個(gè)類。在例5,9中,靜態(tài)變量 count用來保存迄今為止所創(chuàng)建的對(duì)象的數(shù)目。它在聲明的時(shí)候被初始化為0,因?yàn)樵诜抡骈_始不存在任何的事務(wù)。每構(gòu)造一個(gè)新的對(duì)象,它就被標(biāo)記為一個(gè)唯一的值,同時(shí) count將被加1。
例5.9含有一個(gè)靜態(tài)變量的類
class Transaction;static int count =0; //已經(jīng)創(chuàng)建的對(duì)象的數(shù)目int id; //實(shí)例的唯一標(biāo)志function new ();id=count++;//設(shè)置標(biāo)志,count遞增endfunction
endclassTransaction t1,t2;
initial begint1=new(); //第一個(gè)實(shí)例,id=0,count=1t2=new(); //第二個(gè)實(shí)例id=1,count=2$display("Second id =%d ,count=%d ",t2.id,t2.count);end
????????在例5.9中,不管創(chuàng)建了多少個(gè) Transaction對(duì)象,靜態(tài)變量 count只存在一個(gè)。你可以認(rèn)為 count保存在類中而非對(duì)象中的。變量id不是靜態(tài)的,所以每個(gè) Transaction都有自己的id變量,如圖52所示。這樣,你就不需要為 count創(chuàng)建一個(gè)全局變量了。
????????使用ID域是在設(shè)計(jì)中跟蹤對(duì)象的一個(gè)非常好的方法。在調(diào)試測(cè)試平臺(tái)的時(shí)候,你經(jīng)常需要一個(gè)唯一的值。 System Verilog不能輸出對(duì)象的地址,但是可以創(chuàng)建ID域來區(qū)分對(duì)象。當(dāng)你打算創(chuàng)建一個(gè)全局變量的時(shí)侯,首先考慮創(chuàng)建一個(gè)類的靜態(tài)變量。一個(gè)類應(yīng)該是自給自足的,對(duì)外部的引用越少越好。
5.9.2通過類名訪問靜態(tài)變量
????????例5.9中使用了句柄來引用靜態(tài)變量。其實(shí)無需使用句柄,你可以使用類名加上::,即類作用域操作符,如例5.10所示。
class Transaction;static int count =0; //已經(jīng)創(chuàng)建的對(duì)象的數(shù)目int id; //實(shí)例的唯一標(biāo)志function new ();id=count++;//設(shè)置標(biāo)志,count遞增endfunction
endclass
`include "class_static.sv"
module tb;Transaction t1,t2;
initial begint1=new(); //第一個(gè)實(shí)例,id=0,count=1//$display("Second id =%d ,count=%d ",t1.id,t1.count);$display("first count =%d ,", Transaction::count);t2=new(); //第二個(gè)實(shí)例id=1,count=2//$display("Second id =%d ,count=%d ",t2.id,t2.count);$display ("second count is %d ",Transaction::count);endendmodule
5.9.3靜態(tài)變量的初始化
????????靜態(tài)變量通常在聲明時(shí)初始化。你不能簡(jiǎn)單地在類的構(gòu)造函數(shù)中初始化靜態(tài)變量,因?yàn)槊恳粋€(gè)新的對(duì)象都會(huì)調(diào)用構(gòu)造函數(shù)。你可能需要另一個(gè)靜態(tài)變量來作為標(biāo)志,以標(biāo)識(shí)原始變量是否已被初始化。如果需要做一個(gè)更加詳細(xì)的初始化,你可以使用初始化塊。但是要保證在創(chuàng)建第一個(gè)對(duì)象前,就已經(jīng)初始化了靜態(tài)變量。
5.9.4靜態(tài)方法
????????靜態(tài)變量的另一種用途是在類的每一個(gè)實(shí)例都需要從同一個(gè)對(duì)象獲取信息的時(shí)候。例如, transaction類可能需要從配置對(duì)象獲取模式位。如果該位在 transaction類中定義成了非靜態(tài)句柄,那么每一個(gè)對(duì)象都會(huì)有一份模式位的拷貝,造成內(nèi)存浪費(fèi)。例5.11舉例說明了如何使用靜態(tài)變量。例5.11句柄的靜態(tài)存儲(chǔ)
class Transaction;static Condig cfg;//使用靜態(tài)存儲(chǔ)的句柄MODE_E mode;function new ();mode=cfg.mode;endfunctionendclassConfig cfg;initial begincfg=new(MODE_ON);Transaction:: cfg =cfg;end
????????當(dāng)你使用更多的靜態(tài)變量的時(shí)候,操作它們的代碼會(huì)快速增長(zhǎng)為一個(gè)很大的程序。在 System Verilog中,你可以在類中創(chuàng)建一個(gè)靜態(tài)方法以用于讀寫靜態(tài)變量,甚至可以在第一個(gè)實(shí)例產(chǎn)生之前讀寫靜態(tài)變量。
????????例5.12中含有一個(gè)簡(jiǎn)單的靜態(tài)函數(shù)來顯示靜態(tài)變量的值。 System verilog不允許靜態(tài)方法讀寫非靜態(tài)變量,例如id。你可以根據(jù)下面的代碼來理解這個(gè)限制。在例5.12的最后調(diào)用 display_static函數(shù)的時(shí)候,還沒有創(chuàng)建任何 Transaction類的對(duì)象,所以還沒有為變量id分配存儲(chǔ)空間。
例5.12顯示靜態(tài)變量的靜態(tài)方法
class Transaction;static Config cfg;static int count=0;int id;//顯示靜態(tài)變量的靜態(tài)方法static function void display_statics();$display("Transaction cfg.mode %s ,count =%0d ",cfg.mode.name(),count);endfunction
endclassinitial begincfg=new(MODE_ON);Transaction ::cfg =cfg;Transaction :;display_statics();end
5.10類的方法
????????類中的程序也稱為方法,也就是在類的作用域內(nèi)定義的內(nèi)部task或者 function。例5.13為類 Transaction和 PCI_Tran定義了 display()方法。 SystemⅤ erilog會(huì)根據(jù)句柄的類型調(diào)用正確的 display()方法。
例5.13類中的方法
class Transaction;bit [31:0] addr,crc ,data[8];function void display();$display("@ %0t :TR addr =%h ,crc=%h",$time,addr,crc);$write("\t data [0-7]=");foreach (data[i])$write(data[i]);$display();endfunction
endclassclass PCI_Tran;bit [31:0] addr,data;//使用真實(shí)的名字function void display();$display("@%0t :PCI :addr=%h,data=%h",$time,addr,data);endfunction
endclassTransaction t;
PCI_Tran pc;initial begint=new();//創(chuàng)建一個(gè)Transaction 對(duì)象t.display();; //調(diào)用Transaction的方法pc=new();//創(chuàng)建一個(gè)PCI事務(wù)pc.display(); //調(diào)用 PCI事務(wù)的方法end
類中的方法默認(rèn)使用自動(dòng)存儲(chǔ),所以你不必?fù)?dān)心忘記使用 automatic修飾符。
5.11在類之外定義方法
??????在 System Verilog中你可以將方法的原型定義(方法名和參數(shù))放在類的內(nèi)部,而方法的程序體(過程代碼)放在類的后面定義。
????????下面是一個(gè)如何創(chuàng)建一個(gè)塊外聲明的例子。復(fù)制該方法的第一行,包括方法名和參數(shù),然后在開始處添加關(guān)鍵詞 extern。然后將整個(gè)方法移至類定義的后面,并在方法名前加上類名和兩個(gè)冒號(hào)(::作用域操作符)。上例中的類可以如下定義。
例5.14塊外方法聲明
class Transaction;bit [31:0] addr,crc ,data[8];extern function void display();endclassfunction void Transaction::display();$display("@ %0t :TR addr =%h ,crc=%h",$time,addr,crc);$write("\t data [0-7]=");foreach (data[i])$write(data[i]);$display();endfunctionclass PCI_Tran;bit [31:0] addr,data;//使用真實(shí)的名字extern function void display();
endclassfunction void PCI_Tran::display();$display("@%0t :PCI :addr=%h,data=%h",$time,addr,data);endfunctionTransaction t;
PCI_Tran pc;initial begint=new();//創(chuàng)建一個(gè)Transaction 對(duì)象t.display();; //調(diào)用Transaction的方法pc=new();//創(chuàng)建一個(gè)PCI事務(wù)pc.display(); //調(diào)用 PCI事務(wù)的方法end
????????方法的原型定義跟內(nèi)容不相匹配是一個(gè)常見的編碼錯(cuò)誤。 SystemVerilog要求除了多一個(gè)類名和作用域操作符之外,原型定義跟塊外的方法定義一致。此外有些OOP編譯器(g十十和VCS)禁止在原型和類的內(nèi)部指定參數(shù)的默認(rèn)值。因?yàn)閰?shù)默認(rèn)值對(duì)于調(diào)用該方法的代碼非常重要,但是對(duì)于如何實(shí)現(xiàn)就不那么重要了,所以它們最好放在類定義部分。
????????另一個(gè)常見錯(cuò)誤是在類的外部聲明方法時(shí)忘記寫類名。這樣做的結(jié)果是它的作用范圍高了一級(jí)(也許是在整個(gè)程序或包的范圍內(nèi)都可調(diào)用),當(dāng)某個(gè)任務(wù)試圖訪問類一級(jí)的變量和方法的時(shí)候編譯器就會(huì)報(bào)錯(cuò)如例5.15所示。
例5.15類的外部任務(wù)定義忘記類名
class Broken;int id;extern function void display;
endclassfunction void display;//忘記::分隔符$display("Broken :id =%0d ",id);//錯(cuò)誤,找不到id
endfunction
5.12作用域規(guī)則
????????在編寫測(cè)試平臺(tái)的時(shí)候,需要?jiǎng)?chuàng)建和引用許多的變量。 System verilog采用與 Verilog相同的基本規(guī)則,但是略有改進(jìn)。作用域是一個(gè)代碼塊,例如一個(gè)模塊,一個(gè)程序、任務(wù)、函數(shù)、類或者 begin-end塊。For和 foreach循環(huán)自動(dòng)創(chuàng)建一個(gè)塊,所以下標(biāo)變量可以作為該循環(huán)作用域的局部變量來聲明和創(chuàng)建。你可以在塊中定義新的變量。 System Verilog中新增的特性是可以在一個(gè)沒有名字的begin-end塊中聲明變量,例如for循環(huán)內(nèi)定義了索引變量名字可以相對(duì)于當(dāng)前作用域,也可以用絕對(duì)作用域表示,例如以$root開始。對(duì)于一個(gè)相對(duì)的名字, System Verilog查找作用域內(nèi)的名字清單,直到找到匹配的名字。如果不想引起歧義,可以在名字的開頭使用 $root。
????????例5.16在不同的作用域內(nèi)使用了相同的名字。應(yīng)當(dāng)指出的是,在實(shí)際的代碼里應(yīng)當(dāng)使用更加有意義的名字。例子中的limit被用作全局變量、程序變量、類變量,任務(wù)變量和初始化塊中的局部變量。后者是一個(gè)未命名的塊,所以最終創(chuàng)建的標(biāo)記( label)取決于具體的工具。
例5.16名字作用域
program automatic p;int limit; //$root.p.limitclass Foo;int limit ,array[]; //$root.p.Foo.limit//$root.p.Foo.print.limitfunction void print(int limit)for (int i=0; i<limit;i++)$display("%m ::arrat[%0d]=%0d",i,array[i]);endfunctionendclassinitialbeginint limit =$root.limit;Foo bar;bar=new;bar.array=new[limit];bar.print(limit)end
endprogram
????????對(duì)測(cè)試平臺(tái)來說,你可以在 program或者 initial塊中聲明變量。如果一個(gè)變量?jī)H在一個(gè) initial塊中使用,例如計(jì)數(shù)器,應(yīng)當(dāng)在使用它的塊中聲明,以避免跟其他塊出現(xiàn)潛在的沖突。注意:如果在一個(gè)未命名的塊內(nèi)定義變量,如例5.16中的 initia1塊,那么最終在各種工具中的層次結(jié)構(gòu)名字就可能完全不同。
????????類應(yīng)當(dāng)在 program或者 module外的 package中定義。這應(yīng)當(dāng)是所有測(cè)試平臺(tái)都該遵守的,你可以將臨時(shí)變量在測(cè)試平臺(tái)最內(nèi)部的某處定義,如果在一個(gè)塊內(nèi)使用了一個(gè)未聲明的變量,碰巧在程序塊中有一個(gè)同名的變量,那么類就會(huì)使用程序塊中的變量,不會(huì)給出任何的警告。在例5,17中,函數(shù)Bad: display沒有聲明循環(huán)變量i,所以 System Verilog將使用程序級(jí)變量的i。調(diào)用該函數(shù)就會(huì)改變test.i的值,這可能不是你所希望的!
例5.17使用了錯(cuò)誤的變量的類
program test;int i ;//程序級(jí)變量class Bad;logic [31:0] data[];//調(diào)用該函數(shù)將會(huì)改變程序級(jí)的變量ifunction void display;//在下面的雨具里忘了聲明ifor (i=0; i<data.size;i++)$display("data[%0d ]=%x",i,data[i]);endfunction
endclass
endprogram
????????如果你將類移到一個(gè) package中,那么類就看不到程序一級(jí)的變量了,由此就不會(huì)無意調(diào)用到它了。
例5.18將類移入?package來查找程序錯(cuò)誤
package Mistake;class Bad;logic [31:0] data[];//未定義i,不會(huì)被編譯function void display;for (i=0; i<data.size;i++)$display("data[%0d]=%x",i,data[i]);endfunctionendclass
endpackageprogram test;int i; //程序級(jí)變量import Mistake::*;endprogram
5.12.1this是什么
????????當(dāng)你使用一個(gè)變量名的時(shí)候, System verilog將先在當(dāng)前作用域內(nèi)尋找,接著在上一級(jí)作用域內(nèi)尋找,直到找到該變量為止。這也是 Verilog所采用的算法。但是如果你在類的很深的底層作用域,卻想明確地引用類一級(jí)的對(duì)象呢?這種風(fēng)格的代碼在構(gòu)造函數(shù)里最常見,因?yàn)檫@時(shí)候程序員使用相同的類變量名和參數(shù)名。在例5.19中,關(guān)鍵詞“this”明確地告訴 System verilog你正在將局部變量 oname賦給類一級(jí)變量 oname。
例5.19使用this指針指向類一級(jí)變量
class scoping ;string oname;function new(string oname);this.oname=oname; //類變量oname=局部變量onmeendfunction
endclass
5.13在一個(gè)類內(nèi)使用另一個(gè)類
? ? ? ? 通過使用指向?qū)ο蟮木浔?一個(gè)類內(nèi)部可以包含另一個(gè)類的實(shí)例。這如同在 Verilog中、在一個(gè)模塊內(nèi)部包含另一個(gè)模塊的實(shí)例,以建立設(shè)計(jì)的層次結(jié)構(gòu)。這樣包含的目的通常是重用和控制復(fù)雜度。例如,你的每一個(gè)事務(wù)都可能需要一個(gè)帶有時(shí)間戳的統(tǒng)計(jì)塊,它記錄事務(wù)開始和結(jié)束的時(shí)間,以及有關(guān)此次事務(wù)的所有信息,如圖5.3所示。
例5.20給出了 statistics類的定義。
例5.20 statistics類的聲明
class Statistics;time startT,stopT;//事務(wù)的時(shí)間static int ntrans =0; //事務(wù)的數(shù)目static time total_elapsed_time=0;function time how_long;how_long =stopT -startT;ntrans++;total_elapsed_time+=how_long;endfunctionfunction void start;startT=$time;endfunction
endclass
現(xiàn)在你可以在另一個(gè)類中使用這個(gè)類。
class Transaction ;bit[31:0] addr,crc,data[9];Statistics stats;//Statistics 句柄function new();stats=new();//創(chuàng)建stats實(shí)例endfunctiontask create_packet();//填充數(shù)據(jù)包stats.start();//傳送數(shù)據(jù)包endtask
endclass
????????最外層的類 Transaction可以通過分層調(diào)用語法來調(diào)用 Statistics類中的成員例如 stats. startT。一定要記得例化對(duì)象,否則句柄 stats是nu11,調(diào)用 start會(huì)失敗。這最好在上層即 Transaction類的構(gòu)造函數(shù)中完成。
5.13.2編譯順序的問題
????????有時(shí)候你需要編譯一個(gè)類,而這個(gè)類包含一個(gè)尚未定義的類。聲明這個(gè)被包含的類的句柄將會(huì)引起錯(cuò)誤,因?yàn)榫幾g器還不認(rèn)識(shí)這個(gè)新的數(shù)據(jù)類型。這時(shí)侯你需要使用typedef語句聲明一個(gè)類名,如下例所示。
例5.22使用 typedef class語句
typedef class Statistics;//定義低級(jí)別的類class Transaction ;Statistics stats;//使用Statistics類...
endclassclass Statistics ;//定義Statistics 類變量oname...endclass
5.14理解動(dòng)態(tài)對(duì)象
????????在靜態(tài)分配內(nèi)存的語言中,每一塊數(shù)據(jù)都有一個(gè)變量與之關(guān)聯(lián),例如 verilog中可能有一個(gè)wire類型的變量 grant,整數(shù)變量 count和一個(gè)模塊實(shí)例i1。在OOP中,不存在這種一一對(duì)應(yīng)關(guān)系。可能有很多對(duì)象,但是只定義了少量句柄。一個(gè)測(cè)試平臺(tái)在仿真過程中可能產(chǎn)生了數(shù)千次事務(wù)的對(duì)象,但是僅有幾個(gè)句柄在操作它們。如果你只寫過verilog代碼,對(duì)于這種情況你可能需要好好地適應(yīng)一下。
????????在實(shí)際使用中,每一個(gè)對(duì)象都有一個(gè)句柄。有些句柄可能存儲(chǔ)在數(shù)組或者隊(duì)列中,或者在另一個(gè)對(duì)象中,例如鏈表。對(duì)于保存在郵箱( mailbox)中的對(duì)象,句柄就是 SystemVerilog的一個(gè)內(nèi)部結(jié)構(gòu)。關(guān)于郵箱的更加詳細(xì)的信息參見7.6節(jié)。
5.14.1將對(duì)象傳遞給方法
????????當(dāng)你將對(duì)象傳遞給一個(gè)方法的時(shí)候發(fā)生了什么?也許這個(gè)方法只需要讀取對(duì)象中的值,例如上面的 transmit.。又或者你的方法可能會(huì)修改對(duì)象的值,例如創(chuàng)建一個(gè)數(shù)據(jù)包的方法。不管是哪一種情形,當(dāng)你調(diào)用方法的時(shí)候,傳遞的是對(duì)象的句柄而非對(duì)象本身。
????????在圖5,4中,任務(wù) generator調(diào)用了 transmit。兩個(gè)句柄 generator.t和transmit.t都指向同一個(gè)對(duì)象。
????????當(dāng)你調(diào)用一個(gè)帶有標(biāo)量變量(不是數(shù)組,也不是對(duì)象)的方法并且使用ref關(guān)鍵詞的時(shí)候, System verilog傳遞該標(biāo)量的地址,所以方法也可以修改標(biāo)量變量的值。如果你不使用ref關(guān)鍵詞, System verilog將該標(biāo)量的值復(fù)制到參數(shù)變量中,對(duì)該參數(shù)變量的任何改變不會(huì)影響原變量的值。
例5.23傳遞對(duì)象
//將包傳送到一個(gè)32位總線上
task transmit (Transaction t);bus.rx_data<=t.data;t.stats.startT=$time;endtaskTransaction t;initial begint=new();//為對(duì)象分配空間t.addr=42;//初始化數(shù)值transmit(t);//將對(duì)象傳遞給任務(wù)end
????????在例5.23中,初始化塊先產(chǎn)生一個(gè) Transaction對(duì)象,并且調(diào)用 transmit任務(wù),transmit任務(wù)的參數(shù)是指向該對(duì)象的句柄通過使用句柄, transmit可以讀寫對(duì)象中的值。但是,如果 transmit試圖改變句柄,初始化塊將不會(huì)看到結(jié)果,因?yàn)閰?shù)t沒有使用ref修飾符。
????????方法可以改變一個(gè)對(duì)象,即使方法的句柄參數(shù)沒有使用ref修飾符。這容易給新用戶帶來混淆,因?yàn)樗麄儗⒕浔蛯?duì)象混為一談。如上例所示, transmit可以在不改變句柄t的情況下為對(duì)象加蓋時(shí)間戳。如果你不想讓對(duì)象在方法中被修改,那么就傳遞一個(gè)對(duì)象的拷貝給方法,這樣原來的數(shù)據(jù)就保持不變。關(guān)于對(duì)象復(fù)制的更多信息參見5.15節(jié)。
5.14.2在任務(wù)中修改句柄
? ? ? ? 一個(gè)常見的編碼錯(cuò)誤是,當(dāng)你想修改參數(shù)的值的時(shí)侯,忘記在方法的參數(shù)前加ref關(guān)鍵詞,尤其是句柄。在例5.26中,參數(shù)tx沒有被聲明為ref,所以在方法內(nèi)部對(duì)tx的修改不會(huì)被調(diào)用該方法的代碼看到。參數(shù)tr默認(rèn)的信號(hào)方向是 input。
例5.24錯(cuò)誤的事務(wù)生成任務(wù),句柄前缺少關(guān)鍵詞ref
function void create(Transaction tr);//錯(cuò)誤,缺少reftr=new();tr.addr=42;//初始化其他域endfunctionTransaction r;
initial begincreate(t);//創(chuàng)建一個(gè)transaction$display(t.addr);//失敗,因?yàn)閠=nullend
????????盡管 create修改了參數(shù)tr,調(diào)用塊中的句柄t仍為nu11。你需要將參數(shù)tr聲明為ref。
例5.25正確的事務(wù)發(fā)生器,參數(shù)是帶有ref的句柄
function void create (ref Transaction tr);...
endfunction //create
5.14.3在程序中修改對(duì)象
????????在測(cè)試平臺(tái)中,一個(gè)常見的錯(cuò)誤是忘記為每個(gè)事務(wù)創(chuàng)建一個(gè)新的對(duì)象。在例5.26中, generator bad任務(wù)創(chuàng)建了一個(gè)有隨機(jī)值的Transaction對(duì)象,然后將它多次傳送給設(shè)計(jì)。
例5.26錯(cuò)誤的發(fā)生器,只創(chuàng)建了一個(gè)對(duì)象
task generator_bad(int n);Transaction t;t=new();//創(chuàng)新一個(gè)新對(duì)象repeat (n)begint.addr=$rondom();//變量初始化$display("second addr =%h",t.addr);transmit(t); //將它發(fā)送到DUTend
endtask
????????這個(gè)錯(cuò)誤的癥狀是什么?上面的代碼僅創(chuàng)建一個(gè) Transaction,所以每一次循環(huán),generator bad在發(fā)送事務(wù)對(duì)象的同時(shí)又修改了它的內(nèi)容。當(dāng)你運(yùn)行這段代碼的時(shí)候,Display會(huì)顯示很多不同的addr值,但是所有被傳送的 Transaction都有相同的addx數(shù)值。如果 transmit的線程需要耗費(fèi)幾個(gè)周期完成發(fā)送,就有可能出現(xiàn)這種錯(cuò)誤,因?yàn)閷?duì)象的內(nèi)容在傳送的期間被重新隨機(jī)化了。如果 transmit任務(wù)發(fā)送的是對(duì)象的副本,你就可以多次重復(fù)利用這個(gè)對(duì)象了。這種錯(cuò)誤也會(huì)發(fā)生在郵箱中,如例7.31所示。
????????為避免出現(xiàn)這種錯(cuò)誤,你需要在每次循環(huán)的時(shí)候創(chuàng)建一個(gè)新的 Transaction對(duì)象。
例5.27正確的產(chǎn)生器,創(chuàng)建多個(gè)對(duì)象
task generator_good(int n );Transaction t;repeat(n)begint=new();//創(chuàng)建一個(gè)新對(duì)象$display("sending addr=%h",t.addr);transmit(t);//將它發(fā)送到DUTend
endtask
5.14.4句柄數(shù)組
????????在寫測(cè)試平臺(tái)的時(shí)候,可能需要保存并且引用許多對(duì)象。你可以創(chuàng)建句柄數(shù)組,數(shù)組的每一個(gè)元素指向一個(gè)對(duì)象。例5.28給出了一個(gè)保存十個(gè)總線事務(wù)的句柄數(shù)組。
例5.28使用句柄數(shù)組
task generator();transmit tarry[10];foreach (tarry[i])begintarry[i]=new();//創(chuàng)建每一個(gè)對(duì)象transmit(tarry[i]);end
endtask
????????array數(shù)組由句柄構(gòu)成,而不是由對(duì)象構(gòu)成。所以需要在使用它們之前創(chuàng)建所有對(duì)象,就像你為一個(gè)普通的句柄創(chuàng)建對(duì)象一樣。沒有任何辦法可以調(diào)用new函數(shù)為整個(gè)句柄數(shù)組創(chuàng)建對(duì)象。
????????不存在“對(duì)象數(shù)組”的說法,雖然可以使用這個(gè)詞來代表指向?qū)ο蟮木浔鷶?shù)組。你應(yīng)當(dāng)牢記這些句柄可能并沒有指向一個(gè)對(duì)象,也可能有多個(gè)句柄指向了同一個(gè)對(duì)象。
5.15對(duì)象的復(fù)制
????????有時(shí)候可能需要復(fù)制一個(gè)對(duì)象,以防止對(duì)象的方法修改原始對(duì)象的值,或者在一個(gè)發(fā)生器中保留約束。可以使用簡(jiǎn)單的new函數(shù)的內(nèi)建拷貝功能,也可以為更復(fù)雜的類編寫專門的對(duì)象拷貝代碼。8.2節(jié)介紹了為什么需要?jiǎng)?chuàng)建一個(gè)復(fù)制方法。
5.15.1使用new操作符復(fù)制一個(gè)對(duì)象
????????使用new復(fù)制一個(gè)對(duì)象簡(jiǎn)單而且可靠,它創(chuàng)建了一個(gè)新的對(duì)象,并且復(fù)制了現(xiàn)有對(duì)象的所有變量。但是你已經(jīng)定義的任何new()函數(shù)都不會(huì)被調(diào)用。
例5.29使用new復(fù)制一個(gè)簡(jiǎn)單類
class Transaction;bit[31:0] addr,crc,data[8];
endclassTransaction src,dst;initial beginsrc=new;//創(chuàng)建第一個(gè)對(duì)象dst=new src;//使用new 操作符進(jìn)行復(fù)制end
????????這是一種簡(jiǎn)易復(fù)制( shallow copy),類似于原對(duì)象的一個(gè)影印本,原對(duì)象的值被盲目地抄寫到目的對(duì)象中。如果類中包含一個(gè)指向另一個(gè)類的句柄,那么,只有最高一級(jí)的對(duì)象被new操作符復(fù)制,下層的對(duì)象都不會(huì)被復(fù)制。在例5.30中, ransaction類包含了個(gè)指向 statistics類的句柄,原始定義見例5.20。
例5.30使用new操作符復(fù)制一個(gè)復(fù)雜類
class Transaction;bit [31:0] addr ,crc, data[8];static int count =0;int id;Statistics stats;//指向Statistics對(duì)象的句柄function new;stats=new();//構(gòu)造一個(gè)新的Statistics對(duì)象id=count++;endfunction
endclassTransaction src,dst;initial beginsrc=new();//創(chuàng)建一個(gè)transaction對(duì)象src.stats.startT=42;dst=new src;//用new操作符將src拷貝到dst中dst.stats.startT=96;//改變dst 和src的stats$display(src.stats.startT);end
????????初始化塊創(chuàng)建第一個(gè) Transaction對(duì)象,并且修改了它內(nèi)部 statistics對(duì)象的變量,見圖5.5。

????????當(dāng)調(diào)用new函數(shù)進(jìn)行復(fù)制的時(shí)候, Transaction對(duì)象被拷貝,但是 Statistics對(duì)象沒有被復(fù)制。這是因?yàn)楫?dāng)你使用new操作符復(fù)制一個(gè)對(duì)象的時(shí)候,它不會(huì)調(diào)用你自己的new()函數(shù)。相反的變量和句柄的值被復(fù)制,所以現(xiàn)在兩個(gè) Transaction對(duì)象都具有相同的id值,如圖5.6所示。
????????更糟糕的是,兩個(gè) Transaction對(duì)象都指向同一個(gè) Statistics對(duì)象,所以使用src句柄修改 startT會(huì)影響到dst句柄可以看到的值。

5.15.2編寫自己的簡(jiǎn)單復(fù)制函數(shù)
????????如果你有一個(gè)簡(jiǎn)單的類,它不包含任何對(duì)其他類的引用,那么編寫copy函數(shù)非常容易。
例5.31含有copy函數(shù)的簡(jiǎn)單類
class Transaction ;bit[31:0] addr,crc,data[8];//沒有Statistics句柄function Transaction copy;copy =new();//創(chuàng)建目標(biāo)對(duì)象copy.addr=addr;//填入數(shù)值copy.crc=crc;copy.data=data;//賦值數(shù)組endfunction
endclass
例5.32使用copy函數(shù)
Transaction src dst;
initial beginsrc=new();//創(chuàng)建第一個(gè)對(duì)象dst=src.copy;//賦值對(duì)象end
5.15.3編寫自己的深層復(fù)制函數(shù)
????????對(duì)于并非簡(jiǎn)單的類,你應(yīng)該創(chuàng)建自己的copy函數(shù)。通過調(diào)用類所包含的所有對(duì)象的copy函數(shù),可以做一個(gè)深層的拷貝。你自己的copy函數(shù)需要確保所有用戶域(例如ID)保持一致。創(chuàng)建自定義copy函數(shù)的最后階段需要在新增變量的同時(shí)更新它們。
例5.33復(fù)雜類的深層復(fù)制函數(shù)
class Transaction ;bit[31:0] addr,crc,data[8];Statistics stats;//指向Statistics對(duì)象的句柄static int count=0;int id;function new;stats=new();id=count++;endfunctionfunction Transaction copy;copy=new();//創(chuàng)建目標(biāo)copy.addr=addr;//填入數(shù)值copy.crc=crc;copy.data=data;copy.stats=stats.copy();//調(diào)用Statstics::copy函數(shù)id=count++;endfunction
endclass
????????copy調(diào)用了構(gòu)造函數(shù)new(),所以每一個(gè)對(duì)象都有一個(gè)唯一的id。需要為 statistics類和層次結(jié)構(gòu)中的每一個(gè)類增加一個(gè)copy()方法。
例5.34 statistics類定義
class Statistics;time startT,stopT;//Transaction的事件戳function Statistics copy;copy=new();copy.startT=startT;copy.stopT=stopT;endfunction
endclass
????????這樣一來當(dāng)你復(fù)制一個(gè) Transaction對(duì)象的時(shí)候,它會(huì)有自己的 statistics對(duì)象,如例5.35所示。例5.35使用new操作符復(fù)制復(fù)雜類
Transaction src,dst;initialbeginsrc=new();//創(chuàng)建第一個(gè)對(duì)象src.stats.startT=42;//設(shè)置起始時(shí)間dat=src.copy();//使用深層賦值將src賦值給dstdst.stats.startT=96;//僅改變dst的stats的值$display(src.stats.startT);//end

5.15.4使用流操作符從數(shù)組到打包對(duì)象,或者從打包對(duì)象到數(shù)組
????????某些協(xié)議,如ATM協(xié)議,每次傳輸一個(gè)字節(jié)的控制或者數(shù)據(jù)值。在送出一個(gè) transaction之前,需要將對(duì)象中的變量打包成一個(gè)字節(jié)數(shù)組。類似的,在接受到一個(gè)字節(jié)串之后,也需要將它們解包到一個(gè) transaction對(duì)象中。這兩種功能都可以使用流操作符來完成,流操作符的例子見2.11.3節(jié)。
????????你不能將整個(gè)對(duì)象送入流操作符,因?yàn)檫@樣會(huì)包含所有的成員,包括數(shù)據(jù)成員和其他額外信息,如時(shí)間戳和你不想要打包的自檢信息。你需要編寫你自己的pack函數(shù),僅打包你所選擇的成員變量。
例5.36含有pack和 unpack函數(shù)的 Transaction類
class Transaction;bit[31:0] addr,crc,data[8];//實(shí)際數(shù)據(jù)static int count =0;//不需要打包的數(shù)據(jù)int id;function new();id=count++;endfunctionfunction void display();$write("Tr :id =%0d ,addr =%x,crc =%x",id,addr,crc);foreach (data[i])$write("%x",data[i]);$display;endfunctionfunction void pack (ref byte bytes[40]);byte={>>{addr,crc,data}};endfunctionfunction Transaction unpack(ref byte bytes[40]);{>>{addr,crc,data}}=bytes;endfunctionendclass:Transaction
例5.37使用pack和 unpack函數(shù)
Transaction tr,tr2;
byte b[40];//addr+crc+data=40字節(jié)initialbegintr=new();tr.addr=32'h0a0a0a0;//填滿對(duì)象tr.crc=1;foreach (tr.data[i])tr.data[i]=i;tr.pack(b);//打包對(duì)象到字節(jié)數(shù)組$write("Pack results:");foreach (b[i])$write("%h",b[i]);$display;tr=new();tr2.unpack(b);tr.display();end
5.16公有和私有
????????OOP的核心概念是把數(shù)據(jù)和相關(guān)的方法封裝成一個(gè)類。在一個(gè)類中,數(shù)據(jù)默認(rèn)被定義為私有,這樣防止了其他類對(duì)內(nèi)部數(shù)據(jù)成員的隨意訪問。類會(huì)提供一系列的方法來訪問和修改數(shù)據(jù)。這也使得你能夠在不讓類用戶知道的情況下修改方法的具體實(shí)現(xiàn)方式。例如,一個(gè)圖形包可能會(huì)將它的內(nèi)部表示法由笛卡兒坐標(biāo)變成極坐標(biāo),而用戶接口(訪問的方法)的功能卻不會(huì)改變??紤]一下 Transaction含有一個(gè)載荷( payload)和一個(gè)CRC,這樣硬件就可以檢測(cè)到錯(cuò)誤。在傳統(tǒng)的OOP中,你會(huì)定義一個(gè)方法設(shè)置載荷的值同時(shí)也設(shè)置CRC的值,這樣它們就可以保持同步。這樣你的對(duì)象就總是具有正確的數(shù)值。
????????但是測(cè)試平臺(tái)不同于其他的程序,例如網(wǎng)頁瀏覽器或者文字處理器。一個(gè)測(cè)試平臺(tái)需要能夠注入錯(cuò)誤。你需要產(chǎn)生一個(gè)錯(cuò)誤的CRC,以便測(cè)試硬件是如何處理錯(cuò)誤的。OOP語言諸如C十十和Java使你能夠制定變量和方法的可見性。默認(rèn)情況下,任何成員都是局部的,除非加上了標(biāo)記。
????????在 System verilog中,所有成員都是公有的,除非標(biāo)記為local或者protected。你應(yīng)當(dāng)盡量使用默認(rèn)值,以保證對(duì)DUT行為的最大程度的控制,這比軟件的長(zhǎng)期穩(wěn)定性更加重要。例如,CRC公有將使你能夠輕易地往DUT中注入錯(cuò)誤。如果CRC是局部的,你可能需要編寫額外的代碼來避開數(shù)據(jù)隱藏機(jī)制,最終使測(cè)試平臺(tái)變得更大更復(fù)雜。
5.17題外話
????????作為OOP的初學(xué)者,你可能不愿意將數(shù)據(jù)封裝成類,而僅將數(shù)據(jù)存放在一些變量中。避免這種想法!一個(gè)簡(jiǎn)單的DUT監(jiān)視器可能只在接口上采樣幾個(gè)數(shù)值,但不要將它們簡(jiǎn)單地保存在整數(shù)變量中然后傳遞給下一級(jí)。這樣可能會(huì)在一開始節(jié)省一點(diǎn)時(shí)間,但最終你還是需要將這些數(shù)值組合到一起以構(gòu)成一個(gè)完整的事務(wù)。這些事務(wù)中的幾個(gè)可能需要被組合成更高級(jí)別的事務(wù),例如DMA事務(wù)。所以,應(yīng)該立刻將這些接口數(shù)值封裝成一個(gè)事務(wù)類。這樣,你就可以在保存數(shù)據(jù)的時(shí)候同時(shí)保存相關(guān)信息(端口號(hào)、接收時(shí)間),然后將該對(duì)象傳遞給測(cè)試平臺(tái)的其他部分。
5.18建立一個(gè)測(cè)試平臺(tái)
????????你現(xiàn)在已經(jīng)離使用類創(chuàng)建一個(gè)簡(jiǎn)單的測(cè)試平臺(tái)更近了一步了。下面是第1章中的圖。顯然,圖5.9中的事務(wù)是對(duì)象,但是每一個(gè)塊也代表了一個(gè)類。
????????圖中的 Generator、 Agent、 Driver、 Monitor、 Checker和 Scoreboard都是類,被建模成事務(wù)處理器( transactor)。它們?cè)?Environment類內(nèi)部例化。為了簡(jiǎn)單起見,Test處在最高層,即處在例化 Environment類的程序中。功能覆蓋( Functional Coverage)的定義可以放在 Environment類的內(nèi)部或者外部。
????????事務(wù)處理器由一個(gè)簡(jiǎn)單的循環(huán)構(gòu)成,這個(gè)循環(huán)從前面的塊接受事務(wù)對(duì)象,經(jīng)過變換后送給后續(xù)塊。有一些塊,例如 Generator,沒有上游塊,所以該事物處理器就創(chuàng)建和隨機(jī)復(fù)制每一個(gè)事務(wù),而其他的對(duì)象例如 Driver接收到一個(gè)事務(wù)然后將其作為信號(hào)發(fā)送到DUT中。
例5.38基本的事務(wù)類
class Transaction;//通用類Transaction tr;task run;forever begin//從前一個(gè)塊中獲取事務(wù)...//做一些處理//...//發(fā)送到下游塊中//...endendtask
endclass
????????在塊之間如何交換事務(wù)呢?在程序性的代碼中,你需要在一個(gè)對(duì)象里調(diào)用另一個(gè)對(duì)象,或者使用FIFO之類的數(shù)據(jù)結(jié)構(gòu)來保存塊之間的事務(wù)。在第7章你將會(huì)學(xué)到如何使用郵箱,一種能夠延遲一個(gè)線程直到有新的數(shù)值加入的FIFO。
?