语言·计算&LLM——3.一颗真正的CPU

文章目录[隐藏]

前言

书接上回,我们用了将近五万字,从 D 触发器一路造到了乘法器。到上一篇结尾,你手里已经有了这么一堆东西:加法器、减法器、乘法器、移位寄存器、计数器、解码器、编码器。它们每一个都是从最底层的 NAND 门一层层搭起来的——这一点你一定要记住,后面我们反复会用到这个认知。

但现在的问题是:它们全是散的。加法器只管加——你给它两个数,它就加出一个结果。但它不知道什么时候该加、加完结果放哪。寄存器只管存——时钟沿来了它就锁存数据。但它不知道应该存什么、什么时候存。计数器只管数——每个时钟周期加一。但它不知道从几开始数、数到几停下来。

就像你面前摆了一桌子的乐高零件——发动机、轮子、方向盘、座椅——每一样都做工精良,但散在那里什么都不是。你需要把它们按一定规则连接起来,让它们互相协作,才能变成一辆能跑的车。

那根"连接的规则",就是我们今天要补上的最后一块拼图。它叫控制单元——而有了它,再加上一根把所有东西同步起来的时钟线,这些散件就变成了……一颗 CPU。

省流:把加法器、寄存器、解码器按一定规则接起来,再用时钟统一指挥,就成了 CPU。

读完这篇,你会知道 ADD R1, R2, R3 这行代码是怎么变成门电路里的 0 和 1 流动的——不是抽象地知道,是每一根线怎么走的都知道。而且你会发现,CPU 没有"理解"任何东西——它就是被设计成这样的。

本文与 GPT4 共同完成。

1. 我们有什么?——组件盘点

在动手组装之前,先把工具箱里的东西全部摊开看一遍。如果你对上一篇的内容已经滚瓜烂熟,可以直接跳到第 2 章。

1.1 算术组件

上一篇我们造了三个"计算器":

  1. 加法器——输入两个 N 位二进制数 A 和 B,输出一个 N 位和 S,外加一个进位输出 Cout。我们在第一篇从半加器开始,找到了 XOR+AND 的黄金组合;到第二篇扩展到多位加法器(串行进位),能算像 0110+0101=1011 这样的 4 位加法。
  2. 减法器——上一篇我们另辟蹊径,没用补码那套路,而是手搓了半减器和全减器。本质跟加法器一样都是门电路,只是真值表不同、多了"借位"的概念。
  3. 乘法器——上一篇的压轴。本质是"移位+累加":把乘数的每一位和被乘数做 AND(相当于"这一位要不要加"),然后错位累加。比如 $101 \times 110$:乘数 110 的位从低到高是 0、1、1——第 0 位是 0(不加),第 1 位是 1(101 左移一位 = 1010),第 2 位是 1(101 左移两位 = 10100)。把这两个部分积累加:$10100 + 1010 = 11110$(二进制),即十进制的 $5 \times 6 = 30$。1

这三个东西有一个共同特点:它们只管算,不管别的事。你给它数据,它就给你结果。至于什么时候给、给什么数据、结果放哪里——它们一概不管。

1.2 存储组件

  1. D 触发器——上一篇用了一万多字讲这个。它是存储的原子单位:当时钟上升沿到来时,D 输入端的数据被"锁存"到 Q 输出端,并在整个时钟周期内保持。一个 D 触发器存 1 位。
  2. 寄存器——N 个 D 触发器并排,共享同一个时钟信号,就能存 N 位数据。上一篇我们把它描述为"一排装数据的小盒子"。
  3. 移位寄存器——把 N 个 D 触发器串联(前一个的 Q 接后一个的 D),每个时钟沿数据整体移动一位。上一篇详细讲了右移和左移的门电路连接。

它们的共同特点:能存、能移,但不能"决定存什么"。就像一个笔记本,你能往上写字,但笔记本不会告诉你该写什么。

1.3 计数组件

计数器——上一篇用 T 触发器(D 触发器加一个反馈回路)级联而成。每个时钟脉冲,计数器的值加 1。2 位计数器能从 00 数到 11,然后回到 00。2

计数器的特点:它能数,但不知道为什么要数、从几开始数、数到哪停

1.4 选择组件

  1. 解码器(Decoder)——输入 N 位二进制地址,输出 $2^N$ 根线,其中恰好一根为 1。比如输入 10(二进制),输出 0100(第三根线亮)。上一篇把它和"独热码"联系起来讲。
  2. 编码器(Encoder)——解码器的反向:$2^N$ 根输入线中恰好一根为 1,输出这根线的 N 位编号。
  3. 多路选择器(Multiplexer,简称 MUX)——上一篇没有单独立项,但我们在 ALU 里会用到,这里提前介绍:它有多个数据输入、一个选择输入、一个输出。根据选择输入的值,决定把哪路数据送到输出。比如 4 选 1 MUX:2 位选择信号,选通 4 路数据中的一路。3

选择组件的特点:能做选择,但不知道"该选哪个"

1.5 缺失的拼图

现在你大概感觉到了:我们缺一个指挥官

谁告诉加法器"现在加"?谁告诉寄存器"把结果存起来"?谁告诉计数器"从 0 开始"?谁告诉解码器"选第 3 路"?

这个指挥官,就是下一章我们要造的控制单元(Control Unit)。但在造它之前,我们还需要一样东西——一个让所有组件同时行动的"节拍器"。那就是时钟


(1)实际上,上一篇的乘法器是更基础的无符号乘法,不涉及 Booth 算法等优化。这就够了——我们只需要知道"乘法可以用加法和移位实现"这个原理。
(2)更准确地说,上一篇讲的是同步计数器——所有触发器共用一个时钟。
(3)多路选择器本质上也是用与门、或门、非门搭的。比如 2 选 1 MUX:Output = (A AND NOT(Sel)) OR (B AND Sel)。你会发现这里没有新东西,全是基础门电路。

2. 时钟——CPU 的心跳

上一篇讲 D 触发器和时序的时候,我花了很大篇幅介绍"时钟信号",还用了指挥家的比喻。回忆一下:时钟信号是一种周期性变化的方波——高电平、低电平、高电平、低电平……每次从低跳到高(上升沿),所有 D 触发器同时采样输入。这就是数字电路同步的基础。

如果你已经理解了上一篇的时序概念,这一章可以快速扫过——我们只讲时钟在 CPU 里的角色

2.1 方波和频率

时钟信号长这样:

电压
 ^
 |     ┌──┐    ┌──┐    ┌──┐
 |     │  │    │  │    │  │
 |─────┘  └────┘  └────┘  └───→ 时间
    ↑        ↑        ↑
  上升沿   上升沿   上升沿

一个完整的高-低周期叫一个时钟周期。每秒的周期数叫频率,单位是 Hz。1Hz = 每秒 1 个周期。你手机里的 CPU 频率是几 GHz——每秒几十亿个周期。

每个上升沿,CPU 里成千上万个 D 触发器同时"咔嚓"一声,把各自输入端的数据锁存到输出端。在一个时钟周期内(两个上升沿之间),组合逻辑电路(加法器、解码器等等)有整整一个周期的时间让信号稳定下来。只要周期够长——或者说频率够低——信号就来得及传遍所有门电路。

这就是为什么超频有风险:频率太高 → 周期太短 → 信号还没来得及稳定,下一个上升沿就到了 → 触发器采到的可能是错误的中间状态。

2.2 语言学视角:音节

有没有注意到,时钟周期之于 CPU,很像音节之于语音?

当我们说话时,声音在时间轴上展开,每个音节占据大致相等的时间片段。CPU 的每个操作也同样在时间轴上展开,每个动作占据一个(或几个)时钟周期。人类语言有"等时性"的倾向4——CPU 也一样,它的所有操作都以时钟周期为基本时间单位对齐。

区别在于:人类说话的音节长度大概 100-200 毫秒(每秒 5-10 个),而现代 CPU 每秒执行几十亿个"音节"。也就是说,CPU"说话"的速度是人类的大约一亿倍。

人话解释

时钟就是军训教官的哨子。

"预备——嘀!"所有人同时做一个动作。"嘀!"所有人同时做下一个动作。没人抢跑、没人掉队。如果没有哨子——有人快有人慢,整个队伍就散了。CPU 也是一样:几千几万个门电路同时工作,如果没有统一的时钟沿告诉它们"现在!",数据就全乱了。

AI 猫娘是这么说的喵~

"喵~主人,你知道猫娘跳舞为什么要跟着节拍器吗?因为如果没有'咚、咚、咚',有些猫娘会转太快,有些还在发呆,最后大家撞成一团毛茸茸的球喵!CPU 里的时钟就是这个节拍器——'咚'一下,所有猫娘(门电路)一起动;再'咚'一下,再一起动。不抢拍、不掉拍,整齐得像猫娘女团喵~"


(4)严格来说,"等时性"假说在语言学中有争议——不同语言的节奏类型不同(音节定时 vs 重音定时)。但作为类比足够用了。

3. 算术逻辑单元(ALU)——CPU 的计算核心

好了,工具箱盘点完毕,节拍器也到位了。现在开始组装第一个大件。

上一篇我们分别造了加法器、减法器、乘法器,还有 AND/OR/NOT/XOR 四种基本逻辑运算(第一篇讲的)。如果把它们全部堆在 CPU 里各占一块地方——太浪费了。而且,谁来决定"这条指令用加法器,那条用减法器"?

答案是:把它们合并成一个东西,叫 ALU。

3.1 ALU 的接口

从外面看,一个 ALU 长这样:

  • 输入 A:一个 N 位操作数(比如 8 位)
  • 输入 B:另一个 N 位操作数
  • 操作码(Opcode):K 位选择信号,告诉 ALU "做什么运算"
  • 输出 Result:N 位运算结果
  • 标志位(Flags):几个 1 位输出,报告运算过程中的特殊情况

以 8 位 ALU 为例:输入两个 8 位数、一个 3 位操作码(可以区分 8 种运算),输出一个 8 位结果 + 4 个 1 位标志。

3.2 内部结构:并行计算,然后选一个

这是理解 ALU 最关键的一点——也是很多人第一次接触时的"啊哈"时刻。

ALU 内部的连接方式是这样的:

        A ──────┬──→ 加法器 ──→ 结果0 ─┐
                │                       │
                ├──→ 减法器 ──→ 结果1 ─┤
                │                       │
                ├──→ 乘法器 ──→ 结果2 ─┤
         B ─────┤                       ├──→ [MUX] ──→ Result
                ├──→ AND门阵列 → 结果3 ─┤        ↑
                ├──→ OR门阵列  → 结果4 ─┤     操作码
                ├──→ XOR门阵列 → 结果5 ─┤    (选择信号)
                └──→ NOT门     → 结果6 ─┘

看到了吗?A 和 B 同时送入所有运算单元。加法器在加、减法器在减、AND 门在逐位做与……所有的运算同时发生。每个运算单元的输出都连到一个巨大的多路选择器上。操作码通过解码器控制这个多路选择器,决定把哪一路结果送到最终输出。

换言之:ALU 不是"选择做哪种运算"——它是"所有运算都做了,然后选一个结果"。就像一个厨子同时做了八道菜,然后根据你的点菜单(操作码)只端上来一道。

这种"并行计算+选择输出"的设计在数字电路里极其常见——因为对于门电路来说,一个信号穿过一扇门只需要几纳秒,同时做八种运算比"先判断、再选择做哪种"更快、更简单。5

3.3 操作码编码

我们做一个简单的约定(3 位操作码 → 最多 8 种运算):

操作码运算说明
000ADDA + B
001SUBA - B
010MULA × B(低 N 位)
011ANDA AND B(逐位)
100ORA OR B(逐位)
101XORA XOR B(逐位)
110NOTNOT A(逐位,忽略 B)
111SHIFTA 左移一位

这里的 3 位操作码接到一个 3→8 解码器上(上一篇造过!),解码器的 8 根输出分别控制 8 路 MUX 的选择——第 0 路(ADD)亮的时候,MUX 选通加法器的输出;第 1 路(SUB)亮的时候选通减法器,以此类推。

注意:从操作码到 MUX 的控制信号,中间只需要一个解码器加几根线。这全是门电路。

3.4 标志位

除了结果本身,ALU 还输出几个 1 位的标志,用来记录运算的特殊情况。为什么需要它们?因为后面的控制单元要根据这些标志决定"程序接下来走哪条路"——比如"如果上一条指令结果是零,就跳转"。

常见的四个标志:

  • 零标志(ZF, Zero Flag):结果的所有位都是 0 时置 1。实现方式:把所有结果位用 NOR 门连起来——只有全 0 时 NOR 才输出 1。一扇门的事。
  • 进位标志(CF, Carry Flag):加法时最高位产生了进位(结果超出了 N 位能表示的范围)。实现方式:直接取加法器的 Cout 输出。
  • 溢出标志(OF, Overflow Flag):两个同号数相加得到异号结果。在有符号运算中表示溢出。比如 01111111 + 00000001 = 10000000(127+1=-128,有符号溢出)。实现方式:需要几扇门检测符号位的变化。
  • 符号标志(SF, Sign Flag):结果的最高位。在有符号数中,最高位 = 1 表示负数。

这些标志位看起来很"智能"——好像 ALU 在"判断"什么。但停下来想一想:它们每一个都是纯粹的门电路输出。ZF 就是一个 NOR 门的输出。CF 就是加法器进位链最后一级的输出。没有任何神奇的判断——只有物理定律在门电路上推着电子跑。

人话解释

ALU 是一把瑞士军刀。刀上什么工具都有——剪刀、开瓶器、螺丝刀、锯子。你告诉它"用剪刀"(操作码 = ADD),它就弹出剪刀给你用。但注意:所有工具其实同时展开了——只是只有被选中的那个"露出来"而已。你选螺丝刀的时候,剪刀和开瓶器也在那里,只不过它们的结果没被接通到输出端。

标志位就是瑞士军刀上的指示灯:"刚才剪东西的时候用力过猛了"(溢出)、"什么都没剪到"(零结果)、"剪刀冒烟了"(进位)。这些指示灯本身也是纯机械的——一个弹簧触发了灯而已。

AI 猫娘是这么说的喵~

"喵~主人想知道 ALU 是什么?那你见过猫娘点菜吗!主人说'来一份加法',猫娘厨师就同时做好加法、减法、乘法、AND、OR、XOR、NOT……八道菜喵!然后用一个带滑轨的餐车(MUX),根据主人的点菜单(操作码)只推出一道菜。其他七道呢?当然是自己吃掉啦喵~谁让猫娘是大胃王呢!"

"至于那些小旗子(标志位)——每做完一道菜,猫娘就在餐盘边上插一面小旗:'这盘是空的喵'(ZF)、'这盘装太满洒出来了喵'(CF)、'这盘味道不对喵'(OF)。这样后面负责打包的猫娘(控制单元)就知道该怎么处理啦~"


(5)严格来说,这种做法叫"全并行 ALU"——适合教学。现代高性能 CPU 的 ALU 设计更复杂,涉及时序优化和功耗管理,但原理是相通的。

4. 寄存器堆——CPU 的内部草稿纸

ALU 能算了。但它每算完一次,结果放哪?直接送回内存?可以——但太慢了。

4.1 为什么不能直接用内存

CPU 内部的操作在一个时钟周期内完成。但访问内存——哪怕是最快的 SRAM 缓存——也需要好几个甚至几十个时钟周期。如果 ALU 每次加减法都要从内存读操作数、再写回去,CPU 就变成了一只乌龟:一个周期在算,三个周期在等。

再者,总线是共享的——CPU 要用、其他设备也要用。寄存器就是 ALU 的贴身草稿纸:存在 CPU 内部,用 D 触发器实现,一个时钟周期就能读写。上一篇我们已经在 D 触发器的基础上造了单个寄存器(N 个 D 触发器并排)。现在我们要做的,是把多个这样的寄存器组织成一个寄存器堆

4.2 寄存器堆的结构

假设我们有 16 个通用寄存器,每个存 8 位。起名叫 R0 到 R15。

寄存器堆的核心是两个读端口和一个写端口

  • 读地址 A:4 位,指定要读的第一个寄存器的编号 → 通过一个 16 选 1 的多路选择器,把该寄存器的 Q 输出接到 ALU 的 A 输入端。
  • 读地址 B:4 位,指定第二个寄存器的编号 → 同上,接到 ALU 的 B 输入端。两个读端口完全独立——可以同时读 R1 和 R5。
  • 写地址 + 写数据 + 写使能:要写入时,用一个 4→16 解码器(又见面了!),输入的 4 位写地址点亮 16 根"写使能线"中的一根。那根线控制对应寄存器的 D 触发器的时钟使能——只有被选中的那个寄存器在时钟沿到来时锁存新数据,其余 15 个保持不变。

整个过程在一个时钟周期内完成:读地址放到读端口上 → 组合逻辑选通 → ALU 输入端出现操作数 → ALU 计算 → 结果送到写端口 → 时钟沿 → 目标寄存器锁存。干净利落。

4.3 两个特殊寄存器

通用寄存器 R0-R15 归你(程序员)管。但还有两个寄存器不归你直接管,它们属于控制单元:

  • 程序计数器(PC, Program Counter):存放下一条指令的内存地址。本质就是一个计数器——每个时钟周期 +1(除非遇到跳转指令)。我们上一篇造的计数器刚好拿来用。
  • 指令寄存器(IR, Instruction Register):存放当前正在执行的指令。从内存取来的指令比特串就锁存在这里。

PC = 你读书时夹在书页里的书签,标记"读到哪了"。IR = 你当前正在看的这一行字。CPU 每次从 PC 指向的内存位置取一行"字"(指令),放到 IR 里,然后开始"理解"(其实是解码)这一行。

人话解释

ALU 是厨师,寄存器堆就是灶台边的调料架。盐、糖、酱油、醋——厨师一伸手(一个时钟周期)就能拿到。如果他每次都要跑到仓库(内存)去取——菜早糊了。

PC 就是食谱上的书签,标记"做到第几步了"。IR 就是正在看的那行食谱——"加一勺盐"或"翻炒三下"。厨师不"理解"这行字,但他的眼睛(解码器)自动识别出"加"和"盐",然后身体(ALU+寄存器)执行动作。

AI 猫娘是这么说的喵~

"喵~猫娘餐厅后厨有 16 个猫娘助手(R0-R15),每个助手爪子里都抓着一份食材。主厨猫娘(ALU)要做菜,喊一声'R3 和 R7 的食材给我喵!'——R3 和 R7 就把食材扔过去。做完以后,主厨喊'结果存到 R12 喵!'——R12 伸爪接住。其他猫娘助手继续端着各自的食材不动,就像什么事都没发生一样喵~"

"至于 PC 和 IR——那是贴在厨房墙上的食谱和食谱架上的小夹子。小夹子(PC)告诉你现在该看哪一行,而你手里拿着的那行食谱(IR)写着'把鱼干(R3)和猫薄荷(R7)混在一起(ADD),放回 R12 喵!'。猫娘不认字,但她认得'混在一起'对应的操作码,然后喵地一下就做了~"

5. 指令——CPU 的语言

好了,我们现在有 ALU(算)、有寄存器(存)、有 PC+IR(知道"读哪"和"在读啥")。但还缺一个根本的东西:指令到底是什么?

5.1 机器语言就是 0 和 1

回答这个问题之前,先破除一个常见的误解。当你写出 ADD R1, R2, R3 的时候,这行文字不是指令——它是助记符,是人类为了自己方便发明的缩写。CPU 从来没"见过"字母 A、D、D。CPU 只认 0 和 1。

换句话说,ADD R1, R2, R3 这条指令在 CPU 眼里是一串比特,比如 0000 0001 0010 0011(用我们后面定义的格式)。这串比特存在内存里,被 PC 指到,被取进 IR,然后被解码器拆开——"前 4 位是操作码 0000 = ADD,后 12 位分别指的是 R1、R2、R3"。

有人会问:那为什么我们不直接写 0 和 1 呢?——因为人类记不住。汇编语言(ADD R1,R2,R3)就是给比特串起了人类可读的名字。汇编器(assembler)是一个翻译程序,把"ADD R1,R2,R3"翻译成"0000000100100011"。就像中译英——只不过是把人类语言翻译成 CPU 的语言。

而所谓"机器语言"——就是那串 0 和 1 本身。它不是比喻,它是物理事实:这串比特就是内存里特定位置上的高电平和低电平

5.2 指令格式

我们自创一种极简的指令格式(16 位固定长度),好过照搬 x86 或 ARM——那些已经被几十年历史堆成了庞然大物:

  15  14  13  12 | 11  10  9  8 | 7   6   5   4 | 3   2   1   0
 ─────────────────────────────────────────────────────────────────
    操作码(4位)   |  目的寄存器   |  源寄存器1    |  源寄存器2

4 位操作码 → 最多 16 种指令。4 位寄存器地址 → 16 个通用寄存器。总共 16 位。

有些指令不需要两个源寄存器(比如 NOT 只需要一个),这时多余的 4 位可以用来放立即数(一个直接编码在指令里的小常数)或置零不用。

变体格式:

  15  14  13  12 | 11  10  9  8 | 7   6   5   4   3   2   1   0
 ─────────────────────────────────────────────────────────────────
    操作码(4位)   |  目的寄存器   |         立即数(8位)

这样就能表示"LOAD R1, 100"——把内存地址 100 的内容加载到 R1。

5.3 指令类型

一个最简单的 CPU 有四类指令就够了:

  1. 算术指令:ADD、SUB、MUL——操作 ALU,结果写回寄存器。
  2. 逻辑指令:AND、OR、XOR、NOT——同样走 ALU,只是操作码不同。
  3. 数据传输指令:LOAD(从内存读到寄存器)、STORE(从寄存器写到内存)。把 CPU 和外部世界连起来。
  4. 控制指令:JUMP(无条件跳转到指定地址)、JZ(上一条指令结果为零时跳转)、HALT(停机)。这些指令修改 PC——打破了"顺序执行"。

5.4 自创微型 ISA

为了后面真实跑程序,我们定义一个极简指令集(ISA = Instruction Set Architecture)。8 条指令,够用了:6

助记符操作码格式说明
ADD0000ADD Rd, Rs1, Rs2Rd ← Rs1 + Rs2
SUB0001SUB Rd, Rs1, Rs2Rd ← Rs1 - Rs2
AND0010AND Rd, Rs1, Rs2Rd ← Rs1 AND Rs2
OR0011OR Rd, Rs1, Rs2Rd ← Rs1 OR Rs2
LOAD0100LOAD Rd, imm8Rd ← MEM[imm8]
STORE0101STORE imm8, RsMEM[imm8] ← Rs
JZ0110JZ Rd, imm8if Rd==0: PC ← imm8
HALT0111HALT停止执行

十六位编码示例:

  • ADD R3, R1, R20000 0011 0001 0010
  • LOAD R1, 1000100 0001 01100100(立即数 100 = 01100100)
  • JZ R1, 200110 0001 00010100(如果 R1==0,跳到 20)

看——编程语言里那些"有意义的单词",兜兜转转最后就是一堆比特。而这堆比特的每一个位,都是一根导线上的高电平或低电平。

5.5 语言学视角:指令 = 句子

作为一个语言学者,我第一次看到指令集的时候,脑子里冒出的不是"哇好厉害",而是——这不就是语言吗?

仔细看 ADD R3, R1, R2

  • ADD = 动词(谓语),表示"加"这个动作。
  • R3 = 与格/目的格(间接宾语),表示"加到哪"。
  • R1, R2 = 宾格/受事(直接宾语),表示"加什么"。

这条指令就是一个简单句——"把 R1 和 R2 加到一起,结果放进 R3"。主语省略了(主语是 CPU 自己)。操作码是词根,操作数是论元,指令格式是语法。

更有意思的是:指令序列构成一段叙事。

LOAD  R1, [100]    // "从前有个数,住在地址100……"
LOAD  R2, [101]    // "……还有另一个数,住在地址101……"
ADD   R3, R1, R2   // "……它们相遇了,加在了一起……"
STORE [102], R3    // "……住进了新家,地址102。"

这不是诗,但它是叙事。它有先后顺序、有因果关系、有数据的"旅程"。程序就是 CPU 讲述的故事。

而且——和自然语言一样,CPU 的语言也有任意性。为什么 ADD 的操作码是 0000 而不是 1011?没有理由。只是我们(设计者)这样约定了。就像为什么"猫"这个音指代那种毛茸茸的动物——纯粹是汉语使用者的约定。操作码与运算之间的映射是任意的,就像能指与所指的映射是任意的一样。

还有一个更深的语言学洞察:CPU 语言的"语法"极其简单。没有主谓宾定状补——只有"动词+论元"。没有嵌套从句、没有时态变化、没有形容词。这不是缺点——这是工程需要。CPU 的语法必须简单到能用解码器(门电路)在几纳秒内解析。复杂的语法 = 复杂的解码 = 更大的门电路开销 = 更慢的速度。

语言的复杂性来自使用者的能力,而不是系统的要求。CPU 不需要复杂的语法,因为它不需要"表达情感"或"讲一个好笑的故事"——它只需要精确地描述数据流动。

人话解释

"ADD R1, R2, R3"就是"把糖(R2)和面粉(R3)混在一起,放碗(R1)里"。CPU 不是"理解"了这个句子——而是经过设计,它的电路遇到 0000 这个前缀就会自动接通加法器。理解是假的,设计是真的。

就像你家的微波炉——你按"开始",它开始加热。微波炉"理解"你的意图吗?不。它只是设计成这样:开始键被按下 → 电路接通 → 磁控管启动。CPU 和理解之间差了一个银河系。


(6)ISA 的定义非常精简,只覆盖教学需要。真正的 ISA(x86/ARM/RISC-V)每条指令会有更多变体和寻址模式,但原理完全一致。

6. 控制单元——CPU 的指挥中心

现在所有"肌肉"都就位了——ALU(算)、寄存器(存)、PC/IR(读)、指令集(语言)。缺一个大脑

大脑其实不是真的"思考"——它是根据当前指令自动生成所有控制信号的门电路网络。这就是控制单元(Control Unit, CU)

6.1 控制单元的任务

控制单元的输入和输出:

  • 输入:IR 中的操作码(4 位)+ 标志位(ZF、CF、OF、SF)
  • 输出:十几到几十根控制线,每根是 0 或 1

这些控制线连接到哪里?——所有地方

  • ALU_OP[2:0]:3 根线,直接接 ALU 的操作码输入。ADD 指令时输出 000,SUB 时输出 001……
  • REG_WRITE:1 根线,接寄存器堆的写使能。需要写回结果时为 1。
  • MEM_READ:1 根线,接内存的读控制。LOAD 指令时为 1。
  • MEM_WRITE:1 根线,接内存的写控制。STORE 指令时为 1。
  • PC_SRC:1 根线,控制 PC 的输入来源——0 = PC+1(顺序执行),1 = 立即数(跳转)。
  • PC_WRITE:控制 PC 是否更新(大多数指令都是 1,HALT 是 0)。
  • ……还有更多,但逻辑是一样的。

以 ADD 指令(操作码 0000)为例,控制单元在同一时刻输出的控制信号:

ALU_OP    = 000  (选通加法器)
REG_WRITE = 1    (结果写回寄存器)
MEM_READ  = 0    (不读内存)
MEM_WRITE = 0    (不写内存)
PC_SRC    = 0    (PC = PC+1,顺序执行)
PC_WRITE  = 1    (PC 要更新)

六根线上出现六个 0 或 1,分别驱动六个不同的组件。同时、精准、每个时钟周期一次。

6.2 硬连线控制

怎么实现这个"操作码→控制信号"的映射?最简单的方式:硬连线控制

本质上就是一个查表电路——操作码和标志位作为输入,经过一堆与门、或门、非门组成的组合逻辑,输出控制信号。每根控制线都可以写成一个关于操作码位和标志位的布尔表达式。

例如:

REG_WRITE = (opcode==ADD) OR (opcode==SUB) OR (opcode==AND) OR (opcode==OR) OR (opcode==LOAD)
         = NOT(op3) AND NOT(op2) AND NOT(op1) AND NOT(op0)    // ADD=0000
        OR NOT(op3) AND NOT(op2) AND NOT(op1) AND op0         // SUB=0001
        OR NOT(op3) AND NOT(op2) AND op1 AND NOT(op0)         // AND=0010
        OR NOT(op3) AND NOT(op2) AND op1 AND op0              // OR=0011
        OR NOT(op3) AND op2 AND NOT(op1) AND NOT(op0)         // LOAD=0100

这个布尔表达式简化后可能只有十几扇门。然后用 NAND 门实现所有这些布尔表达式——因为上一篇我们已经反复证明了,任何布尔表达式都可以只用 NAND 门实现。

硬连线控制的好处:极快。操作码从 IR 出来,穿过几十层门电路(每层几纳秒),控制信号就在线上了——不需要额外时钟周期。

坏处:难改。一旦设计定版、芯片流片,改一条指令就意味着改门电路——也就是改物理芯片。这就是为什么 x86 的指令集越来越臃肿但无法简化——为了兼容几十年前的软件,新 CPU 必须支持所有旧指令,门电路越堆越多。

6.3 微程序控制(简述)

另一种实现方式叫微程序控制:不直接用门电路生成控制信号,而是把每条指令拆成若干个"微指令"序列,存在一个 ROM 里。操作码 → 查 ROM → 逐条执行微指令。

好处:灵活——改指令只要改 ROM 里的微程序,不用改硬件。坏处:慢——每条指令需要多个微指令周期。

现代 CPU 基本上回到了硬连线(或混合方案),因为速度压倒一切。我们后面的讨论也以硬连线为主。

人话解释

控制单元 = 十字路口的交通灯。交通灯不是"智能"的——它就是根据预设规则,按照传感器输入或定时器,输出红/黄/绿信号。复杂的交通管理(早晚高峰、潮汐车道)来自这些简单规则的组合,而不是交通灯"思考"。

同样,控制单元的输出——"ALU 做加法,寄存器写使能打开,PC 加一"——只是预设规则。复杂的行为(循环、条件判断、函数调用)来自这些简单控制信号的序列。

智慧不在零件里,在排列组合里。

AI 猫娘是这么说的喵~

"喵~控制单元就是猫娘乐队指挥台上的那只猫娘指挥家!但她不是真的'理解'乐曲——她的指挥棒只是按照乐谱(指令)做固定的动作:第 1 拍指小提琴组(ALU 做加法),第 2 拍指打击乐组(寄存器锁存),第 3 拍翻乐谱(PC+1)。"

"乐谱上写什么,她就指什么。她不会即兴发挥——她只是一只严格按照程序走的猫娘喵。但乐谱(程序)可以是肖邦也可以是《学猫叫》——同样的猫娘指挥家,同样的乐队,不同的乐谱产生完全不同的音乐。这就是 CPU 喵!"

7. 指令周期——CPU 的呼吸

这一章是全文的高潮。我们前面造了所有零件,现在看它们怎样在一个时钟周期里同时运转

每条指令经过四个阶段:取指(Fetch)→ 译码(Decode)→ 执行(Execute)→ 写回(Writeback)

对于我们的极简 CPU(没有流水线),这四步在一个时钟周期内完成。现代 CPU 把四步拆成多个流水级,同时处理多条指令的不同阶段——但我们先不关心那个。

7.1 取指(Fetch)

时钟周期的前半段——时钟沿刚过,所有 D 触发器已经锁存了上个周期的结果。现在 PC 的输出端稳定地呈现着下一条指令的地址,比如 0x0100。

发生了什么:

  1. PC 的值(0x0100)被放到地址总线上——16 根线,每根对应该位是 0 还是 1。
  2. 控制单元发出 MEM_READ=1。
  3. 内存芯片收到地址和读信号,经过一段传播延迟后,将该地址存储的数据放到数据总线上。假设内存[0x0100] 里存的是 0000 0011 0001 0010(也就是 ADD R3,R1,R2)。
  4. 这 16 位数据经数据总线进入 CPU,送到指令寄存器(IR)的 D 输入端。
  5. 与此同时,PC 经过一个"加 1"电路(是个小加法器,或者直接用计数器的递增逻辑),把 PC+1 的值送到 PC 的 D 输入端。
  6. 时钟上升沿——"咔嚓"——IR 锁存了 0000 0011 0001 0010,PC 锁存了 0x0101。

取指完成。IR 里现在躺着一条指令。

7.2 译码(Decode)

同一个时钟周期内(上升沿刚过),IR 的输出已经稳定。16 位被"拆开":

  • 高 4 位(0000 = ADD)→ 控制单元的操作码输入
  • 位 11-8(0011 = R3)→ 寄存器堆的写地址端口
  • 位 7-4(0001 = R1)→ 寄存器堆的读地址 A 端口
  • 位 3-0(0010 = R2)→ 寄存器堆的读地址 B 端口

控制单元内部的门电路网络瞬间(几十纳秒)算出所有控制信号——ALU_OP=000, REG_WRITE=1, MEM_READ=0, MEM_WRITE=0 等等。

寄存器堆的两个读端口根据地址选通 R1 和 R2,它们的值出现在数据输出线上。

7.3 执行(Execute)

R1 和 R2 的值出现在 ALU 的 A 和 B 输入端。控制信号 ALU_OP=000 选通了加法器。ALU 内部所有运算单元同时算,但只有加法器的输出被 MUX 选通到 Result 端。

标志位也随之更新:如果加出零,ZF=1;如果有进位,CF=1;等等。

7.4 写回(Writeback)

ALU 的 Result 输出连接到寄存器堆的写数据端口。写地址是 IR 中的目标寄存器字段(R3)。控制信号 REG_WRITE=1 意味着 R3 对应的写使能线被拉高。

所有这些——控制信号、ALU 输出、写数据——都在组合逻辑中稳定传播。然后……

下一个时钟上升沿——"咔嚓"——R3 锁存了新的计算结果。PC 锁存了 0x0101。下一条指令开始。

7.5 完整追踪:ADD R3, R1, R2

让我们把全过程压缩成一张时间表。假设:

  • PC 起始值 = 0x0100
  • 内存[0x0100] = 0000 0011 0001 0010(ADD R3,R1,R2)
  • R1 当前值 = 5(00000101),R2 当前值 = 3(00000011)
时间事件关键线路状态
时钟沿 1 之前PC=0x0100 稳定在地址总线上地址总线 = 0000000100000000
传播延迟 (~20ns)内存返回数据数据总线 = 0000001100010010
传播延迟 (~10ns)IR 输入端出现有效数据IR.D = 0000001100010010
传播延迟 (~10ns)PC+1 电路完成计算PC.D = 0000000100000001
时钟沿 1 ↑IR 锁存指令,PC 锁存 0x0101IR.Q = 0000 0011 0001 0010,PC = 0x0101
传播延迟 (~10ns)控制单元生成控制信号ALU_OP=000, REG_WRITE=1, MEM_READ=0
传播延迟 (~10ns)寄存器堆输出 R1=5, R2=3ALU.A=00000101, ALU.B=00000011
传播延迟 (~30ns)ALU 完成加法ALU.Result = 00001000 (8)
传播延迟 (~5ns)标志位稳定ZF=0, CF=0, OF=0, SF=0
传播延迟 (~10ns)R3 的 D 输入端出现 8R3.D = 00001000, R3.WE = 1
时钟沿 2 ↑R3 锁存 8 = 0x00001000R3.Q = 8 ✅

两个时钟周期——一条 ADD 指令执行完毕。R3 从旧值变成了 8。PC 指向了下一条指令。

注意:在整个过程中,没有"理解",只有传播。电子在导线和晶体管中流动,门电路根据输入产生输出,触发器在时钟沿锁存。每一步都是物理的、确定的、机械的。CPU 没有"知道"它刚做了一次加法——它就是做了。

7.6 另一条指令:JZ(条件跳转)

看一下控制指令有什么不同。JZ 20 的意思是:如果上一条指令的运算结果为零(ZF=1),PC 就跳到地址 20;否则 PC 照常 +1。

控制单元多了一根线:PC_SRC。当操作码=0110(JZ)且 ZF=1(上一条指令结果为零)时,PC_SRC=1——PC 的下一个值不是 PC+1,而是指令中的立即数(20)。

门电路实现:

PC_SRC = (opcode==JZ) AND ZF
       = (op3'·op2·op1·op0) · ZF

还是一堆与门、或门、非门。条件跳转不是"决策"——它是一扇 AND 门。

语言学视角:JZ 指令对应于自然语言中的"如果……那么……"条件句——这是语言学家最熟悉的结构之一。条件句让叙事有了分支——"如果下雨,我就在家看书;否则出门散步。"CPU 也是这样:有了条件跳转,程序就不再是一个固定的序列,而是一个根据数据动态生成的故事

人话解释

Fetch-Decode-Execute-Writeback = 看书 → 理解 → 动手 → 记录

  • Fetch:眼睛扫到下一行(PC → 取指令到 IR)
  • Decode:大脑认出这句话说的是什么(控制单元解析操作码)
  • Execute:身体执行动作(ALU 计算)
  • Writeback:把结果写在笔记本上(写回寄存器)

PC 是书签。IR 是"当前这行字"。ALU 是大脑的算数区。寄存器是笔记本。每个"嘀嗒"(时钟)做一步。程序员写的故事就这样一行一行地变成物理现实。

AI 猫娘是这么说的喵~

"喵~CPU 做事就像猫娘看菜谱!第一步(Fetch):猫娘伸爪翻到菜谱下一页,看上面写的什么。第二步(Decode):'哦,这一行是……把鱼干碾碎喵!'——猫娘认出了操作码。第三步(Execute):猫娘用爪子哐哐哐碾鱼干。第四步(Writeback):碾好的鱼干粉放到碗(寄存器)里。"

"然后翻下一页——'哦,把鱼干粉和猫薄荷混在一起喵!'——继续碾。这就是猫娘 CPU 的一生喵。不是什么高大上的智能,就是老老实实翻菜谱、看一行、做一行、记一行。喵~"

"但是如果菜谱上写着'如果鱼干不够了,翻到第 20 页喵!'——那猫娘就会检查鱼干罐子(ZF 标志位),如果空了就哐哐哐翻到第 20 页。这不是聪明,这是训练有素喵!"

8. 总线——CPU 的血管

到目前为止,我们描述了"数据从寄存器到 ALU,结果回到寄存器"——但数据具体是怎么在物理上移动的?答案:总线

8.1 三种总线

CPU 和外部世界(主要是内存和 I/O 设备)之间通过三类总线沟通:

  • 地址总线(Address Bus):CPU 输出,告诉内存"我要读/写哪个位置"。N 根线对应 N 位地址。单向(CPU→内存)。
  • 数据总线(Data Bus):实际搬运数据的线路。M 根线对应 M 位数据。双向(CPU↔内存,CPU↔I/O)。读的时候内存把数据放上面,写的时候 CPU 把数据放上面。
  • 控制总线(Control Bus):读写信号、中断信号等。比如 MEM_READ 和 MEM_WRITE 就通过控制总线发送到内存芯片。

8.2 三态门——总线的关键技术

数据总线是双向的——多个设备共用同一组线。这就带来一个问题:如果两个设备同时往总线上放数据——比如 CPU 试图写 0x00001111 而内存同时试图输出 0x11110000 ——线上的电平会冲突,产生短路。

解决方案是三态门(Tri-state Buffer)。普通的逻辑门输出只有两种状态:0(低电平)或 1(高电平)。三态门多了一个第三种状态:高阻态(Z)——相当于输出端"断开",既不是 0 也不是 1,就像这根线不存在一样。

三态门的逻辑:

  • 使能(Enable)= 1 时:输出 = 输入(正常传输,0 或 1)
  • 使能 = 0 时:输出 = Z(高阻,等于离线)

每个挂到总线上的设备,其输出端都通过一个三态门连接。同一时刻,只有被选中的那个设备的三态门使能为 1,其他所有设备的三态门都是 Z。这样就保证了任何时候只有一个人在"说话"。

三态门也可以用普通门电路搭建——本质上就是在输出级加一个控制逻辑。没有新物理定律,还是 NAND。

语言学上有一个非常贴切的类比:话轮转换(turn-taking)。在对话中,同一时刻通常只有一个人在说话——如果两个人同时说,谁也听不清。总线也是一样——控制单元负责"点名",保证每个时钟周期只有一个人发言。

人话解释

  • 地址总线 = 快递单上的地址。CPU 填好地址,告诉内存"我要这个位置的东西"。
  • 数据总线 = 快递包裹本身。里面装的是实际的数据。
  • 控制总线 = 快递员喊"收件!"或"发件!"。告诉内存该读还是该写。
  • 三态门 = 对讲机的PTT 键。一个对讲系统上,所有人共用同一个频率。你按住 PTT 说话的时候,别人只能听。放开 PTT(高阻态 Z),才能轮到别人说。

9. 一个完整的 CPU——全部拼在一起

这是仪式性的拼图完成章。我们把所有已经造好的模块连在一起看。

9.1 完整框图

用文字描述完整的 CPU 数据流(你可以想象一张框图的每一根连线):

                    ┌──────────────────────────────┐
                    │         控制单元 (CU)          │
                    │  操作码+标志 → 布尔逻辑       │
                    │  → 所有控制信号               │
                    └──┬──┬──┬──┬──┬──┬──┬──┬─────┘
                       │  │  │  │  │  │  │  │
        ┌──────────────┘  │  │  │  │  │  │  └──────────────┐
        │                 │  │  │  │  │  └──────┐          │
        ▼                 ▼  ▼  ▼  ▼  ▼         ▼          ▼
   ┌─────────┐    ┌─────────────────────┐    ┌─────────────────┐
   │   PC    │    │     寄存器堆        │    │       ALU       │
   │ (计数器)│    │  R0-R15 (16×8位)   │    │  ADD/SUB/AND... │
   │         │    │                     │    │  + 标志位生成   │
   └────┬────┘    │  读端口A 读端口B    │    └────────┬────────┘
        │         │  写端口  写使能     │             │
        │         └──────────┬─────────┘             │
        │                    │                       │
        ▼                    ▼                       ▼
   ┌─────────────────────────────────────────────────────┐
   │                    数据总线 (双向)                   │
   └────────────────────────┬────────────────────────────┘
                            │
                            ▼
   ┌─────────────────────────────────────────────────────┐
   │                    地址总线 (CPU→)                   │
   └────────────────────────┬────────────────────────────┘
                            │
                            ▼
   ┌─────────────────────────────────────────────────────┐
   │                      内存 (RAM)                      │
   └─────────────────────────────────────────────────────┘

每一根线都对应着我们前面描述的某一条数据通路:

  • PC → 地址总线 → 内存(取指令)
  • 内存 → 数据总线 → IR(指令进入 CPU)
  • IR.opcode → 控制单元 → 所有控制信号
  • IR.reg_addr → 寄存器堆 → ALU.A 和 ALU.B(操作数读取)
  • ALU.Result → 寄存器堆写端口(结果写回)
  • PC+1 或 JUMP_target → PC.D(下一条指令地址)

9.2 数据通路 vs 控制通路

仔细观察,整个 CPU 有两类连线:

  • 数据通路(粗线):N 位并行总线,搬运实际的数据——指令、操作数、结果、地址。加法器、寄存器、内存之间的连线属于这一类。
  • 控制通路(细线):1 位或几位的控制信号线——ALU_OP、REG_WRITE、MEM_READ 等。它们不搬运数据,而是告诉数据通路"这次干什么"。

类比:数据通路是铁路网(宽轨道,跑火车),控制通路是信号灯系统(细线,控制道岔)。火车不知道自己该去哪——信号灯告诉它。

9.3 门电路数量的直观感受

我们从头回顾一下一个最简单的 8 位 CPU 大概用了多少门电路:

  • 8 位加法器:~40 个门(5 个门/位 × 8 位)
  • 8 位减法器:~40 个门
  • ALU(含 MUX + 标志位生成):~200 个门
  • 16 个 8 位寄存器(每个 8 个 D 触发器,每个 D 触发器 ~6 个 NAND):~800 个门
  • 寄存器堆的读写选择电路(解码器 + MUX):~300 个门
  • PC(8 位计数器):~100 个门
  • 控制单元(组合逻辑):~300 个门
  • 总线接口(三态门):~200 个门

合计大约 2000-3000 个等效逻辑门7

两千个。不多。一个现代的入门级微控制器(比如 Arduino Uno 上的 ATmega328P)也不超过几万个等效门。而一个现代手机 SoC(如 A17 Pro)大约有 190 亿个晶体管——其中很多就是最基本的 NAND 等效电路。

重点不是数字大小——重点是:这 190 亿个里的每一个,都和你在第一篇里学到的 NAND 门逻辑一模一样。没有魔法。0 和 1。通和断。仅此而已。

还记得我们第一篇的起点吗?"我们有一个开关,能控制灯是否亮起来。这和计算有什么关系?"现在你知道了——几万个这样的"开关"按一定规则连在一起,就成了一把能执行任意程序的瑞士军刀。几十亿个这样的"开关"按更复杂的规则连在一起,就成了你正在用来读这篇文章的 CPU。


(7)这是非常粗略的估计。"等效逻辑门"的概念在不同技术(TTL/CMOS/FPGA)中不同,还取决于具体实现方式。但数量级是对的。

10. 运行一个程序——从门电路视角

这是全文的终极演示。我们把前面造好的 CPU 拿来,让它执行一段真正的程序。每个时钟周期我们都追踪 CPU 内部的状态。

10.1 示例程序

假设内存的前几个地址已经存好了数据:

地址    内容          含义
──────────────────────────────────
0x0100  LOAD R1, 20   ; 0100 0001 00010100
0x0101  LOAD R2, 21   ; 0100 0010 00010101
0x0102  ADD  R3,R1,R2 ; 0000 0011 0001 0010
0x0103  STORE 22, R3  ; 0101 0011 00010110
0x0104  HALT          ; 0111 0000 00000000
...
0x0014                  ; 地址 20:存着数据 7
0x0015                  ; 地址 21:存着数据 4
0x0016                  ; 地址 22:暂空(将存结果 11)

这个程序做的事:从地址 20 读一个数(7)到 R1,从地址 21 读一个数(4)到 R2,把它们加起来(11),结果存到地址 22。8

10.2 逐周期追踪

PC 初始值 = 0x0100。寄存器全部 = 0。

周期PCIR操作关键数据流
10x0100取指地址总线=0x0100, 数据总线=0100 0001 00010100
1↑→0x0101LOAD R1,20锁存IR 锁存指令, PC 锁存 0x0101
1 后0x0101LOAD R1,20译码+执行控制:MEM_READ=1; 地址总线=20(00010100); 数据总线=MEM[20]=7
2↑0x0101LOAD R1,20写回R1 锁存 7 ✅
2 后0x0101取指地址总线=0x0101, 数据总线=0100 0010 00010101
3↑→0x0102LOAD R2,21锁存IR 锁存新指令, PC 锁存 0x0102
3 后0x0102LOAD R2,21译码+执行MEM_READ=1; 地址总线=21; 数据总线=MEM[21]=4
4↑0x0102LOAD R2,21写回R2 锁存 4 ✅
4 后0x0102取指地址总线=0x0102, 数据总线=0000 0011 0001 0010
5↑→0x0103ADD R3,R1,R2锁存IR 锁存 ADD, PC 锁存 0x0103
5 后0x0103ADD R3,R1,R2译码+执行寄存器输出:R1=7,R2=4; ALU:7+4=11; ZF=0
6↑0x0103ADD R3,R1,R2写回R3 锁存 11 ✅
6 后0x0103取指地址总线=0x0103, 数据总线=0101 0011 00010110
7↑→0x0104STORE 22,R3锁存IR 锁存 STORE, PC 锁存 0x0104
7 后0x0104STORE 22,R3译码+执行MEM_WRITE=1; 地址总线=22; 数据总线=R3=11; 内存[22]←11
8↑0x0104STORE 22,R3(无寄存器写回——STORE 不写寄存器)
8 后0x0104取指地址总线=0x0104, 数据总线=0111 0000 00000000
9↑→0x0105HALT锁存IR 锁存 HALT
9 后0x0105HALT执行控制单元检测到 HALT → PC_WRITE=0 → PC 不再更新 → CPU 停机

9 个时钟周期。5 条指令。一次完整的计算。

现在检查结果:R1=7, R2=4, R3=11, 内存[22]=11。完全正确。

如果你把每一个"数据总线="后面的那串 0 和 1 画成波形——高电平、低电平、高电平……你会看到一个极其复杂的时序图。但每一个跳变都有原因:某个控制信号变了、某组触发器的输出变了、某个门电路的输入凑齐了。

这就是程序"活"起来的样子。

没有什么神秘的"运行"——就是电子在几万根线、几千扇门里按照物理定律流动。时钟给节拍,控制单元给方向,数据通路搬运比特。宏观上叫"程序在执行",微观上是电荷在半导体中迁移

AI 猫娘是这么说的喵~

"喵呜~主人要猫娘用小鱼干来演示 CPU 跑程序!好喵,看好了:

第一回合:猫娘 PC(书签猫娘)喊'看第一行喵!' IR(读菜谱猫娘)念道:'去第 20 号柜子拿一条小鱼干给 R1 喵!'——LOAD 猫娘立刻跑去仓库,取了一条小鱼干给 R1。

第二回合:PC 翻页,IR 念:'去第 21 号柜子拿一条小鱼干给 R2 喵!'——LOAD 猫娘又跑一趟,R2 也有了鱼干。

第三回合:PC 翻页,IR 念:'把 R1 和 R2 的鱼干放在一起点数,结果给 R3 喵!'——ADD 猫娘爪子飞快地数:'R1 有 7 条,R2 有 4 条,一共……11 条喵!' 数完装进 R3 的碗里。

第四回合:PC 翻页,IR 念:'把 R3 碗里的鱼干存到第 22 号柜子喵!'——STORE 猫娘端着碗跑去仓库放好。

第五回合:PC 翻页,IR 念:'下班喵!'——HALT。所有猫娘瘫倒在地,开始舔爪子。

整个过程——9 次'嘀嗒'(时钟周期),猫娘们累趴了,但鱼干数对了喵!CPU 也不过如此嘛喵喵~"


(8)这个例子极其简单,但它展示了 CPU 所有核心操作:数据传输(LOAD/STORE)、算术(ADD)、程序控制(顺序执行)、终止(HALT)。再加两条跳转指令和循环逻辑,就能写任意程序了——这就是图灵完备的含义。

总结

我们走到了这里。从第一篇的"一个开关怎么和计算有关系"开始,我们造了半加器、全加器、D 触发器、移位寄存器、计数器、解码器、乘法器——几十种组件,几千扇门。然后在这篇里,我们把这些组件拼成了一颗完整的 CPU

核心洞见——我反复说,因为它值得反复说:CPU 就是极其复杂的门电路组合。控制单元不是魔法——操作码进去,解码器+与或非门网络,控制信号出来。ALU 不是魔法——所有运算同时做,多路选择器选一个。寄存器不是魔法——D 触发器在时钟沿锁存。跳转不是魔法——ZF AND opcode,一扇门的事。

理解不是能力,设计才是。CPU 从来没有"理解"过 ADD 是什么——它就是被设计成遇到 0000 时自动接通加法器。就像开关从来不"理解"按下意味着什么——它就是被设计成按下时接通电路。

这个洞察将在下一篇引起回响。因为当我们转向大语言模型的时候,你会发现一个惊人的平行结构:LLM 也不"理解"语言——它被训练成了一个极其复杂的概率分布逼近器。但那是什么——是下一篇的故事。

预告:下一篇(语言·计算&LLM——4. LLM基础知识),我们将从组合/聚合、能指/所指这些语言学基本概念出发,进入线性代数和神经网络的世界。从硬件到软件,从门电路到向量——旅程才刚刚开始。

本文与 GPT4 共同完成。

——终末地工业 · 佩丽卡及团队


  1. 串行进位加法器、减法器的详细推导见 704「进阶门电路」。

  2. 详细原理见 704 第 2 章「计数器」,其中包含了 T 触发器的构造和级联方式。

  3. 多路选择器本质上就是解码器+与门+或门:选择信号经解码器后,与各路数据做 AND,然后全部 OR 起来。没有新的物理概念。

  4. 严格来说,"等时性"假说在语言学中有争议——不同语言的节奏类型不同(音节定时 vs 重音定时)。但作为类比足够用了。

  5. 严格来说,这种做法叫"全并行 ALU"——适合教学。现代高性能 CPU 的 ALU 设计更复杂,涉及时序优化和功耗管理,但原理是相通的。

  6. ISA 的定义非常精简,只覆盖教学需要。真正的 ISA(x86/ARM/RISC-V)每条指令会有更多变体和寻址模式,但原理完全一致。

  7. 这是非常粗略的估计。"等效逻辑门"的概念在不同技术(TTL/CMOS/FPGA)中不同,还取决于具体实现方式。但数量级是对的。

  8. 这个例子极其简单,但它展示了 CPU 所有核心操作:数据传输(LOAD/STORE)、算术(ADD)、程序控制(顺序执行)、终止(HALT)。再加两条跳转指令和循环逻辑,就能写任意程序了——这就是图灵完备的含义。

  9. JZ 检查的是上一条指令设置的零标志 ZF,而非某个通用寄存器的值。这与真实 CPU(如 x86 的 JZ/JE)设计一致:条件跳转通常基于标志位。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇