全加器不是练习题,是 FPGA 开发的第一道'压力测试'
写完一个 32 位加法器,仿真全绿,综合报告看着也漂亮,一上板却发现数据错得离谱?或者时序报告里赫然标红:'Cin → Cout 路径 Slack = -1.8ns',而你翻遍代码,连个 always_ff 都没用,纯组合逻辑,怎么就违例了?
这不是玄学。这是你在和 FPGA 的物理世界第一次正面交锋。而全加器,就是这场交锋最公平、最透明、也最不容糊弄的试金石。
它只有 3 个输入、2 个输出;没有状态、不涉时序;但它的每一纳秒延时、每一个 LUT 占用、每一次 Carry Chain 是否被启用,都在悄悄告诉你:你的设计思维,到底离真正的 FPGA 工程还有多远。
下面,带你重走一遍这条路——不讲定义,不列公式,只说在 Xilinx Artix-7 上踩过的坑、改过的约束、看懂的布局视图,和最终让 ILA 波形稳如磐石的那一行 assign result = a + b + cin;。
真值表不是起点,是校验终点
很多教程从真值表开始推导 Sum 和 Cout 表达式,这没错,但容易让人误以为'写出正确布尔式=完成任务'。可现实是:Verilog 里写对了,不代表 FPGA 里跑对了。
举个例子:
// 表面上完全等价的两种写法
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
和
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (cin & (a ^ b)); // 等效变形,更贴近 CLA 思想
它们在功能仿真中结果一致,但在综合阶段——前者大概率触发 Carry Chain,后者极可能被工具当成普通 LUT 逻辑拆解。为什么?因为综合器的模式识别引擎,对 + 和特定 & | ^ 组合有预设匹配规则,而不是靠代数等价性做决策。
所以建议是: ✅ 真值表只用于 Testbench 验证 ——写 8 个 case,确保波形 100% 对得上; ❌ 别把它当建模依据 ——FPGA 不吃'数学简洁',吃的是'工具友好'。
Verilog 三种写法,背后是三种工程角色
你在写全加器时,其实在无意识地扮演三种角色。选哪种,取决于你此刻在项目中的位置:
1. 当你是'算法验证者':用行为级(always_comb)
always_comb begin
sum = a ^ b ^ cin;
cout = (a & b) | (b & cin) | (a & cin);
end
✔️ 优点:改一行就能试新逻辑,适合和算法同事对齐;
⚠️ 风险:一旦漏写某个分支(比如忘了处理 default:),综合器会悄悄给你加锁存器——而锁存器在 FPGA 里是时序黑洞,STA 根本不会报,但上板必出毛刺。
💡 我的硬规矩:只要用
always_comb,就必须打开 Vivado 的'Latch Detection'警告,并把-warn_as_error加进综合选项。
2. 当你是'模块交付者':用数据流(assign)
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
✔️ 优点:零歧义、零锁存器风险、资源占用可预测; ✔️ 更关键的是:它天然支持逻辑复制(Logic Replication) 。当你把这个 FA 例化进一个 32-bit 加法器,且 Cin 来自高扇出寄存器时,工具会自动复制 cout 逻辑到多个 Slice——这是你手动优化都难做到的布线优化。
📌 实测对比(Artix-7 XC7A35T):
assign风格:3 个 LUT + 0 个 FF,关键路径 0.42nsalways_comb未加unique case:4 个 LUT + 1 个隐式锁存器,关键路径跳到 0.91ns

