第4章:定点数运算与量化:浮点 vs 定点

说实话,做通信算法硬件加速这么多年,我踩过最大的坑就是「精度」问题。

记得刚入行那会儿,我天真地把MATLAB里的浮点模型直接往FPGA上搬。结果呢?资源爆了,时序也跑不过。后来才明白——定点化不是可选项,而是必选项

这一章,咱们就聊聊浮点和定点的那些事儿。我会把我在项目中积累的量化经验、避坑指南,一股脑儿倒出来。

4.1 浮点 vs 定点:到底差在哪?

先问个问题:为什么通信算法里非要用定点?

说白了,硬件不喜欢浮点。浮点运算器面积大、功耗高、延迟长。你想想看,一个浮点乘法器比定点的大4-5倍,这在芯片上就是真金白银的成本。

对比项 浮点(float32) 定点(Q16)
动态范围 大(~10^38) 小(需设计者控制)
硬件面积 大(~5000 LUT) 小(~500 LUT)
功耗
设计复杂度 低(直接写) 高(需手动管理)

我个人的习惯是:算法验证用浮点,硬件实现用定点。这个原则帮我省了不少返工的麻烦。

4.2 量化误差分析:别让精度吃掉你的性能

量化误差,说白了就是「舍入」带来的损失。

举个例子:你有一个浮点数 0.123456,用8位定点表示,只能保留到 0.123。那 0.000456 就丢了。一次两次无所谓,但经过几百次迭代,误差会累积到不可接受。

关键公式:量化误差 = 量化值 - 真实值

对于均匀量化,误差范围在 [-LSB/2, +LSB/2] 之间。

我在做5G NR信道估计时遇到过一个问题:LMMSE算法里矩阵求逆的精度不够,导致解调性能掉了2dB。查了两天才发现——中间变量的位宽不够

我的经验法则:

  • 输入数据:16位定点(Q1.15)
  • 中间累加:32位定点(Q9.23)
  • 系数/权重:12位定点(Q1.11)
  • 输出数据:16位定点(Q1.15)

这个配置在大多数通信算法里够用,又不浪费资源。

4.3 硬件友好的定点实现策略

嗯,这里要注意:定点实现不是简单地把浮点代码翻译成整数运算。你得从硬件角度重新思考。

4.3.1 定点数格式选择

常用的定点格式有两种:

  • Qm.n格式:m位整数,n位小数,1位符号
  • 整数+缩放因子:用整数表示,再乘以2^(-n)

我个人偏爱第二种。为什么?因为调试方便。你可以在仿真时直接看整数值,心里有数。

4.3.2 乘法器的定点优化

乘法是通信算法里的高频操作。两个Q1.15的数相乘,结果是Q2.30。但你不能直接保留30位小数——太浪费了。

// 浮点版本
float a = 0.5f, b = 0.3f;
float c = a * b;  // 0.15

// 定点版本(Q1.15)
int16_t a_q = 16384;  // 0.5 * 2^15
int16_t b_q = 9830;   // 0.3 * 2^15
int32_t c_q = (int32_t)a_q * b_q;  // 32位中间结果
int16_t c_out = (c_q + (1 << 14)) >> 15;  // 舍入+截断到Q1.15

我曾经踩过的坑:

做OFDM频偏估计时,我忘了做舍入处理,直接右移截断。结果导致频偏估计值总是偏小,锁相环锁不住。后来加上「加0.5再截断」的舍入操作,问题就解决了。

记住:截断是向下取整,舍入是四舍五入。两者差半个LSB,在反馈环路里会被放大。

4.3.3 加法器的溢出处理

加法比乘法简单,但溢出问题更隐蔽。

你想想看,两个16位定点数相加,结果可能是17位。如果不处理,高位就丢了。

我的做法是:

  • 饱和截断:超出范围就钳位到最大值/最小值
  • 扩展位宽:加法器输出多保留1-2位
  • 缩放对齐:加法前先对齐小数点位置

4.4 量化误差的仿真验证

光说不练假把式。我每次做定点化,都会跑一个「浮点 vs 定点」的对比仿真。

具体步骤:

  1. 用浮点模型跑一遍,记录每个节点的值
  2. 用定点模型跑一遍,同样记录
  3. 计算误差的均方根(RMS)和峰值
  4. 如果误差超过设计指标,调整位宽或格式

一个实用的指标:

对于通信接收机,定点化后的EVM(误差矢量幅度)通常要求 < 1%。

如果EVM超过2%,基本可以断定量化方案有问题。

4.5 知识体系总览

下面这张图是我自己整理的定点化设计流程,你可以照着这个思路走:

定点化设计流程 浮点算法模型 动态范围分析(找最大值/最小值) 位宽选择(整数位+小数位) 定点RTL实现 浮点vs定点误差验证 不满足精度要求,调整位宽

这张图里最关键的是那个反馈回路。我见过太多人做完定点化就直接流片,结果回来发现性能不达标,只能改版——那代价可就大了。

我的一个小技巧:

在做定点化之前,先用MATLAB的fi()函数快速验证一下位宽选择是否合理。这个函数可以模拟定点运算的舍入和溢出行为,比直接写RTL快得多。

比如:a_fi = fi(0.5, 1, 16, 15); 就定义了一个Q1.15格式的定点数。

4.6 总结

定点化这件事,说难不难,说简单也不简单。核心就三点:

  • 动态范围要摸清——不知道信号范围,就别谈定点化
  • 中间位宽要留够——宁可多花点资源,也别丢精度
  • 误差验证要做全——浮点vs定点对比,跑通所有测试用例

做到这三点,你的定点化方案基本就稳了。


公众号:蓝海资料掘金营,微信deep3321