从零开始:用Verilog实现半加器,迈入数字设计的第一步
你有没有想过,计算机是怎么做加法的?
别急着回答“当然是CPU算的”——那我再问一句:CPU里的加法器又是怎么工作的?
答案可能比你想象的更简单。一切,都始于一个只有两个逻辑门的小电路——半加器(Half Adder)。
这玩意儿看起来不起眼,但它却是构建整个数字世界算术能力的起点。今天我们就来手把手写一段 Verilog 代码,从真值表到仿真,完整实现一个半加器,并告诉你为什么每一个 FPGA 学习者都应该从它开始。
为什么是半加器?因为它够“小”,也够“深”
在所有组合逻辑电路中,半加器可能是最常被忽略、却又最重要的一环。
它只干一件事:把两个 1 比特的二进制数 A 和 B 相加,输出它们的“和”(Sum)与“进位”(Carry)。听起来很简单对吧?但正是这个简单的功能,构成了全加器、多位加法器、ALU,乃至现代处理器中复杂运算单元的基础。
更重要的是,对于初学者来说,半加器是一个完美的入门项目:
- 它没有时钟,不需要状态机;
- 它逻辑清晰,可以直接从真值表推导出表达式;
- 它代码短小,几分钟就能写完;
- 它可测试性强,仿真结果一目了然;
- 它还能作为模块被复用,体现“积木式设计”的精髓。
换句话说,你想学的 FPGA 开发流程——编码 → 综合 → 仿真 → 下载验证——都可以通过它走通一遍。
半加器是怎么工作的?
我们先来看一张最核心的表格:真值表。
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
看到没?当 A 和 B 都为 1 时,二进制加法产生进位,Sum 变成 0,Carry 输出 1。这就是1 + 1 = 10的本质。
再观察一下规律:
- Sum = A ⊕ B—— 异或运算,表示“不同则为1”
- Carry = A · B—— 与运算,表示“全1才进位”
所以硬件上只需要两个门:一个异或门 + 一个与门。
📌 小贴士:之所以叫“半”加器,是因为它不处理来自低位的进位输入。真正的加法需要考虑三个输入(A、B、Cin),那种电路叫做“全加器”。而一个全加器,通常可以用两个半加器拼出来。
用 Verilog 写一个半加器
接下来就是动手环节了。下面这段代码,就是我们今天的主角:
// 1-bit Half Adder - Behavioral Model module half_adder ( input wire A, input wire B, output wire Sum, output wire Carry ); assign Sum = A ^ B; assign Carry = A & B; endmodule就这么两行?没错!
关键点解析:
- 所有端口都是
wire类型,因为这是纯组合逻辑; - 使用
assign实现连续赋值,对应实际电路中的连线和门级结构; A ^ B自动综合成异或门,A & B综合成与门;- 接口命名清晰,方便后续例化。
这种写法属于数据流建模(Dataflow Modeling),是 Verilog 中最直观、也最常用的风格之一。比起画原理图,这种方式更接近“编程思维”,适合快速原型开发。
怎么验证它真的能工作?写个 Testbench!
写了模块还不算完,我们必须证明它是对的。这就需要一个测试平台(testbench)。
module tb_half_adder; reg A, B; wire Sum, Carry; // 实例化被测模块 half_adder uut ( .A(A), .B(B), .Sum(Sum), .Carry(Carry) ); initial begin $display("时间\tA\tB\tSum\tCarry"); $monitor("%0t\t%b\t%b\t%b\t%b", $time, A, B, Sum, Carry); #10 A = 0; B = 0; #10 A = 0; B = 1; #10 A = 1; B = 0; #10 A = 1; B = 1; #10 $finish; end endmodule这段 testbench 做了什么?
- 声明
reg类型信号作为输入激励(只能在过程块中赋值); - 实例化我们的
half_adder模块,使用命名端口连接,避免接错; $display打印表头,$monitor自动监听信号变化并输出;#10表示延迟 10 个时间单位,模拟输入切换;- 覆盖全部四种输入组合,确保功能完整。
运行后你会在控制台看到类似这样的输出:
时间 A B Sum Carry 0 x x x x 10 0 0 0 0 20 0 1 1 0 30 1 0 1 0 40 1 1 0 1或者用 ModelSim 看波形图,会更直观地看到每个信号的变化趋势。
✅ 提示:
#10在 testbench 中是可以综合的吗?不可以!但在仿真环境中完全合法,仅用于生成激励。
放进 FPGA 会发生什么?
如果你把这段代码放进 Vivado 或 Quartus,会经历这样一个流程:
- 语法检查→ 编译器确认你的 Verilog 没有拼写错误;
- 综合(Synthesis)→ 工具识别出你需要一个 XOR 门和一个 AND 门;
- 映射到 LUT→ 在 FPGA 上,这两个门会被打包进查找表(LUT)中;
- 布局布线→ 工具决定这些逻辑资源放在芯片哪个物理位置;
- 生成比特流→ 最终烧录到 FPGA 上运行。
虽然半加器太简单,几乎不会占用任何资源(可能只用了一个 CLB 的一部分),但它已经完整走完了工业级 FPGA 设计的所有关键步骤。
而且你会发现:你写的每一行代码,都在硬件中有对应的实体存在。这不是软件模拟,而是真正的“硬件即代码”。
它到底有什么用?不只是教学玩具
很多人觉得:“半加器这么简单,现实中谁还单独用它?”
其实不然。它的价值不在独立使用,而在构建更大系统的能力。
典型应用场景包括:
🔹 构建全加器
一个全加器可以这样搭建:
- 第一级半加器计算 A+B 得到 S1 和 C1;
- 第二级半加器将 S1 与 Cin 相加得到最终 Sum;
- 两个 Carry 输出通过或门合并得到最终 Carry_out。
┌────────┐ A ──┤ │ │ HA1 ├── S1 ───────┐ B ──┤ │ │ ┌────────┐ └────────┘ ├───┤ │ │ │ HA2 ├── Sum ┌──────────────────────┘ │ │ Cin ───────────────────────────┤ │ └────────┘ ↑ Carry_in 输入 Final Carry = C1 OR C2🔹 多位加法器的最低位
在一个 4 位加法器中,第 0 位是没有进位输入的,因此可以直接用半加器代替全加器,节省一个门电路。
🔹 ALU 中的基本加法路径
即使是高级处理器中的算术逻辑单元(ALU),其加法功能也是由一个个这样的基础单元堆叠而来。
🔹 教学与实训的核心案例
全国高校 EDA/FPGA 课程几乎都以半加器作为第一个实验项目。原因很简单:它能让学生第一次真正理解“代码变硬件”的全过程。
初学者常踩的坑 & 我的经验建议
别以为这么简单的电路就不会出错。我在带学生做实验时,见过太多“翻车现场”:
❌ 常见错误 1:用了非阻塞赋值<=
always @(*) begin Sum <= A ^ B; // 错!组合逻辑应该用阻塞 = Carry <= A & B; end→ 虽然仿真可能没问题,但容易引起综合工具误解,导致意外锁存器或时序问题。
✅ 正确做法:组合逻辑优先使用assign,或者用always @(*)配合阻塞赋值=。
❌ 常见错误 2:漏掉敏感列表或写错条件
always @(A) begin // 错!只监听 A,B 改变时不触发 Sum = A ^ B; end✅ 应该写成always @(*)或显式列出所有输入:@(A or B)。
✅ 最佳实践建议:
- 命名要有意义:比如
ha_inst_lsb表示“最低位半加器实例”; - 注释要到位:哪怕只有两行代码,也要说明每条语句的作用;
- 保持接口一致性:输入用
input wire,输出用output wire,避免混用 reg/wire; - testbench 要覆盖边界情况:虽然这里只有四种输入,但养成习惯很重要;
- 学会看综合报告:看看工具是否真的把你想要的门电路给综合出来了。
更进一步:你能怎么玩?
掌握了半加器之后,下一步你可以尝试:
- 把两个半加器拼成一个全加器;
- 用 4 个全加器搭一个 4 位行波进位加法器;
- 加入寄存器变成同步加法器;
- 用 Verilog 结构化建模方式重写半加器(用
xor和and原语); - 在 FPGA 开发板上接按键输入、LED 输出,实现真实硬件演示。
甚至有一天,你会意识到:你现在写的这个A ^ B,将来可能会出现在你自己设计的 CPU 核心里。
写在最后:伟大的系统,始于简单的逻辑
有人说,学 FPGA 最难的是入门。
因为你必须同时理解三件事:
- 数字逻辑(硬件行为)
- 编程语法(代码形式)
- 开发流程(工具链)
而半加器,恰好能把这三者完美串联起来。
它不复杂,但足够完整;它很小,但自成一体。当你第一次看到自己写的assign Sum = A ^ B;在 FPGA 上点亮 LED 显示结果时,那种“我真的让硬件动起来了”的感觉,是任何教科书都无法替代的。
每一次
A + B的背后,都有一个默默工作的半加器。
每一段 Verilog 代码的背后,也都藏着一位正在成长的硬件工程师。
所以,别小看这短短几行代码。
你迈出的这一小步,也许正是通往数字系统设计大门的第一步。
如果你正在学习 FPGA,不妨现在就打开编辑器,敲下那句:
assign Sum = A ^ B;然后告诉自己:我,开始懂硬件了。