CS152 · 第 2 课

Simple Machine Implementations, Microcoding

上一课讲完 IBM 360 如何用统一的 ISA 隐藏了 8 位到 64 位数据通路的差异。这一课深入其中一种关键的实现风格——微编码(microcoding)——并用一台微编码的 RISC-V 机器作为具体例子,讲清楚"控制"与"数据通路"到底是如何分离、又如何协作的。

授课:Sophia Shao 预计用时:50 分钟 难度:入门 → 中等

回顾第 1 课


ISA:软硬件之间的契约

指令集架构(ISA)是软件和硬件之间的契约:它通过列出所有程序员可见的状态(寄存器 + 内存)以及作用在这些状态上的指令语义来定义。IBM 360 是历史上第一条把 ISA 与实现(即微架构)彻底分离的产品线。

同一个 ISA 可以有很多种不同的实现:


ISA 与微架构风格的(非强制)对应关系

ISA 设计时常常带有某种特定的微架构风格作为默认预期:

ISA 风格典型对应的微架构风格
Accumulator(累加器型)硬连线、不流水(hardwired, unpipelined)
CISC微编码(microcoded)
RISC硬连线、流水线(hardwired, pipelined)
VLIW定长延迟的顺序并行流水线

但这只是"典型对应",并非强制规则——同一个 ISA 完全可以用任何微架构风格实现:

为什么要学微编码?


控制与数据通路的分离

处理器设计可以分成两部分:

早期计算机设计者面临的最大挑战,是把控制电路设计正确。Maurice Wilkes(EDSAC 的设计者)在 1958 年为 EDSAC-II 发明了"微编程"(microprogramming)的思想来设计控制单元——这个想法其实早已被 Babbage 分析机中的"the Barrel"(滚筒)以及更早的可编程计算器所预示。


技术条件如何决定"硬连线 vs. 微编码"

微码在 1950 年代出现时,三种关键存储/逻辑技术的相对成本是:

关键的成本/速度关系是:逻辑门远比 ROM 或 RAM 贵;ROM 比 RAM 便宜;ROM 比 RAM 快得多。

这就直接解释了 IBM 360 Model 30 的设计选择(第 1 课):与其为每条复杂指令都设计一套专用的硬连线逻辑(很贵),不如把"如何执行每条指令"编码成一系列存储在 ROM 里的微指令(更便宜、更快),再用一个通用的译码器去执行这些微指令——这正是微编码机器的核心思想。而 Model 70 定位高端、性能优先,直接用专用电路(硬连线)实现控制逻辑。


RISC-V ISA 速览

本课程使用 RISC-V(RV32I 基础整数指令集)作为教学 ISA。它是伯克利设计的"第五代"RISC 指令集:现实、完整,但开源且精简;不为某一种特定实现风格过度设计;同时支持 32 位(RV32)和 64 位(RV64)地址空间;为多处理器设计;指令编码高效;便于教学/研究中的裁剪与扩展。

处理器状态:

指令编码:RV32 基础指令集固定为 32 位,最低两位恒为 11(为将来扩展变长指令预留)。指令共有 6 种格式:

格式用途关键字段
R-type寄存器-寄存器运算funct7, rs2, rs1, funct3, rd, opcode
I-type立即数运算、loadimm[11:0], rs1, funct3, rd, opcode
S-typestoreimm[11:5]+imm[4:0], rs2, rs1, funct3, opcode
B-type分支(branch)imm 拆分编码, rs2, rs1, funct3, opcode
U-type高位立即数imm[31:12], rd, opcode
J-type跳转(jump,如 JAL)imm 拆分编码, rd, opcode

无论基础指令是否定长 32 位,所有分支/跳转的目标地址都以 16 位为最小单位对齐。


单总线微编码数据通路

本课用一个简化的"单总线"(single-bus)数据通路来实现微编码的 RISC-V 机器:指令寄存器(Instruction Reg.)、PC、寄存器堆(Registers,含 PC 作为可寻址的一员)、两个锁存器 A/B、ALU、主存(Main Memory)都挂在同一条总线上,每个部件的读写由控制信号(如 RegEn、RegW、ALUEn、MemEn、MALd 等)逐拍触发。

微指令用寄存器传输(register-transfer)记号书写,每一行对应一组具体的控制信号:

一条 RISC-V 指令的完整执行被拆分成几个阶段:取指(Instruction Fetch)→ 译码(Instruction Decode)→ 寄存器读取(Register Fetch)→ ALU 运算 → 可选的内存操作 → 可选的寄存器写回 → 计算下一条指令地址。每个阶段对应一到多条微指令,由 Microcode ROM 中固化的微程序驱动。


微代码实例:从取指到几类典型指令

取指(Instruction Fetch)——所有指令共享的第一步:

Instruction Fetch:  MA,A := PC
                     PC := A+4
                     (等待内存)
                     IR := Mem
                     (根据 opcode 跳转分派)

关键细节:取指同时把 当前指令的 PC 锁存进 A,并立即把 PC 更新为 PC+4(下一条顺序指令的地址)。这意味着分派到具体指令的微码时,A 中保存的是"这条指令自己的地址",而 PC 已经指向"下一条指令"——这个先后关系是后面 JAL/Branch 需要正确处理 PC 相对寻址的关键。

ALU 型指令(R-type)与立即数 ALU 型指令(I-type):

ALU:   A := Reg[rs1]
       B := Reg[rs2]
       Reg[rd] := ALUOp(A,B)
       goto 取指

ALUI:  A := Reg[rs1]
       B := ImmI          // 12 位立即数符号扩展
       Reg[rd] := ALUOp(A,B)
       goto 取指

Load 指令(LW):

LW:    A := Reg[rs1]
       B := ImmI          // 12 位立即数符号扩展
       MA := A+B
       (等待内存)
       Reg[rd] := Mem
       goto 取指

JAL 与 Branch——需要利用取指阶段保存在 A 中的"本指令 PC"来计算 PC 相对跳转目标:

JAL:      Reg[rd] := A     // A 中是取指时保存的本指令 PC
          A := A-4         // 恢复到"取指前"的 PC(因为取指阶段已 +4)
          B := ImmJ        // 跳转型立即数
          PC := A+B
          goto 取指

Branch:   A := Reg[rs1]
          B := Reg[rs2]
          if (!ALUOp(A,B)) goto 取指   // 不跳转:条件不成立
          A := PC                      // 跳转:取当前 PC 重新计算
          A := A-4
          B := ImmB                    // 分支型立即数
          PC := A+B
          goto 取指

这是一份简化的教学"草图"(microcode sketch),不是可直接综合的 RTL。 原始幻灯片对 JAL 中 A 的处理方式给出了两种略有出入的写法(含一条"备选"写法的注释),这提示我们:PC 相对寻址在寄存器传输级别很容易出现"多减一次 4"或"少减一次 4"这类差一错误(off-by-one)。真正精确的行为,要以 Sodor 微编码核心的 microcode.scala 源码为准——这也是 Lab 1 中要求大家去阅读真实 RTL、而不是只信任幻灯片伪代码的原因之一。


常见误区


知识检查

问题 1:1950 年代,为什么用 ROM 存储微指令、用通用译码器执行,比为每条复杂指令直接设计专用硬连线逻辑更划算?

问题 2:下列哪个例子最能说明"ISA 风格并不强制决定微架构实现风格"?

问题 3:在取指微码 MA,A:=PC; PC:=A+4 执行完毕、跳转到具体指令的微码之后,寄存器 A 和 PC 分别保存着什么?


本课要点

ISA 是契约

ISA 规定语义,微架构风格是实现选择,二者并非绑死

控制 vs. 数据通路

数据通路存数算数,控制给数据通路的操作排序

微编码的技术根源

逻辑贵、ROM 比 RAM 便宜又快,催生了微编码机制

寄存器传输记号

微指令就是一组具体的控制信号,如 MA:=PC


参考资料与延伸阅读