跟随每一步
CPU 与冯诺伊曼结构
在屏幕上输入一个计算,过一会儿就看到了答案。中间发生的事却被藏起来了:输入去了哪里?机器在哪里做运算?做完以后,又怎样把结果送回来?这一页把机器打开,让我们沿着信息走一遍。
程序是一组让计算机完成任务的指令。处理器能够执行的指令很具体,例如读取一个数、把两个数相加、保存结果。与其一次背下所有部件的名称,不如选一条指令,看它当前在哪里、接着需要谁来处理。图上的部件会随着这条路线逐个有了用途;忘了某个名字,也可以回到它刚才做的事来认它。
如果你读过时间复杂度,那里数的是算法的工作量,这里解释这些工作怎样由机器完成。看清一条指令以后,我们再讨论多条指令重叠执行。动画特意放慢,方便停下来追踪信息;其中的秒数并不代表真实处理器的速度。
给 CPU 一个任务:读入一个数,加 5,显示答案
假设你输入 7。要得到 12,计算机不仅需要这个 7,还需要指令告诉它去读取、相加和输出。冯诺伊曼存储程序结构把指令与数据保存在同一块主存储器中。CPU,也就是中央处理器,读取并执行这些指令。
内存包含许多带编号的位置。位置的编号叫地址,里面保存的信息叫内容。请求地址 12,就像选择第 12 号抽屉,并不代表抽屉里一定放着数字 12。
信息用二进制位表示,每一位是 0 或 1。总线是一组传送信号的连接。地址总线指出访问哪里,数据总线携带二进制位,控制信号说明 READ(读)或 WRITE(写)等操作。指令本身也会以二进制位的形式经过数据总线。因此下方颜色区分的是信号的作用,不是三种不同的指令。
CPU 怎样知道下一步该做什么?
假设刚读到的指令是“加上地址 12 里的数”。我们既要记住这条指令,又要留住已经输入的数;否则,读入新数时就可能覆盖仍然需要的内容。因此,CPU 内部准备了几个小而快的存储位置,叫寄存器。有的留住指令,有的留住正在计算的数,还有的记住下一条指令在哪里。装置中的简称给这些存储位置起了名字。下面的说明会解释每个简称,让我们能跟随一次传送,看清哪些值被保存、哪些值被移动。
CPU 从内存拿到指令,这叫取指。拿到后,它要辨认指令要求的操作和操作数,也就是这次使用的值或位置,这叫译码。信息确定后,CPU 就可以执行指令。每个阶段可能包含几次更小的传送,这些传送叫微操作。下面列出了五条指令;按“播放本步”,就会演示当前这次传送。初始状态一直保留,直到你主动推进。每次传送后,可以找出值从哪里来、到了哪个部件,并检查哪些值没有变化。你可以重播本次传送或回看上一步,也可以主动开启连续播放。连续播放中的四秒停留只是观察时间,不是必须读完的时限。
先给每个暂存的值分清职责
| 简称 | 需要 | 暂存位置 |
|---|---|---|
| PC | 下一条指令去哪里读? | 程序计数器:保存那个内存地址。 |
| MAR | 此刻要访问哪一个内存位置? | 内存地址寄存器:暂存这次选中的地址。 |
| MDR | 这次经过内存连接的是什么值? | 内存数据寄存器:暂存这次正在传送的值。 |
| IR | 当前正在执行哪条指令? | 指令寄存器:留住当前这条指令。 |
| ACC | 正在计算的数存在哪里? | 累加器:保留当前计算使用的数。 |
这些小存储位置还不会自己决定下一步。控制单元 CU 负责安排操作;算术逻辑单元 ALU 才对数做加法等运算。下面这个教学处理器用 ACC 保存结果,真实处理器也可以有其他寄存器组织方式。
例如,输入 7 后执行 ADD [12],意思是“把地址 12 里的数加上来”。若那里存着 5,计算前 IR 留着 ADD [12],MAR 留着 12,MDR 接住 5,ACC 仍留着 7。等 ALU 算完,ACC 才变成 12。地址 12 与结果 12 碰巧同数,却承担不同职责。
运行之前:这段程序要做什么
我们要把输入值与内存地址 12 中的数相加。初始输入为 7,地址 12 中存着 5,因此预期得到 12。地址 0—4 存放下面五条指令。地址是位置编号,位置里保存的数才是内容。
- 01
IN把输入值读入累加器 ACC。初始情况下,ACC 变成 7。 - 02
ADD [12]读取内存[12],让算术逻辑单元 ALU 把它与 ACC 相加,再把结果存回 ACC。初始情况下,7 + 5 得到 12。 - 03
STORE [13]把 ACC 复制到内存地址 13,保存计算结果。 - 04
OUT把 ACC 送到输出设备,输出设备保留这个结果。 - 05
HALT停止读取后续指令。
每条指令都按“取指 → 译码 → 执行”展开。模型停在初始状态,直到你主动点“播放本步”。传送完成后,先检查数值,可以重播或退回,再决定是否继续。熟悉后可以开启连续播放;每次传送后会留出阅读时间,模型离开视野时停止推进。
选定下一条指令
PC 保存下一条指令的地址。先沿琥珀色数据通路把这些位送入 MAR,随后 MAR 才通过蓝色地址总线向内存送出地址。
0功能剖面 · 信号已放慢以便观察
板上各部件:IN 输入 · OUT 输出 · RAM 内存 · PC 程序计数器 · MAR 地址寄存器 · MDR 数据寄存器 · IR 指令寄存器 · CU 控制单元 · ACC 累加器 · ALU 算术逻辑单元。
蓝色选定内存地址;琥珀色传送数据位,也包括寄存器之间复制的地址;紫色传送 READ 等控制信号。一个光点代表一次传送,演示中的传输时间已大幅放慢。
查看内存和数字的表示方式
地址和数值使用十进制。0x 表示十六进制字,每一位对应四个二进制位。同一块内存的 0 至 4 号位置保存指令,12 号保存第二个数,13 号保存答案。其余位置初始为零,程序没有使用它们。
| 地址 | 字 | 含义 |
|---|---|---|
| 0 | 0x100 | IN |
| 1 | 0x20C | ADD [12] |
| 2 | 0x30D | STORE [13] |
| 3 | 0x400 | OUT |
| 4 | 0x500 | HALT |
| 12 | 0x005 | 5 |
| 13 | 0x000 | 0 |
选择部件:为什么需要它?
PC · 程序计数器
记住下一条指令的地址,否则 CPU 就不知道从哪里继续。这个按字寻址的机器中,每条指令占一个字,所以顺序执行时 PC 加 1。
这个三维模型把功能部件展开,留出空间以便跟踪信号,并非某一款商业芯片的晶体管版图。本机使用一个累加器、12 位数据字、4 位地址空间,以及明确的输入/输出指令。现代 CPU 的具体组织方式可以很不一样。
内存里存的是二进制位,ADD 又是从哪里来的?
一个二进制位只有 0 和 1 两种取值。固定数量的位组成一个字,这里一个字有 12 位。我们用前四位作为操作码 opcode,说明要做什么;后八位作为操作数字段。对 ADD 和 STORE,操作数字段的低四位指定 16 个内存位置中的一个,其余四位为零。
| 便于阅读的指令 | 操作码 | 操作数字段 | 含义 |
|---|---|---|
IN | 0001 | 00000000 | 输入 → ACC |
ADD [12] | 0010 | 00001100 | ACC + 内存[12] → ACC |
STORE [13] | 0011 | 00001101 | ACC → 内存[13] |
OUT | 0100 | 00000000 | ACC → 输出 |
HALT | 0101 | 00000000 | 停止取指 |
从右往左,二进制各位置表示 1、2、4、8、16……,每向左一位就乘 2。因此 00001100 中只有表示 8 和 4 的两位是 1,数值就是 。十六进制用一个符号表示 0 至 15,10 至 15 分别写 A、B、C、D、E、F,所以 12 写作 C。前缀 0x 表示十六进制:0x00C、二进制 000000001100 和十进制 12 表示同一个数。 例如 0010 选择 ADD,00001100 表示 12。因此 0010 00001100 的含义是“加上地址 12 中保存的值”。IR 保存这些位,控制单元对它们译码。ADD 是这个编码便于人阅读的名字,并不是硬件在理解英语单词。
为什么保存指令和数据的寄存器要分开?
执行 ADD [12] 时,MDR 需要从内存接收数字 5。如果指令只保存在 MDR,读取 5 就会把指令覆盖。所以取指时先把指令复制进 IR,接下来读取数据也不会丢掉正在执行的操作。
时钟协调各个存储值何时变化。一条程序指令不一定等于一个时钟周期:取指、译码与执行可以需要多个周期。动画放慢了传送,以便检查;它播放了多少秒,并不是实际 CPU 速度。
下一条指令一定要等前一条全部完成吗?
现在我们已经能跟随一条指令。设想把它的工作分给五个阶段:第一条指令离开取指阶段后,这个阶段就可以取下一条;同时,第一条在另一个阶段被译码。这种重叠执行的安排叫流水线。它是一种实现方式,不是冯诺伊曼结构必然要求的设计。
从内存读取指令。
辨认操作并读出输入。
做运算,或计算地址。
按指令要求读写数据。
把结果保存到目标寄存器。
接下来的演示改用一台五阶段流水线机器,用 R1、R2……多个寄存器代替单一累加器。Ri 表示寄存器组中的第 i 个位置。每行是一条指令,每列是一个时钟周期。阶段之间的金色寄存器组就是流水线寄存器,用来在时钟边沿之间保存中间值、目标寄存器编号和控制位。
| 指令 / 周期 → | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| I1: R1 ← R2 + R3 | IF | ID | EX | MEM | WB | |||
| I2: R4 ← R6 + R5 | IF | ID | EX | MEM | WB | |||
| I3: R7 ← R8 + R9 | IF | ID | EX | MEM | WB | |||
| I4: R10 ← R11 + R12 | IF | ID | EX | MEM | WB |
不同指令可以同时使用不同阶段
第 3 个周期中,I1 正在执行、I2 正在译码、I3 正在取指。阶段之间的流水线寄存器保存各条指令的中间信息,使下一周期可以分别向前推进,不把它们混在一起。
先假设每个阶段都用一个周期,各指令相互独立,而且没有等待。四条指令分开执行要 个周期。重叠后,第一条用 5 个周期,后面 3 条每周期完成一条,总共 8 个周期。对 k 条指令:
第一条指令仍然需要五个周期。改善的是吞吐量,也就是一段时间内完成多少条指令。切换数据依赖的例子,就会看到真实流水线为什么不总能每周期完成一条。需要的结果尚未就绪时就必须等待,除非有结果前递等机制,可以更早安全地提供它。
取指和读数据能同时使用同一块内存吗?
如果内存只有一个访问端口,就不能。MEM 阶段的读取或存储可能与 IF 取指发生资源冲突,需要等待或使用不同的存储组织。表中的指令只对寄存器做加法,它们在 MEM 阶段只是传递已有值,不访问内存,所以这个例子没有假设单端口内存可以同时完成两次访问。
检查:蓝色地址信号携带的是答案吗?
不是。它指出一个位置;数据信号携带内容,控制信号说明读还是写。要存储一个答案,这三种作用都需要。
