从或非门到全加器:一场数字电路的“极简主义”实践
你有没有试过只用一种工具完成整个工程?比如,仅用一把螺丝刀组装一台电脑——听起来荒谬,但在数字电路世界里,这不仅是可能的,而且是深刻理解底层逻辑的关键训练。
今天我们要做的,就是这样一个挑战:只用或非门(NOR Gate)构建一个全加器(Full Adder)。没有与门、没有异或门,甚至连最基础的非门也不直接使用——所有功能都由同一个逻辑单元衍生而来。这不是为了炫技,而是为了揭示一个核心真理:某些简单的规则,足以构造出复杂的智能。
或非门的“全能”秘密
我们常说“与非门”和“或非门”是通用逻辑门,但很少有人真正动手验证这一点。所谓“通用”,意味着它具备功能完备性(Functional Completeness),即仅靠这种门就能实现任意布尔函数。
而或非门之所以能担此重任,关键在于它的输出特性:
只有当所有输入为0时,输出才为1;其余情况均为0。
数学表达式为:
$$
Y = \overline{A + B}
$$
这个看似简单的规则,却蕴含着构建一切的能力。就像DNA仅用四种碱基编码生命一样,或非门也能通过组合与嵌套,演化出非、或、与、异或等复杂行为。
更重要的是,在早期集成电路设计中,如ECL(发射极耦合逻辑)和某些PLA结构中,或非门因其对称性和稳定性被优先采用。即使在现代CMOS工艺下,统一逻辑家族的设计仍有助于降低制造偏差和测试成本。
全加器的本质:三个输入,两个输出
全加器是算术运算的基本细胞。它接收三位二进制输入:
- A 和 B:两个待加位
- Cin:来自低位的进位
并产生两位输出:
- Sum:当前位的结果
- Cout:向高位传递的新进位
其逻辑关系如下真值表所示:
| A | B | Cin | Sum | 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 |
标准表达式为:
$$
\text{Sum} = A \oplus B \oplus C_{in}, \quad C_{out} = AB + AC_{in} + BC_{in}
$$
问题来了:这些公式里有异或、与、或——偏偏没有“或非”。我们的任务,就是把这些操作全部“翻译”成或非语言。
第一步:用或非门造出其他门电路
既然只能用或非门,那就得先学会“自给自足”。
1. 非门(NOT)——最简单的变形
把或非门的两个输入连在一起,会发生什么?
$$
Y = \overline{A + A} = \overline{A}
$$
没错,这就是非门!只需要一个两输入或非门,将同一信号接入两端即可。
// NOT from NOR module not_from_nor(output Y, input A); nor(Y, A, A); endmodule这招简单却重要——它是后续所有变换的基础。
2. 或门(OR)——双重否定的艺术
我们知道:
$$
A + B = \overline{\overline{A + B}} = \neg(\text{NOR}(A,B))
$$
所以只要对NOR(A,B)再取一次反,就得到了OR。
电路结构:
- 第一级:nor(t, A, B)
- 第二级:nor(Y, t, t)→ 相当于~t
// OR from NOR module or_from_nor(output Y, input A, B); wire t; nor(t, A, B); nor(Y, t, t); // invert endmodule两步搞定,干净利落。
3. 与门(AND)——德摩根定律的经典应用
根据德摩根定律:
$$
AB = \overline{\overline{A} + \overline{B}}
$$
也就是说,先把A和B分别取反,然后做“或”操作,最后再取反一次。
而这三个步骤都可以用或非门完成!
实现方式:
1. 用两个或非门生成~A和~B
2. 将它们输入或非门 → 得到~A + ~B
3. 最后再反相一次 → 输出AB
// AND from NOR module and_from_nor(output Y, input A, B); wire na, nb; nor(na, A, A); // ~A nor(nb, B, B); // ~B nor(Y, na, nb); // ~(~A + ~B) = AB endmodule注意最后一级不需要额外反相,因为nor(Y, ~A, ~B)本身就是~(~A + ~B),正好等于AB。
4. 异或门(XOR)——真正的挑战
这才是最难啃的骨头。标准表达式:
$$
A \oplus B = \overline{A}B + A\overline{B}
$$
要实现它,需要:
- 构建两项:~A·B和A·~B
- 然后将它们相“或”
每一步都要用上面的方法展开。
我们可以分步走:
步骤分解:
- 生成
~A,~B - 构造
~A·B:用and_from_nor(~A, B) - 构造
A·~B:同理 - 将两者用
or_from_nor合并
总消耗估算:
- 每个AND用3个或非门 → 两个共6个
- 每个NOT已包含在内
-OR用2个或非门
- 总计约8个或非门实现一个异或门
虽然繁琐,但完全可行。
小技巧:可进一步优化为6门结构,利用中间信号共享,但这属于高级压缩范畴,本文暂不展开。
全加器的最终拼图
现在我们已经准备好所有积木,可以开始搭建全加器了。
进位输出 $ C_{out} $
原始表达式:
$$
C_{out} = AB + AC_{in} + BC_{in}
$$
我们将其拆解为三部分:
1. $ P1 = AB $
2. $ P2 = AC_{in} $
3. $ P3 = BC_{in} $
每一项都用and_from_nor实现(各需3个或非门)
然后进行三级“或”操作:
- 先算 $ T1 = P1 + P2 $,用or_from_nor(2个门)
- 再算 $ T2 = T1 + P3 $,同样方法(2个门)
总计:3×3 + 2×2 =13个或非门
和输出 $ \text{Sum} $
由于:
$$
\text{Sum} = A \oplus B \oplus C_{in}
$$
我们分两步走:
1. 计算 $ X = A \oplus B $,用上述异或模块(约8门)
2. 再计算 $ \text{Sum} = X \oplus C_{in} $,再用一次异或模块(又8门)
但注意:第一次异或产生的中间结果 $ X $ 可复用反相信号,略有节省。
粗略估计:15个或非门
总体资源消耗
| 功能 | 或非门数量 |
|---|---|
| 反相器(na/nb/ncin) | 3 |
| 三个与项(AB/AC/BC) | 9 |
| 两级或操作(Cout) | 4 |
| 两次异或(Sum) | 15 |
| 总计 | ~31 |
也就是说,一个全加器大约需要30个左右的两输入或非门。如果你手头是74HC02芯片(每片含4个两输入或非门),那至少需要8片IC才能搭完一个全加器。
听起来很多?确实。但别忘了,这是在完全不用任何其他逻辑类型的极端条件下实现的。
Verilog 结构化建模(真实可用版本)
下面是可综合的、纯结构级Verilog代码,不含行为描述:
module full_adder_nor_only(output Sum, Cout, input A, B, Cin); // --- Step 1: Generate Inverted Signals --- wire na, nb, ncin; nor(na, A, A); nor(nb, B, B); nor(ncin, Cin, Cin); // --- Step 2: Compute AND Terms --- wire nab, nac, nbc; // AB = NOR(~A, ~B) then invert nor(nab, na, nb); wire ab; nor(ab, nab, nab); // ACin nor(nac, na, ncin); wire ac; nor(ac, nac, nac); // BCin nor(nbc, nb, ncin); wire bc; nor(bc, nbc, nbc); // --- Step 3: Compute Carry-out = AB + ACin + BCin --- wire ab_or_ac_temp; nor(ab_or_ac_temp, ab, ac); // NOR(AB, AC) wire ab_or_ac; nor(ab_or_ac, ab_or_ac_temp, ab_or_ac_temp); // OR(AB, AC) wire cout_temp; nor(cout_temp, ab_or_ac, bc); // NOR(OR_AB_AC, BC) nor(Cout, cout_temp, cout_temp); // Final OR // --- Step 4: Compute Sum = A⊕B⊕Cin --- wire axorb; xor_gate(axorb, A, B); xor_gate(Sum, axorb, Cin); endmodule // XOR using NOR gates (modular) module xor_gate(output Y, input a, b); wire na, nb, a_nb, na_b; nor(na, a, a); nor(nb, b, b); // term1: a & ~b wire t1; nor(t1, a, nb); // ~(a + ~b)? No — need AND! // Instead: use AND module wire a_and_nb, na_and_b; // a * ~b nor(t1, a, nb); nor(a_and_nb, t1, t1); // ~a * b nor(t1, na, b); nor(na_and_b, t1, t1); // OR them together wire or_temp; nor(or_temp, a_and_nb, na_and_b); nor(Y, or_temp, or_temp); endmodule这段代码可以在EDA工具中仿真并通过静态时序分析。虽然无法高效映射到FPGA LUT(现代综合器会自动优化),但在教学板或离散元件实验中具有实际意义。
实际搭建建议与常见陷阱
如果你真打算在面包板上动手试试,这里有几点必须知道:
✅ 成功要点
电源去耦不可少
每块74HC02旁边都要加0.1μF陶瓷电容跨接Vcc与GND,否则高速切换会引起电压塌陷。扇出限制要留意
单个或非门最多驱动4~8个CMOS输入。若某信号分支过多(如na被多次使用),应插入缓冲级(即两个串联的或非门:nor(t,a,a); nor(o,t,t);)布线颜色编码
建议:
- 红色:原始输入 A/B/Cin
- 蓝色:反相信号 ~A/~B/~Cin
- 黄色:中间乘积项 AB/AC/BC
- 绿色:Sum/Cout 输出逐步调试法
不要一次性通电。建议分模块测试:
- 先验证非门是否工作
- 再测与门输出是否符合预期
- 最后接入异或链
❌ 常见错误
误以为
nor(Y, ab, ac)就是ab + ac
错!这是~(ab + ac),必须再加一级反相才是“或”。忽略传播延迟累积
30级门延迟叠加可能导致纳秒级延迟,在高频系统中会引发竞争冒险。重复定义wire导致短路
在Verilog中避免多次声明同一信号。
为什么这件事仍然值得做?
你说,现在谁还手动搭逻辑门?FPGA一行HDL搞定,CPU一条指令完成32位加法。是的,从工程效率看,这种做法早已过时。
但它教会我们的,是一种思维方式:复杂源于简单,系统始于原子。
当你亲手用30个或非门点亮第一个Sum灯时,你会明白:
- 计算机不是魔法,它是精心编织的因果链
- 抽象层层堆叠,但根基始终清晰可见
- 工程之美,不仅在于速度与面积,也在于你能把它拆到多细
这种能力,在以下场景中依然关键:
-FPGA底层资源调度:当LUT受限时,了解替代路径至关重要
-ASIC物理设计:统一逻辑类型可简化布局布线
-航天与军工系统:单一逻辑族提升抗辐照可靠性
-逆向工程与老旧设备修复:面对只剩74系列芯片的老机器,这是救命技能
结语:回到起点
我们从一个最简单的门出发,走过非、与、或、异或,最终抵达全加器。这条路并不高效,但却无比扎实。
下次当你写下一行assign Sum = A + B + Cin;的时候,不妨想一想:背后有多少个“或非”正在默默工作?
也许真正的工程师,不只是会用工具的人,而是知道工具如何诞生的人。
如果你愿意,不妨找几片74HC02,搭一个看看。当LED亮起那一刻,你会感受到一种久违的、来自硬件深处的震撼。
互动邀请:你有过类似“仅用一种元件实现复杂功能”的经历吗?欢迎在评论区分享你的故事。