Xilinx Vivado HLS中Floating-Point(浮點(diǎn))設(shè)計(jì)編碼風(fēng)格與技巧
2013-09-02
作者:王宏強(qiáng) – Xilinx DSP Specialist
盡管通常Fixed-Point(定點(diǎn))比Floating-Point(浮點(diǎn))算法的FPGA實(shí)現(xiàn)要更快
Xilinx Vivado HLS工具支持C/C++ IEEE-54標(biāo)準(zhǔn)單精度及雙精度浮點(diǎn)數(shù)據(jù)類型,,可以比較容易,,快速地將C/C++ Floating-Point算法轉(zhuǎn)成RTL代碼。與此同時(shí),,為了達(dá)到用戶期望的FPGA資源與性能, 當(dāng)使用Vivado HLS directives時(shí)需要注意C/C++編碼風(fēng)格與技巧相結(jié)合,。
1.1 單雙精度浮點(diǎn)數(shù)學(xué)函數(shù)
#include
float example(float var)
{
return log(var); // 雙精度自然對數(shù)
}
在C設(shè)計(jì)中, 這個(gè)例子, Vviado HLS 生成的RTL實(shí)現(xiàn)將輸入轉(zhuǎn)換成雙精度浮點(diǎn),并基于雙精度浮點(diǎn)計(jì)算自然對數(shù),然后將雙精度浮點(diǎn)輸出轉(zhuǎn)換成單精
#include
float example(float var)
{
return logf(var); // 單精度自然對數(shù)
}
在C設(shè)計(jì)中, logf才是單精度自然對數(shù), 這個(gè)例子 Vviado HLS 生成的RTL實(shí)現(xiàn)將基于單精度浮點(diǎn)計(jì)算自然對數(shù), 而且沒有輸入輸出單雙精度的互轉(zhuǎn)。
1.2 浮點(diǎn)運(yùn)算優(yōu)化
我們先來看一個(gè)例子,,三個(gè)從代數(shù)上看起來差不多的寫法,,但其在Vivado HLS中綜合出來的是三個(gè)完全不一樣的結(jié)果。
void example(float *m0, float *m1, float *m2, float var)
{
*m0 = 0.2 * var; // 雙精度浮點(diǎn)乘法,,單雙精度類型轉(zhuǎn)換
*m1 = 0.2f * var; // 單精度浮點(diǎn)乘法
*m2 = var / 20.0f; // 單精度浮點(diǎn)除法
}
Vivado HLS將日m0, m1, m2綜合成不同的RTL實(shí)現(xiàn),。
因?yàn)?.2是一個(gè)不能精確表征的雙精度數(shù)字, 所以m0運(yùn)算會被Vivado HLS綜合成一個(gè)雙精度浮點(diǎn)乘法,, 并且將var 轉(zhuǎn)換成雙精度,, 然后將雙精度乘法輸出m0轉(zhuǎn)換成單精度。
特別注意,,如果希望Vivado HLS綜合出單精度常熟,,需要在常數(shù)后面加f, 如0.2f。這樣m1綜合成一個(gè)單精度乘法的輸出,。同理,,m2將被Vivado HLS綜合成單精度除法的輸出。
我們來看另外一個(gè)例子,。
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; // 兩個(gè)雙精度浮點(diǎn)乘法
}
再來看另一個(gè)例子,。
void example(float *m0, float *m1, float var)
{
*m0 = 0.5 * var; //
*m1 = var/2; //
}
m0運(yùn)算會被Vivado HLS綜合成一個(gè)雙精度浮點(diǎn)乘法, 并且將var 轉(zhuǎn)換成雙精度,, 然后將雙精度乘法輸出m0轉(zhuǎn)換成單精度,。
m1運(yùn)算會被Vivado HLS綜合成簡單的右移運(yùn)算。所以如果用戶希望實(shí)現(xiàn)對var除以2,, 就寫成m1這種表達(dá)式,,而不是m0的表達(dá)式。
由于浮點(diǎn)運(yùn)算相比整型,,定點(diǎn)運(yùn)算耗用更可觀的資源,。Vivado HLS會盡量用更有效的資源來實(shí)現(xiàn)浮點(diǎn)運(yùn)算,,當(dāng)數(shù)據(jù)的相關(guān)性及約束許可的情況下,在Vivado HLS中,,會盡量復(fù)用一些浮點(diǎn)運(yùn)算單元,。為了說明這個(gè),我們看一個(gè)簡單的四個(gè)浮點(diǎn)加法例子,, Vivado HLS復(fù)用一個(gè)浮點(diǎn)加法器來串行實(shí)現(xiàn)四個(gè)浮點(diǎn)加法,。
void example(float *r, float a, float b,
float c, float d)
{
*r = a + b + c + d;
}
有時(shí)設(shè)計(jì)需要更高的throughput及更低的latency。這時(shí)就需要提高設(shè)計(jì)的并行度,。以下面例子
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];
}
}
然而,,如果更多復(fù)雜的運(yùn)算,,或許會導(dǎo)致不獨(dú)立的浮點(diǎn)運(yùn)算,在這種情況下,,Vivado HLS不能重新排列這些運(yùn)算的順序,,這樣會導(dǎo)致更低的,,不是所期望的復(fù)用,。 下面舉例來說明如何提高帶有反饋浮點(diǎn)運(yùn)算的性能。
這個(gè)例子的累加會導(dǎo)致recurrence,,并且通常浮點(diǎn)加法的latency大于一個(gè)時(shí)鐘周期,,加的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;
}
為了對上面例子并行展開,,可以對代碼如下做較小的改動(dòng),,也就是拆成先部分累加,再最后累加,,當(dāng)然也需要對輸
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) { // 手動(dòng)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];
}