《FPGA並行編程》讀書筆記(第一期)05_FFT
1. 緒論
當取樣樣本數量爲N時,直接使用矩陣向量乘法來執行DFT需O(N^2)次乘法和加法操作,我們可以通過利用矩陣中的常數係數的結構來降低運算的複雜度。快速傅立葉變換(FFT)使用基於S矩陣對稱性的分塊處理方法,因Cooley-Tukey算法而廣爲流行,它需要O(nlogn)次操作來計算與DFT相同的函數。這可以在大規模信號執行傅立葉變換時提供顯着的加速。
2. 讀書筆記源說明
源代碼見PP4FPGAS_Study_Notes_S1C05_HLS_FFT
3. 5個Solution帶你學習FFT算法加速
3.1 工程組織結構
本節有5個Solution,來對1024點的FFT算法進行加速。因這個優化的自由度很大,最後小編僅僅做個例子來帶大家學習加速的方法,而不是實現一個最優的算法。
3.2 S1_Baseline
根據FFT算法抽象出來的代碼如下:
#include "math.h"
#include "fft.h"
#ifdef S1_baseline
unsigned int reverse_bits(unsigned int input) {
int i, rev = 0;
for (i = 0; i < M; i++) {
rev = (rev << 1) | (input & 1);
input = input >> 1;
}
return rev;
}
void bit_reverse(DTYPE X_R[SIZE], DTYPE X_I[SIZE]) {
unsigned int reversed;
unsigned int i;
DTYPE temp;
for (i = 0; i < SIZE; i++) {
reversed = reverse_bits(i); // Find the bit reversed index
if (i <= reversed) {
// Swap the real values
temp = X_R[i];
X_R[i] = X_R[reversed];
X_R[reversed] = temp;
// Swap the imaginary values
temp = X_I[i];
X_I[i] = X_I[reversed];
X_I[reversed] = temp;
}
}
}
void fft(DTYPE X_R[SIZE], DTYPE X_I[SIZE]) {
DTYPE temp_R; // temporary storage complex variable
DTYPE temp_I; // temporary storage complex variable
int i, j, k; // loop indexes
int i_lower; // Index of lower point in butterfly
int step, stage, DFTpts;
int numBF; // Butterfly Width
int N2 = SIZE2; // N2=N>>1
bit_reverse(X_R, X_I);
step = N2;
DTYPE a, e, c, s;
stage_loop:
for (stage = 1; stage <= M; stage++) { // Do M stages of butterflies
DFTpts = 1 << stage; // DFT = 2^stage = points in sub DFT
numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
k = 0;
e = -6.283185307178 / DFTpts;
a = 0.0;
// Perform butterflies for j-th stage
butterfly_loop:
for (j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=1 max=512
c = cos(a);
s = sin(a);
a = a + e;
// Compute butterflies that use same W**k
dft_loop:
for (i = j; i < SIZE; i += DFTpts) {
#pragma HLS LOOP_TRIPCOUNT min=1 max=512
i_lower = i + numBF; // index of lower point in butterfly
temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
X_R[i_lower] = X_R[i] - temp_R;
X_I[i_lower] = X_I[i] - temp_I;
X_R[i] = X_R[i] + temp_R;
X_I[i] = X_I[i] + temp_I;
}
k += step;
}
step = step / 2;
}
}
#endif
首先要做的是進行仿真,驗證代碼的正確性。
算法正確後,再對算法進行C Synthesis,結果下圖:
可以看出效率非常之低,接下來用我們的祕籍進行優化。
3.3 S2_Code_Restructured與S3_LUT
首先進行代碼重構,將所有的循環進行展開,方便後面對每個循環進行單獨優化(因每個循環邊界不同,需要不同的優化方案).
以後因爲代碼太長僅貼出部分代碼。
以後因爲代碼太長僅貼出部分代碼。
以後因爲代碼太長僅貼出部分代碼。
void fft_stage_1( DTYPE X_R[SIZE], DTYPE X_I[SIZE],
DTYPE Out_R[SIZE], DTYPE Out_I[SIZE]) {
int DFTpts = 1 << 1; // DFT = 2^stage = points in sub DFT
int numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
int step = SIZE >> 1;
DTYPE k = 0;
DTYPE e = -6.283185307178 / DFTpts;
DTYPE a = 0.0;
// Perform butterflies for j-th stage
butterfly_loop:
for (int j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
DTYPE c = cos(a);
DTYPE s = sin(a);
a = a + e;
// Compute butterflies that use same W**k
dft_loop:
for (int i = j; i < SIZE; i += DFTpts) {
#pragma HLS LOOP_TRIPCOUNT min=512 max=512 avg=512
int i_lower = i + numBF; // index of lower point in butterfly
DTYPE temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
DTYPE temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
Out_R[i_lower] = X_R[i] - temp_R;
Out_I[i_lower] = X_I[i] - temp_I;
Out_R[i] = X_R[i] + temp_R;
Out_I[i] = X_I[i] + temp_I;
}
k += step;
}
}
void fft_stage_2( DTYPE X_R[SIZE], DTYPE X_I[SIZE],
DTYPE Out_R[SIZE], DTYPE Out_I[SIZE]) {
int DFTpts = 1 << 2; // DFT = 2^stage = points in sub DFT
int numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
int step = SIZE >> 2;
DTYPE k = 0;
DTYPE e = -6.283185307178 / DFTpts;
DTYPE a = 0.0;
// Perform butterflies for j-th stage
butterfly_loop:
for (int j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=2 max=2 avg=2
DTYPE c = cos(a);
DTYPE s = sin(a);
a = a + e;
// Compute butterflies that use same W**k
dft_loop:
for (int i = j; i < SIZE; i += DFTpts) {
#pragma HLS LOOP_TRIPCOUNT min=256 max=256 avg=256
int i_lower = i + numBF; // index of lower point in butterfly
DTYPE temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
DTYPE temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
Out_R[i_lower] = X_R[i] - temp_R;
Out_I[i_lower] = X_I[i] - temp_I;
Out_R[i] = X_R[i] + temp_R;
Out_I[i] = X_I[i] + temp_I;
}
k += step;
}
}
...
...
...
void fft(DTYPE X_R[SIZE], DTYPE X_I[SIZE], DTYPE OUT_R[SIZE], DTYPE OUT_I[SIZE]) {
DTYPE Stage1_R[SIZE], Stage1_I[SIZE];
DTYPE Stage2_R[SIZE], Stage2_I[SIZE];
DTYPE Stage3_R[SIZE], Stage3_I[SIZE];
DTYPE Stage4_R[SIZE], Stage4_I[SIZE];
DTYPE Stage5_R[SIZE], Stage5_I[SIZE];
DTYPE Stage6_R[SIZE], Stage6_I[SIZE];
DTYPE Stage7_R[SIZE], Stage7_I[SIZE];
DTYPE Stage8_R[SIZE], Stage8_I[SIZE];
DTYPE Stage9_R[SIZE], Stage9_I[SIZE];
DTYPE Stage10_R[SIZE], Stage10_I[SIZE];
bit_reverse(X_R, X_I, Stage1_R, Stage1_I);
fft_stage_1(Stage1_R, Stage1_I, Stage2_R, Stage2_I);
fft_stage_2(Stage2_R, Stage2_I, Stage3_R, Stage3_I);
fft_stage_3(Stage3_R, Stage3_I, Stage4_R, Stage4_I);
fft_stage_4(Stage4_R, Stage4_I, Stage5_R, Stage5_I);
fft_stage_5(Stage5_R, Stage5_I, Stage6_R, Stage6_I);
fft_stage_6(Stage6_R, Stage6_I, Stage7_R, Stage7_I);
fft_stage_7(Stage7_R, Stage7_I, Stage8_R, Stage8_I);
fft_stage_8(Stage8_R, Stage8_I, Stage9_R, Stage9_I);
fft_stage_9(Stage9_R, Stage9_I, Stage10_R, Stage10_I);
fft_stage_10(Stage10_R, Stage10_I, OUT_R, OUT_I);
}
C Synthesis的結果爲:
可以看出這個代碼不是非常可行,因爲展開了循環,所以資源佔用率非常之高。首先對資源佔用最高的sin、cos的計算利用查找表的方式進行實現(這裏友情提醒下,進行代碼重構後第一件事是必須進行C Simulation,因爲代碼重構極易出錯)。部分代碼如下:
void fft_stage_1( DTYPE X_R[SIZE], DTYPE X_I[SIZE],
DTYPE Out_R[SIZE], DTYPE Out_I[SIZE]) {
int stage = 1;
int DFTpts = 1 << stage; // DFT = 2^stage = points in sub DFT
int numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
int step = SIZE >> stage;
DTYPE k = 0;
DTYPE e = -6.283185307178 / DFTpts;
// Perform butterflies for j-th stage
butterfly_loop:
for (int j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
DTYPE c = cos_coefficients_table[j<<(10-stage)];
DTYPE s = sin_coefficients_table[j<<(10-stage)];
// Compute butterflies that use same W**k
dft_loop:
for (int i = j; i < SIZE; i += DFTpts) {
#pragma HLS LOOP_TRIPCOUNT min=512 max=512 avg=512
int i_lower = i + numBF; // index of lower point in butterfly
DTYPE temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
DTYPE temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
Out_R[i_lower] = X_R[i] - temp_R;
Out_I[i_lower] = X_I[i] - temp_I;
Out_R[i] = X_R[i] + temp_R;
Out_I[i] = X_I[i] + temp_I;
}
k += step;
}
}
進行C Synthesis後的效果對比如下:
不僅延遲有少量提升,資源利用率還大幅優化。然後這還是遠遠不夠的!!!後面會有更神奇的辦法推出。
3.4 S4_DATAFLOW
DATAFLOW的官方解釋見pragma HLS dataflow
下面簡單說說我的理解:
DATAFLOW優化可以在一個函數或循環的操作之前的函數或循環完成其所有操作之前就開始操作。與PIPELINE類似,可以結合PIPELINE進行理解。接下來我們來看看DATAFLOW的效果如何。首先部分源代碼如下:
void fft(DTYPE X_R[SIZE], DTYPE X_I[SIZE], DTYPE OUT_R[SIZE], DTYPE OUT_I[SIZE]) {
#pragma HLS DATAFLOW
DTYPE Stage1_R[SIZE], Stage1_I[SIZE];
DTYPE Stage2_R[SIZE], Stage2_I[SIZE];
DTYPE Stage3_R[SIZE], Stage3_I[SIZE];
DTYPE Stage4_R[SIZE], Stage4_I[SIZE];
DTYPE Stage5_R[SIZE], Stage5_I[SIZE];
DTYPE Stage6_R[SIZE], Stage6_I[SIZE];
DTYPE Stage7_R[SIZE], Stage7_I[SIZE];
DTYPE Stage8_R[SIZE], Stage8_I[SIZE];
DTYPE Stage9_R[SIZE], Stage9_I[SIZE];
DTYPE Stage10_R[SIZE], Stage10_I[SIZE];
bit_reverse(X_R, X_I, Stage1_R, Stage1_I);
fft_stage_1(Stage1_R, Stage1_I, Stage2_R, Stage2_I);
fft_stage_2(Stage2_R, Stage2_I, Stage3_R, Stage3_I);
fft_stage_3(Stage3_R, Stage3_I, Stage4_R, Stage4_I);
fft_stage_4(Stage4_R, Stage4_I, Stage5_R, Stage5_I);
fft_stage_5(Stage5_R, Stage5_I, Stage6_R, Stage6_I);
fft_stage_6(Stage6_R, Stage6_I, Stage7_R, Stage7_I);
fft_stage_7(Stage7_R, Stage7_I, Stage8_R, Stage8_I);
fft_stage_8(Stage8_R, Stage8_I, Stage9_R, Stage9_I);
fft_stage_9(Stage9_R, Stage9_I, Stage10_R, Stage10_I);
fft_stage_10(Stage10_R, Stage10_I, OUT_R, OUT_I);
}
綜合的結果如下:
Interval有一個數量級的飛躍,後面還可以繼續優化提升。
大家在此一定要注意一個問題,我最初對這個代碼使用DATAFLOW進行優化時,出現了一個致命的問題,小編當時花了一天才解決,下面簡單說說這個問題的解決過程,希望可以給小夥伴們有點啓發。
最初我加入DATAFLOW時出現瞭如下報錯,每次都綜合不成功,出現瞭如下報錯。
很神奇的錯誤,Google了很多遍,都沒有找到合適的解決方案。最後發現下圖的log文件
根據這個log文件在Xilinx官方論壇找到了解決方案,Xilinx論壇說這個問題是版本的問題,解決辦法是比較愚蠢的(去掉DATAFLOW就可以了),感覺這個解決方法就像fp(時間座標爲2019-8-15),沒有啥意義。我當時又試了試我筆記本的另一個系統(ubuntu下的HLS),對同樣的代碼進行綜合並沒看有發現錯誤。現在是同一臺機器、同一個HLS版本(2018.2)、同樣的代碼,卻在不通的操作系統下出現如此的差異。這時候我就想到了一個問題,根據log文件的Stack提示聯想到內存的堆棧,而且每塊代碼都需要一定的內存進行計算
DATAFLOW本身是不會有問題的,有問題的是內存的分配,對那麼多函數運用DATAFLOW,導致Windows下的內存分配出現溢出之類的問題,而在Linux系統下,由於對內存的管理比較好,所以沒有出現問題。上面僅僅是小編的猜想,具體還摸不清楚原因,大家看看就好。
3.5 S5_Effect_Improve
然後大家對每塊代碼都進行個性的優化,部分代碼如下。
void fft_stage_1( DTYPE X_R[SIZE], DTYPE X_I[SIZE],
DTYPE Out_R[SIZE], DTYPE Out_I[SIZE]) {
int stage = 1;
int DFTpts = 1 << stage; // DFT = 2^stage = points in sub DFT
int numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
DTYPE e = -6.283185307178 / DFTpts;
// Perform butterflies for j-th stage
butterfly_loop:
for (int j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
// Compute butterflies that use same W**k
dft_loop:
for (int i = j; i < SIZE; i += DFTpts) {
#pragma HLS PIPELINE II=6
#pragma HLS LOOP_TRIPCOUNT min=512 max=512 avg=512
DTYPE c = cos_coefficients_table[j<<(10-stage)];
DTYPE s = sin_coefficients_table[j<<(10-stage)];
int i_lower = i + numBF; // index of lower point in butterfly
DTYPE temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
DTYPE temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
Out_R[i_lower] = X_R[i] - temp_R;
Out_I[i_lower] = X_I[i] - temp_I;
Out_R[i] = X_R[i] + temp_R;
Out_I[i] = X_I[i] + temp_I;
}
}
}
void fft_stage_2( DTYPE X_R[SIZE], DTYPE X_I[SIZE],
DTYPE Out_R[SIZE], DTYPE Out_I[SIZE]) {
int stage = 2;
int DFTpts = 1 << stage; // DFT = 2^stage = points in sub DFT
int numBF = DFTpts / 2; // Butterfly WIDTHS in sub-DFT
DTYPE e = -6.283185307178 / DFTpts;
// Perform butterflies for j-th stage
butterfly_loop:
for (int j = 0; j < numBF; j++) {
#pragma HLS LOOP_TRIPCOUNT min=2 max=2 avg=2
// Compute butterflies that use same W**k
dft_loop:
for (int i = j; i < SIZE; i += DFTpts) {
#pragma HLS PIPELINE II=6
#pragma HLS LOOP_TRIPCOUNT min=256 max=256 avg=256
DTYPE c = cos_coefficients_table[j<<(10-stage)];
DTYPE s = sin_coefficients_table[j<<(10-stage)];
int i_lower = i + numBF; // index of lower point in butterfly
DTYPE temp_R = X_R[i_lower] * c - X_I[i_lower] * s;
DTYPE temp_I = X_I[i_lower] * c + X_R[i_lower] * s;
Out_R[i_lower] = X_R[i] - temp_R;
Out_I[i_lower] = X_I[i] - temp_I;
Out_R[i] = X_R[i] + temp_R;
Out_I[i] = X_I[i] + temp_I;
}
}
}
根據對比報告可以看出
在資源佔用大幅減少的情況下,Interval也優化了將近一個數量級。上面代碼僅僅是小編隨便進行優化的,大家要想實用這些代碼需要親自動手優化。
4. 結論
上一章節我們進行了256點的DFT,大家可以修改進行下1024點的DFT,對比Interval,會發現相差將近3個數量級,FFT使得數字信號的傅里葉計算成爲可能。
快速傅里葉變換廣泛的應用於工程、科學和數學領域。這裏的基本思想在1965年纔得到普及,但早在1805年就已推導出來。1994年美國數學家吉爾伯特·斯特朗把FFT描述爲“我們一生中最重要的數值算法”,它還被IEEE科學與工程計算期刊列入20世紀十大算法。
原創不易,切勿剽竊!
歡迎大家關注我創建的微信公衆號——小白倉庫
原創經驗資料分享:包含但不僅限於FPGA、ARM、RISC-V、Linux、LabVIEW等軟硬件開發。目的是建立一個平臺記錄學習過的知識,並分享出來自認爲有用的與感興趣的道友相互交流進步。