盡管通常Fixed-Point(定點)比Floating-Point(浮點)算法的FPGA實現(xiàn)要更快
Xilinx Vivado HLS工具支持C/C++ IEEE-54標準單精度及雙精度浮點數(shù)據(jù)類型,可以比較容易,快速地將C/C++ Floating-Point算法轉成RTL代碼。與此同時,為了達到用戶期望的FPGA資源與性能, 當使用Vivado HLS directives時需要注意C/C++編碼風格與技巧相結合。
1.1 單雙精度浮點數(shù)學函數(shù)
#include
float example(float var)
{
return log(var); // 雙精度自然對數(shù)
}
在C設計中, 這個例子, Vviado HLS 生成的RTL實現(xiàn)將輸入轉換成雙精度浮點,并基于雙精度浮點計算自然對數(shù),然后將雙精度浮點輸出轉換成單精
#include
float example(float var)
{
return logf(var); // 單精度自然對數(shù)
}
在C設計中, logf才是單精度自然對數(shù), 這個例子 Vviado HLS 生成的RTL實現(xiàn)將基于單精度浮點計算自然對數(shù), 而且沒有輸入輸出單雙精度的互轉。
1.2 浮點運算優(yōu)化
我們先來看一個例子,三個從代數(shù)上看起來差不多的寫法,但其在Vivado HLS中綜合出來的是三個完全不一樣的結果。
void example(float *m0, float *m1, float *m2, float var)
{
*m0 = 0.2 * var; // 雙精度浮點乘法,單雙精度類型轉換
*m1 = 0.2f * var; // 單精度浮點乘法
*m2 = var / 20.0f; // 單精度浮點除法
}
Vivado HLS將日m0, m1, m2綜合成不同的RTL實現(xiàn)。
因為0.2是一個不能精確表征的雙精度數(shù)字, 所以m0運算會被Vivado HLS綜合成一個雙精度浮點乘法, 并且將var 轉換成雙精度, 然后將雙精度乘法輸出m0轉換成單精度。
特別注意,如果希望Vivado HLS綜合出單精度常熟,需要在常數(shù)后面加f, 如0.2f。這樣m1綜合成一個單精度乘法的輸出。同理,m2將被Vivado HLS綜合成單精度除法的輸出。
我們來看另外一個例子。
void example(float *m0, float *m1, float var)
{
*m0 = 0.2f * 5.0f * var; // *m0 = var;常數(shù)乘法被優(yōu)化掉
*m1 = 0.2f * var * 5.0f; // 兩個雙精度浮點乘法
}
再來看另一個例子。
void example(float *m0, float *m1, float var)
{
*m0 = 0.5 * var; //
*m1 = var/2; //
}
m0運算會被Vivado HLS綜合成一個雙精度浮點乘法, 并且將var 轉換成雙精度, 然后將雙精度乘法輸出m0轉換成單精度。
m1運算會被Vivado HLS綜合成簡單的右移運算。所以如果用戶希望實現(xiàn)對var除以2, 就寫成m1這種表達式,而不是m0的表達式。
由于浮點運算相比整型,定點運算耗用更可觀的資源。Vivado HLS會盡量用更有效的資源來實現(xiàn)浮點運算,當數(shù)據(jù)的相關性及約束許可的情況下,在Vivado HLS中,會盡量復用一些浮點運算單元。為了說明這個,我們看一個簡單的四個浮點加法例子, Vivado HLS復用一個浮點加法器來串行實現(xiàn)四個浮點加法。
void example(float *r, float a, float b,
float c, float d)
{
*r = a + b + c + d;
}
有時設計需要更高的throughput及更低的latency。這時就需要提高設計的并行度。以下面例子
void example(float r0[32], float a[32], float b[32])
{
#pragma HLS interface ap_fifo port=a,b,r0
#pragma HLS array_reshape cyclic factor=2 variable=a,b,r0
for (int i = 0; i < 32; i++)
{
#pragma HLS pipeline
#pragma HLS unroll factor=2
r0[i] = a[i] + b[i];
}
}
然而,如果更多復雜的運算,或許會導致不獨立的浮點運算,在這種情況下,Vivado HLS不能重新排列這些運算的順序,這樣會導致更低的,不是所期望的復用。 下面舉例來說明如何提高帶有反饋浮點運算的性能。
這個例子的累加會導致recurrence,并且通常浮點加法的latency大于一個時鐘周期,加的pi
float example(float x[32])
{
#pragma HLS interface ap_fifo port=x
float acc = 0;
for (int i = 0; i < 32; i++)
{
#pragma HLS pipeline
acc += x[i];
}
return acc;
}
為了對上面例子并行展開,可以對代碼如下做較小的改動,也就是拆成先部分累加,再最后累加,當然也需要對輸
float top(float x[32])
{
#pragma HLS interface ap_fifo port=x
float acc_part[4] = {0.0f, 0.0f, 0.0f, 0.0f};
for (int i = 0; i < 32; i += 4) { // 手動unroll by 4
for (int j = 0; j < 4; j++) { // 部分累加
#pragma HLS pipeline
acc_part[j] += x[i + j];
}
for (int i = 1; i < 4; i++) { //最后累加
#pragma HLS unroll
acc_part[0] += acc_part[i];
}
return acc_part[0];
}