System verilog基礎(chǔ)-子程序和測(cè)試平臺(tái)
????????initial語(yǔ)句塊和always語(yǔ)句塊都是過(guò)程語(yǔ)句,initial 和 always 一樣,無(wú)法被延遲執(zhí)行,在仿真一開始它們都會(huì)同時(shí)執(zhí)行,而不同initial和always直接在執(zhí)行順序上沒(méi)有先后。always 可以搭配@(event)來(lái)實(shí)現(xiàn)組合邏輯和時(shí)序邏輯的描述,但是initial語(yǔ)句塊它是不可綜合的代碼,不能寫在rtl代碼中,通常用于仿真時(shí)賦初值使用。
任務(wù)和函數(shù)
任務(wù)(task)和函數(shù)(function)之間有很明顯的區(qū)別,其中最重要的一點(diǎn)是,任務(wù)可以消耗時(shí)間而函數(shù)不能。函數(shù)里面不能帶有諸如#100的時(shí)延語(yǔ)句或諸如@ (posedge clk)、wait(ready)的阻塞語(yǔ)句,也不能調(diào)用任務(wù)。
????????void 函數(shù),沒(méi)有返回值,它的主要用途在于調(diào)試和驗(yàn)證,比如在過(guò)程中調(diào)用void函數(shù),打印一些信息而不返回信息,打印一些諸如狀態(tài)機(jī)當(dāng)前狀態(tài),某參數(shù)當(dāng)前數(shù)值等信息。
????????function,只能有一個(gè)返回值,不能帶有延時(shí)語(yǔ)句,可以執(zhí)行一些組合邏輯計(jì)算。

仿真結(jié)果:

????任務(wù)相較于函數(shù)更加靈活,且 task 無(wú)法通過(guò) return 返回結(jié)果,因此只能通過(guò) output、inout 或者ref的參數(shù)來(lái)返回。task 內(nèi)部可以置入耗時(shí)語(yǔ)句,而 function 不能。常見的耗時(shí)語(yǔ)句有 @event、wait event、#delay 等。
?????如果要調(diào)用 function,則使用 function 和 task 均可對(duì)其調(diào)用;而如果要調(diào)用task,僅能使用task調(diào)用,因?yàn)槿绻徽{(diào)用的task內(nèi)置了耗時(shí)語(yǔ)句,則外部調(diào)用它的方法類型必須為task。
面試常出的題:task和function有什么區(qū)別?
????????在函數(shù)和任務(wù)中,如果定義函數(shù)或任務(wù)的時(shí)候,沒(méi)有給變量標(biāo)明方向,那么默認(rèn)其方向?yàn)檩斎?。缺省的類型和方向是“Logic 輸入“。
變量聲命周期:
在SV中,將數(shù)據(jù)的生命周期分為動(dòng)態(tài)(automatic)和靜態(tài)(static)。
局部變量的生命周期同其所在域共存亡,例如function/task中的臨時(shí)變量,在其方法調(diào)用結(jié)束后,臨時(shí)變量的生命也將終結(jié),所以它們是動(dòng)態(tài)生命周期。
全局變量即伴隨著程序執(zhí)行開始到結(jié)束一直存在,例如module中的變量默認(rèn)情況下全部為全局變量,用戶也可理解為module中的變量在模擬硬件信號(hào),所以它們是靜態(tài)生命周期。
如果數(shù)據(jù)變量被聲明為automatic,那么在進(jìn)入該進(jìn)程/方法后,automatic變量會(huì)被創(chuàng)建,而在離開該進(jìn)程/方法后,automatic變量會(huì)被銷毀。而static變量則從仿真開始一直持續(xù),不會(huì)被銷毀且可被多個(gè)進(jìn)程和方法共享。
調(diào)用function的時(shí)候,默認(rèn)function的類型是static的,實(shí)際上我們更加需要的是動(dòng)態(tài)變量的函數(shù),所以通常在定義function的時(shí)候,會(huì)在function和函數(shù)名之間加一個(gè) automatic聲明。
對(duì)于automatic方法,其內(nèi)部的所有變量默認(rèn)也是automatic,即伴隨automatic方法的生命周期建立和銷毀。
對(duì)于static方法,其內(nèi)部的變量默認(rèn)也是static類型。
????????在Verilog中,在子程序的開頭把input 和 inout的值復(fù)制給本地變量,在子程序退出時(shí)則復(fù)制output和inout值。除了標(biāo)量以外,沒(méi)有任何把存儲(chǔ)器傳遞給Verilog子程序的辦法。
????????而在System Verilog中,參數(shù)的傳遞方式可以指定為引用 ref 而不是復(fù)制。這種ref參數(shù)類型比input、output、inout更好用。

????????使用reg和const進(jìn)行參數(shù)傳遞。System Verilog規(guī)定了ref參數(shù)只能被用于帶自動(dòng)存儲(chǔ)的子程序中。如果你對(duì)程序或模塊指明了automatic 屬性,則整個(gè)子程序內(nèi)部都是自動(dòng)存儲(chǔ)的。如上面的例子,function調(diào)用的變量是ref類型的,所以要把function定義成automatic,如果沒(méi)定義就會(huì)報(bào)錯(cuò),因?yàn)槟J(rèn)的function和task類型是static。
仿真結(jié)果:

上面的例子還用到了const修飾符。其結(jié)果是,雖然數(shù)組變量a指向了調(diào)用程序中的數(shù)組,但子程序不能修改數(shù)組的值。如果你試圖修改數(shù)組的值,編譯器將報(bào)錯(cuò)。
對(duì)于參數(shù)傳遞的方法,還可以采用名字進(jìn)行參數(shù)傳遞:

仿真結(jié)果:

$time 與 $realtime的對(duì)比
????????系統(tǒng)任務(wù)$time 的返回值是一個(gè)根據(jù)所在模塊的時(shí)間精度要求進(jìn)行舍入的整數(shù),不帶小數(shù)部分,而$realtime的返回值則是一個(gè)帶小數(shù)部分的完整實(shí)數(shù)。
連接設(shè)計(jì)和測(cè)試平臺(tái)
什么是接口?
接口可以用作設(shè)計(jì),也可以用作驗(yàn)證,在驗(yàn)證環(huán)境中,接口可以使得連接變得簡(jiǎn)潔而不易出錯(cuò)。interface和module的使用性質(zhì)很像,它可以定義端口,也可以定義雙相信號(hào);它可以使用initial和always,也可以定義function和task。

使用了interface之后,結(jié)構(gòu)圖變成:

可以看到,使用interface之后,整個(gè)測(cè)試框圖簡(jiǎn)潔了很多。
????????接口將有關(guān)信號(hào)封裝在同一個(gè)接口中,對(duì)于設(shè)計(jì)和驗(yàn)證環(huán)境都便于維護(hù)和使用。如果你需要添加新的信號(hào),只需要在接口中定義這個(gè)信號(hào)即可。由于接口既可以在硬件世界(module)和軟件世界(class)中使用,interface作為SV中唯一的硬件和軟件環(huán)境的媒介交互,它的地位不可取代。(class可以通過(guò)指針來(lái)取到interface中的信號(hào))
????????在interface的端口列表中只需要定義時(shí)鐘、復(fù)位等公共信號(hào),或者不定義任何端口信號(hào),轉(zhuǎn)而在變量列表中定義各個(gè)需要跟DUT和TB連接的Logic變量。interface也可以通過(guò)參數(shù)化(parameter)提高復(fù)用性。
面試問(wèn)題:模塊和接口有什么區(qū)別?答:模塊可以例化模塊,模塊也可以例化接口,但是接口只能例化接口而不能例化模塊。
?接口中的clocking
????????硬件和軟件之間的連接可以用interface實(shí)現(xiàn),也可以通過(guò)modport來(lái)進(jìn)一步限定信號(hào)傳輸?shù)姆较?,避免端口連接的錯(cuò)誤。在接口中也可以聲明clocking(時(shí)序塊)和采樣的時(shí)鐘信號(hào),用來(lái)做信號(hào)的同步采樣。clocking 塊基于時(shí)鐘周期對(duì)信號(hào)進(jìn)行驅(qū)動(dòng)或者采樣的方式,通過(guò)人為的添加delay,使得testbench不再苦惱于如何準(zhǔn)確及時(shí)地對(duì)信號(hào)驅(qū)動(dòng)或者采樣,消除了由于仿真器賦值引發(fā)的delta-cycle帶來(lái)的信號(hào)競(jìng)爭(zhēng)的問(wèn)題。
????????定義一個(gè)clocking 塊,命名為bus,并由clock1的上升沿來(lái)驅(qū)動(dòng)和采樣:

第二行指出:在clocking塊中所有的信號(hào),默認(rèn)情況下都會(huì)在clocking事件的前10ns來(lái)對(duì)輸入進(jìn)行采樣,在事件的后2ns對(duì)其進(jìn)行輸出驅(qū)動(dòng)。
第三行:聲明了要對(duì)其提前10ns采樣的輸入信號(hào)data、ready、enable
第四行:聲明了輸出驅(qū)動(dòng)的ack信號(hào),該驅(qū)動(dòng)信號(hào)的事件是時(shí)鐘clock1的下降沿,覆蓋了原有default設(shè)定的上升沿后2ns。
第五行:addr的輸入,會(huì)在clock上升沿到來(lái)的前1step采樣數(shù)據(jù),即在時(shí)鐘clock1上升沿的前一個(gè)時(shí)間片采樣,采樣到上一個(gè)時(shí)鐘周期的值。

????????如果TB在采樣DUT送出的數(shù)據(jù),在時(shí)鐘與被驅(qū)動(dòng)信號(hào)之間存在delta-cycle 時(shí),應(yīng)該考慮在時(shí)鐘采樣沿的更早時(shí)間段去模擬建立時(shí)間要求采樣,這種方法可以避免由于delta-cycle問(wèn)題帶來(lái)的采樣競(jìng)爭(zhēng)問(wèn)題。當(dāng)我們把clocking運(yùn)用到interface中,用來(lái)聲明各個(gè)接口與時(shí)鐘的采樣和驅(qū)動(dòng)關(guān)系后,可以大大提高數(shù)據(jù)驅(qū)動(dòng)和采樣的準(zhǔn)確性,從根本上消除采樣競(jìng)爭(zhēng)的問(wèn)題。

仿真結(jié)果

實(shí)際波形

總結(jié):在clocking 塊中,定義了采樣input信號(hào),會(huì)在時(shí)鐘上升沿的提前3ns進(jìn)行采樣。所以才會(huì)有同時(shí)display,結(jié)果vld的值和ck.vld的值不同的原因,這就是clocking塊的作用,利用這個(gè)特性,可以模擬信號(hào)的建立時(shí)間和保持時(shí)間,來(lái)解決因?yàn)橥瑫r(shí)刻數(shù)據(jù)賦值仿真器會(huì)有一個(gè)delta-cycle延遲帶來(lái)的各種問(wèn)題。
測(cè)試的結(jié)束
在testbench里做測(cè)試的時(shí)候可以寫program來(lái)做測(cè)試,program有一個(gè)特點(diǎn)就是在program里的代碼都被執(zhí)行后,program會(huì)自動(dòng)結(jié)束,但是如果program中有forever語(yǔ)句會(huì)永遠(yuǎn)執(zhí)行下去的話,可以用$exit()來(lái)強(qiáng)制退出。當(dāng)系統(tǒng)發(fā)現(xiàn)所有的program都執(zhí)行完畢了,就會(huì)自動(dòng)結(jié)束仿真了。
面試:program和module有什么區(qū)別?
program可以看做是軟件的部分,所以program中不能出現(xiàn)和硬件行為相關(guān)的語(yǔ)句,比如always、module、interface,也不能出現(xiàn)其他program的例化語(yǔ)句。program中可以發(fā)起多個(gè)initial塊,也可以定義新的變量。
program的內(nèi)部變量賦值方式,應(yīng)該采用阻塞賦值(模擬軟件行為),program內(nèi)部在驅(qū)動(dòng)外部的硬件信號(hào)時(shí)應(yīng)該采用非阻塞賦值(硬件方式)。
總結(jié):????module(硬件盒子)、program(軟件盒子)、interface(軟硬件接口),在為驗(yàn)證環(huán)境建立獨(dú)立的測(cè)試盒子,可以考慮用program來(lái)幫助消除采樣競(jìng)爭(zhēng)問(wèn)題,以及自動(dòng)結(jié)束測(cè)試用例。也可以采用module硬盒子的方式,使用interface clocking來(lái)消除采樣信號(hào)競(jìng)爭(zhēng)問(wèn)題,使用$stop()、$finish()系統(tǒng)方法來(lái)顯示結(jié)束測(cè)試用例。
調(diào)試方法
????????如果要實(shí)現(xiàn)對(duì)字符串string賦值,那么可以采用$sformatf()的方式來(lái)對(duì)字符串變量格式化,例如string_s = $sformatf("Hello,%s",name_s),這里name_s是名字的字符串變量。
設(shè)置斷點(diǎn)
????????設(shè)置斷點(diǎn)是測(cè)試?yán)锩孀钪匾囊粋€(gè)方法之一,作為一個(gè)軟件工程師,靈活的使用斷點(diǎn)是快速定位bug的方法。比如設(shè)置了三個(gè)斷點(diǎn),不斷的仿真,經(jīng)過(guò)了第一個(gè)、第二個(gè)斷點(diǎn),發(fā)現(xiàn)一直到不了第三個(gè)斷點(diǎn),那么就有可能是在第二和第三個(gè)斷點(diǎn)之間出現(xiàn)了仿真掛起hang-on。

點(diǎn)擊代碼前面的數(shù)字,就可以給代碼打上斷點(diǎn),然后每run一次,就會(huì)進(jìn)行一步,每次運(yùn)行到斷點(diǎn)的時(shí)候就會(huì)停下來(lái),再次run時(shí),才會(huì)運(yùn)行打上斷點(diǎn)的代碼行,在左側(cè)的objects窗口可以看到變量的數(shù)值,結(jié)合設(shè)置斷點(diǎn)和觀察變量數(shù)值,可以達(dá)到快速debug的目的。

更多時(shí)候,我們會(huì)在view欄中,打開Local,局部變量,來(lái)查看斷點(diǎn)部分的變量數(shù)值,因?yàn)閛bjects里面只能顯示靜態(tài)的變量,我們調(diào)試的會(huì)有很多動(dòng)態(tài)變量(比如automatic 的function)沒(méi)法在object中顯示,所以就需要打開local窗口查看。
如果要把變量在仿真的時(shí)候強(qiáng)行改變,可以右鍵變量→Modify → Force

這里強(qiáng)行把變量改成一個(gè)值,有三種選擇,F(xiàn)reeze、Drive、Deposit三種,這三種有什么區(qū)別呢?
Freeze:把變量賦值為Value,且整個(gè)仿真階段不會(huì)改變,哪怕別處又給該變量賦值,但不會(huì)生效。
Drive、Deposit:兩者功能類似,都是給變量賦值,相當(dāng)于此刻有硬件給信號(hào)賦值,兩者的區(qū)別在于Deposit的賦值有更高的優(yōu)先級(jí),即如果此刻有多個(gè)信號(hào)給b_bit_vs_logic賦值,那么賦值結(jié)束后會(huì)采用Deposit的值,如果時(shí)Drive和多個(gè)信號(hào)賦值沖突了,會(huì)出現(xiàn)仿真問(wèn)題,不確定值。