加強(qiáng)公司內(nèi)部網(wǎng)站建設(shè)佛山網(wǎng)站建設(shè)模板
本筆記使用的Vitis HLS版本為2022.2,在windows11下運(yùn)行,仿真part為xcku15p_CIV-ffva1156-2LV-e,主要根據(jù)教程:跟Xilinx SAE 學(xué)HLS系列視頻講座-高亞軍進(jìn)行學(xué)習(xí)
從這一篇開始正式進(jìn)入HLS對(duì)C++代碼的優(yōu)化筆記
目錄
- 1.循環(huán)優(yōu)化中的基本參數(shù)
- 2.PIPELINE & UNROLL
- ????2.1.PIPELINE
- ????2.2.UNROLL
- 3.LOOP_MERGE
1.循環(huán)優(yōu)化中的基本參數(shù)
以如下程序?yàn)槔?#xff0c;top.h:
#include <ap_int.h>#define N 3#define XW 8
#define BW 16typedef ap_int<XW> dx_t;
typedef ap_int<BW> db_t;
typedef ap_int<BW+1> do_t;void func(dx_t xin[N], dx_t a, db_t b, db_t c, do_t yo[N]);
top.cpp:
#include "top.h"void func(dx_t xin[N], dx_t a, db_t b, db_t c, do_t yo[N])
{int i = 0;loop:for (i = 0; i < N; i++){yo[i] = a * xin[i] + b + c;}
}
從本篇開始,top函數(shù)更改為通用的func,這樣只改其中的邏輯和參數(shù)就行,記得第一次運(yùn)行要在設(shè)置中更改Top函數(shù),更改方式請(qǐng)參考上一篇筆記。
從程序代碼出發(fā),對(duì)loop標(biāo)簽下的for循環(huán)進(jìn)行分析,可以得到其全過程時(shí)序如下,其中每個(gè)屬性的含義:
1 clock cycle:一個(gè)基本操作需要一個(gè)時(shí)鐘周期
Loop Trip Count:循環(huán)次數(shù),N==3,所以為3
Loop Iteration Latency:一輪循環(huán)執(zhí)行所需時(shí)間
Loop Iteration Interval(Loop II):兩輪循環(huán)相隔的時(shí)間,這里和循環(huán)執(zhí)行時(shí)間相等的原因:1.HLS識(shí)別到循環(huán)次數(shù)為常數(shù),省略了對(duì)變量i的操作,如果N是變量則這個(gè)時(shí)間會(huì)相應(yīng)增加。2.在編程中并未讓其流水線操作或展開(UNROLL),否則這個(gè)時(shí)間會(huì)減少或直接為0。
Loop Latency:循環(huán)總執(zhí)行時(shí)間,等于Loop Iteration Latency * Loop Trip Count
Function Latency:函數(shù)的執(zhí)行時(shí)間,這里應(yīng)該是Rd a,b,c,這三個(gè)變量傳進(jìn)來之后不再改變,讀一次就行
Function Initial Interval(II):兩輪函數(shù)的執(zhí)行間隔,這里應(yīng)該放在Rd之前,函數(shù)放在一個(gè)循環(huán)內(nèi)時(shí):1.如果函數(shù)執(zhí)行結(jié)束到下一輪該函數(shù)執(zhí)行之間有操作的話,這一時(shí)間會(huì)增加2.如果對(duì)循環(huán)和函數(shù)進(jìn)行優(yōu)化,函數(shù)可能并發(fā)執(zhí)行(多個(gè)函數(shù)實(shí)例,間隔為0)、流水線執(zhí)行(間隔減小)

2.PIPELINE & UNROLL
2.1.PIPELINE
For循環(huán)最常見也是最常用的優(yōu)化就是PIPELINE,可以右鍵Directive中循環(huán)的標(biāo)簽進(jìn)行添加(所有循環(huán)都應(yīng)添加標(biāo)簽,便于進(jìn)行優(yōu)化和DEBUG),也可以在循環(huán)的左大括號(hào)下一行添加#pragma HLS PIPELINE:
loop:for (int i = 0; i < N; i++){
#pragma HLS PIPELINE/* 函數(shù)體 */}
添加PIPELINE后,第一節(jié)提到的Loop Iteration Interval (Loop II)循環(huán)迭代間隔就變?yōu)榱?,因?yàn)?次循環(huán)間沒有數(shù)據(jù)依賴,所以間隔為一個(gè)cycle,循環(huán)耗時(shí)從9減少到5:

2.2.UNROLL
UNROLL會(huì)將循環(huán)全部展開,如循環(huán)次數(shù)為3,則將原本1組電路進(jìn)行3次運(yùn)算更改為3組電路分別進(jìn)行1次計(jì)算,通過消耗N倍資源換取N倍效率,其添加方法與PIPELINE相同:
#pragma HLS UNROLL

有時(shí),循環(huán)的次數(shù)實(shí)在過多,全部展開會(huì)用光板子的資源,那么我們可以將其部分展開,使用UNROLL的參數(shù)factor進(jìn)行配置,如factor=3時(shí),會(huì)將循環(huán)展開為3組電路,消耗3倍資源換取3倍效率:
// factor為復(fù)制份數(shù)(消耗資源倍數(shù))
#pragma HLS UNROLL factor=3
我們之前提到過,將C++原本的數(shù)據(jù)類型換為HLS的任意精度數(shù)據(jù)類型可以增加資源利用率,那么我們復(fù)制了這么多循環(huán),for()中i的類型是否要考慮更換呢?事實(shí)上,vitis會(huì)考慮i的最大值,也就是N,來對(duì)其實(shí)際占用的空間進(jìn)行資源優(yōu)化,int i = 0;和ap_int<4> i = 0;實(shí)際沒有區(qū)別,如果i<N的N是變量,那么N的數(shù)據(jù)類型是很重要的,這一點(diǎn)在以后會(huì)講到。
3.LOOP_MERGE
考慮如下圖中的情況,add循環(huán)和sub循環(huán)都用到了a[i]和b[i],并且循環(huán)上限都為N,從硬件設(shè)計(jì)的角度上講,這兩個(gè)循環(huán)完全可以合并,如右圖中的邏輯:

在這里我們引入loop region的概念,它實(shí)際上就是對(duì)一段大括號(hào)括起的代碼塊聲明的標(biāo)簽,在這里用作LOOP_MERGE的作用域。
然后我們對(duì)loop region添加LOOP_MERGE約束,與for添加約束的邏輯相同,通過圖形界面Directive右鍵添加,或者在代碼塊第一行編寫#pragma HLS LOOP_MERGE,就可以實(shí)現(xiàn)兩個(gè)循環(huán)的并發(fā)執(zhí)行:
void func(data_t a[N], data_t b[N], data_t c[N], data_t d[N])
{int i;loop_region:{
#pragma HLS LOOP_MERGEadd:for (i = 0; i < N; i++){c[i] = a[i] + b[i];}sub:for (i = 0; i < N; i++){d[i] = a[i] - b[i];}}
}
這樣不僅能提高效率,并且在代碼編寫的過程中可以更好的區(qū)分邏輯,讓代碼有更好的可讀性和可維護(hù)性。
如果兩個(gè)循環(huán)的邊界不同,如下圖:

合并時(shí)將根據(jù)更大的循環(huán)決定循環(huán)次數(shù),這沒有問題,較少的那個(gè)循環(huán)之后就不會(huì)獲取這個(gè)循環(huán)模塊的sub輸出了。

需要注意的是,如果兩個(gè)(或多個(gè))循環(huán)的循環(huán)上限有常量也有變量,那么LOOP_MERGE會(huì)報(bào)錯(cuò):

而如果循環(huán)上限有2個(gè)及以上的變量,那么即便變量的范圍相同,LOOP_MERGE也會(huì)報(bào)錯(cuò):

如何避免這樣的問題?如果編程者可以通過設(shè)計(jì),預(yù)先確定多個(gè)變量中哪個(gè)變量最小,那么就可以LOOP_MERGE最小變量次數(shù)的循環(huán),然后在loop region外執(zhí)行剩余的循環(huán),如下是能確定K<=J的情況:

下一篇筆記將對(duì)For循環(huán)的DATAFLOW、嵌套和其他優(yōu)化進(jìn)行介紹。