在使用高層次綜合,創(chuàng)造高質(zhì)量的RTL設(shè)計(jì)時(shí),一個(gè)重要部分就是對(duì)C代碼進(jìn)行優(yōu)化。Vivado Hls總是試圖最小化loop和function的latency,為了實(shí)現(xiàn)這一點(diǎn),它在loop和function上并行執(zhí)行盡可能多的操作。比如說(shuō),在function級(jí)別上,高級(jí)綜合總是試圖并行執(zhí)行function。
除了這些自動(dòng)優(yōu)化,directive是用來(lái):
(1) 并行執(zhí)行多個(gè)tasks,例如,同一個(gè)function的多次執(zhí)行或同一loop的多次迭代。這是流水線結(jié)構(gòu)。
(2) 調(diào)整數(shù)組的物理實(shí)現(xiàn)((block RAM),函數(shù),循環(huán)和端口,以提高數(shù)據(jù)的可用性,并幫助數(shù)據(jù)流更快地通過(guò)設(shè)計(jì)。
(3) 提供關(guān)于數(shù)據(jù)dependency的信息,或者缺乏數(shù)據(jù)dependency,允許執(zhí)行更多的優(yōu)化。最終的優(yōu)化是修改C源代碼,以消除在代碼中意外的dependency,但是這可能會(huì)限制硬件的性能。
本文使用的sample設(shè)計(jì)是一個(gè)matrix multiplier函數(shù)。目標(biāo)是在每一個(gè)時(shí)鐘周期處理一個(gè)新的sample,并實(shí)現(xiàn)數(shù)據(jù)流接口。
優(yōu)化matrix multiplier
solution1
這里使用矩陣乘法器設(shè)計(jì),來(lái)顯示如何可以完全優(yōu)化基于loop的設(shè)計(jì)。設(shè)計(jì)目標(biāo)是在每個(gè)時(shí)鐘周期讀取一個(gè)使用FIFO接口的sample,同時(shí)最大限度地減少了面積。此分析包括一個(gè)比較在loop級(jí)優(yōu)化和在function級(jí)優(yōu)化的方法。
對(duì)比loops和function pipeline的使用,創(chuàng)建一個(gè)可以處理采樣時(shí)鐘的設(shè)計(jì)。分析設(shè)計(jì)不符合性能要求的兩個(gè)最常見(jiàn)的原因:loops dependency和數(shù)據(jù)流的限制(或瓶頸)。
Step 1:創(chuàng)建并打開(kāi)Project
找到Design_Optimization lab1文件夾,依次在Command Prompt 窗口輸入vivado_hls –f run_hls.tcl和vivado_hls –p matrixmul_prj
Step 2:綜合分析設(shè)計(jì)
綜合后的結(jié)果:
?
(1)圖中,總的interval為80個(gè)時(shí)鐘周期。因?yàn)槊總€(gè)輸入數(shù)組中都有九個(gè)元素,所以設(shè)計(jì)每輸入讀取需要約九個(gè)周期。
(2)interval比latency多一個(gè)時(shí)鐘周期,所以沒(méi)有在硬件上并行執(zhí)行。
(3)interval/latency是由于嵌套loops
I.Product inner loop:
-有一個(gè)2個(gè)時(shí)鐘周期的延遲。
-總的迭代有6個(gè)時(shí)鐘周期。
II.COL loop:
-它需要1個(gè)時(shí)鐘輸入和1個(gè)時(shí)鐘退出。
-它需要8個(gè)時(shí)鐘周期為每個(gè)迭代(1 + 6 + 1)。
-總的有24個(gè)周期完成所有迭代。
III.頂層loops每次迭代需要26個(gè)時(shí)鐘周期,總的loops迭代共78時(shí)鐘周期。
為了改善initiation interval,則需要:pipeline loops或pipeline整個(gè)function,并比較這兩種結(jié)果。當(dāng)pipelining loops時(shí),loops的initiation interval是監(jiān)控的重要度量指標(biāo)。即使設(shè)計(jì)達(dá)到loop可以在每個(gè)時(shí)鐘周期處理一個(gè)sample,函數(shù)的initiation interval仍然需要包含函數(shù)內(nèi)的loops來(lái)完成所有數(shù)據(jù)的處理。
solution2: Pipeline the Product Loop
Step 1創(chuàng)建solution2,在Product loop下面插入pipeline directive(這里在Directive Editor下選擇pipeline)
?
注意:當(dāng)pipeline嵌套loop時(shí),通過(guò)pipeline最內(nèi)部Loop最大的好處就是,即有利于處理數(shù)據(jù)的sample。高級(jí)綜合自動(dòng)應(yīng)用loop flattening,折疊嵌套loop,刪除loop轉(zhuǎn)換(本質(zhì)上是創(chuàng)建一個(gè)更多迭代的單循環(huán),但時(shí)鐘周期整體較少)。
Step 2 綜合設(shè)計(jì)到RTL級(jí)
在綜合過(guò)程中,我們得到Console pane中報(bào)告的信息,顯示loop flattening是loop Row上執(zhí)行,默認(rèn)內(nèi)部Interval target為1由于依賴關(guān)系不能在loop Product上完成。
?
圖中表明,雖然Product loop已經(jīng)被pipeline,interval為2,但是頂層loop沒(méi)有被pipeline。頂層loop不能pipeline的原因是,loop flattening只發(fā)生在loop Row,在loop Col 到Product loop上沒(méi)有l(wèi)oop flattening。下面解釋loop flattening不能flatten所有nested loop的原因。
Step 3 打開(kāi)Analysis窗口,選擇state C1的write operation,右擊選擇Goto Source
?
狀態(tài)C1下的寫(xiě)操作是由于代碼在Product loop前就已經(jīng)設(shè)置res為0。因?yàn)閞es是在頂層函數(shù),在RTL里這是在寫(xiě)入一個(gè)端口:這個(gè)操作必須發(fā)生在loop Product執(zhí)行之前。因?yàn)樗皇且粋€(gè)內(nèi)部操作,而是會(huì)對(duì)I / O行為產(chǎn)生影響,這種操作不能移動(dòng)或優(yōu)化。這可以阻止Product loop被flatten進(jìn)入row_col loop。
更重要的是,對(duì)于Product loop來(lái)說(shuō),為什么只有II為2是可能flatten的。
這個(gè)問(wèn)題被稱作carried dependency,這個(gè)dependency發(fā)生在一個(gè)loop的迭代操作和相同loop的不同迭代操作。例如,一個(gè)操作分別發(fā)生在K = 1時(shí),當(dāng)K = 2時(shí)(其中k是循環(huán)指數(shù))。
第一個(gè)操作是在line 60數(shù)組res上的存儲(chǔ)(內(nèi)存讀操作)。
第二個(gè)操作是在line 60數(shù)組res上的下載(內(nèi)存寫(xiě)操作)。
從圖中可以看到line 60是從數(shù)組res的讀?。ㄓ捎?=操作符)和寫(xiě)入數(shù)組res。數(shù)組默認(rèn)映射到RAM block,下面Performance View的細(xì)節(jié)解釋了為什么會(huì)發(fā)生這種沖突。
?
成功的schedule中,Product loop的下一次迭代如上所示。在這個(gè)schedule中,initiation interval(II)= 2,即loop操作每?jī)蓚€(gè)周期重啟。任何block RAM之間的訪問(wèn)沒(méi)有沖突。(沒(méi)有突出顯示的單元迭代重疊。)
不成功的schedule顯示了為什么loop不能在II = 1時(shí)pipeline。此時(shí),下一次迭代將需要1個(gè)時(shí)鐘周期后開(kāi)始。當(dāng)?shù)诙蔚鷩L試一個(gè)地址去讀入時(shí),第一次迭代中寫(xiě)入block RAM的操作仍然發(fā)生。這些地址是不同的,并且都不能在同一時(shí)間被應(yīng)用到block RAM。
因此,你不能將Product loop的initiation interval 設(shè)置為1。下一步是pipeline Col loop。這將自動(dòng)展開(kāi)Product loop,并創(chuàng)建更多的operators,因此需要更多的硬件資源,但它確保在Product loop的不同迭代之間沒(méi)有dependency。
?
?
在綜合過(guò)程中,在控制臺(tái)窗格中報(bào)告的信息顯示loop Product被展開(kāi),loop flattening是在loop Row上執(zhí)行,默認(rèn)initiation intervalv為1的目標(biāo)不能在loop Row_Col上實(shí)現(xiàn),這是由于數(shù)組a上memory資源的限制。
?
綜合報(bào)告顯示,如上所述,對(duì)loop Row_Col的interval只有2:目標(biāo)是每個(gè)周期處理一個(gè)sample。你可以再一次使用Analysis窗口來(lái)證明為什么不實(shí)現(xiàn)initiation target。
Step 3 打開(kāi)Analysis perspective,在Performance View里,展開(kāi)Row_Col loop
?
在數(shù)組a中有三個(gè)讀操作。兩個(gè)讀操作開(kāi)始于C1狀態(tài),第三個(gè)讀操作開(kāi)始與C2狀態(tài)。數(shù)組被實(shí)現(xiàn)為block RAMs和數(shù)組,這是參數(shù)的函數(shù)是實(shí)現(xiàn)塊內(nèi)存端口。在這兩種情況下,一個(gè)block RAM最大只能有兩個(gè)端口(對(duì)于雙端口block RAM來(lái)說(shuō))。訪問(wèn)數(shù)組a通過(guò)一個(gè)單一block RAM接口,沒(méi)有足夠的端口能夠在一個(gè)時(shí)鐘周期中讀取所有三個(gè)值。
另一種查看該資源限制的方法是使用到Resource窗口。
Step 4打開(kāi)Resource tab,擴(kuò)展Memory Ports
狀態(tài)C1下面兩個(gè)讀操作與那些狀態(tài)C2下的讀操作重疊,因此,只有一個(gè)單一的周期是可見(jiàn)的:很顯然該資源被用于在多個(gè)狀態(tài)。即使當(dāng)端口a的問(wèn)題得到解決,相同的問(wèn)題會(huì)發(fā)生在端口b:它也有三個(gè)讀操作。
?
高級(jí)綜合允許數(shù)組被partitioned, mapped together和 re-shaped。這些都允許在不改變?cè)创a的情況下,對(duì)數(shù)組進(jìn)行修改。
solution4: Reshape the Arrays
Step 1 創(chuàng)建solution4,在Directive里給數(shù)組a和數(shù)組b插入ARRAY_RESHAPE,選擇dimension分別為2和1
?
Step 2 Run C Synthesis
綜合報(bào)告顯示頂層loop Row_Col現(xiàn)在是每個(gè)時(shí)鐘周期處理數(shù)據(jù)的一個(gè)sample。
? 頂層模塊需要12個(gè)時(shí)鐘周期才能完成。
? 經(jīng)過(guò)3次循環(huán),Row_Col loop輸出sample(迭代延遲)。
? 然后每個(gè)周期讀取1個(gè)sample(Initiation Interval)。
? 9次iterations/samples(Trip count)完成所有samples。
? 3 + 9 = 12個(gè)時(shí)鐘周期
函數(shù)可以完成并返回開(kāi)始處理下一組數(shù)據(jù)。現(xiàn)在,把block RAM接口設(shè)置為FIFO接口,數(shù)據(jù)流形式。
solution5: Apply FIFO Interfaces
Step 1 創(chuàng)建solution5,在Directive里給數(shù)組a,b,res插入INTERFACE,在mode里選擇ap_fifo
?
Step 2 Run C Synthesis,Console窗口報(bào)錯(cuò)
?在line57寫(xiě)[ 0 ] [ 0 ]。
?然后在line60寫(xiě)[ 0 ] [ 0 ]。
?然后在line60一個(gè)寫(xiě)[ 0 ] [ 0 ]。
?然后在line60一個(gè)寫(xiě)[ 0 ] [ 0 ]。
?在line57寫(xiě)入[ 0 ] [ 1 ](在增量指標(biāo)J后)。
?然后在line60寫(xiě)[ 0 ] [ 1 ]。
連續(xù)四個(gè)寫(xiě)入地址[ 0 ] [ 0 ]不能構(gòu)成一個(gè)數(shù)據(jù)流模式,而是隨機(jī)存取。
?
檢查代碼后發(fā)現(xiàn)在數(shù)組a和數(shù)組b存在類似的問(wèn)題。它是使用一個(gè)FIFO接口訪問(wèn)代碼已經(jīng)寫(xiě)好的數(shù)據(jù),這是不可能實(shí)現(xiàn)的。在使用FIFO接口時(shí),Vivado Hls的優(yōu)化directives會(huì)有不足,因?yàn)楫?dāng)前代碼執(zhí)行了一定的讀寫(xiě)順序。下面pipeline整個(gè)function,對(duì)比這兩種方法的差異。
solution6: Pipeline the Function
Step 1 創(chuàng)建solution6,刪掉loop Col下面的Directive,給matrixmul函數(shù)插入PIPELINE Directive
?
Step 2 Run C Synthesis,并比較各個(gè)report
?
solution6在較少的時(shí)鐘就可以完成,并可以每5個(gè)時(shí)鐘周期開(kāi)始一個(gè)新的transaction。然而,消耗的資源也大幅增加,因?yàn)樗械难h(huán)在設(shè)計(jì)中被打開(kāi)。
Pipelining loops允許循環(huán)保持rolling,從而提供了一個(gè)很好的方法來(lái)控制area。當(dāng)pipeline一個(gè)函數(shù)時(shí),函數(shù)中包含的所有 loops都是打開(kāi),這是一個(gè)pipeline的要求。流水線功能設(shè)計(jì)可以每5個(gè)時(shí)鐘周期處理一組(9個(gè))新的samples。這超過(guò)了每個(gè)時(shí)鐘處理1個(gè)sample的要求,因?yàn)楦呒?jí)綜合的默認(rèn)行為是產(chǎn)生一個(gè)最高性能的設(shè)計(jì)。pipeline function會(huì)產(chǎn)生最好的性能,然而,如果它超過(guò)所需性能,它可能需要多個(gè)額外的directives。
優(yōu)化 I/O Accesses的C代碼
進(jìn)一步優(yōu)化需要重新編寫(xiě)代碼,下面介紹如何修改matrixmul.cpp的代碼,來(lái)幫助克服一些在代碼中固有的性能限制。
Step 1 創(chuàng)建并打開(kāi)Project
找到Design_Optimization lab2文件夾,依次在Command Prompt 窗口輸入vivado_hls –f run_hls.tcl和vivado_hls –p matrixmul_prj
Step 2 打開(kāi)matrixmul.cpp源代碼
?
審查代碼并確認(rèn)以下:
?之前的directives在這里(包括FIFO接口)以pragmas形式指定的代碼。
? for循環(huán)已被添加到緩存行和列讀取。
?當(dāng)最終的結(jié)果是計(jì)算為每個(gè)值時(shí),一個(gè)臨時(shí)變量被用于累計(jì),并且端口res只能被寫(xiě)入。
?因?yàn)閷?duì)于for循環(huán)緩存行和列需要多個(gè)周期去執(zhí)行讀取,pipeline directive已應(yīng)用于Col for循環(huán),來(lái)確保這些緩存for循環(huán)自動(dòng)打開(kāi)。
Step 3 Run C Synthesis
?
該設(shè)計(jì)已被完全綜合,每個(gè)時(shí)鐘周期讀取一個(gè)使用數(shù)據(jù)流FIFO接口的sample。
總結(jié)
本文介紹了如何分析pipelined loops,并準(zhǔn)確地理解哪些限制阻止優(yōu)化目標(biāo)的實(shí)現(xiàn)。以及對(duì)function 和 loop 進(jìn)行pipeline的優(yōu)點(diǎn)和缺點(diǎn)。代碼中意外dependencies可以阻止硬件設(shè)計(jì)目標(biāo)實(shí)現(xiàn),如何通過(guò)修改源代碼來(lái)克服它們。
評(píng)論