← 上一章:第4章 | 📖 目录 | 下一章:第6-8章 →


第 5 章 中央处理器 CPU —— a+b 真正被”一步一步”执行的地方

a+b 在这一章:前四章把舞台搭好了——a、b 存成补码、住进内存、变成了机器指令。现在主角 CPU 登场,它要做的事极其机械却极其精确:取指令 → 翻译指令 → 执行指令,一拍一拍地,把 LOAD a / ADD b / STORE 真正跑完。这是全书最重、最难、分值最高的一章,请慢读。


5.1 CPU 的功能和组成

【知识点·CPU 四大功能】

  • 🎓 指令控制(按序控制程序流程,首要任务)、操作控制(产生并送出各部件操作信号)、时间控制(对各操作定时)、数据加工(算术逻辑运算,基本任务)。
  • 🌱 CPU 就是个超级守纪律的工头:①按顺序派活 ②对每个工人发出”动手”信号 ③掐着秒表控制节奏 ④亲自干计算的活。

【知识点·CPU 组成】

        ┌────────────────────── CPU ──────────────────────┐
        │  运算器                控制器                     │
        │  ┌──────┐    ┌──────────────────────────────┐    │
        │  │ ALU  │    │ PC 程序计数器 (下条指令地址)  │    │
        │  │通用寄 │    │ IR 指令寄存器 (当前指令)      │    │
        │  │存器R0│    │ 指令译码器 ID                 │    │
        │  │..R3  │    │ 时序产生器                    │    │
        │  │ DR   │    │ 操作控制器 OC                 │    │
        │  │ PSW  │    │ AR 地址寄存器                 │    │
        │  └──────┘    └──────────────────────────────┘    │
        │            (+ Cache)                              │
        └───────────────────────────────────────────────────┘
  • 🎓 现代 CPU = 运算器 + Cache(高速缓存——比内存快得多的小容量存储器,位于 CPU 内部)+ 控制器。六大寄存器:PC(Program Counter,程序计数器,存下条指令地址)、IR(Instruction Register,指令寄存器,存当前指令)、AR(Address Register,地址寄存器——暂存要访问的内存地址)、DR(Data Register,数据缓冲寄存器——数据进出 CPU 的中转站)、通用寄存器R0–R3、PSW(Program Status Word,程序状态字——存进/借位、溢出、零等标志的寄存器)。
  • 🌱 记住这”六大金刚”:PC=记着下一句在哪,IR=手里正读的这句,AR=要访问的内存门牌号,DR=进出数据的中转站,R=干活的草稿本,PSW=记录”刚才算溢出没、是不是0”的小本本。

【知识点·操作控制器与时序】

  • 🎓 数据通路=数据在部件间流动的路径;操作控制器(OC,Operation Controller)为数据通路提供各操作信号(硬连线 or 微程序两种实现);时序产生器对信号定时。
  • 🌱 操作控制器=指挥交通的红绿灯,决定”现在哪条路通、数据往哪走”;时序产生器=给红绿灯定时的时钟。

5.2 指令周期(核心中的核心)

【知识点·三个时间单位的包含关系】

指令周期 (取出并执行完一条指令的全部时间)
   ├── 取指周期 + 执行周期 (+ 间址周期 + 中断周期)
   └── 每个周期 = 若干个 机器周期(CPU周期)
            └── 每个机器周期 = 若干个 时钟周期(节拍/T)  ← 最小时间单位
  • 🎓 指令周期=取指到执行完的全部时间;机器周期(CPU周期)是其子段,常以访存时间为基准;时钟周期(节拍)是最基本的时间单位=主频倒数。不同指令的指令周期可不同。
  • 🌱 一条指令从头到尾=指令周期;把它分成几个大步骤=机器周期;每个大步骤里再分成几个最小节拍=时钟周期。像”做一道菜(指令周期)→切配/炒/装盘(机器周期)→每步里的一刀一铲(时钟周期)”。

【知识点·取指周期(所有指令都一样,公共操作)】

取指(以取我们的 ADD 指令为例):
 ① PC 的内容(指令地址)送到地址总线 → 启动读内存
 ② 从内存读出 ADD 指令 → 经指令总线 → 装入 IR 指令寄存器
 ③ PC 自动 +1 (指向下一条指令,为顺序执行做准备)
 ④ IR 中的操作码 OP 送译码器 → CPU 认出"这是一条 ADD 指令"
   至此取指结束,进入执行周期
  • 🎓 取指周期完成”按 PC 取指令入 IR、PC+1、操作码译码”,对所有指令都相同,故由公共微程序/逻辑实现。
  • 🌱 取指就是”照着 PC 这个门牌去内存把这句指令搬进手里(IR),门牌号顺手+1,然后看清这句是干嘛的(译码)”。

【知识点·执行周期(按指令不同而不同)—— 以 a+b 的 ADD 为例】

ADD 指令执行 (把 b 加到存着 a 的寄存器 R1):
 ① 操作控制器选 R1 为源、目的寄存器,从内存/寄存器取出 b
 ② OC 给 ALU 发"做加法"信号,R1(a) 与 b 送入 ALU
 ③ ALU 算出 a+b,结果经数据总线 → DR → 写回 R1
 ④ 同时把溢出/零等标志写入 PSW
   ADD 执行结束
  • 课本五条典型指令的数据通路要会画:
    • MOV(传送):寄存器→ALU(Arithmetic Logic Unit,算术逻辑单元——专门做加减乘除和逻辑运算的电路)→DR→目标寄存器。
    • LAD(取数 Load):把内存数据取入寄存器(我们的 LOAD a)。
    • ADD(加法):取指 + 执行加法(我们的核心)。
    • STO(存数 Store):把寄存器结果写回内存(我们的 STORE 和)。
    • JMP(转移):把目标地址送 PC(if/循环用)。

🌱 大白话总结指令周期:CPU 就是无限循环这两步——“去内存拿下一句话(取指)、照着这句话干活(执行)“,拿一句干一句,直到程序结束。我们的 a+b 就是这样被 LOAD→ADD→STORE 三句话跑完的。

【知识点·方框图语言】

  • 🎓 用方框(一个CPU周期内的操作)、菱形(判别测试)、~(公共操作)描述指令周期,是设计控制器的依据。
  • 🌱 就是把”取指-执行”流程画成流程图,方便照着设计电路。

5.3 时序产生器和控制方式

【知识点·时序信号的作用与体制】

  • 🎓 时序信号指挥机器按拍工作,并用来区分”取指(指令流)“与”执行(数据流)“;体制为电位-脉冲制。
  • 🌱 时序就是机器的”节拍器/鼓点”,所有部件踩着同一个鼓点动作;靠周期信息分清”现在搬进来的是指令还是数据”。

【知识点·三种控制方式】

  • 🎓 ①同步控制:统一时钟、定长机器周期,简单但简单指令浪费时间;②异步控制:每条指令用多久占多久、靠应答握手,效率高但控制复杂;③联合控制:大部分同步、少数异步(微程序控制器常用)。
  • 🌱 同步=全班统一做操踩点(整齐但快的人要等慢的);异步=各人做完自己喊”好了”(高效但乱);联合=大体统一、个别灵活。

5.4 微程序控制器(高频设计题)

核心思想:用”软件的办法”设计控制器——把控制信号编成一条条微指令存进只读的控制存储器,执行时一条条取出来产生控制信号。

【知识点·微命令、微操作、微指令、微程序】

  • 🎓 微命令=控制部件发出的最小控制信号;微操作=微命令对应的操作过程(二者一一对应);微指令=同一CPU周期内并行的若干微命令的组合(含操作控制字段+顺序控制字段);微程序=一串微指令,一条机器指令对应一段微程序。
  • 🌱 微命令=“开这扇门”这种最小指令;微指令=同一拍要同时下达的一组微命令打个包;微程序=完成一条机器指令(如ADD)所需的一整套微指令脚本。控制存储器 μCM(micro Control Memory,微控制存储器——只读的小型内存,专门存放微程序脚本)就是存这些脚本的只读小仓库。
微程序控制器工作流程:
 ① 开机→取指微程序入口(μCM 0号)→产生"取指令"的控制信号→机器指令进 IR
 ② 由 IR 的操作码 OP → 经微地址形成部件 → 得到该指令对应微程序的入口微地址
 ③ 从 μCM 逐条取微指令执行,每条自带"下一条微地址"
 ④ 该机器指令的微程序执行完 → 回到取指微程序入口 → 取下一条机器指令
   周而复始,直到程序结束

【知识点·微指令编码方式】

  • 🎓 ①直接表示法(每位对应一个微命令,直观但字长);②编码表示法(相斥微命令分段编码,字短但要译码、变慢);③混合表示法。
  • 🌱 直接法=每个开关一根线(清楚但线多);编码法=用”几位二进制选一个”(省线但要先翻译);混合=两者结合。

【知识点·微地址形成】

  • 🎓 入口地址由机器指令操作码经形成部件产生(多路转移);后继微地址:①计数器方式(μPC(micro Program Counter,微程序计数器——存放下一条微指令地址的寄存器)+1,简单但不能多路并行转移);②多路转移方式(按条件转移,灵活)。
  • 🌱 怎么知道下一条微指令在哪:要么”顺着往下数+1”,要么”看条件跳到对应分支”。

【知识点·水平型 vs 垂直型微指令】

  • 🎓 水平型:一次并行多个微命令,字长、微程序短、速度快、灵活但难写;垂直型:类似机器指令、一条只1~2个微操作,字短、微程序长、慢但易写。
  • 🌱 水平型=一行命令同时干很多事(高效但难编);垂直型=一行只干一件事(好懂但啰嗦)。

【知识点·动态微程序设计】

  • 🎓 用 EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦可编程只读存储器——上电就能改写内容的只读芯片)作控存可改写微程序→改变指令系统(可仿真别的机器),称动态微程序设计(相对静态)。
  • 🌱 把”脚本仓库”做成可改写的,就能临时换一套指令系统,甚至假装成另一台机器。

5.5 硬连线控制器(高频设计题)

【知识点·硬连线(组合逻辑)控制器】

  • 🎓 微操作控制信号由”指令操作码译码 · 节拍信号 · 状态条件”的布尔代数表达式,用门电路/触发器直接连线实现;追求元件少、速度快。
  • 🌱 微程序是”查脚本”,硬连线是”把逻辑直接焊成电路”——更快但改起来要重新焊,没有微程序灵活。RISC(Reduced Instruction Set Computer,精简指令集计算机)多用它。
某控制信号 = OP译码输出 · 节拍电位 · 节拍脉冲 · 状态条件
课本例: LDIR = M1·T4         (取指阶段第4拍打入指令寄存器)
        PC+1 = M1            (取指阶段让PC加1)
        LDR2 = M2·T4·ADD     (执行ADD指令时第2机器周期第4拍打入R2)
  • 🌱 意思就是:“只有在【取指阶段】的【第4拍】,才给指令寄存器发’装入’信号”——把每个信号该在啥时候出现,写成一串”与”的条件。

🔑 微程序 vs 硬连线对比(必考):微程序=存储逻辑、规整易改、较慢;硬连线=组合逻辑、快、难改。


5.6 流水 CPU(超高频,计算+分析)

【知识点·并行性的三种形式】

  • 🎓 ①时间并行(重叠/流水):多过程在时间上错开、轮流用同一套部件;②空间并行(资源重复):多套部件同时干;③时间+空间并行(如超标量,效益最好)。
  • 🌱 流水=洗车流水线,错开步骤轮流用设备;资源重复=直接开4条洗车道;两者结合=4条道每条还是流水线。

【知识点·指令流水线(IF取指-ID译码-EX执行-WB写回)】

流水线四段缩写:IF(Instruction Fetch,取指)、ID(Instruction Decode,译码)、EX(Execute,执行)、WB(Write Back,写回)。有些流水线还加一段 MEM(Memory access,访存——专门读写内存)。

非流水(串行): 指令1[IF ID EX WB] 指令2[IF ID EX WB] ...  慢
流水线:
  时钟→  1    2    3    4    5    6
  I1    IF   ID   EX   WB
  I2         IF   ID   EX   WB
  I3              IF   ID   EX   WB
  I4                   IF   ID   EX   WB
  → 流水线"装满"后,每个时钟周期就完成1条指令!
  IF取指 ID译码 EX执行 WB写回
  • 🎓 把指令执行分成若干段,每段一个部件、段间加锁存器,多条指令重叠执行;各段时间应尽量相等,否则会”堵塞/断流”。
  • 🌱 第1条指令在”译码”时,第2条已经在”取指”了——像工厂流水线,每个工位永不闲着,整体吞吐量翻倍。

【知识点·流水线性能公式(必考计算)】

k 段流水线处理 n 个任务:
  时钟周期数 Tk = k + (n−1)
  加速比 Ck = 串行时间 / 流水时间 = n·k / [k + (n−1)]
  (n 很大时 Ck → k,即理论上最多加速 k 倍)

【知识点·流水线三大相关(冲突)—— 必考】

  • 🎓 ①资源相关:多条指令同周期争用同一部件→延迟或增设部件;②数据相关:后条指令要用前条还没写完的数据:
    • RAW(Read After Write,写后读——后条指令读的寄存器,前条指令还没写完,最常见的数据冒险)、WAR(Write After Read,读后写)、WAW(Write After Write,写后写);简单顺序流水线只有 RAW。解决:推后读 或 设置直接通路 Forwarding(旁路,也叫数据旁路——把上条指令的计算结果直接”私线”送给下条指令,不等写回寄存器)。
    • ③控制相关:转移指令打乱顺序→延迟转移法(编译重排,“先执行再转移”)、转移预测法(硬件预测+目标指令Cache)。
  • 🌱 三种”撞车”:①两条指令同时要用同一个工具(资源);②第2条要用第1条还没算完的结果(数据,最常见);③遇到 if 跳转,不知道下一条该取哪条(控制)。
数据相关 RAW 示例:
  ADD R1,R2,R3   ; (R2)+(R3)→R1   ← R1 还没写回
  SUB R4,R1,R5   ; (R1)−(R5)→R4   ← 就急着读 R1 !会读到旧值
  解决: 加"旁路 Forwarding"把ALU结果直接送给下一条,不等写回

课本判型例:ADD R1,R2,R3 / SUB R4,R1,R5→RAW;STO M,R3 / ADD R3,R4,R5→WAR;MUL R3,.. / ADD R3,..→WAW。

【知识点·超标量 / 超流水】

  • 🎓 超标量:一个时钟周期发射多条指令(多条流水线,空间并行),如奔腾的U/V双流水线;超流水:把流水段进一步细分、提高时钟频率。
  • 🌱 超标量=同时开好几条流水线一拍出好几条指令;超流水=把每个工位再切细、鼓点打得更密。

5.7 RISC CPU

【知识点·RISC 机器特点】

  • 🎓 三要素:精简指令集 + 大量通用寄存器 + 优化流水线;特征:定长指令、寻址简单、只 Load/Store 访存、硬连线控制、平均每指令约1个周期、寄存器多。
  • 🌱 RISC 就是”指令少而简单、寄存器多、全靠流水线跑得快”的设计哲学,编译器更累但硬件更爽。

🔥 5.8 综合实操:跟着一条 ADD 指令,把第 5 章的所有知识”走一遍”

为什么要单设这一节? 第 5 章前面讲了一堆抽象的”周期”、“控制器”、“通路”,但你脑子里可能还是一团云。这一节我们用我们 a+b 程序里那条最普通的 ADD R3, R1, R2,把它从 PC 进位到结果写回,每一拍信号怎么走、控制器怎么发命令、微程序长啥样、流水线遇到冒险怎么办——全部画一遍。看完这一节,CPU 在你脑子里就”活”了。


5.8.1 准备:把我们这台”教学 CPU”的零件先摆出来

📌 先打消两个可能的迷糊:

① “总线” 是啥? 想成 CPU 内部的一条公共导线束(比如 32 根线一捆,能同时传 32 位)。多个寄存器都”挂”在它上面,但同一时刻只能有一个寄存器把数据”释放”上去(叫”驱动总线”),其他寄存器只能”收”——两个同时驱动就会电气短路(高电平和低电平在导线上打架),所以必须靠控制信号严格调度。这是单总线 CPU 的核心限制。

② “11 拍” 是怎么数出来的? 我们下面把这条 ADD 拆成 9 个真正干活的拍 (T1T9) + 大约 2 拍收尾(复位信号、把控制权交回取指公共段),合起来约 11 拍。重点看 T1T9,T10~T11 是工程上的”行政收尾”,不影响你理解原理。

我们假设一台简化的单总线 CPU,只看跟 ADD 相关的部件:

   ┌──────────────────────── 教学 CPU 内部 ───────────────────────┐
   │                                                                │
   │     ┌──────┐                  ┌──────┐                        │
   │     │  PC  │ ← 下条指令地址    │  IR  │ ← 当前指令              │
   │     └───┬──┘                  └───┬──┘                        │
   │         │                         │                            │
   │     ┌───┴─────────── 内部数据总线 ─────────────┴───┐           │
   │     │           ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃              │           │
   │     ↓           ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓              ↓           │
   │  ┌────┐       ┌──────────┐       ┌─────────┐    ┌────┐         │
   │  │ AR │       │ 寄存器组 │       │   ALU   │    │ DR │         │
   │  │地址│       │ R0 .. R3 │ ────→ │  + - &  │ →  │数据│         │
   │  └─┬──┘       └────┬─────┘  ↑    └────┬────┘    └─┬──┘         │
   │    │               │         │         │           │            │
   │    │               │      暂存 Y       ↓         (出/入数据)    │
   │    │               │   (ALU第二输入)  PSW                       │
   │    │                                  (标志)                    │
   │    │                                                            │
   │    └─→ 通过地址总线 ──→ 内存 ←─── 数据通过数据总线 ←────────────┘
   └────────────────────────────────────────────────────────────────┘

   关键约定(重要!):
   ① 单总线 → 同一拍只能有一个部件"放数据上总线"、一个或多个"从总线收"
   ② 寄存器之间的传送:源→总线→目的,需要"写使能"信号触发
   ③ ALU 是"组合电路",输入一变它的输出立刻变;但要"锁住"输出,得加一拍写到目的寄存器

我们 a+b 程序里这条指令的”故事设定”:

   指令:  ADD R3, R1, R2     ; 含义: R3 ← R1 + R2  (RISC 三地址格式: 两源+一目的)
   注: 前面第4章为简化用了二地址的 ADD R1,b;从这里起切换到现代RISC三地址格式
   假设该指令的机器码(32 位 RISC 风格)已经躺在内存地址 100 处
   假设当前: PC = 100, R1 = 5, R2 = 3
   预期结果: R3 = 8, PC = 104(指向下一条)

5.8.2 拍一拍走:ADD 指令完整数据通路(11 拍)

我们把整条指令拆成 11 个时钟周期 T,按”取指 → 译码 → 执行 → 写回”四段:

══════════════════════ 第 1 段:取指周期 (FI / Fetch) ══════════════════════

T1:  把 PC 的内容送到地址寄存器 AR
     ┌────┐                       ┌────┐
     │ PC │ ──放上总线─→ ●●●● ──→ │ AR │     PC 内容 100 进入 AR
     └────┘    PC_out=1            └────┘    AR_in=1
                                              (其他寄存器都不收)

T2:  AR 把地址送上"地址总线"启动内存读,同时让 PC = PC + 4(为下一条做准备)
     AR ────────→ 地址总线 ─→ 内存控制器: "请读地址 100 处的数据"
     同时: PC ← PC + 4   (PC = 104)        ← PC_inc=1 信号
     ↑
     为啥 +4 而不是 +1 ?因为我们假设是 32 位定长指令 = 4 字节,
     内存按字节编址,下一条指令在地址 100+4=104 处。
     8 位机器就 +1,16 位机器就 +2,看一条指令几字节决定。

T3:  内存把数据(指令机器码)通过数据总线送回 → 装入 IR
     内存 ──→ 数据总线 ●●●● ──→ │ IR │     MEM_out=1, IR_in=1
                                  └────┘
     此刻 IR 里装的就是 ADD R3,R1,R2 这条指令的二进制

══════════════════════ 第 2 段:译码周期 (ID / Decode) ══════════════════════

T4:  把 IR 中的"操作码字段"送到译码器
     │ IR │ ── 取出 OP 字段(比如前6位) ──→ 译码器 ──→ "我认识!是 ADD 指令"
     └────┘                                         译码器输出告诉控制器:
                                                    后面 7 拍要走 ADD 的微序列

══════════════════════ 第 3 段:执行周期 (EX / Execute) ══════════════════════

T5:  把 R1 的值送到 ALU 的输入 A
     │ R1 │ ──放上总线─→ ●●●● ──→ ALU 的 A 输入端    R1_out=1, ALU_A_in=1
     └────┘                       (此时 ALU 看见 A=5)

T6:  把 R2 的值送到暂存器 Y
     │ R2 │ ──放上总线─→ ●●●● ──→ │ Y │              R2_out=1, Y_in=1
     └────┘                       └────┘              (Y 直接接到 ALU 的 B 输入)
     此时 ALU 同时"看见" A=5, B=3, 但还没让它算

     ↑ 为什么不能也像 R1 一样直接送 ALU?
     答:因为这是【单总线】CPU——T5 这一拍 R1 已经"独占"了总线送给 ALU_A,
        总线就那一条,R2 这一拍上不去。所以工程师专门留了一个【暂存器 Y】,
        Y 的输出端**直接物理连**到 ALU 的 B 输入,不走总线。这样 T5 用总线
        送 R1 到 ALU_A,T6 用总线送 R2 到 Y,下一拍 ALU 两个输入就齐了。
        → "暂存器 Y" 不是教材凭空摆的,是单总线架构必然要补的零件。

T7:  让 ALU 做加法 → 结果 8 出现在 ALU 的输出端
     A=5  ─┐
           ├──→  ALU(操作=ADD)  ──→  输出端 = 8         ALU_op=ADD
     B=3  ─┘                          (但还没存到任何地方)
     同时: ALU 顺手设置 PSW 里的"是否进位/溢出/零"标志

══════════════════════ 第 4 段:写回周期 (WB / Write Back) ══════════════════════

T8:  把 ALU 的输出锁存进 DR(数据缓冲寄存器)
     ALU 输出 ──放上总线─→ ●●●● ──→ │ DR │           ALU_out=1, DR_in=1
                                    └────┘
                                    DR 里现在是 8

T9:  把 DR 的值写回到 R3
     │ DR │ ──放上总线─→ ●●●● ──→ │ R3 │              DR_out=1, R3_in=1
     └────┘                       └────┘
                                  R3 现在 = 8 ✓

T10-T11:  收尾:"撤掉" T9 用过的所有使能信号(不然下一拍它们还会乱触发)、
          让控制器把"下一条指令的微程序入口地址"加载到微地址寄存器
          → 取指公共段重新开始,CPU 进入下一条指令
          (这两拍是工程"行政开销",原理上和 T1-T9 一样靠时钟驱动的状态切换)

🌱 大白话整理这 11 拍:一条 ADD 指令听起来就是”把两个数相加”,可 CPU 真做起来是:

  1. 找指令(3 拍:T1 报地址 → T2 启动内存读+顺手 PC+4 → T3 取回指令)
  2. 看清指令(1 拍:T4 译码”哦这是加法”)
  3. 干活(3 拍:T5 把 R1 送 ALU 输入 A → T6 把 R2 送暂存 → T7 ALU 算出 8)
  4. 存结果(2 拍:T8 把 8 存到 DR → T9 再从 DR 写回 R3)

🔑 看到这里你应该懂了”为什么单总线慢”:每拍只能一个数走总线,所以 R1、R2 不能同时进 ALU,要靠暂存器 Y 搭桥。多总线(双/三总线)CPU 就是为了让 T5、T6 能并行成一拍,这就是教材里”单/双/三总线运算器”的真实含义。


5.8.3 把这 11 拍”翻译”成微程序(微指令格式 + 完整脚本)

第 5.4 节抽象地讲了微程序,现在我们把上面的 11 拍翻译成一段真实的微程序,让你看到”一条 ADD = 一段微程序”具体长啥样。

先设计微指令格式(水平型,每位对应一个微命令):

   微指令格式(24 位,简化):

   ┌─────────────────── 操作控制字段 (18 位) ───────────────────┬─── 顺序控制 (6位) ───┐
   │ PC_out PC_inc AR_in MEM_out MEM_R IR_in R1_out R2_out R3_in │   下一条微地址        │
   │   1bit   1bit  1bit   1bit   1bit  1bit  1bit   1bit  1bit  │      6 bit            │
   │ Y_in ALU_A_in ALU_op[3bit] ALU_out DR_in DR_out (其他...)    │ (跳到哪条微指令)      │
   └─────────────────────────────────────────────────────────────┴───────────────────────┘
       ↑
       每一位 = 一根控制线,"1"=本拍发出该信号;"0"=不发

为了让你看懂二进制串里每一位是啥,我们简化到 16 个关键信号,位段对应表如下:

   位号:    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   含义:    PC_ PC_ AR_ MEM MEM IR_ R1_ R2_ R3_ Y_  ALU ALU ALU ALU DR_ DR_
            out inc in  _out _R  in  out out in  in  _Ain op2 op1 op0 in  out
            ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──
            ↑                                                              ↑
       第 15 位为 1 = 让 PC 把内容放上总线                  第 0 位为 1 = 让 DR 把内容放上总线

每条微指令就是一串 16 位 0/1,“1”代表这一拍点亮对应那根信号线。控制器一拍取一条微指令,把这 16 位摊开送到 16 根控制线上,CPU 各部件按这些信号同时动一下——这一拍干的事就齐了。

ADD 指令的完整微程序(放在控制存储器 μCM 中,假设入口地址 μA = 010000):

   按上面 16 位位段表(位 15 是 PC_out, 位 14 PC_inc...位 0 DR_out)

   μ地址     16位微指令      "1"对应哪几根线点亮      动作         对应主程序拍
   ───────────────────────────────────────────────────────────────────────
   μ000000  1010000000000000  PC_out(15) + AR_in(13)     PC→AR         T1
   μ010001  0100010000000000  PC_inc(14) + MEM_R(11)     PC+4,启动读   T2
   μ010010  0001010000000000  MEM_out(12) + IR_in(10)    MEM→IR        T3
   μ010011  0000000000000000  (译码: 内部组合逻辑)       译码→by_OP    T4
   ── 上面 4 条是所有指令共享的"取指+译码"公共段 ─────────────────────

   μ100000  0000001000100000  R1_out(9) + ALU_Ain(5)     R1→ALU 输入A  T5  ← ADD 入口
   μ100001  0000000101000000  R2_out(8) + Y_in(6)        R2→Y          T6
   μ100010  0000000000011100  ALU_op=111(位4,3,2)        ALU 算加法    T7
   μ100011  0000000000011110  ALU_op=111 + DR_in(1)      ALU→DR(专线)  T8
   μ100100  0000000010000001  R3_in(7) + DR_out(0)       DR→R3         T9 → 回 μ000000

🔎 怎么读这表? 拿 μ100000 那行举例:0000 0010 0010 0000 —— 从位 15 数到位 0,只有第 9 位(R1_out)和第 5 位(ALU_Ain)是 1。意思是这一拍:① R1 把内容放上总线;② ALU 的 A 输入端”打开”接收。组合起来就是”R1→总线→ALU_A”。每条微指令就是这样一张”本拍要点亮哪几根线”的开关清单。

🌱 大白话:CPU 内部有一片只读小内存(控制存储器 μCM,几 KB),里面烧死了所有指令的”动作脚本”。每条机器指令(如 ADD)对应一段微程序(这里是 5 条微指令,T5–T9);每条微指令的每一位就是一根控制线的开关状态。CPU 时钟一拍就读出一条微指令,把这 24 个开关位”一字排开”送到各个部件——所有部件按这些开关同时动一下——这一拍就完成了。然后跳到下一条微指令地址,再来一拍……整个 CPU 就是被这片”小脚本仓库”驱动着跑。

🔑 微程序设计的精华:

  • 取指 + 译码(μ000000 – μ010011)4 条微指令是所有指令共享的,所以叫”取指公共段”
  • 译码后通过 OP 字段经”微地址形成部件”算出对应指令的微程序入口(如 ADD → μ100000)
  • 改指令系统?只要烧一片新的 μCM 即可——这就是”动态微程序设计”能”让一台机器仿真另一台”的物理基础

5.8.4 流水线 + Forwarding:当 ADD 紧跟 LOAD,硬件如何”接力”

🔄 先做术语对齐(很多教材偷懒不对齐,造成混乱):

5.8.2 单总线 9 拍的叫法5.8.4 流水线 5 段的叫法含义
T1~T3 (3 拍)IF (Instruction Fetch)取指:从内存读出指令
T4 (1 拍)ID (Instruction Decode)译码 + 读寄存器组
T5~T7 (3 拍)EX (Execute)ALU 算
(LOAD/STORE 才用)MEM (Memory access)真正访存读/写数据
T8~T9 (2 拍)WB (Write Back)把结果写回寄存器

同一个事,两套不同粒度的切分:5.8.2 是”按时钟拍”细到每根控制线;5.8.4 是”按功能段”粗到每段一个流水工位。流水线把每段塞一个独立电路,所以多条指令可重叠跑。

回忆我们 a+b 真实的指令序列:

   I1: LOAD  R1, [a]      ; R1 ← M[a]       要 5 拍 (IF→ID→EX→MEM→WB)
   I2: LOAD  R2, [b]      ; R2 ← M[b]
   I3: ADD   R3, R1, R2   ; R3 ← R1 + R2    ← 它要用 I1 的 R1 和 I2 的 R2!
   I4: STORE R3, [z]      ; M[z] ← R3

问题来了:流水线让指令重叠执行——

   时钟→     1    2    3    4    5    6    7
   I1: LOAD  IF   ID   EX   MEM  WB           ← R1 在第 5 拍 WB 才写回
   I2: LOAD       IF   ID   EX   MEM  WB      ← R2 在第 6 拍 WB 才写回
   I3: ADD             IF   ID   EX   MEM  WB
                                ↑
                              第 5 拍 I3 在 EX,要用 R1, R2
                              可 I1 的 R1 这一拍才刚 WB(写回)!
                              I2 的 R2 更糟,下一拍才 WB!
                              ↓
                            如果不处理,I3 在 EX 读到的是旧 R1, R2 → 算错

这就是典型的 RAW 数据冒险——I3 要”读”I1 还没”写”完的 R1。

解决方案 1:Forwarding(旁路) —— 不等 R1 真正写到寄存器,直接从”上一条指令的 ALU/MEM 输出端”拉一根线送给”当前指令的 ALU 输入端”。

🛟 MUX 是啥? = 多路选择器 (Multiplexer),一种”几进一出”的电子开关。给它 N 个输入和一个”选哪个”的控制信号,它就把指定那个输入直接通到输出端。它是数字电路里”if 选 A 否则选 B”的硬件实现——下面 Forwarding 就靠它”在两路数据里挑正确那路”。

   流水线 EX 段的硬件(加 Forwarding 后):

           ┌── 寄存器组的 R1 输出 ──────────────────────┐
           │                                              │
   I1的    │   ┌─ 多路选择器 MUX ──→ ALU 的 A 输入        │
   MEM段 ─────┤                                          │
   读出值  │   └── 选择控制信号: 比较"前一条目的寄存器号"  │
   (R1新值)│       与"当前指令源寄存器号"相同 → 选旁路    │
           │                          不同 → 选寄存器组   │
           └──────────────────────────────────────────────┘

   于是: 第 5 拍 I3 的 EX 段需要 R1 时,
        MUX 一看"I1 的目的寄存器号=1,I3 的源寄存器号=1,相同!"
        → 直接走旁路,把 I1 MEM 段刚读出的 R1 新值"截胡"送过来
        → I3 用的就是正确的 R1

🌱 大白话:寄存器组就像公司”总账本”,每次更新要走流程(WB 段)。而 Forwarding 是在车间里搭了根私线——上条指令的工人刚算出新值(在 MEM/EX 出口),就直接抛过去给下条指令的工人用,不等他先走”上报到总账本”的繁琐流程。所以叫”旁路”——绕过寄存器组这条正路。

解决方案 2:插气泡 (Stall) —— 旁路也救不了的时候(比如 LOAD 后紧接着就用),只能让流水线停一拍:

   不能旁路的情况:
   I1: LOAD R1, [a]        ; R1 要在 MEM 段(第 4 拍)才能从内存取回
   I2: ADD  R3, R1, R2     ; I2 第 3 拍就要在 EX 用 R1 → 比 R1 出现还早!

   时钟→  1    2    3    4    5    6
   I1:    IF   ID   EX   MEM  WB
   I2:         IF   ID   ⊘    EX   MEM  ← 第 4 拍插一个"气泡"(NOP)
                       ↑
                    硬件检测到这种"LOAD-USE 冒险"
                    自动让 I2 暂停一拍,让 I1 的 MEM 先跑完
                    然后旁路救场: I1 的 MEM 输出 → 直送 I2 的 EX

🔑 真实 CPU 中两种策略并用:编译器尽量”重排指令”把 LOAD 和它的使用者拉开距离(避免插气泡),实在拉不开的硬件自动插气泡保正确。


5.8.5 一句话总结:5.8 节给你脑子里留下了什么

读完这一节,你应该能在脑子里像放电影一样演一遍:

一条 ADD 指令进入 CPU → 11 拍数据通路(取指 3 + 译码 1 + 执行 3 + 写回 2 + 收尾)→ 这 11 拍其实是控制存储器里 5 条 ADD 微指令在驱动 → 多条指令重叠跑就成了流水线 → 数据相关用 Forwarding 私线接力 → 救不了就插气泡。

这就是”CPU 执行一条指令”的全部真相。

🎯 第 5 章前面所有抽象的”周期/微指令/控制器/流水线”概念,到这一节都落到了一条具体指令上。这就是从”知道”到”理解”的最后一步——把抽象框架贴回到具体动作。


⭐ 本章在 a+b 故事里的位置

到这里,a+b 已经真正跑完了:CPU 用取指-译码-执行的节拍,把 LOAD a→ADD b→STORE 一条条做掉,结果算出来了。但你可能注意到:a 从内存到 CPU、结果从 CPU 回内存,这些数据走的是什么路?多个部件抢着用这条路怎么办?下一章——总线。



📝 章节小测

先动笔算完再看答案。本章是全书分值最高的章节,流水线和微程序综合题必出。

【题 1 · 计算 · 流水线】 一条 6 段流水线,每段耗时 1 个时钟周期(周期 = 2ns),连续执行 200 条指令。 (1) 求流水线执行时间和加速比。 (2) 如果第 3 段因为功能复杂,耗时 3ns(其余仍 2ns),流水线时钟周期应设为多少?新的执行时间和加速比是多少?

【题 2 · 分析 · 数据相关】 判断以下指令序列中相邻指令之间的数据相关类型(RAW / WAR / WAW / 无),并说明哪些可以用 Forwarding 解决。

I1: ADD  R1, R2, R3    ; R1 ← R2 + R3
I2: SUB  R4, R1, R5    ; R4 ← R1 − R5
I3: AND  R1, R6, R7    ; R1 ← R6 & R7
I4: OR   R8, R1, R4    ; R8 ← R1 | R4

【题 3 · 简答 · 微程序 vs 硬连线】 微程序控制器和硬连线控制器的核心区别是什么?各适合什么场景?(不超过 5 句话)

【题 4 · 设计 · 微操作信号】 在单总线 CPU 中,执行 STORE R2, [addr](把 R2 的值写入内存地址 addr)需要哪些微操作?按时钟周期列出(假设 addr 已在 IR 的地址码字段中)。

【题 5 · 判断】 “在五段流水线(IF-ID-EX-MEM-WB)中,LOAD 指令后紧跟一条使用其结果的 ADD 指令,仅靠 Forwarding 就能消除数据冒险。” 对还是错?

🎯 本章高频考点 & 易错坑

  • 分析设计(核心):指令周期数据通路、微程序设计(微指令格式/微地址/微操作信号)、硬连线逻辑表达式。
  • 计算:流水线时钟周期数、加速比;判断 RAW/WAR/WAW;画流水线时空图。
  • 简答:CPU功能、六大寄存器、微程序vs硬连线、三种控制方式、三大相关。
  • ⚠️ 坑:机器周期⊇时钟周期;一条机器指令=一段微程序;简单流水线只有RAW;典型RISC通常采用流水线但流水CPU不一定是RISC(教材口径);溢出标志在PSW。

← 上一章:第4章 | 📖 目录 | 下一章:第6-8章 →