4. 常用降噪滤波器

浮点仿真跑通了,滤波器效果听着也不错。但别高兴太早——DSP 芯片可不认浮点。它只认定点数,说白了就是整数。这一步要是没做好,前面所有工作全白费。

我个人习惯,在写一行 C 代码之前,先用 MATLAB 的 Fixed-Point Designer 把定点化仿真做透。为什么?因为定点化带来的误差是「结构性」的,一旦定下来,后面改起来成本极高。你想想看,要是芯片都打样回来了才发现精度不够,那乐子就大了。

4.1 为什么非做定点化不可?

浮点运算在 DSP 上不是不能做,但代价太大。我见过不少新手,觉得现在 DSP 性能强了,直接上浮点不就完了?嗯,这里有个误区。

看这张对比表,你就明白了:

对比项 浮点运算 定点运算
单次乘法周期 4~8 个时钟 1 个时钟
芯片面积 大(约 3x) 小
功耗 高(约 2.5x) 低
动态范围 极大(IEEE 754) 有限,需精心设计
开发难度 低 高(但值得)

说白了,定点化就是用「有限的精度」换「极致的效率」。对于降噪滤波器这种实时性要求极高的场景,定点是唯一的选择。

4.2 Fixed-Point Designer 到底能干什么?

这个工具箱不是简单地把浮点数转成定点数。它做三件核心的事:

  1. 自动定标——根据数据的动态范围,自动选择最合适的整数位宽和小数位宽
  2. 溢出检测——仿真过程中标记所有可能溢出的位置
  3. 误差分析——对比定点输出和浮点输出,量化精度损失

我记得第一次用这个工具箱时,发现它还能生成定点化的 MATLAB 代码。这个功能后来帮我省了不少事——直接拿生成的代码改改就能当 C 参考实现。

4.3 实操:把浮点滤波器转成定点

咱们拿第三章那个 16 阶 FIR 滤波器举例。浮点版本长这样:

% 浮点原型
fs = 16000;
fpass = 3400;
fstop = 4000;
b = fir1(16, fpass/(fs/2), 'low');

现在要做定点化。第一步,看看数据的动态范围:

% 用一段真实语音信号做激励
[x, fs] = audioread('speech_sample.wav');
y_float = filter(b, 1, x);

% 查看系数和输出的范围
fprintf('系数范围: [%f, %f]\n', min(b), max(b));
fprintf('输出范围: [%f, %f]\n', min(y_float), max(y_float));

跑完之后,我得到的结果是系数在 ±0.3 左右,输出在 ±1.2 左右。这意味着什么?

  • 系数用 Q1.15 格式就够了(1 位整数,15 位小数)
  • 输出需要 Q2.14 格式(2 位整数,14 位小数)

接下来用 Fixed-Point Designer 做正式转换:

% 创建定点化配置
F = fimath('OverflowMode', 'saturate', ...
           'RoundingMethod', 'floor', ...
           'ProductMode', 'FullPrecision', ...
           'SumMode', 'KeepLSB', ...
           'SumWordLength', 32);

% 把系数转成定点
b_fixed = fi(b, 1, 16, 15, 'fimath', F);

% 把输入信号也转成定点
x_fixed = fi(x, 1, 16, 15, 'fimath', F);
我的习惯:RoundingMethod 我一般先用 'floor',因为硬件实现最简单。如果精度不够,再换成 'round'。别一上来就用最复杂的舍入方式,那会增加硬件开销。

4.4 误差分析——别被「看起来差不多」骗了

定点化做完,不能只看波形「差不多」。我吃过这个亏——有一次定点仿真 SNR 有 60dB,觉得稳了,结果板子跑起来只有 40dB。后来发现是中间计算节点的位宽没给够。

正确的做法是逐点对比:

% 定点滤波
y_fixed = filter(b_fixed, 1, x_fixed);

% 转回浮点做对比
y_fixed_double = double(y_fixed);

% 计算误差
error = y_float - y_fixed_double;
snr = 20 * log10(norm(y_float) / norm(error));

fprintf('定点化 SNR: %.2f dB\n', snr);

% 看误差分布
figure;
histogram(error, 50);
title('量化误差分布');

一般来说,对于语音降噪应用,SNR 要大于 50dB 才算合格。如果低于这个值,就要检查:

  • 是不是某个中间变量溢出了?
  • 是不是系数精度不够?
  • 是不是乘法累加过程中截断太多?
我曾经踩过的坑:有一次 SNR 始终上不去,查了两天才发现是滤波器内部状态变量的位宽设成了 16 位,但实际动态范围需要 20 位。Fixed-Point Designer 的溢出检测其实已经标红了,但我没仔细看。从那以后,我每次都会把所有的 warning 过一遍,一个都不放过。

4.5 核心流程:一张图说清楚

下面这张图是我做定点化仿真的标准流程,每个项目都这么走:

定点化仿真与误差分析流程 浮点滤波器原型 动态范围分析(min/max) 定标决策(WL/IL/FL) 定点仿真(Fixed-Point Designer) 误差分析(SNR/直方图) SNR ≥ 50dB? 不满足,重新定标 关键参数说明 WL: 字长(总位宽) IL: 整数位宽 FL: 小数位宽 WL = IL + FL + 1(符号位)

4.6 几个实用技巧

做定点化这么多年,我总结了几条经验,分享给你:

  • 先粗后细——先用 16 位全局位宽跑一遍,看哪里溢出,再针对性地加宽。别一开始就追求完美,那会把自己累死。
  • 关注乘累加节点——滤波器的核心是乘累加,这里的位宽最容易不够。我一般会把乘积位宽设成 32 位,累加器设成 40 位。
  • 用真实信号做激励——正弦波测试通过不代表实际场景能过。一定要用真实的语音或噪声信号去测。
  • 保留裕量——仿真时 SNR 做到 55dB,实际硬件可能只有 50dB。留 5dB 的裕量比较稳妥。

核心原则:定点化不是「把浮点转成定点」这么简单。它是一个反复迭代的过程——定标、仿真、分析误差、再定标。别指望一次搞定。

好了,定点化仿真做到这一步,你已经有了一个可以在 DSP 上跑的「数字原型」。下一件事,就是把这个原型搬到真正的硬件上去。不过在那之前,我建议你把仿真报告保存好——后面联调出了问题,这些数据就是排查的依据。