从零搭建全加器:用与或非门实现二进制加法的底层逻辑
你有没有想过,计算机是如何做加法的?
我们每天都在敲代码、调算法,但很少有人真正关心“1+1=2”在硬件层面是怎么完成的。其实,这一切的背后,都始于一个看似简单却极为关键的电路——全加器。
而今天,我们要做的,不是调用现成的IP核,也不是使用FPGA内置的加法器模块,而是从最基础的与门、或门、非门出发,亲手“造出”一个能工作的全加器。这不仅是一次数字电路的实践之旅,更是一场对计算本质的深度探索。
全加器是什么?为什么它如此重要?
想象你在设计一块CPU,或者调试一个嵌入式系统的算术单元。无论多复杂的运算,最终都会被拆解为最基本的二进制加法。而支撑这些加法操作的核心构件,就是全加器(Full Adder, FA)。
它和我们熟悉的半加器不同,不仅能处理两个输入位 A 和 B 的相加,还能接收来自低位的进位 Cin,并输出本位的和 S 以及向高位的进位 Cout。正是这种“带进位”的能力,使得多个全加器可以级联起来,构成任意位宽的加法器——比如4位、8位甚至64位加法器。
它的三个输入:
-A:被加数
-B:加数
-Cin:低位进位
两个输出:
-S:本位和
-Cout:进位输出
虽然现代芯片中的加法器早已采用超前进位、传输门等高级结构来优化速度,但理解如何仅用与、或、非三种基本门来构建全加器,依然是学习数字逻辑设计不可绕过的一课。
真值表驱动设计:从行为到逻辑
一切设计,始于真值表。
我们枚举所有可能的输入组合(共 $2^3 = 8$ 种),观察输出规律:
| A | B | Cin | S | Cout |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
通过分析你会发现:
- 当三个输入中有奇数个1时,S = 1 → 这正是异或的特性!
- 只要任意两位同时为1,就会产生进位 → 即 AB + ACin + BCin
于是我们可以写出布尔表达式:
$$
S = A \oplus B \oplus C_{in}
$$
$$
C_{out} = AB + AC_{in} + BC_{in}
$$
第二个公式还可以改写为:
$$
C_{out} = AB + (A \oplus B) \cdot C_{in}
$$
这个形式更有工程意义:先计算是否“天生进位”(AB),再看是否有“传递进位”(A⊕B 且 Cin 存在)。
但问题来了:题目要求只能使用与、或、非门,而这里出现了异或操作。怎么办?
答案是:把异或门“拆开”,用与或非重新实现。
异或门的本质:如何只用与或非构造 XOR?
异或门的功能很简单:两输入不同时输出1,相同时输出0。
其布尔表达式为:
$$
A \oplus B = \overline{A}B + A\overline{B}
$$
这句话翻译成电路语言就是:
- 如果 A 是 0 且 B 是 1 → 输出 1
- 或者 A 是 1 且 B 是 0 → 输出 1
这两个条件用“或”连接即可。
所以,我们需要:
- 两个非门:生成 $\overline{A}$ 和 $\overline{B}$
- 两个与门:分别实现 $\overline{A}B$ 和 $A\overline{B}$
- 一个或门:合并结果
总共5个基本门(2非 + 2与 + 1或)就能等效替代一个异或门。
别小看这个替换——它是将抽象逻辑落地为物理电路的关键一步。
构建完整全加器:一步步连接你的逻辑块
现在我们把整个过程串起来。
第一步:分解核心信号
定义中间变量:
- $ P = A \oplus B $:表示 A 和 B 是否不同
- $ G = A \cdot B $:表示是否“直接生成进位”
这两步都可以仅用与或非实现。
第二步:求最终输出
和输出:
$$
S = P \oplus C_{in} = (A \oplus B) \oplus C_{in}
$$
再次使用上面的异或结构,只不过这次输入是 $P$ 和 $C_{in}$进位输出:
$$
C_{out} = G + (P \cdot C_{in})
$$
即:“固有进位” 或 “由前级传播来的进位”
注意:这里的 $P \cdot C_{in}$ 是一个与门,不需要异或!
所需元件清单(单个全加器)
| 类型 | 数量 | 用途说明 |
|---|---|---|
| 与门 | 5 | AB、P·Cin、两个异或中的与项(各两个) |
| 或门 | 2 | 合并两个异或项、合并进位项 |
| 非门 | 4 | ~A、~B、~P、~Cin(每个异或需要两次取反) |
实际中可通过共享反相信号优化资源。例如 ~A 可供多个模块复用,减少重复非门。
Verilog 实现:让门级逻辑“活”起来
下面这段代码完全遵循“仅使用与、或、非”的约束,没有任何xor关键字,适合用于教学演示或ASIC手动布局验证。
module full_adder_using_and_or_not ( input A, input B, input Cin, output S, output Cout ); // 中间信号声明 wire not_A, not_B, not_Cin; wire ab_xor_term1, ab_xor_term2; wire ab_xor; // A ⊕ B wire ab_and; // A · B wire p_and_cin; // (A⊕B) · Cin wire cout_term1, cout_term2; // === 步骤1:实现 A ⊕ B === assign not_A = ~A; assign not_B = ~B; assign ab_xor_term1 = not_A & B; // ~A * B assign ab_xor_term2 = A & not_B; // A * ~B assign ab_xor = ab_xor_term1 | ab_xor_term2; // === 步骤2:实现 A · B === assign ab_and = A & B; // === 步骤3:实现 (A⊕B) ⊕ Cin === wire not_ab_xor, not_Cin; wire xor2_term1, xor2_term2; assign not_ab_xor = ~ab_xor; assign not_Cin = ~Cin; assign xor2_term1 = not_ab_xor & Cin; assign xor2_term2 = ab_xor & not_Cin; assign S = xor2_term1 | xor2_term2; // === 步骤4:实现 Cout = AB + (A⊕B)·Cin === assign p_and_cin = ab_xor & Cin; assign Cout = ab_and | p_and_cin; endmodule代码亮点:
- 完全避免xor操作符,体现底层实现细节
- 信号命名清晰,便于跟踪波形调试
- 模块结构分明,适合扩展为参数化多位加法器
如何测试?别忘了写 Testbench!
再好的设计也需要验证。下面是一个简洁有效的测试平台,覆盖全部8种输入组合:
module tb_full_adder; reg A, B, Cin; wire S, Cout; // 实例化被测模块 full_adder_using_and_or_not uut ( .A(A), .B(B), .Cin(Cin), .S(S), .Cout(Cout) ); initial begin $display("A B Cin | S Cout"); $display("---------------"); for (int i = 0; i < 8; i++) begin {A, B, Cin} = i; #10; $display("%b %b %b | %b %b", A, B, Cin, S, Cout); end $finish; end endmodule运行后你会看到类似输出:
A B Cin | S Cout --------------- 0 0 0 | 0 0 0 0 1 | 1 0 0 1 0 | 1 0 ... 1 1 1 | 1 1每一行都应与真值表一致,否则说明某处逻辑出错。
实际应用:不只是纸上谈兵
你可能会问:这种“手工搭门”的方法有什么实际价值?
答案是:它教会你如何思考硬件。
场景一:FPGA 原型开发
在资源受限的低端FPGA上,有时无法调用LUT自动综合XOR。此时手动生成等效结构可提高可控性。
场景二:教学实验与课程设计
电子类专业学生常需在面包板上用74系列IC搭建全加器。典型配置如下:
- 74HC04:非门(NOT)
- 74HC08:与门(AND)
- 74HC32:或门(OR)
只需几片芯片,就能点亮LED展示加法结果。
场景三:ASIC 设计中的门级优化
在标准单元库中,虽然XOR有专用单元,但在某些低功耗场景下,设计师会评估是否用NAND/NOR重构以节省面积或降低动态功耗。
设计权衡:你知道串行进位的瓶颈吗?
当你把多个全加器级联成n位加法器时,就形成了所谓的串行进位加法器(Ripple Carry Adder)。工作流程如下:
FA0: A0+B0+Cin → S0, C1 FA1: A1+B1+C1 → S1, C2 ... FAn-1: An-1+Bn-1+Cn-2 → Sn-1, Cout看起来很自然,但有个致命缺点:进位必须一级一级传递。
这意味着总延迟正比于位数 × 单级门延迟。对于32位系统,可能要经过上百纳秒才能得到结果,在高速处理器中显然不可接受。
这也是为什么现代CPU采用超前进位加法器(Carry Lookahead Adder),通过提前计算进位来打破链式依赖。
但请记住:没有串行进位的理解,就不可能真正掌握超前进位的设计思想。
调试建议:那些新手容易踩的坑
信号极性搞反了?
检查非门连接是否正确,尤其是 $\overline{A}B$ 写成了 $A\overline{B}$ 就会出错。忘了接地 Cin?
最低位加法器的 Cin 应该接 0(GND),否则初始进位不确定。扇出过大导致驱动不足?
一个非门输出如果连太多负载,可能导致电压不稳定。建议每门输出不超过3~5个输入。时序仿真未加延迟?
在Verilog中加入#10延迟有助于观察信号变化顺序,避免竞争冒险。
写在最后:回到本质,理解计算
今天我们完成了什么?
我们从一张真值表出发,推导出布尔表达式,将异或拆解为与或非组合,最终构建了一个功能完整的全加器。整个过程没有依赖任何高级抽象,完全是“自底向上”的工程思维体现。
也许你会说:“现在谁还这么干?”
但正是这种回归本质的训练,让你不再把“加法”当作理所当然的功能,而是看到背后层层叠加的逻辑之美。
下次当你写下a + b的时候,不妨想一想:
那一个个与门、或门、非门,正在硅片深处默默为你执行着最原始也最伟大的计算。
如果你正在学习数字电路、准备面试,或是想重温基础知识,不妨动手画一遍电路图,或者在Logisim里连一次线。你会发现,真正的理解,永远来自亲手实现。
欢迎在评论区分享你的实现截图或遇到的问题,我们一起讨论!