两个元件搭建CPU:从与非门到逻辑运算的完整路径
2026/9/17 6:01:14 网站建设 项目流程

开篇先说个反直觉的结论:CPU 内部没有任何"智能"可言,也没有藏在某个角落里的"灵魂芯片"。你去拆一颗现代 CPU,用显微镜看它的硅片,看到的只有几十亿个极其规则的微小结构,它们全都是由同一种东西堆出来的——逻辑门。而逻辑门继续往下拆,能拆到两个最底层的万能元件:与非门(NAND)和或非门(NOR)。标题里说的"2个元件,构成了非常巧妙的逻辑",在我理解就是指这一对"数字电路里的万能积木"。这篇文章就把这个从两个门到一颗 CPU 的完整链条讲清楚,适合正在学数字逻辑、计算机组成原理,或者准备做单周期 CPU 课程设计的同学,看完你会对"CPU 为什么能算、能记、能执行指令"有一个从底层到顶层的通透认识。

1. 为什么偏偏是这两个元件——"万能门"的底牌

1.1 先看懂与非门和或非门到底是什么

与非门(NAND)说白了就是"先与、再非":输入 A 和 B 都为 1 时,输出为 0;其他任何情况,输出都是 1。或非门(NOR)则是"先或、再非":输入 A 和 B 只要有一个为 1,输出就是 0;只有当两个输入都为 0 时,输出才为 1。两张真值表摆出来,一切就清楚了:

ABA NAND BA NOR B
0011
0110
1010
1100

很多初学者看到这两张表的第一反应是:这不就是两个普通逻辑门吗,有什么神奇的?真正神奇的地方在于,任何逻辑电路,大到 CPU 里的一个 64 位加法器,小到一个指示灯开关电路,都可以只用与非门、或者只用或非门搭出来。这种性质在数字逻辑里叫"功能完备性"(Functional Completeness)。也就是说,你不需要准备与门、或门、非门、异或门一大堆门电路,只需要 NAND 一种门,就能把它们全部变出来;同样,NOR 一种门也能做到。

1.2 为什么功能完备性对 CPU 如此重要

这里要理解一个工程层面上的逻辑:芯片工厂生产逻辑门,不是像写代码那样需要什么就现写什么。在硅片上,每多一种门电路类型,就意味着多一套掩膜版、多一组工艺验证、多一批潜在缺陷点。假如 CPU 里同时要使用与门、或门、非门、异或门、同或门等十几种门,制造复杂度会直线上升。而一旦确认"只需要 NAND 和 NOR 两种基础单元就能表达任何逻辑",整个芯片后端设计就变得极其规整——标准单元库里用这两种门做骨架,EDA 工具自动化地完成几十亿晶体管的布局布线。

我当年第一次在 Logisim 里做单总线 CPU 设计时,老师给了一个硬性要求:基础门只能用 NAND 和 NOR。当时还觉得是故意刁难,做到一半才明白,这个限制反而逼你理解了逻辑化简的本质:用最少的门类型、最少的总门数,完成同样的功能,这正是工业界芯片设计的常规操作。

1.3 一个容易踩的误区:"与非门"不是"先与后非"两步

很多人画电路图时,潜意识里把 NAND 当成了"一个与门后面挂一个非门",想当然地以为它有时序上的先后。实际上在组合逻辑里,NAND 就是一个独立的完整逻辑单元,不存在"先与后非"的中间节点。这一点在做门级延迟分析时特别关键:一个 NAND 门的传播延迟是固定的,而不是"与门延迟 + 非门延迟"。如果在 Logisim 里用分立的与门和非门去搭,仿真波形看起来差不多,但到了真实电路里,时序预算完全对不上。

2. 从两个门变出全套逻辑——只用手头这两张牌

2.1 用与非门搭出非门、与门、或门

先动手做最基础的三件事。把 NAND 的两个输入端短接在一起,输入 A,输出就是 NOT A。原因很简单:当 A=0 时,NAND(0,0)=1;当 A=1 时,NAND(1,1)=0。这就是一个标准的反相器。

搭与门也不难:先用 NAND 得到 NOT(A AND B),后面再接一级"输入短接的 NAND"(也就是非门),双重取反就回到 A AND B。表达式写出来就是:

AND(A,B) = NAND(NAND(A,B), NAND(A,B))

搭或门稍微绕一下,但这里恰好是理解德摩根定律的最佳现场。德摩根定律说:NOT(A OR B) = (NOT A) AND (NOT B),反过来 NOT(A AND B) = (NOT A) OR (NOT B)。利用它,可以得到:

OR(A,B) = NAND(NOT A, NOT B) = NAND(NAND(A,A), NAND(B,B))

也就是说,先把 A、B 各自取反,再对两个反相信号做 NAND。你可以逐行代入真值表验证:A=0、B=1 时,NAND(A,A)=1,NAND(B,B)=0,再 NAND(1,0)=1,结果确实等于 0 OR 1 = 1。

2.2 再接再厉:用与非门搭出异或门

比与或非再进一步的是异或门(XOR),它才是计算器里真正的主角——因为二进制加法的本质就是异或。用 NAND 搭 XOR,常见做法是先构造中间信号:

S1 = NAND(A,B) XOR(A,B) = NAND( NAND(A,S1), NAND(B,S1) )

这个结构很多教材直接给结论,但你要真正理解它的逻辑:当 A、B 都为 1 时,S1=0,导致 NAND(A,S1)=NAND(1,0)=1、NAND(B,S1)=1,最终输出 0,符合异或"相同为 0"的定义;当 A、B 都为 0 时,S1=1,NAND(A,S1)=1,最终 NAND(1,1)=0,也符合"相同为 0"。剩下两种一真一假的情况,你代进去会发现输出都是 1。折腾这一圈,你已经证明了一件事:只用一种 NAND 门,就能表达完整的布尔逻辑。NOR 门也能完成同样的推导,只是中间信号的具体搭法略有差别。

2.3 异或门在 Logisim 仿真里的一个坑

很多同学在 Logisim 里搭上述异或门时,会遇到"输出总是 1,或者偶尔是 0"的怪现象。排查后发现,大多数情况是因为连线没有画到门的同一个输入引脚上,或者把 NAND 的输入顺序接反了。还有一类问题是:为了省事,直接调用 Logisim 自带的 XOR 元件,然后手动尝试用真值表去"对齐"自定义电路,忽略了 NAND 搭建的 XOR 可能存在输出阻抗不匹配的问题——这在实际晶体管级电路里更明显,但在 Logisim 这种纯逻辑仿真工具里通常看不出来,等以后做版图设计时你才会碰壁。所以建议初学者养成习惯:每个中间节点都命名,并在 Logisim 里贴上标签总线(Label),方便逐点查波形。

3. 会算数了——用门电路搭出二进制加法器

3.1 半加器的门级实现

加法是 CPU 运算能力的起点。先看一位二进制加法:A 加 B,输出两个量,一个是"本位和"Sum,一个是"进位"Carry。真值表很简单:0+0=00,0+1=01,1+0=01,1+1=10。

对照这个表你会发现,Sum 恰好就是 A XOR B,而 Carry 恰好就是 A AND B。上一节刚搭好 XOR 门,现在把它连接一个 AND 门,就得到了半加器(Half Adder)

Sum = A ⊕ B Carry = A · B

在 Logisim 里测试时,把 A、B 接到两个输入引脚,Sum 和 Carry 各接一个 LED,拨动开关,你会看到 LED 的亮灭完全符合真值表。这一步虽然简单,但它揭示了 CPU 运算背后的核心思想:一切算术运算,归根到底都是逻辑运算的组合。CPU 里没有"乘法器芯片"那种神秘的东西,只有一堆异或门、与门、或门按特定方式连在一起。

3.2 全加器:处理进位才是难点

半加器只能处理两个一位数相加,但多位加法里,每一位都要考虑来自低位的进位。于是需要全加器(Full Adder):它有三个输入 A、B、Cin(来自低位的进位),两个输出 Sum 和 Cout(向高位的进位)。逻辑表达式是:

Sum = A ⊕ B ⊕ Cin Cout = A·B + Cin·(A⊕B) 或者等价地 Cout = A·B + A·Cin + B·Cin

用上一章搭好的 XOR 和 AND、OR 门,可以比较直观地搭出全加器:先用一个 XOR 得到 A⊕B,再和 Cin 做第二次 XOR 得到 Sum;进位部分用一个与门计算 A·B,再用一个与门计算 Cin·(A⊕B),最后用或门把两路进位合并。

这里有一个常见的理解误区:用"两个半加器拼一个全加器"是可行方案,但不是唯一方案。你完全可以直接做卡诺图化简,得出更紧凑的表达式。比如 Cin·(A⊕B) 这个中间信号,在门级实现时可以和 Sum 共用一部分电路,从而节省门数。我在做单总线 CPU 课程设计时对比过两种做法:直接教科书式搭法用了 19 个 NAND 门,化简后的紧凑实现只要 14 个 NAND 门。别小看这 5 个门的差距,一个 8 位加法器就是 40 个门,放到 64 位 ALU 里就是几百个门的差别,功耗和延迟都跟着变化。

3.3 进位链的延迟:为什么 CPU 不能无限提高频率

全加器串联成多位加法器时,低位进位要一级一级往上传递,这就是行波进位加法器(Ripple Carry Adder)。它的最大问题是:最坏情况下,进位要从最低位一直"滚"到最高位,每一位全加器都有固定的门延迟,所以位数越多,整个加法器的延迟就越长。这直接限制了 CPU 主频的提升——一条加法指令的执行时间被最长路径的延迟卡死。

为了解决这个问题,芯片里用到了超前进位加法器(Carry Look-ahead Adder),核心思想是提前计算每个位置的"进位生成信号"(Generate,G = A·B)和"进位传播信号"(Propagate,P = A⊕B),再用并行逻辑一次性算出各级进位。这是一个典型的"用面积换速度"的工程权衡:更复杂的逻辑、更多的门,换来更短的延迟。理解了这个取舍,你再看 CPU 里的整数加法器、浮点运算器,就能明白那些看似复杂的结构,本质上都是在时间和面积之间做妥协。

4. 会记忆了——从两个门到触发器,时序逻辑的诞生

4.1 反馈回路:两个与非门组成 SR 锁存器

组合逻辑只能根据当前输入产生输出,它没有"记忆"。但 CPU 必须记住状态:程序计数器 PC 要记住下一跳指令地址,寄存器要暂存运算中间结果。怎么让电路记住东西?答案是反馈(Feedback)——把输出接回输入。

拿两个 NAND 门做一个最简单的 SR 锁存器:第一个 NAND 的输出接到第二个 NAND 的一个输入,第二个 NAND 的输出再接回第一个 NAND 的另一个输入。外部输入是 S(置位)和 R(复位)。这个交叉耦合的结构非常巧妙:当 S=1、R=1 时,电路保持原来的状态;S=0、R=1 时,输出 Q=1,完成置位;S=1、R=0 时,输出 Q=0,完成复位;而 S=0、R=0 是禁止态,因为两个输出会同时为 1,破坏 Q 和 !Q 互补的关系。

我在 Logisim 里第一次搭这个电路时,盯着波形看了半天才真正理解:所谓的"记忆",不过是有两个信号互为对方的输入,互相维持对方的电平。没有魔法,就是最基本的 0 和 1 相互"顶住"。

4.2 加入时钟:从锁存器到 D 触发器

SR 锁存器的最大问题是输入一变输出立刻跟着变,而且有禁止态。实际 CPU 需要的是"在某个精确的时间点,把输入值锁存住",于是引入了时钟信号。先做 D 锁存器:在 SR 锁存器前面加两个与门,把 S 和 R 统一成一路输入 D 和时钟 CLK;当 CLK=1 时,输出跟随 D 变化;CLK=0 时,输出保持。但这还不够——CPU 是同步时序系统,它要求在时钟边沿(上升沿或下降沿)这个瞬间采集数据,而不是整个高电平期间都透传。

这就引出了边沿触发的 D 触发器(Flip-Flop),也就是需要两级锁存器串联,形成"主从"结构。第一级为主触发器,第二级为从触发器。时钟上升沿到来前,主触发器采样输入;沿到达后,主触发器把数据传给从触发器,同时自己与输入隔离。这个"边缘采样"的行为,让整个 CPU 可以有一个统一的节拍,所有寄存器在同一时刻同时更新,互不干扰。

4.3 时序预算:建立时间和保持时间,CPU 稳定性的命门

真正做 CPU 设计时,有两个参数几乎天天挂在嘴边:建立时间(Setup Time)和保持时间(Hold Time)。建立时间要求数据必须在时钟沿到来之前提前一段稳定时间到达触发器输入;保持时间要求时钟沿过后,数据还要继续稳定一小段。如果违反了这两个要求,触发器输出就会出现"亚稳态"——既不是确定的 0 也不是确定的 1,而是悬在中间的不稳定状态,可能把后续电路全部带乱。

这解释了为什么 CPU 有最高主频限制:两个触发器之间的组合逻辑延迟如果太长,超过了时钟周期减去建立时间,数据就来不及在下一个沿之前稳定下来。同时它也解释了为什么超频到极限后系统会随机出错——不是所有门都扛不住,而是某个最差路径的时序已经崩溃了。当年我在 Logisim 里做单周期 CPU,仿真环境下高速时钟永远不会出错,但同样的设计换到真实 FPGA 上,跑高频就频繁出问题,原因就在这里。

5. 串起来——从寄存器、ALU 到一条指令的完整旅程

5.1 CPU 的"器官":寄存器堆与 ALU

有了 D 触发器,下一步就是抠出 CPU 的骨架。寄存器堆(Register File)就是一组D触发器加上读写译码逻辑,用于暂存操作数和运算结果。在 MIPS 单周期 CPU 中,寄存器堆有 32 个 32 位寄存器,读两个、写一个,需要设计独立的读端口和写端口,还需要考虑写冲突处理——同一周期内写入和读出同一个寄存器,到底是以旧值还是新值为准,这是课程设计里很容易忽略的细节。

ALU(算术逻辑单元)则是把所有运算逻辑集中在一起:加法、减法、与、或、异或、比较等。它的本质就是一张大的真值表——根据控制信号 3 位或 4 位,从多个运算结果中选择一个输出。关键点在于,ALU 里的各种运算单元是并行计算的,比如加法器和比较器同时都在跑,最后用多路选择器(MUX)根据控制信号选出结果。这种"并行计算 + 选择输出"的模式,和软件里的 if-else 完全不同,是理解 CPU 高性能的基础。

5.2 控制信号:CPU 的"指挥中心"

如果说 ALU 是 CPU 的肌肉,寄存器堆是记忆,那控制器就是大脑。控制器接收指令的操作码和功能码,通过译码逻辑产生一堆控制信号,告诉 ALU"这次做加法",告诉寄存器堆"这个周期要写回结果",告诉 PC"下一条指令要不要跳转"。在单周期 CPU 设计里,控制器通常用 ROM 或者组合逻辑实现,把每条指令需要的控制信号做成一张表。

我做了 MIPS 单周期 CPU 课程设计后,最直观的感受是:每条指令本质上就是一个状态字。比如add rd, rs, rt这条指令,控制表里 RegDst=1、ALUSrc=0、MemtoReg=0、RegWrite=1、ALUSelect=0010。这些 0 和 1 不是随便填的,它们直接驱动硬件电路完成一连串动作。写控制表的时候,建议边写边画数据通路,对着图核对每一个信号流向,不然很容易漏信号。此前见过很多同学在这里翻车,原因都是只看指令名字,不看数据通路。

5.3 单总线 CPU 的独有难点:总线的竞争与仲裁

热搜里反复出现"单总线 CPU 设计",这里单独说一下。单总线结构意味着所有部件都挂在一组 32 位数据总线上,同一时刻只能有一个部件向总线写数据。这就产生了一个核心问题:输出控制——每个部件的总线输出端都要加三态门(缓冲器),只有被控制信号选中的那个部件才开通三态门,把数据送上总线,其余部件的输出都处于高阻态。

高阻态"Z"在 Logisim 仿真里经常被忽略,因为默认状态下未连接的网络可能被当成普通未接状态处理,但实际做总线模拟时会遇到"多个输出同时驱动总线"的冲突报错。我的教训是:搭单总线 CPU 前,先把每个部件的"输出使能"信号列成一个矩阵表格,横轴是部件,纵轴是控制信号,确保任何一拍只有一个使能位为 1。设计完成后,使用 Logisim 的"分步时钟"逐步验证每条指令的取指、译码、执行过程,避免一次性跑整体导致出错后根本不知道问题在哪个环节。

5.4 一条指令的完整旅程:以加载指令为例

lw rt, offset(rs)(从内存读数据到寄存器)这条指令来走一遍。第一步,PC 寄存器输出当前指令地址,送往指令存储器,同时 PC 要经过加法器加 4 得到下一条指令地址(分支处理这里简化不做展开)。第二步,读出的指令被拆分成字段:opcode 告诉控制器这是lw,rs 字段送到寄存器堆作为读地址之一,rt 字段作为目标寄存器地址,offset 低位部分经过符号扩展成为 16 位到 32 位的立即数。第三步,ALU 将寄存器堆读出的 rs 值和扩展后的立即数相加,得到内存地址,送往数据存储器。第四步,数据存储器在控制信号的驱动下把对应地址的数据读出到总线上。最后一步,数据总线上的值经写回通路进入寄存器堆的写端口,在时钟沿写入 rt 寄存器。整个流程就是这么一环扣一环。

这整个过程里,你会在每一步都看到前面章节提到的门电路的影子:PC 加 4 用的是加法器(里面全是 NAND 搭的 XOR),控制译码用的是逻辑门阵列,寄存器堆的写入里面是 D 触发器。可以说,CPU 的灵魂不是什么玄学,就是这些基础逻辑单元按一个精妙的时序组织在一起

6. 落到硅片——CMOS 工艺下"两个元件"的真正身份

6.1 物理层面看:两个晶体管搭出一个基础门

从数字逻辑层面看,万能元件是 NAND 和 NOR 两个门;但再往下钻一层,到了芯片物理实现,你会发现硅片上的基本"积木"其实是两种晶体管:NMOS 管和 PMOS 管。一个 NAND 门用 4 个晶体管就能搭出来,两个 PMOS 并联在上拉网络,两个 NMOS 串联在下拉网络;一个 NOR 门则反过来,两个 PMOS 串联,两个 NMOS 并联。

这个结构的巧妙之处在于,它完美实现了"互补":任何时刻,从电源到输出的上拉通路和从输出到地的下拉通路,保证只有一条导通。这保证了逻辑正确性,同时静态功耗几乎为零——只有在翻转瞬间,两条通路短暂同时导通,才会产生电流。现代 CPU 几十亿晶体管,绝大多数时间待在待机状态,低压低功耗设计正是依赖这种互补特性。

我曾经对照教科书把 NAND 门在晶体管级搭了一遍,看到 PMOS 和 NMOS 的位置关系后,才明白为什么 NAND 门比 NOR 门在工艺上更受青睐:NMOS 的导通能力通常强于 PMOS,NAND 结构里下拉网络是 NMOS 串联,本质上对下降沿更友好;而 NOR 结构里上拉网络是 PMOS 串联,充电速度较慢。所以业界标准单元库里 NAND 门的占比往往高于 NOR 门。这也是为什么很多教材说"多用 NAND,少用 NOR"。

6.2 从门到核:几十亿晶体管的层级组织

你用 4 个晶体管得到一个 NAND 门,用 14 个 NAND 门得到一个全加器,用 64 个全加器得到一个 64 位加法器,用加法器、乘法器、移位器组成 ALU,用寄存器堆和数据通路组成一个核心,再把核心、缓存、总线接口组合成一颗完整的 CPU。这是一个标准的自顶向下逐层封装过程。每一层都只对上层暴露接口,对下层隐藏细节——这和软件工程里"函数、模块、系统"的分层思想惊人地一致。

从这个角度看,"两个元件"的内涵就更有意思了:无论从逻辑门层面还是晶体管层面,所有复杂度的根源都极其简单。说 CPU 可怕,它确实有上百亿个晶体管;说 CPU 可爱,它的出发点真的就是两个基本单元加一堆连线。我在带学弟学妹做课程设计时经常说:你要是觉得 CPU 难,那就是还没把"全加器 → ALU → 数据通路"这条线打通;真打通了,后面就是水到渠成的事。

6.3 懂门电路在工程里还有用吗

有人可能会问:现在写代码用 C、做设计用 Verilog,谁还手工搭门电路?我的回答是:用不用是一回事,懂不懂是另一回事。写 Verilog 时,综合工具会把你写的a & b映射成某个标准单元,但如果你不理解门级结构,就理解不了为什么同样的逻辑用不同的写法综合出来的面积和延迟差别巨大。做时序约束时,不了解建立时间和保持时间的概念,面对的时序报告就是天书。更直白点说,芯片后端要做功耗优化、要修 hold violation、要做时钟树综合,每一个环节都建立在"我知道电路最终会变成一堆 NAND、NOR 门"这个认知之上。

到现在我自己调试电路时,有两条实用经验值得分享:第一,遇到复杂逻辑先画真值表,真值表能穷举所有情况,比直觉可靠得多;第二,在 Logisim/FPGA 上做仿真时,永远从最小子模块验证起,全加器单独测通过了,再连成加法器测;加法器通过了,再接到 ALU 里测。那种一口气把整个 CPU 画完再统一仿真、出了问题满头雾水的做法,我至少见过二十个同学踩过坑。

CPU 的灵魂不是某个部件,而是这套"从两个最简单元件出发,通过分层组合逼近任意复杂功能"的方法论。你能拿两个 NAND 门搭出一颗能跑指令的 CPU,这本身就是整个计算机工程最迷人的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询