在AURIX项目的开发过程中,如果碰到浮点运算的结果跟预期对不上,很多时候问题的根源并不在算法代码本身。这是因为TASKING工具链会根据当前选择的芯片型号以及编译时指定的选项,自动去挑选对应的C标准库和浮点运行库,这样一来,硬件浮点单元(FPU)有没有被用上、是否退回到软件浮点、单精度还是双精度处理、有没有开启异常捕获模式,这些因素都会直接影响到最终的计算结果。下面就以TASKING VX-toolset for TriCore这一工具为例来说明,虽然SmartCode和不同版本在界面上可能有些差别,但排查的思路是相通的。
一、怎样选择浮点库
在一般的项目中,不太建议自己去手工指定要链接哪一个具体的库文件,更稳妥的做法是先保证选对了芯片型号,然后统一去设置浮点模型,让工具链的控制程序自动完成库的匹配工作。
1、先把目标芯片确认清楚
整个配置的起点,是确保工具链认对了当前要用的处理器。可以通过菜单进入【Project】→【Properties】→【C/C++Build】→【Settings】,在里面查看处理器型号是否跟板子上的一致;如果用的是命令行方式,则优先通过--cpu参数来指定,比如TC39x系列。控制程序拿到这个芯片信息后,就会自己判断芯片内部有没有集成FPU,并且把处理器、内核以及链接相关的配置都传递给后面的编译和链接步骤。
2、普通的浮点计算优先交给硬件FPU处理
对于大多数浮点运算,利用硬件FPU能够得到比较高的执行效率,也比较省心。TriCore工具链在默认情况下,会自动选择那些文件名里带有_fpu后缀的库,比如libc_fpu.a和libfp_fpu.a,这类库里装的都是直接使用硬件浮点指令的函数。目前AURIX家族中常见的TriCore内核都配备了单精度的硬件FPU,所以用它来处理float类型的运算是很合适的。
3、需要纯软件浮点时再单独调整
要是项目里明明白白要求不能依赖硬件浮点,那就得专门去设置一下,通过--fp-model=+soft这个编译选项来告诉工具链。启用这个选项之后,工具链就会转而链接那些不带_fpu后缀的库,从而完全用软件来模拟浮点操作。这种方式更适合一些特殊的兼容性验证场景,或者是为了跟旧代码保持完全一致,但切换之后,运行效率通常会下降不少,一些边界行为的精细表现也可能跟硬件浮点不一样,这些变化都要重新测试评估。
4、浮点异常捕获模式要谨慎开启
如果需要让程序能够捕捉到浮点运算过程中出现的异常,比如上溢、下溢或者无效运算等,可以在编译选项中加入--fp-model=+trap。这时候,被链接进来的库文件名里往往会带上一个字母t作为标记,例如libfpt_fpu.a。不过,开启异常捕获会明显增加运行时的开销,因此它更适合用在调试阶段或者有明确安全需求的地方,在没有对整体工程进行充分验证之前,不建议把它全局都打开。
二、浮点计算结果异常时要核对哪些选项
一旦发现计算结果跟预期不符,比较好的排查顺序是先比较编译时所用的参数,再去检查代码本身。特别是当库文件、目标文件以及主工程可能来自不同的项目时,浮点模型不一致的问题藏得很深,需要多加留意。
1、确认double类型有没有被降级成float处理
编译选项里有一个--fp-model=+float,它会通知编译器,把程序里所有的double都当成单精度的float来看待。这样做能在一定程度上减少存储占用和运算量,但付出的代价就是精度会显著降低。前面提到AURIX上常见的TriCore FPU只具备单精度硬件加速的能力,如果程序确实需要用到真正的双精度计算,就必须改用--fp-model=-float,同时要确保所有参与链接的库和目标文件,都是在同样的双精度设置下编译出来的,否则很容易出现不一致。
2、核对EABI兼容设置有没有引起冲突
如果工程里开启了EABI兼容性的要求,那么double类型就会被强制保持64位的语义,不能随意压缩。假如这个时候又同时打开了把double当成float的开关,编译器很可能直接报出一条类似“ctc E490:Only 64-bit double is allowed for EABI compliance”的错误信息,遇到这种情况,就需要把浮点模型老老实实地设回--fp-model=-float。
3、留心那些浮点优化子选项的影响
--fp-model本身还包含一批更细的优化开关,比如contract、fastlib、nonan、rewrite和negzero等。它们可能会改变表达式的合并规则,影响数学库函数的精度,或者调整对NaN、Inf以及正负零这些特殊值的处理方式。在调试数值偏差的时候,一种很有效的思路是先把所有这些优化选项关掉,回到一个比较严格或精确的基准模式下去跑一遍,如果偏差消失了,再一个个地把优化选项重新打开,来确定是哪一个子选项引起了结果的变化。
4、检查静态库是否使用相同的浮点模型构建
如果项目用到了提前编译好的静态库,这些库在生成时所用的浮点设置,也必须和主工程保持完全一致。不匹配时,即使编译能通过,运行时也会出现奇怪的数值错误。因此,切换浮点模型后,一定要用新设置把静态库也重新编译一遍。
三、浮点配置修改之后怎么进行验证
当把浮点相关的编译选项都调整完以后,不能光看到工程编译通过了就觉得事情已经做完了,还需要进一步去确认链接进来的库文件是不是预期的版本,并且用一组专门准备的边界数据来实际跑一跑,看看结果是否稳定。
1、做一次彻底的Clean Build
通过【Project】→【Clean】把之前编译留下的旧目标文件全部清除掉,然后再从头构建整个工程。这一步非常重要,因为不同浮点模型下生成的.o文件绝对不能混在一起用,如果项目里还引用了外部的静态库,也要先确认那些库已经用一致的浮点设置重新生成过,再把它们链接进来。
2、翻看生成的map文件
在编译输出目录里找到map文件,打开之后搜索一下被链接进去的库名字。如果看到名称里带有_fpu后缀,就说明当前使用的是硬件FPU版本;如果名称中还出现了t字母,则表明异常捕获模式正在生效。通过map文件,可以很直接地核实自己的配置有没有真的被工具链采纳。
3、补充一组边界测试用例
数值精度的验证,至少要覆盖零值、非常小的数、非常大的数、NaN、Inf、正零与负零,以及各种类型之间的显式转换场景。另外,TASKING在不同版本中对浮点运行库也做过若干次的修正,所以每次升级工具链之后,也应该把这一套边界测试重新跑一遍,确认没有引入新的回归问题。
总结
关于TASKING中浮点库的选择方法,以及当浮点计算结果出现偏差时应该重点核对哪些编译选项,整个处理顺序可以概括成:先设好目标芯片,让工具链自动匹配合适的库;再仔细检查组选项里--fp-model的主开关、EABI兼容性设定,以及那些细分的优化子选项;如果结果仍然不正常,就要重点排查double有没有被压成float处理、软硬件浮点版本有没有混用、静态库是否已经用新配置重新构建。在修改完这些配置之后,要执行一次Clean Build,接着通过map文件确认链接的库是否准确,最后用一套边界测试数据把数值计算的正确性验证一遍,这样整个浮点环境的排查和确认工作才算基本完成。