今天別的先不聊,就單從代碼習(xí)慣出發(fā)聊聊SystemVerilog編碼層面提速的若干策略。
本篇的主體策略來自Cliff Cummings和其團(tuán)隊多年以來得出的一些研究結(jié)論,所展示的策略主要偏重于定性分析,而非定量分析,偏重結(jié)論而非詳細(xì)的理論論述。如果大家感興趣,可以自己設(shè)計仿真實驗進(jìn)一步定量分析,或深入查閱文獻(xiàn)資料深究原理。
值得一提的是,本文雖偏重定性分析和結(jié)論擺出,但是這些結(jié)論還是具有很不錯的價值,例如對SystemVerilog仿真速度的編碼層面優(yōu)化方法提供了一些思路和認(rèn)知,對SystemVerilog代碼風(fēng)格建立提供了一個新的觀察視角,當(dāng)你在代碼提速優(yōu)化“山窮水盡”之時,也許因為某條“柳暗花明”。
好了,廢話不說了,請出干貨:
1.頻繁的函數(shù)/任務(wù)調(diào)用會增加開銷
比如:用foreach遍歷方式計數(shù)(foreach有內(nèi)置函數(shù)),不如單獨的計數(shù)器!如下代碼:
這樣寫比較慢:
這樣寫比較快:
對于簡單調(diào)用,編譯器可以將函數(shù)/任務(wù)內(nèi)聯(lián)以避免堆棧幀操作,但復(fù)雜調(diào)用因為編譯器性能考慮原因通常不會內(nèi)聯(lián),每個函數(shù)/任務(wù)都將數(shù)據(jù)引用或完整的數(shù)據(jù)副本推送到調(diào)用堆棧,并處理任何指定的返回。如此就會增加仿真時間了。如果這個函數(shù)/任務(wù)本身又被循環(huán)掉用,時間就會浪費更多!
上面的反例代碼,通過foreach遍歷來統(tǒng)計mad_q中的元素數(shù),每次都需要掉用一次內(nèi)部的內(nèi)置函數(shù),將會慢于一個獨立的計數(shù)器!
2.計算表達(dá)式、引用請“逃出”循環(huán)
例2.1:循環(huán)條件中不要帶計算,每次循環(huán)都會計算一次
這樣寫比較慢:
這樣寫比較快:
例2.2:和循環(huán)因子無關(guān)的計算應(yīng)在循環(huán)外計算好
這樣寫比較慢:
這樣寫比較快:
例2.3:引用不要和循環(huán)沾邊
這樣寫比較慢:
這樣寫比較快:
這個例子比較慢的代碼把例如comms.proto.pkt….的引用帶入了循環(huán)里。
在硬件世界中,可以預(yù)先計算分層引用,因為這些引用在運行時是靜態(tài)的。在systemverilog testbench中,引用通常是同時遍歷類實例層次結(jié)構(gòu)和動態(tài)類型,所有這些都可以在仿真運行期間更改。因此,模擬器必須遍歷所有引用才能獲得數(shù)據(jù),這顯然會降低速度。
3.對于條件的相關(guān)編碼長點兒心吧
例3.1:簡單的條件短路
第一行if中通過“或”聯(lián)系起來的條件,當(dāng)其中term1為1時,則后續(xù)不用判斷則可以得出if條件整體成立。
同理第二行if中通過“與”聯(lián)系起來的條件,當(dāng)其中term1為0時,則后續(xù)不用判斷則可以得出if條件整體不成立。
所以這樣寫這個條件會比較快,例如:
if(最高頻率的條件 || 次高頻率的條件 || 最低頻率的條件),把最高頻率的寫在最前面。
例3.2:能條件成立后才進(jìn)行計算的,就不要著急放到前面算。
比如下面這個例子,data的計算是調(diào)用了randomize()這個函數(shù),但是用這個值是在一個If(live==TRUE)條件成立之后才用的!假如條件沒成立,那就是沒用上,沒用上前面是不白算了?自然就浪費資源了!(我們前面講循環(huán)的時候說該算的提前算好,看到條件這里的時候我們可能要多想想了,原來不是啥都趕前面算就好啊,哈哈)
例3.3:UVM平臺中妙用uvm_report_enabled()函數(shù)作為條件來優(yōu)化。
如下例,如果打印詳細(xì)級別設(shè)置為UVM_DEBUG或高于UVM_DEBUG,則觸發(fā)消息打印。
例3.4:再來一個UVM平臺中玩好條件的案例,monitor或者driver進(jìn)行port傳遞時,以port的size()為條件,減少不必要的打數(shù)據(jù)包的次數(shù)。
4.連接處logic的語義顯式聲明wire,可以折疊為同一對象,加快仿真速度(RTL or TB)
這樣寫比較慢:
這樣寫比較快:
SystemVerilog中的logic類型,它可以有wire線存儲或var變量存儲,如果沒有顯式聲明,則存儲類型由仿真器根據(jù)上下文確定。
別小看這個類型,對仿真差別很大哦,如果是wire型,仿真器可以折疊為同一對象以獲得更高的仿真速度,但是變量卻不能!
因為logic類型的語義除了在input、inout之外的所有情況下全都默認(rèn)為變量存儲!所以你的代碼有時候可能仿真正確,但不知道為啥比想象中的慢!
如上例子中A2.y、A2.X1.y和A2.X1.T1.y是不同的,粗體wire聲明允許將它們折疊為單個對象。(當(dāng)然上例子中input本身默認(rèn)為wire類型不需要顯式聲明,但是全部顯式聲明更加清楚,這個代碼風(fēng)格更好)
5. 在“向量”上直接操作比操作bit更快
這樣寫比較慢:
這樣寫比較快:
如上例32bit的a_t、c_t,可以看作32個1bit的變量組成的“向量”。對于這個“向量”直接操作會快于對其32個1bit循環(huán)操作。
順便一提,上面的反例中,除了位操作,而且效率低下的示例使用了一個generate語句,它創(chuàng)建了一個靜態(tài)層次結(jié)構(gòu)。這樣的跨層次結(jié)構(gòu)的問題,仿真器會進(jìn)行優(yōu)化,但是對于復(fù)雜的問題,往往不能做到很好的優(yōu)化,會變成隱藏的性能問題。
6.盡量用ref,少傳遞復(fù)雜數(shù)據(jù)結(jié)構(gòu)
ref會直接對目標(biāo)方法的內(nèi)存進(jìn)行操作,這樣便節(jié)省了資源,尤其是對于很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)例如具有數(shù)百個字段的結(jié)構(gòu)體、或具有數(shù)百個元素的隊列、動態(tài)數(shù)組、聯(lián)合數(shù)組等。其實,很多時候函數(shù)只需要擁有讀取大型數(shù)據(jù)對象的訪問權(quán)限即可,根本不會寫入它。
7.動態(tài)數(shù)據(jù)結(jié)構(gòu),不要濫用、想清楚再用
“動態(tài)數(shù)據(jù)結(jié)構(gòu)”如隊列、動態(tài)數(shù)組、聯(lián)合數(shù)組是常見性能問題的來源,不要濫用。SystemVerilog和大多數(shù)具有這些類型的語言通常都是如此。
所以,盡可能使用靜態(tài)數(shù)組而不是動態(tài)數(shù)組。即使數(shù)組長度有少量變化,最好指定靜態(tài)數(shù)組稍大一些,而不是承擔(dān)動態(tài)數(shù)組的開銷(內(nèi)存占用空間和垃圾收集時間)。比如可能有2--10個int型的元素,直接定義和使用“int A[10];”,或者更大點“int A[12];”來存儲元素,而不是直接定義使用動態(tài)數(shù)組“int A[ ];”來動態(tài)分配空間。
除此之外,動態(tài)數(shù)組和隊列有各自適合的場景,他們都可以完成對方的功能,但是不要隨意混用,否則都會有不好的性能。動態(tài)數(shù)組最適合查找,隨機(jī)插入/刪除操作,隊列最適合自動調(diào)整大小的前后操作,仿真器具有不同的內(nèi)部表示來優(yōu)化他們各自的操作,所以盡量讓他們?nèi)ズ线m自己的“崗位”。
8.能用單個對象,不要多new
比較慢的寫法:
比較快的寫法:
低效的內(nèi)存可能導(dǎo)致嚴(yán)重的cache miss,堆管理開銷和垃圾收集開銷,這些都可能難以通過分析發(fā)現(xiàn),所以養(yǎng)成好的代碼習(xí)慣,例如盡量少new不必要的對象、不是必需情況下盡量少深拷貝動態(tài)對象。
9.可以考慮靜態(tài)類代替動態(tài)類
接著上一條,如果同一組類反復(fù)被分配內(nèi)存和釋放內(nèi)存,仿真器通過內(nèi)存管理反復(fù)循環(huán),降低了仿真時間,而如果是靜態(tài)定義的類,仿真的整體內(nèi)存占用保持一致,從而執(zhí)行速度會變快!
10.簡單異構(gòu)數(shù)據(jù)結(jié)構(gòu)能用結(jié)構(gòu)體就不要用類
很多人常常有種想法認(rèn)為class是基于面向?qū)ο笠氲母案呒墶钡姆庋b方式,結(jié)構(gòu)體好像更“l(fā)ow”一點,其實不然!單獨的類將需要堆管理并可能涉及垃圾收集,簡單的struct(結(jié)構(gòu)體)不會,所以更快。簡單異構(gòu)數(shù)據(jù)結(jié)構(gòu)能用結(jié)構(gòu)體就不要用類了吧。
11.接口中的“重”功能放在接口中而不是類中
這樣寫比較慢:
這樣寫比較快:
將接口“重”的功能放入接口而不是類中也更具仿真效率。
首先,因為功能與接口本身相關(guān)聯(lián),可重用性更好。
其次,在接口上操作的類包含與接口相關(guān)聯(lián)的基本操作使接口的任何未來用戶都可以復(fù)制此基本代碼,但是通過virtual接口無法有效地引用它們。
12.減少動態(tài)task或者function的喚醒
SystemVerilog仿真器是由事件驅(qū)動的,它們在給定時間點運行的事件越多,運行速度越慢。SystemVerilog中最常見的進(jìn)程應(yīng)該就是帶有敏感信號(如clk)的always塊來,正因如此常見,這個靜態(tài)進(jìn)程在所有仿真器中都進(jìn)行了高度優(yōu)化,但是,動態(tài)task或者function(如DPI(或任何外部)功能,虛擬類任務(wù)/功能和虛擬接口任務(wù)/功能)的副作用可能會導(dǎo)致仿真器禁用優(yōu)化!這種情況,“坐著不如躺著”少喚醒最安全。就像前面例3.2條件的處理那樣,盡量減少他們的執(zhí)行,如下
值得一提的是,除了這樣還有一種玩法可以減少執(zhí)行次數(shù):用iff,如下例子
13.對于UVM平臺中帶約束的隨機(jī),盡量分解或簡化
這樣寫比較慢:
這樣寫會快很多:
在上圖反例中,循環(huán)中對其相鄰對每個數(shù)組元素設(shè)置約束,假設(shè)100個元素,就相當(dāng)于必須同時求解100個約束。下面的代碼使用post_randomize,經(jīng)統(tǒng)計,可以將運行時性能提高1000倍!
14.斷言的序列和屬性盡量避免使用局部變量
這樣寫比較慢:
這樣寫比較快:
雖然可能需要局部變量來操縱序列和屬性內(nèi)部的數(shù)據(jù),但它們在仿真過程中增加了開銷。在可能的情況下,應(yīng)避免使用局部變量。
15.覆蓋率收集時,盡可能減少采樣事件
這樣寫比較慢:
這樣寫比較快:
上面第二段代碼之所以比第一段快,是因為合并使用了相同事件的采樣過程,更少的coverage采樣事件可以減少仿真時間。
所以除此之外,盡量使用特定事件觸發(fā)器而不是諸如系統(tǒng)時鐘之類的通用事件來采樣覆蓋率、覆蓋組共享共同表達(dá)式等手段也可以減少仿真時間。
16. 可以使用宏加快循環(huán)計算
對于如下循環(huán)代碼,reverse()函數(shù)會在大量的數(shù)據(jù)點被掉用,每次調(diào)用reverse( ) 都需要創(chuàng)建可能影響緩存命中的堆棧幀,仿真速度會非常慢。使用REVERSE宏,就會使仿真更快。當(dāng)然宏過度使用會增加調(diào)試難度和內(nèi)存消耗。
結(jié)語
正如前文所說:“專輯發(fā)文順序與提速收益無關(guān)”,本篇的提效手段,對于代碼規(guī)模不大的驗證業(yè)務(wù),說實話并不是收益最大的提速方式,甚至有的收益難以感知,屬于“勒緊褲腰帶”的致富方式。但是“粒粒皆辛苦”,多條并用,積少成多,當(dāng)驗證業(yè)務(wù)規(guī)模大的時候(除了芯片規(guī)模大之外還包括仿真數(shù)據(jù)量很大時,例如大數(shù)據(jù)量圖像視頻的壓測場景)你將獲得一個還不錯的速度收益。
審核編輯:劉清
-
Verilog
+關(guān)注
關(guān)注
29文章
1366瀏覽量
111868 -
計數(shù)器
+關(guān)注
關(guān)注
32文章
2284瀏覽量
96046 -
編譯器
+關(guān)注
關(guān)注
1文章
1656瀏覽量
49900 -
模擬器
+關(guān)注
關(guān)注
2文章
894瀏覽量
44043
原文標(biāo)題:驗證仿真提速系列--SystemVerilog編碼層面提速的若干策略
文章出處:【微信號:處芯積律,微信公眾號:處芯積律】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
[啟芯公開課] SystemVerilog for Verification
round robin 的 systemverilog 代碼
做FPGA工程師需要掌握SystemVerilog嗎?
(2)打兩拍systemverilog與VHDL編碼 精選資料分享
SystemVerilog編碼層面提速的若干策略SoC芯片簡析
SystemVerilog Assertion Handbo
SystemVerilog的斷言手冊
基于OFDM和循環(huán)延遲分集的空時頻編碼策略

基于雙向MIMO中繼系統(tǒng)的一種預(yù)編碼策略

評論