首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

利用赛灵思Vivado HLS 实现浮点设计

利用赛灵思Vivado HLS 实现浮点设计

作者:James Hrica
赛灵思公司高级软件应用工程师
jhrica@xilinx.com

在赛灵思FPGA 上用C/C++ 源代码轻松实现浮点运算硬件是Vivado HLS 工具的一大功能。但浮点运算并非像看上去那么容易掌握。
大多数设计人员在设计中使用定点算术逻辑来运算数学函数,因为这种方法速度快,占用面积小。不过在许多情况下,使用浮点数值格式进行数学计算更为有利。虽然定点格式可以实现精确的结果,但给定的格式动态范围非常有限,故设计人员必须进行深度分析,判断贯穿复杂设计的数位增长特征。而且在采用定点格式的时候,设计人员还必须引入许多中间数据类型(有着不同的定点格式),才能实现理想的结果质量(QoR)。相比之下,浮点格式能够在宽泛得多的范围内表达实数,便于设计人员在许多算法的一长串计算中使用单一的数据类型。
从硬件设计的角度来看,手动实现浮点格式的成本较高。在设计中实现这种格式需占用较大的面积。另外这种格式还会增大时延,因为与实现整数(定点)算术所需的逻辑相比,使用浮点格式实现给定算术运算所需的逻辑要复杂得多。
幸运的是,赛灵思推出一款全新VivadoTM 高层次综合(HLS)工具,能够帮助设计人员把C/C++ 设计规范用于寄存器传输级(RTL),实现需要采取浮点计算的设计。Vivodao 可以显著地减少在硬件中实现浮点算法所需的设计工作量。虽然在浮点设计上运行HLS 的基本做法一目了然,但一些细节需要详细说明。本文的讨论话题将涉及基本内容和高级内容,涵盖设计性能、面积,以及使用Vivado HLS 工具在赛灵思FPGA 中实现的浮点逻辑进行验证。
浮点数据和双精度数据
Vivado HLS 工具支持C/C++ 的浮点数据和双精度数据类型。这两类数据依据的是IEEE 754 标准[1] 定义的单精度和双精度二进制浮点格式。同样,《赛灵思LogiCORETM IP 浮点运算器6.1 版本产品指南》PG060[2] 也对浮点格式和算术实现进行了良好总结。在采用浮点运算时需要重视的一点是这种数值格式无法表达每一个实数,因此精度有限。

这一点比看上去更微妙、更复杂,而且关于这点已有大量专著。欢迎设计人员详细阅读参考资料[3、4、5 和6]。一般来说,即便是在纯软件环境下,针对相同计算采用不同算法乃至相同算法的不同实现(微架构)也无法得到完全相等的二进制结果。这种误差的来源有多种,其中包括舍入误差的累加。而这种累加对运算进行的次序很敏感。另外,FPU 对扩展精度的支持也会影响舍入结果。以x87 的80 位格式为例,SIMD(SSE 等)指令的行为就与x87 不同。另外,许多浮点字面值只能近似地表达,即便是对有理数也是如此。其它可能导致误差的因素有库函数逼近原理,例如三角函数,常量传用或是合并效应。
此外,部分浮点算法支持“次正常”值,用于表达比浮点格式正常表达值小的数值。例如在单精度格式中,最小的正常浮点值是2-126。但是在支持次正常值的情况下,尾数位可用于通过固定指数值2-127 表达定点数值。
现在介绍用于验证浮点计算结果的简单软件示例。
下面的例1 说明用不同方法(乃至貌似相同的方法)完成相同计算会得到的结果略有不同。同时,例2 说明二进制浮点格式不能准确地表达所有数值(甚至整数)。
例1:相同计算得到不同结果
// Simple demo of floating point predictability problem
int main(void) {
float fdelta = 0.1f; // Cannot be represented exactly
float fsum = 0.0f;
while (fsum < 1.0f)
fsum += fdelta; float fprod = 10.0f * fdelta;
double dprod = float(10.0f * fdelta);
cout.precision(20);
cout << "fsum: " << fsum << endl;
cout << "fprod: " << fprod << endl;
cout << "dprod: " << dprod << endl;
return 0;
}
Program output:
fsum: 1.0000001192092895508
fprod: 1
dprod: 1.0000000149011611938

示例1 的第一个输出是将0.1 的近似值累加十次,会造成舍入误差的积累。每次迭代都会将不精确的单精度0.1 加到累加和中,然后存储到单精度(32 位)寄存器中。随着累加和指数(以2 为底)的增加(从-4 到0),不论浮点单元(FPU)内部的精度如何,中间和都会发生四次舍入。
第二个值的计算采用x87 扩展精度执行,结果在存储为单精度格式之前进行舍入。第三个值的乘法也采用扩展精度执
行,但舍入后存储为双精度格式,会导致结果产生不同偏差。

注意:在编译不同的机器架构或采用不同编译器的情况下,该代码也可能产生不同的结果。
案例2:整数也会丧失精度
// Another demo of floating-point predictability problem
int main(void)
{
int i;
float delta = 1.0f;
for (int i = 0; i < 100000000; i++) {
float x = (float)i + delta;
if (x / (float)i <= 1.0f) {
// (x / i) should always be > 1.0
printf("!!! ERROR on iteration #%d !!!\n", i + 1);
return -1;
}
}
return 0;
}
Program output:
!!! ERROR on iteration #16777217 !!!

这个例子说明, 在存储为单精度浮点格式时, 大于16,777,216(224)的整数值会丧失精度。这是因为有效位数为24 位,超过这个位数,i 的最低有效位在转换为浮点格式时必须舍弃。
使用VIVADO HLS 工具进行浮点设计的基础
要让HLS 原生地支持基本算术运算符(+, -, *, /)、关系符(==, !=, <, <=, >, >=) 和格式转换(比如整数/ 定点转浮点,浮点转双精度),需要将这些运算映射到在最终RTL 中进行初始化的赛灵思LogiCORE IP 浮点运算器内核上。另外,对sqrt() 函数族的调用(从C/C++ 标准数学库中调用)以及1.0/x 和1.0/sqrt(x) 形式的代码,也需要映射到合适的浮点运算器内核上。虽然CORE GeneratorTM 能够为定制精度浮点类型构建这些内核,但Vivado HLS 工具只生成符合IEEE-754 标准的单精度和双精度内核。软件产生的结果与浮点运算器内核硬件产生的结果之间存在微小的差异,这种差异的来源可能在于运算器内核对次正常输入的处理方式是“一次性归零”,即在遇到次正常值时,用0.0 将其替代。关于这些内核的工作方式的详细介绍,请参阅《LogiCORE IP 浮点运算器6.1 版本产品指南》PG060。[2]

如果运算过程没有反馈路径,设计人员可以在完全流水线的环境下使用HLS 支持的全部浮点运算符和函数,并在每个时钟周期内生成一个结果。
Vivado HLS 工具还支持C(99)/C++ 标准数学库提供的许多其它函数,但其中没有浮点运算器内核。关于完整的函数列表,请参阅《Vivado 设计套件用户指南:高层次综合2012.2版本》 UG902。[7] 为在赛灵思FPGA 上实现,许多函数的底层近似算法已经过选择和优化,其精度特点可能在一定程度上与软件标准中的规定有所不同。
Vivado HLS 只能在硬件中以单精度和双精度浮点运算的方式实现标准的数学库函数。如果用户用整数或定点参数调用这些函数或是返回变量,工具将在必要的情况下进行浮点格式转换,以浮点方式完成计算。
如果运算过程没有反馈路径,设计人员可以在完全流水线的环境下使用HLS 支持的全部浮点运算符和函数,并在每个时钟周期内生成一个结果。Vivado HLS 工具虽然可以针对不常发生的串行执行来调度这些运算,但这样难以实现最大的面积效率,尤其对于触发器利用率来说尤为突出。甚至对于“/”和sqrt() 也是如此,因为这两者有低吞吐量的内核可以使用。
在基于ANSI/ISO-C 的项目中使用
要在基于ANSI/ISO-C 的项目中使用所支持的标准数学库函数,需要将math.h 报头文件包含在所有需要调用函数的源文件里。基础函数用于运算( 和返回) 双精度值, 例如double sqrt(double)。大多数函数的单精度版都在函数名称上附有“f”,例如float sqrtf(float)、float sinf(float)和float ceilf(float)。需要记住这点,否则即使参数和返回变量是单精度值,Vivado HLS 也会执行FPGA 资源占用规模更大的双精度函数。另外,格式转换也会占据更多资源,增加计算时延。

使用ANSI/ISO-C 时还需要注意另外一个问题,即在以软件方式编译和运行代码时(包括RTL 协同仿真中的C 语言测试台)所使用的算法与在RTL(由HLS 生成)中使用的算法不同。在软件中调用的是GCC libc 函数,而硬件侧使用的是Vivado HLS 工具的数学函数库代码。这会导致两者间存在比特级的不匹配,但从分析角度来说,两种结果都与真实的结果极为接近。
例3:无意识地使用双精度数学函数
// Unintended consequences?
#include
float top_level(float inval)
{
// double-precision natural logarithm
return log(inval);
}

这个例子在实现RTL 过程中将输入转换为双精度格式,以双精度计算自然对数,然后再将结果转换为单精度输出。
例4:单精度数学函数的显式使用
// Be sure to use the right version of math functions...
#include
float top_level(float inval)
{
// single-precision natural logarithm
return logf(inval);
}

由于调用的是单精度对数函数,故在RTL 中实现的过程中无需进行输入/ 输出格式转换。
在C++ 项目中使用
在使用C++ 进行设计时,取得标准数学函数库支持的最直接办法是将 系统头文件包含在所有需要调用函数的源文件中。这个头文件提供各种版本的基础(双精度)函数,函数经加载后可作为参数,并在std 命名空间中返回单精度(float)值。要使用函数的单精度版,必须将std 命名空间包含在范围内,方法有两种:1 使用范围解析运算符(:;2 利用指令导入整个命名空间。

例5:明确范围解析
#include
float top_level(float inval)
{
// single-precision natural logarithm
return std::log(inval);
}

例6:使命名空间的内容进入文件范围
#include
using namespace std;
float top_level(float inval)
{
// single-precision natural logarithm
return log(inval);
}

和在ANSI/ISO-C 项目中使用 的情况一样,当在代码中使用 中的函数时(代码通过Vivado HSL工具进行综合),以软件方式运行代码的结果与用RTL 实现代码的结果也可能存在差异,因为两种方式采用的近似算法不同。为此,Vivado HLS 编程环境提供一些特定的算法,可用于对RTL 进行综合以便用于C++ 建模。
在对HLS 的C++ 代码修改情况进行验证,以及后续用C++ 测试台对得到的RTL 进行协同仿真的过程中,赛灵思建议在HLS 源代码中使用相同的数学库调用,并在测试台代码中使用C++ 标准库,以便生成参考值。这样能够在开发过程中为HLS 模型和数学库提供多一层验证。
要采用这种方法,应只将Vivado HLS 工具头文件包含在需要综合为RTL 的源文件中。只有在验证HLS设计时使用的源文件(比如测试程序和支持代码)才需要包含 系统头文件。 头文件中函数的HLS 版是hls::namespace 的组成部分。对于需要针对软件建模和验证进行编译的HLS 版函数来说,应针对每一个函数调用使用hls::scope 解析。
注意: 在使用C++ 标准数学库调用时, 不应导入hls::namespace(通过“using namespace hls”命令),因为这样会在HLS 综合的过程中导致编译错误。例7a 说明的就是这个问题。
例7A:测试程序
使用标准C++ 数学库
// Contents of main.cpp - The C++ test bench
#include
#include
using namespace std;
extern float hw_cos_top(float);
int main(void)
{
int mismatches = 0;
for (int i = 0; i < 64; i++) {
float test_val = float(i) * M_PI / 64.0f;
float sw_result = cos(test_val); //float
std::cos(float)
float hw_result = hw_cos_top(test_val);
if (sw_result != hw_result) {
mismatches++;
cout << "!!! Mismatch on iteration #" << i;
cout << " -- Expected: " << sw_result;
cout << "\t Got: " << hw_result;
cout << "\t Delta: " << hw_result - sw_result;
cout << endl;
}
}
return mismatches;
}

例7B:HLS 设计代码
使用HLS_MATH 库
// Contents of hw_cos.cpp
#include
float hw_cos_top(float x)
{
// hls::cos for both C++ model and RTL co-sim
return hls::cos(x);
}

编译该代码并以软件方式运行(比如在Vivado HLS 图形用户界面中“Run C/C++ Project”),hw_cos_top() 返回的结果与HLS 生成的RTL 所产生的结果相同,同时该程序还会根据软件参考模型(即std:cos())测试不匹配结果。如果之前将 包含在hw_cos.cpp 中,则C/C++ 项目进行编译并以软件方式运行时就不会出现不匹配情况,但在RTL 协同仿真的过程中会出现不匹配。
由于浮点运算使用的资源量比整数运算或者定点运算要大得多,所以Vivado HLS 工具会尽量高效地使用这些资源。
重要的是不要假定Vivado HLS 工具会进行看似理所应当又微不足道的优化。和大多数C/C++ 软件编译器一样,涉及浮点字面值(数值常量)的表达式可能不会在HLS 综合过程中得到优化。请考虑下列示例代码。
例8:代数等价;
差异巨大的HLS 实现
// 3 different results
void top(float *r0, float *r1, float *r2, float inval)
{
// double-precision multiplier & conversions
*r0 = 0.1 * inval;
// single-precision multiplier
*r1 = 0.1f * inval;
// single-precision divider
*r2 = inval / 10.0f;
}

如果把这个函数综合到RTL,得到用于计算r0、r1 和r2的三个线路会有显著不同。根据C/C++ 的规则,字面值0.1代表一个无法精确表达的双精度数。因此工具会实例化一个双精度(double)乘法器内核,以及用于将inval 转换为双精度,再将乘积转换回单精度类型(*r0 的类型)的内核。如果需要的是单精度(float)常量,应给该字面值附加一个f,比如0.1f。因此,上述r1 的值就是(非精确)0.100 的浮点表达与inval的单精度乘积。最终,r2 用单精度除法内核求得,inval 是分子,10.0f 是分母。实数值10 可以确切地用二进制浮点格式表达。因此根据inval 值的情况,r2 的计算可能是精确的,而r0 或者r1 都欠精确。
由于浮点运算的顺序可能会影响结果(例如,在不同的时间进行舍入),因此表达式中的多个浮点字面值可能无法合并到一起。
例9:运算顺序会影响常量合并
// very different implementations
void top(float *r0, float *r1, float inval)
{
// *r0 = inval; constants eliminated
*r0 = 0.1f * 10.0f * inval;
// two double-precision multiplies
*r1 = 0.1f * inval * 10.0f;
}

在本例中,由于该表达式运算顺序从r0 开始,编译器将整个表达式识别为恒等式,不生成任何硬件。但是相同的情况并不适用于r1。r1 生成了两个乘法器。
例10:避免在整数表达式中使用浮点值
void top(int *r0, int *r1, int inval)
{
*r0 = 0.5 * inval;
*r1 = inval / 2;
}

在本例中,HLS 赋给r0 的逻辑采用如下实现过程:先把inval 转换为双精度格式以便乘以0.5(双精度值),然后再转换回整数。另一方面,HLS 将与2 的整数次幂进行的乘法和除法运算分别优化为向左移位和向右移位操作,然后只需通过简单的线路选择就可以在硬件中实现(根据运算的方向和类型,进行适当的补零或者符号扩展)。因此,在实现相同的算术结果时,用于给r1 赋值的逻辑其效率要高得多。
并行性、同步性和资源共享
由于浮点运算使用的资源量比整数运算或者定点运算要大得多,所以Vivado HLS 工具会尽量高效地使用这些资源。在
数据关系和约束允许的条件下,该工具一般会让源运算的多次调用共享浮点运算器内核。下面的例子将对四个浮点值求和,用以说明这个概念。

例11:使用单内核完成多重运算
// How many adder cores?
void top (float *r, float a, float b, float c, float d)
{
*r = a + b + c + d;
}

在数据带宽允许的情况下,可以将本需要顺序执行的大量运算在给定时间内同步完成,以达到提高运算量的目的。在下面的例子中,HLS 工具创建的RTL 将流水线循环中两个源数组的元素相加,生成结果数组的值。Vivado HLS 把顶层数组参数映射到存储器接口上,从而限制每周期的访问数量(例如,对双端口RAM 来说是每周期两次,FIFO 等是每周期一次)。
例12:独立求和
// Independent sums, but I/O only allows
// throughput of one result per cycle
void top (float r0[32], float a[32], float b[32])
{
#pragma HLS interface ap_fifo port=a,b,r0
for (int i = 0; i < 32; i++) {
#pragma HLS pipeline
r0 = a + b;
}
}

在默认条件下,Vivado HLS 工具安排该循环迭代32 次并实现单个加法器内核,前提是输入数据持续不断,且输出FIFO 不会满。得到的RTL 模块需要使用32 个周期,还需要一些周期用于清空加法器流水线。在I/O 数据速率允许的条件下速度越快越好。另一方面如果数据速率提高,设计人员还可以用HLS 技术相应地提升处理速度。作为对上述例子的扩展,我们可以使用Vivado HLS 工具的数组维度改变指令将接口宽度加倍,从而增大I/O 带宽。要提高处理速度,需将循环局部展开两倍,使其与带宽的增加相匹配。
例13:独立求和
// Independent sums, with increased I/O
// bandwidth -> high throughput and area
void top (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 = a + b;
}
}

使用这些增加的指令,Vivado HLS 工具能综合出具有两个加法器流水线的RTL。两个流水线可以并行工作,使迭代数量减半,每次迭代能产生两个输出样本。这样做之所以可行,是因为每个计算都是完全独立的,而且加法运算的次序不会影响结果的精度。不过如果出现更加复杂的计算,比如需求出一连串独立浮点运算的结果,Vivado HLS 工具就无法重新安排这些运算的次序。最终导致并行性或者共享性低于预期。另外,如果流水线数据路径中存在反馈或者递归,那么通过同步化来增大设计的吞吐量就需要对源代码结构进行一些手动重调整。
例14 详细说明了Vivado HLS 工具如何处理浮点运算的反馈和递归。下面的代码涉及流水线部分的浮点累加。
例14:通过运算处理相关性
// Floating-point accumulator
float top(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;
}
return acc;
}
由于这种形式的累加会导致递归,浮点加法的延迟一般大于一个周期,故该流水线不能实现一个周期一次累加的吞吐量。

例如,如果浮点加法器的延迟是四个周期,则流水线初始化间隔也是四个周期(请参阅图2 所示关于Vivado HLS 工具综合的报告)。由于只有在完成一次累加之后才能开始另一次累加,因此这个例子中所能实现的最大吞吐量是每四个周期完成一次累加。累加循环迭代32 次,每次行程耗时四个周期,最终共需要128 个周期,以及用于清空流水线的附加周期。


一种性能更高的替代方法是在相同的加法器内核上穿插进行四个局部累加,每四个周期完成一次局部累加,从而减少完成32 次加法运算所需的时间。但是Vivado HLS 工具无法用例14 提供的代码实现这种优化方案,因为这需要变更累加的运算次序。如果每次部分累加都将x[] 的第四个元素作为输入,那么单个加数的次序发生变化就会造成结果的不同。
避开这个局限的方法是对源代码进行小幅修改,让自己的意图更加明显。下面的示例代码引入一个用于存储部分和的数组acc_part[4]。部分和随后进行加总,同时让主累加循环部分展开。
例15:对运算进行显式的重排序,得到更高性能
// Floating-point accumulator
float top(float x[32])
{
#pragma HLS interface ap_fifo port=x
float acc_part[4] = {0.0f, 0.0f, 0.0f, 0.0f};
// Manually unroll by 4
for (int i = 0; i < 32; i += 4) {
// Partial accumulations
for (int j = 0; j < 4; j++) {
#pragma HLS pipeline
acc_part[j] += x[i + j];
}
// Final accumulation
for (int i = 1; i < 4; i++) {
#pragma HLS unroll
acc_part[0] += acc_part;
}
}
return acc_part[0];
}

采用这种代码结构,Vivado HLS 工具能够通过交替周期的方式将四次部分累加调度到一个加法器内核上,从而实现更高效率的资源利用(见图3)。后续的最终累加也可以根据其他因素使用相同的加法器内核。现在主累加循环需要八次迭代(32/4),每次迭代用四个周期完成四个部分累加。这样,完成相同工作量所需的时间大大减少,而且只需多占用很少量的FPGA 资源。最终累加循环也使用相同的加法器内核,会使周期数量有所增加,但增加的数量是固定的,而且考虑到通过避免展开主累加循环而节省的大量周期,这个增加值是很小的。可以进一步优化最终累加算法,但会造成性能面积比下降。


在有更大I/O 带宽的情况下,我们可以设定更大的展开因数,让更多的算术内核承担运算任务。在上述的例子中,如果每个时钟周期有两个x[] 元素可用,就可以把展开因素增加到8。此时能实现两个加法器内核,每个周期能完成八个部分累加。目标器件的选择和用户的时间约束会给具体的运算器延迟造成影响。一般来说,有必要运行HLS 并对较为简单的基础案例进行性能分析(如例14 所示),用以判断理想的展开量。
控制实现资源
赛灵思LogiCORE IP 浮点运算器内核能够控制一些运算的DSP48 利用率。例如,乘法器内核有四个变量,它们可以用逻辑资源(LUT) 交换DSP48 用量。在正常情况下,Vivado HLS工具会根据性能约束自动判断需要使用哪类内核。设计人员可使用Vivado HLS 工具的RESOURCE 指令覆盖自动选项,并针对给定的运算设定所使用的浮点运算器类型。例如,对于例14 中提供的代码,加法器一般会使用“全占用”内核的方式来实现,并使用Kintex ™ -7 FPGA 上的两个DSP48E1资源。如综合报告(图4)的“组件”部分所示。


下面的示例代码强制把加法运算映射到FAddSub_nodsp内核上,如图5 所示。


例16:使用“RESOURCE”指令设定浮点运算器内核变量
// Floating-point accumulator
float top(float x[32])
{
#pragma HLS interface ap_fifo port=x
float acc = 0;
for (int i = 0; i < 32; i++) {
#pragma HLS pipeline
#pragma HLS resource variable=acc \
core=FAddSub_nodsp
acc += x;
}
return acc;
}

关于RESOURCE 指令的详细使用介绍以及可用的内核清单,敬请参阅《Vivado HLS 用户指南》 UG902[7]。
验证浮点计算的结果
用不同方式运行相同计算所得的浮点结果之间存在数位级(或者更高)的差异,这里面的原因有很多。误差会发生的位置也不同,包括不同的近似算法、计算顺序的重新排序导致的舍入差异、以及次正常值的处理(此时浮点运算器内核清零)。

一般来说,两个浮点值的比较结果(特别是相等比较),可能造成误导。两个被比较的值可能只在“最后一位”(ULP;二进制格式中的作用最小的位)存在差异,从而导致极小的相对误差,造成“==”运算符号返回假值。例如,在使用单精度浮点格式的情况下,如果两个操作数都非零(也非次正常值),1ULP 的差异代表0.00001% 的相对误差。因此,在比较浮点数时,应避免使用“==”和“! =”运算符。要检查两个值是否“足够接近”,可以使用可接受误差阈值。
在大多数情况下,设置可接受的ULP 或相对误差级就可以,比设置绝对误差(或者“ε”)阈值要好。但是如果需要比较的值中有一个是零(0.0),这种方法就失效了。如果比较的值中有一个是恒零值或可以产生恒零值,那么就需要使用绝对误差阈值。下面的示例代码提供了一种用于比较两个浮点数近似相等的方法,便于您设置ULP 和绝对误差限值。这个函数可以用在C/C++“测试台”代码中,用于验证HSL 源代码的修改情况,并验证Vivado HLS 工具的RTL 协同仿真。您还可以在HLS 的实现代码中使用类似的方法。
例17:用于测试浮点值近似相等的C 语言代码
// Create a union based type for easy access to
// binary representation
typedef union {
float fval;
unsigned int rawbits;
} float_union_t;
bool approx_eqf(
float x, float y,
int ulp_err_lim, float abs_err_lim
)
{
float_union_t lx, ly;
lx.fval = x;
ly.fval = y;
// ULP based comparison is likely to be meaningless
// when x or y is exactly zero or their signs
// differ, so test against an absolute error
// threshold this test also handles (-0.0 == +0.0),
// which should return true. N.B. that the
// abs_err_lim must be chosen wisely, based on
// knowledge of calculations/algorithms that lead
// up to the comparison. There is no substitute for
// proper error analysis when accuracy of results
// matter.
if (((x == 0.0f) ^ (y == 0.0f)) ||
(__signbit(x) != __signbit(y))) {
return fabs(x - y) <= fabs(abs_err_lim);
}
// Do ULP base comparison for all other cases
return abs((int)lx.rawbits -
(int)ly.rawbits) <= ulp_err_lim;
}

关于应该将ULP 和绝对误差阈值设置在什么水平这个问题没有唯一的答案,因为设置会因设计而异。相对于基准结果而言,复杂的算法可能会在输出中累加更多ULP 误差。其他关系运算符也可能会误导结果。例如,在测试某值是否小于(或者大于)另一值时,误差仅有几个ULP,这种情况下是否可以做出合理的结论?我们可以将上面提供的函数与小于/ 大于比较法结合使用,用来判别含糊的结果。
强大的功能
在赛灵思FPGA 上用C/C++ 源代码轻松实现浮点算法硬件(RTL 代码)是Vivado HLS 工具所具备的一种强大功能。但浮点算法的使用不管是从软件的角度、硬件的角度还是混合的角度都并非像看上去那么直观。IEEE-754 标准二进制浮点格式的非精确性给验证计算结果带来难度。另外,设计人员在C/C++ 源代码层面以及应用HLS 优化指令的时候,都必须额外小心,才能在FPGA 资源利用和设计性能方面获得理想的结果。

关于更多实际操作的建议,请参阅“Vivado 高层次综合产品” 页:http://www.xilinx.com/products/d ... ion/esl-design/hls/ ;Vivado 视频辅导:http://www.xilinx.com/training/vivado;以及“浮点运算器产品”页:http://www.xilinx.com/products/i ... rty/FLOATING_PT.htm
参考资料:
1. 用于二进制浮点算法的ANSI/IEEE 标准,ANSI/IEEE 标准754-2008;IEEE-754
2. PG060,LogiCORE IP 浮点运算器6.1 版产品指南
3. http://randomascii.wordpress.com/category/floating-point/
4. http://docs.oracle.com/cd/E19957-01/8063568/ncg_goldberg.html
5. http://www.lahey.com/float.htm
6. Adam Taylor,《FPGA 数学基础知识》,赛灵思期刊第80 期,http://issuu.com/xcelljournal/docs/xcell80
7. UG902,《Vivado 设计套件用户指南:高层次综合2012.2版》;UG871,《Vivado 设计套件辅导:高层次综合V2012.2 版》
记录学习中的点点滴滴,让每一天过的更加有意义!
返回列表