FPGA中全加器实现计数器为何比'+‘推断加法时钟性能更优?
测试背景与两种实现方案
我在ICE40和Gatemate FPGA上测试了44位计数器的加法运算性能,采用两种不同实现方式:
1. NaturalCount(Chisel '+’ 运算符推断实现)
直接利用Chisel内置的+运算符实现计数器递增,综合后会调用FPGA的专用进位单元(如ICE40的SB_CARRY),资源占用更紧凑。
// Natural counter with classic addition class NaturalCount(val COUNT_WIDTH: Int = 32) extends Module { val io = IO(new Bundle { val count = Output(UInt(COUNT_WIDTH.W)) }) val MAXCOUNT = BigInt(1) << COUNT_WIDTH val counterSize = log2Ceil(MAXCOUNT) val counterValue = RegInit(0.U(counterSize.W)) counterValue := counterValue + 1.U io.count := counterValue }
2. FullAdderCount(链式全加器手动实例化)
通过手动实例化链式全加器构建加法逻辑,综合后不依赖专用进位单元。
/* FullAdder counter */ class FullAdderCount(val COUNT_WIDTH: Int = 32) extends Module { val io = IO(new Bundle { val count = Output(UInt(COUNT_WIDTH.W)) }) val counterValue = RegInit(0.U(COUNT_WIDTH.W)) val addition = Module(new FullAdderAddition(COUNT_WIDTH)) addition.io.a := counterValue addition.io.b := 1.U counterValue := addition.io.s io.count := counterValue }
实测性能结果
ICE40(icestorm工具链:yosys+nextpnr)
- NaturalCount:使用SB_CARRY专用进位单元,资源占用更少,但最大时钟频率仅117-121MHz;
- FullAdderCount:未使用SB_CARRY,时钟性能反而更优,最大频率可达380-436MHz。
Gatemate FPGA
两种实现的最大时钟频率完全一致,均为189.79MHz。
疑问解答:专用进位单元为何性能未达预期?
这是正常现象,核心原因在于FPGA架构特性与综合工具优化逻辑的共同作用:
ICE40 SB_CARRY的进位链延迟限制
ICE40的SB_CARRY单元是为通用加法场景设计的,但44位的长行波进位链会带来线性增长的传播延迟。而手动实现的链式全加器,综合工具可灵活重构逻辑(比如用LUT构建进位旁路或并行进位结构),绕过长进位链的延迟瓶颈。综合工具的映射策略差异
当使用Chisel的+运算符时,yosys会优先将加法逻辑映射到SB_CARRY组成的行波进位链——这种结构资源效率高但延迟大;而手动实例化全加器时,工具没有被限制到专用进位单元,能更自由地利用LUT的并行逻辑优化进位路径,从而获得更高时钟频率。Gatemate性能一致的原因
Gatemate的CC_ADDF专用进位单元本身设计了更高效的进位链结构,或者综合工具对两种实现采用了相同的优化策略(均映射到专用进位链),因此两者性能无差异。
总结
这种性能差异不是设计问题,而是FPGA硬件架构与综合工具优化逻辑共同作用的结果。针对ICE40这类长进位链延迟较大的FPGA,手动优化加法路径能获得更高性能;而在Gatemate这类进位链设计更优的平台上,两种实现的性能差距会消失。
内容的提问来源于stack exchange,提问作者FabienM

