☰
手搓CPU指南:从逻辑门到能运行程序的计算机
2026/10/6 10:10:33 网站建设 项目流程

第一次在Logisim里点亮自己手搓的CPU时,屏幕上的小灯按照预设程序依次亮起,那个瞬间让我觉得,之前所有关于计算机组成原理的抽象概念都找到了着落。从逻辑门到CPU,听起来像是高高在上的工程奇迹,但如果你愿意从最底层的开关开始,一层一层往上堆,就会发现它其实是一条极其清晰的逻辑链。这篇文章就是带你把这条链子从头搭一遍——用逻辑门构造出一个能运行简单程序的计算机,中途会给你罗列每种门电路的符号、真值表和数学表达式,也会分享我踩过的时序坑。整个过程不需要你有芯片制造经验,只需要耐心、逻辑思维,以及一台能运行Logisim的电脑。

1. 先放下“高大上”的畏惧:逻辑门就是三个开关的排列组合

很多人一听“手搓CPU”,脑子里立刻浮现出晶圆厂、光刻机、几亿晶体管。实际上,CPU再复杂,剥到最底层也只有几种最基本的逻辑门。逻辑门不过是对电信号做简单判断的电路单元:输入是0或1,输出也是0或1。你可以把它想象成一个带按钮的小盒子,真值表就是说明书,告诉你什么按钮组合会亮灯。

1.1 逻辑门其实是一种“电路版的小型判断器”

从电工视角看,两个开关串联,必须同时按下灯才亮,这就是与门(AND);两个开关并联,按下任何一个灯都亮,这就是或门(OR);一个开关常闭,按下去灯反而灭,这就是非门(NOT)。计算机里的0和1,对应的是电路中的低电平和高电平,逻辑门就是对这些电平做数学运算。

我们做CPU时,不需要关心门内部的晶体管怎么排布,只需要把它当成一个封装好的、行为确定的零件。就像写程序不需要关心CPU内部每一条微指令一样,我们用逻辑门的“行为”去搭建更高层的模块。这种自底向上的抽象,是计算机体系结构里最核心的思维方式。

1.2 八种常用逻辑门与真值表里的“套路”

网上一搜“逻辑门”经常看到“列出8种逻辑门”这类问题。我通常记下面这八种:与门(AND)、或门(OR)、非门(NOT)、与非门(NAND)、或非门(NOR)、异或门(XOR)、同或门(XNOR)、缓冲器(Buffer)。其中缓冲器虽然不做逻辑运算,但用于信号整形和驱动,在CPU时钟树里很常见。

下表汇总了它们的符号特征、数学表达式和双输入真值表(非门和缓冲器为单输入):

逻辑门数学表达式输入组合输出
与门 ANDY = A · B00 / 01 / 10 / 110 / 0 / 0 / 1
或门 ORY = A + B00 / 01 / 10 / 110 / 1 / 1 / 1
非门 NOTY = ¬A 或 A'0 / 11 / 0
与非门 NANDY = ¬(A · B)00 / 01 / 10 / 111 / 1 / 1 / 0
或非门 NORY = ¬(A + B)00 / 01 / 10 / 111 / 0 / 0 / 0
异或门 XORY = A ⊕ B00 / 01 / 10 / 110 / 1 / 1 / 0
同或门 XNORY = ¬(A ⊕ B)00 / 01 / 10 / 111 / 0 / 0 / 1
缓冲器 BufferY = A0 / 10 / 1

注意与非门和或非门很特殊:与非门是“先与后非”,或非门是“先或后非”。它们有一个重要性质——任何一种逻辑门都可以只用与非门或只用或非门搭出来,这叫逻辑门完备性。实际芯片里,与非门常常比与门更快,所以很多CPU内部使用的标准单元是把与门先实现成与非门再加反相器。

看到这些真值表,你可能会觉得太简单。但请记住:你手搓CPU的全部工作,就是把这些门拼成加法器、加法器拼成ALU、ALU拼成数据通路。这个“拼”的过程才是真正的乐趣。

1.3 用生活类比理解:开关、灯泡和真值表

我给新手讲逻辑门时最爱用的类比是“朋友聚餐点菜”:与门是“两位都同意才吃火锅”,或门是“有一个人想吃火锅就吃”,非门是“不吃火锅”。组合起来还能做更复杂的判断:比如“只有不点外卖并且不是周三的时候才营业”——这就是与非逻辑。

数字电路里,我们从不设计一个“单独”的逻辑门,而是设计一个多输入的逻辑网络。比如一个三输入多数表决器:输出为1当且仅当至少有两个输入为1。这个电路可以用三个与门加一个或门搭出来。画真值表时,你会看到从输入组合到输出结果之间没有“中间状态”,这就是组合逻辑的特点——输出只依赖当前输入,不记忆任何历史。这也引出了手搓CPU的第一个必要模块:组合逻辑是CPU的“肌肉”,而时序逻辑是它的“记忆”。

2. 手搓CPU的“工地”选择:Logisim模拟器是新手最快路径

确定要开始手搓CPU后,第一个选择题是:用真实芯片还是用模拟器?我的建议很明确——第一颗CPU一定在Logisim里完成,因为它能让你把精力全部放在“逻辑对不对”上,而不是被物理接线的噩梦拖垮。

2.1 为什么我不推荐一上来就用面包板和芯片

面包板加74系列芯片确实很酷,但有一个致命问题:连线数量呈指数爆炸。一个最简单的4位加法器,大概要接几十根线;一个完整CPU,上千根线绝对逃不掉。你在插拔过程中稍微碰错一根跳线,排查到天亮都很正常。而且真实的门电路有传播延时、毛刺、电流驱动能力问题,新手很难分辨到底是逻辑错了还是物理接触不良。

Logisim这类模拟工具则完全不同。它把每个逻辑门画成一个图形元件,你只需要用鼠标画线连接,信号传播是瞬时的(也可以模拟延时),不存在接触不良。它还有内置的时钟、RAM、ROM、LED、键盘等组件,正好覆盖CPU设计所需的周边设备。更关键的是,很多大学的计算机组成原理课程都在用Logisim,你能找到大量教程和开源作业参考。

2.2 Logisim与硬件的对应关系:每个模块都能搬实物

有人担心模拟器搭出来的东西“不真实”。其实Logisim里的元件基本都有对应芯片型号:与门对应7408,或门对应7432,非门对应7404,D触发器对应74LS74,寄存器对应74LS374,RAM可以对应微芯的SRAM芯片。你在模拟器里画出的模块,本质上就是一块标准TTL电路图。

在我的实操项目里,我先用Logisim搭了一个8位教学CPU,之后把它翻译成Verilog代码烧录到FPGA开发板上,同样能跑通。Logisim里定义的输入输出端口、时钟、总线宽度,几乎一比一映射到了硬件描述语言。所以它不是玩具,而是教科书级的“数字电路仿真工作台”。

2.3 环境搭建与最小验证电路

Logisim是纯Java应用,去官网下载jar包后,只要有Java环境就能运行。也可以用集成度更高的Logisim Evolution分支,它对总线、子电路、脚本支持更好,适合做稍大型的CPU项目。

安装完成后,我建议你做的第一件事不是直接去搭CPU,而是花十分钟验证一下工具本身:拖一个两输入与门,接两个输入引脚和一个输出引脚,用“手动拨动”输入引脚,观察输出变化。这个小实验能让你彻底理解真值表里的内容是“活”的。紧接着再把非门接在与门后面,看一下与非门的输出,这就是一种“逻辑门到逻辑门”的组合。

提示:在Logisim中使用“Ctrl+R”可以重新仿真,用“Ctrl+T”可以单步走时钟。搭CPU之前,多吃透这几个快捷键,后面调试会快很多。

3. 从加法器到ALU:CPU的算力基础是这样长出来的

逻辑门本身做不了数学运算,但加法器可以把逻辑门组合起来完成二进制加法。加法器是ALU(算术逻辑单元)的核心,而ALU又是CPU里真正“干活”的部件。从门到加法器,你会第一次体会到“组合逻辑的魔法”。

3.1 半加器、全加器与进位链

先看两个1位二进制数相加。可能的组合:0+0=0,0+1=1,1+0=1,1+1=0且进位1。输出包含“和”与“进位”。观察真值表你会发现:和就等于A异或B,进位就等于A与B。于是半加器只需一个异或门和一个与门,两块小元件搞定。

但真实加法器处理第n位时,要接收来自低位的进位,于是引出全加器。全加器有三个输入:A、B和进位输入Cin,两个输出:和S与进位输出Cout。它的布尔表达式是:

  • S = A ⊕ B ⊕ Cin
  • Cout = A·B + A·Cin + B·Cin

如果老老实实按表达式画逻辑门,会用到两个异或门、两个与门、一个或门。更直观的做法是把两个半加器串起来,再经过一个或门合并进位。这样电路层次清晰,也便于在Logisim里封装成“全加器”子电路。

把4个全加器串联起来,低位的Cout接高位的Cin,就得到4位行波进位加法器。这种加法器结构简单但速度慢,因为进位要一级一级“滚”上去。我在模拟器里测试过,4位还算快,16位以上就会感觉到延时。现代CPU会用超前进位加法器或并行前缀加法器,不过在原理层面,行波进位加法器已经足够说明问题。

3.2 ALU设计与操作码

有了加法器,再并上逻辑运算单元,就可以设计ALU。一个典型8位ALU的输入为两个8位操作数A和B,输出为8位结果,另外还有一个4位控制信号来决定执行哪种操作。控制信号就是“操作码”的雏形。

以我的8位演示CPU为例,ALU控制字如下:

控制字功能
0000A + B
0001A - B
0010A & B
0011A | B
0100A ^ B
0101~A
0110左移一位
0111右移一位

减法可以通过“A + (~B + 1)”实现,也就是补码取反加一。逻辑与、或、异或分别用一个对应逻辑门组完成。最后用多路选择器(Multiplexer)根据控制字选择最终输出。MUX是数据通路里的“开关枢纽”,它也是用逻辑门构造的:二选一MUX表达式为Y = (A · ~Sel) + (B · Sel),更大的MUX可以层层扩展。

在Logisim里,我建议把ALU封装成独立子电路,输入输出打上引脚标签。这样后面接寄存器堆和控制器时,不会把画面噎住。

4. 寄存器、存储器与指令:让CPU有“记忆”和“理解力”

组合逻辑输出只由当前输入决定,所以要存储数据、执行程序,就必须有时序逻辑。时序逻辑的记忆单位是触发器,一堆触发器组成寄存器,一堆寄存器组成寄存器堆,再加上按地址寻址的存储器,计算机的“记忆体系”就这样搭起来了。

4.1 寄存器堆:用D触发器做可读写的临时仓库

最简单的存储单元是D锁存器,但CPU内部为了避免数据在时钟高电平期间穿透,基本都使用边沿触发的D触发器。D触发器在时钟上升沿瞬间,把输入D上的值锁存到输出Q。你可以用四个与非门搭一个D触发器的经典结构,也可以直接在Logisim里用内置D触发器。

寄存器堆是一组可读写的寄存器阵列。读写流程和RAM很像:给出地址信号,选中某个寄存器;给出写使能信号,在时钟沿把数据写入;给出读地址,三态缓冲器把数据送到读总线。在MIPS风格的CPU里,寄存器堆通常有32个32位寄存器,但我们做教学CPU时可以简化成8个8位寄存器,这样指令编码更紧凑。

需要注意的是,寄存器堆的读操作在组合逻辑里就能完成,地址一稳定,读数据就稳定;但写操作必须依赖时钟边沿。这个“读是组合,写是时序”的特点,是后续设计单周期CPU的关键——一条指令可以在同一个时钟周期内读取老值,而在下一个周期才写入新值,从而避免写后读冲突。

4.2 内存与地址解码:数据不再是“无源之水”

除了CPU内部的寄存器,系统还需要较大的内存来存放程序和中间数据。我的8位CPU设计里,程序存储器(Instruction Memory)和数据存储器(Data Memory)分开,这是典型的哈佛结构,以免取指和访存抢占同一块空间。

存储器与CPU的连接,说穿了就是三根总线的交互:地址总线由CPU输出给存储器的地址端口,数据总线负责双向传输数据,控制线给出读信号RD或写信号WR。存储器的容量由地址线位数决定,例如12位地址线能寻址4KB。在手搓CPU时,可以用Logisim自带的RAM组件,但也建议亲手做一个地址解码器:把高位地址映射到不同的存储芯片片选端,低位地址接到芯片内偏移。这个解码器本质上就是一堆与非门和反相器,做一次就彻底理解“存储器扩展”了。

4.3 指令集设计:一条加法指令的背后

CPU要“理解”程序,必须先和程序员约定指令格式。用最简的8位设计举例:指令由操作码和操作数组成,共8位。如果操作码占4位,操作数最多还是4位,足够表达寄存器编号和立即数范围。下面是我在这个教学CPU中定义的部分指令:

指令助记符机器码功能
LOAD R1, [10]0001 0001 00001010将地址10的数据载入R1
ADD R1, R2, R30010 0011 00100010R1 = R2 + R3
STORE R1, [11]0011 0001 00001011将R1存入地址11
JMP 00100 0000 00000000跳转至地址0

这只是为了让原理可见而做的简化,实际指令编码会更规整。但不管怎么编码,CPU拿到一个机器码后,都要把它拆成操作码、源寄存器地址、目的寄存器地址和立即数等字段。这个“拆”的动作叫做译码,由控制器完成。

5. 控制器与数据通路:把每一块拼图按节拍缝合

如果说ALU是大脑皮层,那么控制器就是指挥中枢。控制器根据当前指令的操作码,输出一系列控制信号:告诉寄存器堆“是否写”、告诉ALU“做什么运算”、告诉数据存储器“读还是写”、告诉多路选择器“选择哪条路径”。一块没有控制器的ALU只是一堆硬件,插上控制器它才变成可编程的机器。

5.1 单周期控制器的核心思路

单周期CPU的设计思路是:一个时钟周期内完成一条指令的全部步骤。所以时钟周期必须足够长,以容纳最慢指令的延时。控制器本质上是一张“大真值表”:输入是指令操作码,输出是各个控制信号的电平。你可以用一堆逻辑门直接实现这张表,也可以用微型只读存储器实现,后者就是微程序控制器的雏形。

我实际设计时,先列出每条指令需要的控制信号,再填表。拿上面的ADD指令来说:ALU要执行加法;寄存器堆要写目的寄存器;来源选择MUX选寄存器堆;内存写使能低电平;PC要加1。将这些信号写成一列真值表,然后对每个输出信号化简,就能得到控制逻辑的门级电路。在Logisim里,你也可以用“Mux”或“译码器”搭建控制信号发生器,但为了更接近真实原理,我建议先用逻辑门手工搭一遍。

单总线CPU是另一个常见教学模型:内部所有部件都挂在一组总线上,分时传输数据和地址。单总线结构省硬件,但性能低,每次操作可能要分配多个时钟节拍。这就要用状态机来管理节拍了,所以很多实验课会单独开“单总线CPU微程序控制器设计”,就是把控制信号放进微程序存储器,用微指令地址寄存器一步步读出执行。

5.2 时钟真的来了:从组合逻辑到时序逻辑

把组合逻辑和寄存器堆连接后,必须引入统一的时钟信号,让所有时序操作同步。时钟就是方波信号,上升沿告诉寄存器“写入”,下降沿则可以用来准备下一组数据。我见过很多初学者刚连好电路时忘记加时钟,结果数据完全不动;加了时钟又忘记设置频率,导致仿真看起来像卡住。

在Logisim里,时钟源频率可以调低到1Hz,用LED观察寄存器写入瞬间的状态变化;也可以调高看到连续运行效果。建议从1Hz开始调,你的眼睛才能跟上每一个边沿。

5.3 数据通路走一遍:以一条加法指令为例

现在把所有模块串起来,构成完整数据通路。执行“ADD R1, R2, R3”的过程如下:

  1. 程序计数器PC把当前地址送往指令存储器;
  2. 指令存储器输出机器码“0010 0011 00100010”;
  3. 控制器译码,确认这是一条加法指令,并使能寄存器堆读出R2与R3;
  4. 两个读数据信号送往ALU,ALU根据控制信号执行加法;
  5. ALU结果送到寄存器堆的写数据端,同时控制器使能寄存器堆写入;
  6. 在下一个时钟上升沿,R1更新为相加结果;同时控制器让PC加1,指向下一条指令。

在这个流程里,每一个部件的操作都发生在同一时钟周期,只在最后的沿上统一“落笔”。这种设计虽然简单,但很好地体现了计算机的“取指—译码—执行”循环。后续做多周期CPU时,你会把每个步骤拆到不同时钟周期,让硬件复用,但基础模型还是这个单周期循环。

6. 我踩过的坑和给后来者的三条忠告

手搓CPU最难的不是把门电路拼对,而是在出错时能找到原因。我在Logisim里从单个加法器一路搭到完整CPU,至少遇见三类坑:时序怪象、总线冲突、测试盲目。这里挑最有代表性的讲。

6.1 时序竞争:为什么逻辑门延时会导致“幽灵信号”

模拟器默认是无延时“理想”行为,但一旦开启门延时模拟,你可能会看到一些奇妙的现象:某条组合逻辑信号在时钟沿前后短暂翻转,导致寄存器写入错误数据。这就是竞争冒险。真实芯片里更常见。

解决思路有两个:一是保证控制信号在数据稳定之后才有效,可以通过控制时钟相位来实现;二是做同步设计——所有寄存器只允许时钟边沿触发,组合逻辑输出不做异步反馈。手搓CPU时,尽量把“组合逻辑结果”和“寄存器更新”严格用时钟边沿分离,就能规避大部分竞争问题。

6.2 测试驱动:每加一个模块先写点程序验证

不要一口吃成胖子。我的做法是每完成一个子模块,就写一个“能点亮/能计算的测试”。加法器搭好,就输入几组数据看LED结果;寄存器堆搭好,就手动拨地址和写数据,观察存储值变化;ALU搭好,就把所有控制字都跑一遍。全部模块测试通过后,再把它们连成数据通路,这时大概率遇到的错误会少很多。

等最小CPU能跑通“读内存—加一—写回内存”这种测试程序时,再增加分支跳转指令。如果你在仿真里看到PC乱跳或者寄存器值莫名其妙,别急着改某一个门,八成是某个MUX选择信号没连对。此时用Logisim的“探针”工具跟踪关键信号,一次看一个,比蒙头猜高效十倍。

6.3 从单周期到流水线的扩展思路

一颗能跑简单程序的CPU,已经足以让你自豪地称为“手搓CPU”了。但真正的现代CPU还有两大挑战:流水线、乱序执行。流水线把指令拆成多个阶段,每个阶段同时处理不同指令,以时间换吞吐。在教学层面,你可以试着把单周期CPU改成五级流水线:取指、译码、执行、访存、写回。你会发现最大难点在于指令间的数据冒险和控制冒险。这是《计算机组成与设计》里最精华的内容,也是从“能跑”到“跑得快”的分水岭。

如果你有FPGA开发板,还可以把Logisim中的CPU翻译成Verilog,综合到实际芯片上。那种亲眼看自己设计的CPU在硬件上运行的感觉,比模拟器又爽一个数量级。手搓CPU这条路一旦走通,再看任何编程语言、操作系统底层的逻辑,你都会多一层“知其所以然”的底气。不要被几个月前连逻辑门都记不住的那个自己吓到,从第一个与非门开始,你现在就能动手。

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

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

立即咨询