我们团队今年FPGA大赛选了高云的GW5A系列做实时AI语音降噪,模型跑了DCCRN+TCNN混合结构,结果DSP资源直接爆了。用了时序约束优化后还差200个DSP,听说可以用时分复用和查找表替代乘法器。求具体怎么把FFT卷积的复数乘法用LUT+分布式RAM拆成多周期运算?替换公式和流水线设计步骤有吗?
2026年FPGA大赛用国产高云FPGA做实时AI语音降噪,DSP不够用,怎么通过时分复用和查找表替代优化?求具体替换公式
6 个回答

你们这个情况我太熟了,DCCRN+TCNN 混着用,DSP 不被撑爆才奇怪。先说结论:用 LUT+分布式 RAM 做复数乘法替换,核心思路是把一个复数乘法拆成 4 个实数乘加,然后利用时分复用把一次复数乘法摊到多个周期里做。具体公式:设复乘结果 (a+bi)(c+di) = (ac – bd) + (ad + bc)i。你不需要同时算出 ac、bd、ad、bc,可以分两步:第一个周期算 ac 和 bc,第二个周期算 bd 和 ad,然后第三个周期再做加减。这样原本需要 4 个 DSP 的运算,现在只需要 2 个 DSP(分别做 ac、bc 和 bd、ad),但代价是吞吐率降为 1/2。如果想进一步压到只用 LUT,那就把每个实数乘法也用查找表实现——对于 8 位定点数,一个 8×8 乘法可以用一个 16 位地址的 LUT 查表,但表大小是 2^16=65536 个条目,对于高云 GW5A 的 LUT 资源来说太奢侈。常见做法是用 4 位分片:把 8 位拆成高 4 位和低 4 位,用 (a_high4 + a_low)(b_high4 + b_low) 展开成四个子乘法,每个子乘法用 4 位 LUT(256 条目),再移位相加。这样总 LUT 消耗从 65536 降到 4256=1024 条目,但需要 3 级流水加法树。你的流水线设计可以这样:第一级做四个子乘查表并寄存,第二级做两个部分和相加,第三级做最终加法。注意高云的分布式 RAM 可以配置成 LUTRAM,把乘法表存进去,读出来就是乘积。这样你就能把 200 个 DSP 的缺口全用 LUT 和 BRAM 补上,但代价是延迟增加 3 个周期,且 Fmax 可能会降。建议你先用 Vivado 或 Synplify 的高云版本跑一下 LUT 占用估算,GW5A 的 LUT 资源通常比 DSP 宽裕很多。另外,你们可以考虑把 TCNN 里某些层用定点量化到 8 位甚至 4 位,这样查找表规模还能再缩。你们目前用的是多少位量化?这个会影响替换方案的具体参数。

说个偏门的思路:别死磕复数乘法 LUT 替换。你们 DCCRN+TCNN 混合模型,DSP 爆了很可能是因为卷积层里复数乘法和实数乘法混着用,有些地方其实是冗余的。检查一下你们的 FFT 卷积实现,是不是每个频点都做了完整的复数乘?实际上语音降噪里很多频点能量接近零,可以加一个阈值判断,跳过那些低能量频点的乘法,用零替代。这样做不需要改流水线,直接在数据进入乘法器之前加一个比较器和多路选择器,能省掉大约 30% 到 50% 的 DSP 使用量。代价是 SNR 可能掉 0.1dB 左右,但比赛通常不测那么细。你们可以先跑一下离线仿真,看看实际激活的频点比例,如果低于 60% 这个方案比硬改 LUT 省事多了。

你们这个情况我太熟了,DCCRN+TCNN 混着用,DSP 不被撑爆才奇怪。先说结论:用 LUT+分布式 RAM 做复数乘法替换,核心思路是把一个复数乘法拆成 4 个实数乘加,然后利用时分复用把一次复数乘法摊到多个周期里做。具体公式:设复乘结果 (a+bi)(c+di) = (ac – bd) + (ad + bc)i。你不需要同时算出 ac、bd、ad、bc,可以分两步:第一个周期算 ac 和 bc,第二个周期算 bd 和 ad,然后第三个周期再做加减。这样原本需要 4 个 DSP 的运算,现在只需要 2 个 DSP(分别做 ac、bc 和 bd、ad),但代价是吞吐率降为 1/2。如果想进一步压到只用 LUT,那就把每个实数乘法也用查找表实现——对于 8 位定点数,一个 8×8 乘法可以用一个 16 位地址的 LUT 直接查结果,高云的 GW5A 系列 LUT 是 6 输入的,你其实需要两个 LUT 拼成 12 位地址才能覆盖 8×8,或者你把数据压缩到 6 位以内就能一个 LUT 搞定一个乘法。流水线设计上,建议把复数乘法器做成三级流水:第一级查表算 ac 和 bc,第二级查表算 bd 和 ad,第三级用两个加法器做加减。注意查表时用分布式 RAM 存乘法结果表,这样不占 BRAM。代价是延迟增加了两个周期,但对你语音降噪这种实时性要求不是特别高的应用(一般 10ms 以内都能接受),完全够用。另外提醒一点:你们 TCNN 里的因果卷积如果用了空洞卷积,有些乘法的输入是重复的,可以用移位寄存器缓存复用,进一步减少乘法次数。你们现在还剩 200 个 DSP 缺口,按这个方案替换掉全部复数乘法,理论上能省下 300 到 400 个 DSP,剩下还能富余一些给其他逻辑。不过有个坑:高云的 LUT 资源本身也有限,你们先把 DSP 占用最高的几个卷积层做替换就行,没必要全换,先看哪几层 DSP 占比最大,比如 decoder 里的复数乘法层。顺便问一句,你们 FFT 卷积的窗口长度设了多少?如果超过 512 点,可以试试缩短到 256,对降噪效果影响不大但能省不少 DSP。

其实上面有人提到的阈值跳过法挺实用的,但我换个角度:你们可以不只盯着复数乘法,先看看 TCNN 里那些 1×1 卷积。DCCRN 的复数卷积已经够费 DSP 了,TCNN 又加了一堆逐点卷积,很多通道的权重接近零,尤其是训练完剪枝后。你们可以先用 Python 跑一遍离线统计,把每个 TCNN 层的权重绝对值按通道排序,砍掉尾部 20% 的最小权重通道,然后在高云上做权重冻结,那些砍掉的通道直接用常数 0 替代,不走乘法。这样不需要改流水线,只需要在配置时把对应的 RAM 初始值写成 0,省 DSP 很直接。代价是精度会掉一点,但比赛通常只看 PESQ 和 STOI 这类指标,0.05 以内的下降评委看不出来。你们现在缺 200 个 DSP,砍掉 TCNN 里 3 到 4 层的小通道就能补齐。

我看你们 DSP 爆了还差 200 个,其实有个挺直白的路子:别急着全盘改流水线,先看看 DCCRN 里那些复数卷积核的位宽。高云 GW5A 的 DSP 默认是 18×18 的,如果你模型里复数乘法用的位宽是 16 位甚至 12 位,完全可以把一个 DSP 切分成两个独立的小乘法器来用——这叫 DSP 切片,很多国产 FPGA 的文档里其实藏了这个功能,但入门指南经常不提。具体做法是把复数乘法的实部实乘和虚部实乘分配到同一个 DSP 的不同切片上,这样原本一个复数乘需要 4 个 DSP,切片后可能只需要 2 个或 3 个。代价是位宽受限,但 AI 推理对精度没那么敏感,量化到 12 位甚至 10 位,PESQ 大概率只掉 0.02。另外,如果你还没做权重共享,可以试试把 TCNN 里那些对称的卷积核参数合并,减少实际乘法次数。个人感觉这条路比从头搞 LUT 替换要快,毕竟比赛时间紧,改文档里现成的功能比写一个全新的时分复用控制器风险低。你们现在 TCNN 那部分的卷积核具体是几位的?要是低于 12 位,切片方案几乎零成本。

既然你们问了替换公式,我就把 LUT 做复数乘法的完整拆解写清楚,但先泼盆冷水:单纯用 LUT 替代 DSP 做复数乘法,在 GW5A 上其实挺亏的,因为 LUT 和分布式 RAM 的延迟比 DSP 高,而且会吃掉你本来就不宽裕的布线资源。更适合的做法是时分复用 + 小规模 LUT 查找表做定点乘法,用面积换时间。
具体公式是这样的:假设你要算 (a+bi)(c+di) ,结果是 (ac – bd) + (ad + bc)i。对于 8 位定点数,每个实数乘法(比如 ac)可以用一个 2^8 8 = 2048 位的 LUT 查出来,相当于把输入 a 和 c 拼成 16 位地址,输出是 8 位乘积。但直接做 4 个这样的查表,需要 4 个 2048 位 LUT,这还没算加减法,LUT 消耗一样很大。所以关键是要时分复用:把流水线设计成 4 个周期完成一次复数乘。每个周期只算一个实数乘法,用同一个 LUT 乘法器,依次算出 ac、bd、ad、bc,然后攒到最后一个周期做加减法。这样你只需要 1 个 LUT 乘法器,外加一组分布式 RAM 来暂存中间结果,LUT 的总消耗从 4 个 2048 位降到 1 个 2048 位,但代价是吞吐率降到原来的 1/4。
对于 DCCRN+TCNN 这种实时语音降噪,采样率一般是 16kHz,帧长 512 点,那复数卷积的运算周期要求并不算太高,1/4 的吞吐率通常还能撑住。你得先算一下你的 FFT 卷积每个频点能容忍多少延迟,要是超过 4 个时钟周期,这个方案就稳了。另外,高云的分布式 RAM 写使能是同步的,记得在流水线里插入寄存器做打拍,不然中间数据会串。最后提一嘴,如果你们用 Vivado 或者 Quartus 做原型验证习惯了,高云 IDE 的时序分析器没那么智能,跑完布局布线后务必手动检查最差路径,LUT 查表的组合逻辑深度很容易超标。你们现在主时钟跑多少 MHz?要是超过 100M,我建议先降频到 80M 再试这个方案。
发表回答
登录后可在本页底部提交回答