UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Xilinx Employee
Xilinx Employee
6,267 Views
Registered: ‎03-19-2013

Xilinx Vivado HLS中Floating-Point(浮点)设计编码风格与技巧

Xilinx Vivado HLSFloating-Point(浮点)设计

编码风格与技巧

George Wang (王宏强) – Xilinx DSP Specialist

 

尽管通常Fixed-Point(定点)比Floating-Point(浮点)算法的FPGA实现要更快,且面积更高效,但往往有时也需要Floating-Point来实现。这是因为Fixed-Point有限的数据动态范围,需要深入的分析来决定整个设计中间数据位宽变化的pattern,为了达到优化的QoR,并且要引入很多不同类型的Fixed-Point中间变量。而Floating-Point具有更大的数据动态范围,从而在很多算法中只需要一种数据类型的优势。

Xilinx Vivado HLS工具支持C/C++ IEEE-54标准单精度及双精度浮点数据类型,可以比较容易,快速地将C/C++ Floating-Point算法转成RTL代码。与此同时,为了达到用户期望的FPGA资源与性能, 当使用Vivado HLS directives时需要注意C/C++编码风格与技巧相结合。

 

  1. 编码风格

1.1      单双精度浮点数学函数

 

#include <math.h>

float example(float var)

{

return log(var); // 双精度自然对数

}

 

在C设计中, 这个例子, Vviado HLS 生成的RTL实现将输入转换成双精度浮点,并基于双精度浮点计算自然对数,然后将双精度浮点输出转换成单精度浮点.

 

#include <math.h>

float example(float var)

{

return logf(var); // 单精度自然对数

}

在C设计中, logf才是单精度自然对数, 这个例子 Vviado HLS 生成的RTL实现将基于单精度浮点计算自然对数, 而且没有输入输出单双精度的互转。

 

1.2      浮点运算优化

我们先来看一个例子,三个从代数上看起来差不多的写法,但其在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实现。

因为0.2是一个不能精确表征的双精度数字, 所以m0运算会被Vivado HLS综合成一个双精度浮点乘法, 并且将var 转换成双精度, 然后将双精度乘法输出m0转换成单精度。

特别注意,如果希望Vivado HLS综合出单精度常熟,需要在常数后面加f, 如0.2f。这样m1综合成一个单精度乘法的输出。同理,m2将被Vivado HLS综合成单精度除法的输出。

 

我们来看另外一个例子。

 

void example(float *m0, float *m1, float var)

{

*m0 = 0.2f * 5.0f * var; // *m0 = var;常数乘法被优化掉

*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综合成简单的右移运算。所以如果用户希望实现对var除以2, 就写成m1这种表达式,而不是m0的表达式。

 

  1. 并行度与资源复用

由于浮点运算相比整型,定点运算耗用更可观的资源。Vivado HLS会尽量用更有效的资源来实现浮点运算,当数据的相关性及约束许可的情况下,在Vivado HLS中,会尽量复用一些浮点运算单元。为了说明这个,我们看一个简单的三个浮点加法例子, Vivado HLS复用一个浮点加法器来串行实现三个浮点加法,代码及HLS综合结果如下。

void example(float *r, float a, float b,

float c, float d)

{

*r = a + b + c + d;

}

  

如果希望并行三个浮点加法来实现,可以加上pipeline directive,由于浮点运算的精度与运算顺序有很大的关系,HLS工具不会改变用户代码的计算顺序,这样只是个级联结构。代码及HLS综合结果如下, latency是35:

void example(float *r, float a, float b,

float c, float d)

{

#pragma HLS PIPELINE

*r = a + b + c + d;

}

 

如果希望并行三个浮点加法来实现,同时降低latency,可以加上pipeline directive的同时将代码简单修改成加法树结构,这样的代码及HLS综合结果如下, latency从35降低到23:

void example (float *r, float a, float b,

float c, float d)

{

#pragma HLS PIPELINE

float e, f;

e = a + b;

f = c + d;

*r = e + f;

}

 

 

有时设计需要更高的throughput及更低的latency。这时就需要提高设计的并行度。以下面例子来说明,在Vivado HLS就需要对for循环loop加pipeline与unroll 的directives。同时需要通过设置a,b,r0 为FIFO, 并对其重排以提高I/O带宽两倍。这样Vivado HLS就会综合出两个浮点加法来并行实现,这是因为每个加法器计算是完全独立的。

 

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大于一个时钟周期,加的pipeline directive并不能达到一个时钟周期完成一次累加的throughput。

 

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;

}

 

为了对上面例子并行展开,可以对代码如下做较小的改动,也就是拆成先部分累加,再最后累加,当然也需要对输入数据进行简单的重新排列,以获得相应的I/O带宽,从而达到期望的并行度。

 

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];

}