从半加器开始:用Verilog点亮你的第一个FPGA逻辑
你有没有想过,计算机是怎么做加法的?
不是打开计算器点两下那种“加法”,而是真正意义上——硬件层面的二进制相加。
答案就藏在一个看似简单的电路里:半加器(Half Adder)。
在 FPGA 设计的世界中,我们不写“程序”来运行任务,而是用代码去“搭建电路”。而Verilog HDL就是这门“造芯语言”中最常用的一种。当你写下一行assign Sum = A ^ B;,其实是在告诉综合工具:“请给我造一个异或门。”
今天,我们就从最基础、也最重要的起点出发——用 Verilog 实现一个半加器,带你走完从逻辑理解到仿真验证的完整设计流程。别小看它,所有复杂的 CPU 加法器,都是由这样一个个小小的“1+1=?”单元搭起来的。
半加器是什么?为什么它是数字系统的“第一块积木”?
想象你在纸上做二进制加法:
1 + 1 ---- 10结果是10,也就是十进制的 2。这里有两个输出:
-和(Sum)是低位的结果 →0
-进位(Carry)是高位的溢出 →1
这个过程就是半加器要完成的任务:对两个 1 位二进制数进行相加,输出它们的和与进位。
它的输入只有两个:A和B;输出也只有两个:Sum和Carry。
但它没有考虑来自更低一位的进位输入(Cin),所以叫“半”加器。
全加器才会处理 Cin,但那个可以由两个半加器拼出来。
它的真值表长这样:
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
一眼就能看出规律:
-Sum = A ⊕ B(异或)
-Carry = A & B(与)
就这么简单?没错!可正是这种简洁性,让它成为学习 FPGA 设计的最佳入口。
三种写法,三种思维:如何用 Verilog 描述同一个电路?
在 Verilog 中,描述同一个功能可以有不同的“风格”。就像写作有散文、诗歌、说明文之分,HDL 也有不同建模方式。掌握它们,等于掌握了看待硬件的不同视角。
方法一:数据流建模 —— “我想表达的是关系”
这是最直观、最常用的写法。你不再关心用了什么门,只关心信号之间怎么算。
module half_adder ( input wire A, input wire B, output wire Sum, output wire Carry ); assign Sum = A ^ B; assign Carry = A & B; endmoduleassign是连续赋值语句,适用于组合逻辑。- 所有
wire类型,因为它是“连线”,不是存储。 - 综合后直接映射为一个 XOR 门和一个 AND 门。
✅优点:简洁清晰,易读易维护,适合大多数场景。
🧠思维方式:我关注的是“数据如何流动”。
方法二:结构化建模 —— “我要亲手搭出每根线”
如果你想看得更“底层”一点,可以直接调用 FPGA 内部的原始门级元件(primitives)。这种方式像是在画电路图。
module half_adder_structural ( input wire A, input wire B, output wire Sum, output wire Carry ); xor (Sum, A, B); // xor(out, in1, in2) and (Carry, A, B); endmodule你看,连模块都没有名字,这就是原语实例化语法。Xilinx 和 Intel 的器件库都支持这些基本门。
✅优势:完全掌控硬件结构,教学时特别有用。
🔍应用场景:当你想分析最终网表结构,或者做低功耗优化时,这种写法能让你看到“真实世界”的门。
不过实际项目中很少这么写,毕竟综合工具自己就能生成最优门级结构。但知道它存在,很重要。
方法三:行为级建模 —— “当条件满足时,该做什么?”
虽然半加器是纯组合逻辑,但我们也可以用时序块always @(*)来描述它。这是构建复杂数字系统的核心范式。
module half_adder_behavioral ( input wire A, input wire B, output reg Sum, output reg Carry ); always @(*) begin Sum = A ^ B; Carry = A & B; end endmodule注意变化:
- 输出变成了reg类型。因为在always块里赋值的信号必须声明为reg。
- 敏感列表用了@(*),表示自动包含所有输入,避免遗漏导致锁存器误生成。
⚠️ 虽然功能一样,但初学者常在这里犯错:
- 忘记写敏感列表 → 综合出错误逻辑
- 在组合逻辑中使用非阻塞赋值<=→ 可能引入意外时序
- 不完整分支导致锁存器插入
所以记住一句口诀:组合逻辑用 always @(*),阻塞赋值 =,变量声明为 reg。
🧠 这种写法的价值在于——它是通往状态机、ALU、CPU 等复杂模块的大门。
综合之后发生了什么?看看 FPGA 到底“造”了啥
你以为写的只是代码?其实在综合阶段,EDA 工具已经把它变成了真正的硬件结构。
以 Xilinx Artix-7 为例:
- 每个 LUT6(查找表)可以实现任意 6 输入以下的组合函数。
- 半加器只需要两个 2 输入函数:
- XOR2 → 占用 1 个 LUT
- AND2 → 占用 1 个 LUT
- 总共消耗2 个 LUT,零寄存器,零布线资源(理想情况下)
而且现代综合器非常聪明。如果你写了三个半加器,它可能会把重复逻辑合并,甚至将部分逻辑折叠进同一个 LUT。
💡 实际项目中,像这样的小模块往往不会单独保留,而是被优化掉或内联到上级模块中。这也是为什么我们强调:不要为了省资源刻意拆分小模块,让工具去做优化。
别急着烧板子!先仿真:你的第一份 Testbench
在 FPGA 开发中,有一条铁律:先仿真,再下载。否则你可能花半小时等布局布线,结果发现逻辑错了。
下面是你需要掌握的第一个测试平台(Testbench):
module tb_half_adder; reg A, B; wire Sum, Carry; // 实例化被测模块 half_adder uut (.A(A), .B(B), .Sum(Sum), .Carry(Carry)); initial begin $monitor("Time=%0t | A=%b B=%b | Sum=%b Carry=%b", $time, A, B, Sum, Carry); // 测试全部输入组合 A = 0; B = 0; #10; A = 0; B = 1; #10; A = 1; B = 0; #10; A = 1; B = 1; #10; $finish; end endmodule关键点解析:
-$monitor:实时打印信号值,调试神器。
-#10:延迟 10 个时间单位(比如 ns),用于推进仿真时间。
-$finish:结束仿真。
运行结果应与真值表一致:
Time= 0 | A=0 B=0 | Sum=0 Carry=0 Time=10 | A=0 B=1 | Sum=1 Carry=0 Time=20 | A=1 B=0 | Sum=1 Carry=0 Time=30 | A=1 B=1 | Sum=0 Carry=1✅ 全部匹配 → 设计正确!
这时候你才可以放心进入下一步:综合、实现、生成比特流.bit文件,最后下载到 FPGA 开发板上,接 LED 观察输出。
那么,半加器真的只能用来教学吗?
当然不是。
尽管单个半加器无法独立构成多位加法器,但它是构建更复杂算术单元的基石。例如:
如何用半加器组成全加器?
全加器需要处理三个输入:A、B、Cin。我们可以这样构造:
First HA: Second HA: ┌──────┐ ┌──────┐ A ─────┤ XOR ├─── S1 ─────┤ XOR ├──→ Sum └──────┘ └──────┘ │ ↑ B ─────────┘ ┌───┴───┐ │ AND │ └───┬───┘ ┌─────▼─────┐ Cin ────────────────────┤ OR ├──→ Carry Out └───────────┘- 第一个半加器计算 A+B,得到 S1 和 C1
- 第二个半加器将 S1 与 Cin 相加,得到最终 Sum
- 两个进位(C1 和中间与门输出)通过或门合并为最终 Carry
虽然这不是最优结构(现代 FPGA 通常用进位链 carry-chain 实现高速加法),但这个思路展示了模块化设计的力量:大功能 = 小模块 + 连接逻辑。
实战建议:写好每一个“Hello World”级别的模块
哪怕是最简单的半加器,也有值得遵循的最佳实践:
| 建议 | 说明 |
|---|---|
| 命名规范 | 使用sum_out,carry_out比s,c更清晰 |
| 添加注释 | 模块顶部写明功能、作者、日期,方便团队协作 |
| 保持可综合性 | 避免在always块中使用#5这类延迟(仅仿真可用) |
| 优先使用标准语法 | 减少对特定厂商原语的依赖,提升跨平台兼容性 |
| 参数化预留扩展性 | 即使当前是 1bit,也可尝试定义parameter WIDTH=1 |
更重要的是:养成“先仿真”的习惯。哪怕你觉得逻辑很简单,也可能因为笔误导致功能异常。仿真是最快、最安全的验证手段。
结尾:从“1+1”走向“亿级门电路”
你可能觉得,一个只能算两位加法的电路,有什么意义?
但请记住:
- 每一台电脑的 ALU,都始于类似的加法单元;
- 每一块 GPU 的矩阵运算核心,背后是成千上万个并行加法器;
- 每一次神经网络推理,都在反复执行乘累加(MAC),而乘法本质也是加法的迭代。
半加器,是你踏入数字世界的第一步。
Verilog,是你与硬件对话的语言。
FPGA,是你亲手构建系统的沙盘。
当你第一次看到 LED 按照你的逻辑点亮时,你会明白:那不只是光,那是你设计的电路在呼吸。
而现在,你已经准备好迈出下一步了——试试用四个半加器做一个 4 位串行加法器?或者挑战一下超前进位加法器(CLA)?
路很长,但从这一刻起,你已经在路上了。
如果你在实现过程中遇到了问题,欢迎留言讨论。我们一起 debug,一起成长。