第 5 章 · CPU 结构与数据通路

上一章我们把 ISA 当合同读,这一章掀开 CPU 的盖子,看合同怎么被执行。方法是跟着一条指令走完全程:就拿第 4 章那条 add a0, a0, a1(RISC-V 版的 a0 = a0 + a1)当导游,看它从内存里被捞出来、被翻译、被运算、把结果写回去的每一步经过哪些硬件。走完这一遍,「CPU 是个自动执行指令的机器」这句话就从口号变成了具体的电路和数据流,第 6 章的流水线也只是在这条路上多开几个窗口。

5.1 先认识里面的零件

CPU 内部的寄存器分两类,分界线是「汇编程序员看不看得到」。

看得见的(ISA 合同里白纸黑字):通用寄存器(x0 到 x31、rax 这些)、PC(存放下一条指令地址)、SP(栈指针)。第 10 章写汇编时你打交道的全是它们。

看不见的(微架构的内部零件):IR(指令寄存器,锁存刚取回来的那条指令)、MAR(存要访问的内存地址)、MDR(存和内存交换的数据)。这几个是本章新朋友,它们存在的理由只有一个:内存太慢,而且和 CPU 不同步。CPU 发出读请求后要干等,等待期间的中间状态必须有人保管;MAR、MDR 就是摆在 CPU 和内存边界上的「交接柜」,让「发出请求」和「收到结果」拆成两个互不干扰的阶段。跨异步边界的数据必须先寄存再使用,这是数字设计的老规矩,记下结论就行。

5.2 单周期数据通路:一条指令的流水账

把所有零件连起来就是数据通路(datapath)。跟着 add a0, a0, a1 走:

① 取指:  PC 的值送进 MAR,内存把指令吐到 MDR,再锁进 IR;
         同时 PC 加 4(指向下一条)
② 译码:  IR 里的操作码和三个寄存器号(x0? a0? a1?)被切出来,
         送进控制器的译码器
③ 读源:  寄存器文件按两个源寄存器号,同时读出 a0 和 a1 的值
④ 运算:  两个值进 ALU,操作码告诉它做加法,结果出来
⑤ 写回:  结果按目的寄存器号写回寄存器文件里的 a0

数据通路里的关键结构就三个,值得分别记一眼:

  • 寄存器文件:32 个寄存器打成的包,特点是「同时读两个、写一个」,一个周期内完成 a0 = a0 + a1 需要的全部寄存器访问。
  • ALU:第 3 章造好的那个多面手,这里负责真正的加法。
  • 多路选择器(MUX):数据通路上到处是岔路(ALU 的第二个输入可以来自寄存器,也可以来自指令里的立即数;写回的数据可以来自 ALU,也可以来自内存),MUX 就是扳道岔的,扳哪个方向由控制信号说了算。

控制信号从哪来?从译码器:它盯着 IR 里的操作码,算出这一拍每个 MUX 该选哪路、寄存器文件该不该写、ALU 该做哪种运算。整台机器就是「数据通路走数据,控制信号扳道岔」的两层皮。

5.3 控制器的两种做法

把操作码翻译成控制信号,有两种工程实现,风格截然相反。

硬布线:用组合逻辑门直接从操作码算出所有控制信号。快(一层门的延迟),但指令集一大,门电路规模爆炸,而且改一条指令等于改电路。

微程序:把每条指令的解释过程写成一段「微指令」序列,存在 CPU 内部的一块只读存储器(控制存储器)里,执行指令变成了运行这段微程序。灵活(修 bug 改微码就行,微码补丁这个概念就是这么来的),慢(要一遍遍查表)。

规律:简单的指令硬布线,复杂的指令微码。这是现代 x86 的实际做法:常用指令(加、移位、load)走硬布线一线天,冷门的复杂指令走微码慢慢解释。RISC 处理器指令少而简单,几乎全程硬布线。有意思的是这里出现了一个递归:微程序是「用程序解释指令」,和第 1 章讲的层次结构(每层都是下一层的解释器)是同一个思想在 CPU 内部的重演。

5.4 单周期和多周期:一笔时钟账

上面的流水账如果要求一个时钟周期内全部走完,就叫单周期实现。算一笔账就知道它亏在哪。

假设各阶段延迟:取指 2 ns,译码 0.5 ns,读寄存器 0.5 ns,ALU 1 ns,访存 2 ns,写回 0.5 ns。

  • 单周期:时钟周期必须迁就最慢的指令。一条 load(取指+译码+读寄存器+访存+写回)要走 5.5 ns,于是时钟周期定成 5.5 ns,主频约 180 MHz。而一条纯加法指令只用 4.5 ns,剩下的 1 ns 全在干等。
  • 多周期:把执行拆成 5 拍,每拍只做一个阶段,时钟周期按最慢的单个阶段定,2 ns(主频 500 MHz)。加法指令 4 拍完成,load 5 拍。单条指令的 CPI 变大了,但每拍短了。

两种方案的净收益要看总时间,多周期通常赢,而且部件还能复用(一个 ALU 前一拍取指算 PC+4,后一拍执行算加法,省一套硬件)。但真正的答案是第三种:流水线。它把多周期「每拍更短」和「各阶段硬件专用化」组合起来,让多条指令在通路里重叠行进,周期短而吞吐接近每拍一条,这是第 6 章的全部内容。

单周期多周期
时钟周期由谁决定最慢指令最慢阶段
CPI14~5
部件复用无(冗余)有
命运教学模型流水线的前身

5.5 指令周期的标准姿势

把上面的流程抽象成教材术语,一条指令的生命周期分四段:取指、间址(如果操作数还要再访存一次取地址,比如间接寻址)、执行、中断(每条指令结束时检查有没有中断请求,第 9 章的主角)。考试爱考这个划分,工程上你只需要记住它对应的是流水线五级(取指、译码、执行、访存、写回)的雏形:第 6 章把这些「阶段」各自配上专属硬件,让五条指令同时在场。

另外一个伏笔:PC = PC + 4 是顺序执行的全部秘密。jmp、call、分支说到底就是「把别的值写进 PC」,再无魔法。第 6 章你会看到,让流水线头疼的分支问题,根源就是这个「PC 有时会被改写」。

练习

  1. 走一遍:把 ld a0, 0(a1)(从 a1 指向的内存加载到 a0)在 5.2 的数据通路上走一遍,标出哪几步用 ALU、哪一步用了 MAR 和 MDR,和 add 指令的路径比,多了哪段、少了哪段。
  2. 手算:用 5.4 的延迟数据,算单周期机跑「10% load + 90% 加法」的程序和 5 拍多周期机跑同一程序,各需要多少 ns;再算流水线(理想 CPI 1、周期 2 ns)的答案。三个数字摆在一起,你就懂为什么要流水线了。
  3. 思考:MAR/MDR 为什么必须存在?假如内存和 CPU 同速同同步,它们能不能省掉?这个假设在现代计算机上为什么不成立(往第 7 章想)?
  4. 思考:微码既然能修改( Intel 发过微码安全补丁),那「CPU 的行为」是不是也可能被恶意改写?搜一搜「microcode update」的安全争议,想想 ISA 合同和微码实现之间的关系。