《FPGA並行編程》讀書筆記(第一期)04_DFT

1. 緒論

本章介紹了DFT,並將重點放在了介紹了DFTFPGA實現中的算法優化。 DFT運算的核心是以一組固定係數執行矩陣向量乘法,因此首先進行矩陣乘法的優化策略分析後進行DFT算法的優化。
DFT算法的相關知識大家自行在書籍中查閱,我在這裏就不班門弄斧了。
大家切記先把原理搞懂,算法加速只是工具,理解清楚算法內部的原理纔是加速的核心。
大家切記先把原理搞懂,算法加速只是工具,理解清楚算法內部的原理纔是加速的核心。
大家切記先把原理搞懂,算法加速只是工具,理解清楚算法內部的原理纔是加速的核心。

2. 讀書筆記源說明

本章內容的源代碼見PP4FPGAS_Study_Notes_S1C04_HLS_DFT
在這裏插入圖片描述

3. 6個Solution帶你學習矩陣乘法加速

3.1 工程組織結構

本小節一共有6個Solution,來對矩陣乘法進行優化。做實驗之前,先把課本上的背景知識認真閱讀下!
在這裏插入圖片描述

3.2 S1_Baseline

根據算法抽象出來的基礎代碼如下。

//*********************S1_Baseline
#ifdef S1_Baseline
void matrix_vector(BaseType M[SIZE][SIZE], BaseType V_In[SIZE], BaseType V_Out[SIZE]) {
	BaseType i, j;
data_loop:
	for (i = 0; i < SIZE; i++) {
		BaseType sum = 0;
	dot_product_loop:
		for (j = 0; j < SIZE; j++) {
			sum += V_In[j] * M[i][j];
		}
		V_Out[i] = sum;
	}
}

#endif

首先對基礎代碼進行仿真,保證算法的正確性。
在這裏插入圖片描述
從上圖可以看出算法正確,那麼可以進行後面的優化工作。
在這裏插入圖片描述
可以看出該代碼的執行效率非常低,那麼小的矩陣運算盡然還要花費2.73us之久,後面我們逐漸對它進行優化。

3.3 S2_Manual_UNROLL與S3_Auto_UNROLL

爲了讓大家回憶起以前用過的循環展開,這裏面再把代碼貼出。

//*********************S2_Manual_UNROLL
#ifdef S2_Manual_UNROLL
void matrix_vector(BaseType M[SIZE][SIZE], BaseType V_In[SIZE], BaseType V_Out[SIZE]) {
	BaseType i, j;
data_loop:
	for (i = 0; i < SIZE; i++) {
		BaseType sum = 0;
		V_Out[i] =	V_In[0] * M[i][0] + V_In[1] * M[i][1] + V_In[2] * M[i][2] +
					V_In[3] * M[i][3] + V_In[4] * M[i][4] + V_In[5] * M[i][5] +
					V_In[6] * M[i][6] + V_In[7] * M[i][7];
	}
}

#endif


//*********************S3_Auto_UNROLL
#ifdef S3_Auto_UNROLL

void matrix_vector(BaseType M[SIZE][SIZE], BaseType V_In[SIZE], BaseType V_Out[SIZE]) {

	BaseType i, j;
data_loop:
	for (i = 0; i < SIZE; i++) {
		BaseType sum = 0;
	dot_product_loop:
		for (j = 0; j < SIZE; j++) {
#pragma HLS UNROLL skip_exit_check
			sum += V_In[j] * M[i][j];
		}
		V_Out[i] = sum;
	}
}

#endif

首先進行我們之前用過的循環展開,可以看出手動循環與自動循環展開的效果一樣。
在這裏插入圖片描述
效率提升了4倍及以上,資源佔用僅提高了兩倍,但是大家有沒有發現效率的提升並沒有想象中的那麼大呢,這是因爲輸入數據並不可以同時訪問,從Analysis界面也可以看出端倪。
在這裏插入圖片描述
這裏面並沒有展現出完全的並行效果,這和數據的輸入方式有關係。在沒有指定的情況下這些數據就相當於用雙端口BRAM存儲,同一時間只能訪問兩個地址的數據。這裏面還隱藏着一些計算單元的流水,對資源利用率高,後面會有專門的Solution進行介紹。
在這裏插入圖片描述
從資源界面可以看出,僅僅例化了2個乘法。
在這裏插入圖片描述
這些運算單元利用PIPELINE可以提高資源的利用率,後面會細說。那麼如何實現下圖高度並行的算法呢?S4_ARRAY_PARTITION給您帶來解答。
在這裏插入圖片描述

3.4 S4_ARRAY_PARTITION與S5_PIPELINE

大家不要忘了,我第二章節講解的ARRAY_PARTITION,這個優化策略可以實現課本圖片對應的並行算法,在此之上在運用PIPELINE來進一步提高算法效率。
在這裏插入圖片描述
觀察綜合結果
在這裏插入圖片描述
運行效率確實提升了不少,現在僅需要0.13us就可以實現簡單的矩陣乘法,速度提升了20倍以上。但不知大家是否發現了一個這樣的問題,DSP資源佔用了24個,雖然我們使用的ZYNQ7020資源非常豐富具有220個DSP資源,但是爲了一個簡單的運算竟然佔用了10%DSP資源,在我看來着實不是很實惠。下面介紹功能單元的流水,來進一步提高資源的利用率,大家在資源與速度方面來進行權衡。

3.5 S6_Unit_PIPELINE

代碼如下
在這裏插入圖片描述
這裏面PIPELINE的II的數值與指定的ARRAY_PARTITIONfactor參數大小有關係。還有大家要注意數組利用資源的方式要注意,這裏就先不給小夥伴們提及了。
在這裏插入圖片描述
可以通過綜合結果看出資源佔用減少了,但同時算法運行的Latency也增加了,這就是前面經常所說的用時間換面積,均衡時間與資源實現目標。
分析Analysis界面可以發現,實現了功能模塊級別的PIPELINE,提高資源的利用率。
在這裏插入圖片描述
大家可以自發的嘗試factorII的數值,來加深對PIPELINE的理解。現在小夥伴們應該要掌握的是,我們可以在算法級別、循環級別與功能級別等多個層次上進行流水線操作。另外要提的是,提高資源利用率的重點在要儘可能的提高運算單元的使用頻率,不讓它們有一絲一毫的空閒時間,實現資源利用最大化。

3.6 小結

本小節是利用UNROLLARRAY_PARTITIONPIPELINE來提高矩陣運算的效率,但由於資源的限制,小夥伴們要適時的犧牲Latency,來降低資源的使用。另外本小節提及的的新知識是單元的PIPELINE,通過壓榨運算單元的空閒時間,進一步提高運算單元的使用效率,達到時間與空間的均衡。

4. 5個Solution帶你學習DFT運算加速

4.1 工程組織結構

在這裏插入圖片描述
本小節一共有5個Solution來帶領小夥伴們學習DFT運算加速,大家學習之前,千萬要仔細閱讀課本的原理知識。

4.2 S1_Baseline

#ifdef S1_Baseline
//*****************S1_Baseline
void dft(DTYPE sample_real[SIZE], DTYPE sample_imag[SIZE]) {
	int i, j;
	DTYPE w;
	DTYPE c, s;
	// Temporary arrays to hold the intermediate frequency domain results
	DTYPE temp_real[SIZE];
	DTYPE temp_imag[SIZE];
	// Calculate each frequency domain sample iteratively
	dft_each_Calculate:
	for (i = 0; i < SIZE; i += 1) {
		temp_real[i] = 0;
		temp_imag[i] = 0;
		// (2 * pi * i)/N
		w = (-2.0 * 3.141592653589  / SIZE) * (DTYPE)i;
		// Calculate the jth frequency sample sequentially
		dft_jthCalculate:
		for (j = 0; j < SIZE; j += 1) {
			// Utilize HLS tool to calculate sine and cosine values
			c = cos(j * w);
			s = sin(j * w);
			// Multiply the current phasor with the appropriate input sample and keep
			// running sum
			temp_real[i] += (sample_real[j] * c - sample_imag[j] * s);
			temp_imag[i] += (sample_real[j] * s + sample_imag[j] * c);
		}
	}
	// Perform an inplace DFT, i.e., copy result into the input arrays
	ARRAY_Copy:
	for (i = 0; i < SIZE; i += 1) {
		sample_real[i] = temp_real[i];
		sample_imag[i] = temp_imag[i];
	}
}
#endif

首先對該代碼進行仿真,驗證由算法抽象出來的代碼的正確性。
在這裏插入圖片描述
代碼的運算精度符合要求,該代碼符合要求。綜合結果如下。
在這裏插入圖片描述
發現256點的DFT運算竟然需要46ms之多,這個代碼CPU進行運算的話會快很多,爲啥號稱運算效率極高的FPGA運算竟需要如此長的時間。經過我們接下來的優化,你們會發現FPGA的神奇之處,可以將運算效率提高到這種地步。

4.3 S2_SPipeline與S3_Loop_Interchange

首先我們對代碼的內層循環加入PIPELINE
綜合後發現代碼的Latency確實減少了不少。
在這裏插入圖片描述
但同時發現Console中、Warning等均出現一個警告之類的東西。
在這裏插入圖片描述
PIPELINE僅僅只有II=5纔可以實現流水,分析代碼發現是因爲內層循環因爲循環之間都需要對同一個值進行讀取和寫入,因此才造成效率的降低,因此我們這裏要考慮如何重構代碼來解除這個限制。
這裏我僅僅給大家提供幾個關鍵點,具體理解還需大家讀懂書中的內容。

  • 我們使用的方法被稱爲循環交換與流水線交織處理,通過交換內層循環與外層循環,來解決限制。
  • S矩陣是對角對稱的,也是能夠循環交換的前提條件。
  • 仔細理解下面這張圖,對理解如何進行循環交換代碼非常重要。
    在這裏插入圖片描述
    經過循環交換後的代碼如下
#ifdef S3_Loop_Interchange
//*****************S3_Loop_Interchange
void dft(DTYPE sample_real[SIZE], DTYPE sample_imag[SIZE]) {
	int i, j;
	DTYPE w;
	DTYPE c, s;
	// Temporary arrays to hold the intermediate frequency domain results
	DTYPE temp_real[SIZE]={0};
	DTYPE temp_imag[SIZE]={0};
	// Calculate the jth frequency sample sequentially
	dft_jthCalculate:
	for (j = 0; j < SIZE; j += 1) {
		// (2 * pi * i)/N
		w = (-2.0 * 3.141592653589  / SIZE) * (DTYPE)j;
		// Calculate each frequency domain sample iteratively
		dft_each_Calculate:
		for (i = 0; i < SIZE; i += 1) {
#pragma HLS PIPELINE II=1
			// Utilize HLS tool to calculate sine and cosine values
			c = cos(i * w);
			s = sin(i * w);
			// Multiply the current phasor with the appropriate input sample and keep
			// running sum
			temp_real[i] += (sample_real[j] * c - sample_imag[j] * s);
			temp_imag[i] += (sample_real[j] * s + sample_imag[j] * c);
		}
	}
	// Perform an inplace DFT, i.e., copy result into the input arrays
	ARRAY_Copy:
	for (i = 0; i < SIZE; i += 1) {
#pragma HLS PIPELINE II=1
		sample_real[i] = temp_real[i];
		sample_imag[i] = temp_imag[i];
	}
}
#endif

小夥伴們可要仔細研究代碼哦,初始代碼如何經過循環交換轉成上圖的代碼纔是算法加速的精髓!現在我們對綜合結果對比下,發現Latency減少爲原來的1/80!
在這裏插入圖片描述
現在大家認爲進行256DFT運算是CPU效率高還是FPGA效率高?但是現在還存在着一個重大的問題。看綜合報告可以看出,資源佔用率太高了!!!
在這裏插入圖片描述
這時候我們就要分析原因了,看到底是哪個地方佔用那麼多的資源。
在這裏插入圖片描述
原來sin、cos的計算是罪魁禍首,這時候我們就要想利用什麼方案纔可以解決這個問題呢?大家可能想到利用咱們第二章學到的CORDIC可以極大的提高sin、cos的計算效率,這裏我們沒有采用這個方案,感興趣的小夥伴可以嘗試下。我們本次採用的方案是利用好S矩陣的優勢,利用查找表的方案實現sin、cos的計算,具體細節見下一小節。

4.4 S4_LUT

小夥伴們觀察下這個S矩陣
在這裏插入圖片描述
大家有沒有發現S矩陣中的這些向量有非常多重複的向量,我們可以發現,第二行S[1][ ]相對應的向量覆蓋了來自其他行的所有向量。可以利用這個特性來設計程序。具體重構代碼的思路我就不多說了,重構後代碼如下。
在這裏插入圖片描述
需要注意的地方已經標註,大家好好體會下,不會的可以後臺聯系我。綜合後報告對比如下圖。
在這裏插入圖片描述
發現資源利用減少
10餘倍
之多,四不四非常神奇。大家可能還想繼續減少Latency,依靠目前學過的優化策略那就只能犧牲面積來換速度了。

4.5 S5_Manual_Unroll

手動展開代碼如下圖所示。

#ifdef S5_Manual_Unroll
//*****************S5_Manual_Unroll
#include"coefficients256.h"
#include "ap_int.h"

void dft(DTYPE sample_real[SIZE], DTYPE sample_imag[SIZE]) {
	int i, j;
	DTYPE c_0, s_0;
	DTYPE c_1, s_1;
	// Temporary arrays to hold the intermediate frequency domain results
	DTYPE temp_real[SIZE]={0};
	DTYPE temp_imag[SIZE]={0};
	// Calculate the jth frequency sample sequentially
	dft_jthCalculate:
	for (j = 0; j < SIZE; j += 2) {
		// Calculate each frequency domain sample iteratively
		dft_each_Calculate:
		for (i = 0; i < SIZE; i += 1) {
#pragma HLS PIPELINE II=1
			// Utilize HLS tool to calculate sine and cosine values
			c_0 = cos_coefficients_table[(ap_uint<8>)(i * j)];
			s_0 = sin_coefficients_table[(ap_uint<8>)(i * j)];
			c_1 = cos_coefficients_table[(ap_uint<8>)((i) * (j+1))];
			s_1 = sin_coefficients_table[(ap_uint<8>)((i) * (j+1))];
			// Multiply the current phasor with the appropriate input sample and keep
			// running sum
			temp_real[i] += (sample_real[j] * c_0 - sample_imag[j] * s_0) +
							(sample_real[j + 1] * c_1 - sample_imag[j + 1] * s_1);
			temp_imag[i] += (sample_real[j] * s_0 + sample_imag[j] * c_0) +
							(sample_real[j + 1] * s_1 + sample_imag[j + 1] * c_1);
		}
	}
	// Perform an inplace DFT, i.e., copy result into the input arrays
	ARRAY_Copy:
	for (i = 0; i < SIZE; i += 1) {
#pragma HLS PIPELINE II=1
		sample_real[i] = temp_real[i];
		sample_imag[i] = temp_imag[i];
	}
}
#endif

通過綜合報告可以看出雖然速度增加一半,但是資源佔用也同時增加了一半,面積與速度需要大家權衡。
在這裏插入圖片描述

4.6 小結

本小節我們進行了DFT算法的加速實驗,從綜合報告對比來看,在資源佔用比較小的情況下,可以將速度提高160倍以上。本小節重點是對DFT算法的理解、DFT算法的循環交換、DFT算法中S矩陣查找表策略、依據實現目標在速度與面積上取得均衡,小夥伴們這些知識都掌握了嗎?



原創不易,切勿剽竊!

在這裏插入圖片描述

歡迎大家關注我創建的微信公衆號——小白倉庫
原創經驗資料分享:包含但不僅限於FPGA、ARM、RISC-V、Linux、LabVIEW等軟硬件開發。目的是建立一個平臺記錄學習過的知識,並分享出來自認爲有用的與感興趣的道友相互交流進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章