← 返回博客CPU 体系结构

指令寄存器与译码阶段

了解教学 CPU 如何捕获指令、提取操作码、寄存器和立即数字段,并把它们转化为经过合法性验证的控制信号,同时结合 RISC-V 与 x86 背景。

DennyDenny
阅读约需 9 分钟
Chalkboard feature illustration for “指令寄存器与译码阶段”

概要: 在简单 CPU 中,指令寄存器(IR)会捕获取回的指令,让译码逻辑看到稳定的位。译码会识别指令格式、提取寄存器与立即数字段、检查编码是否合法,并为后续阶段生成控制信息。现代处理器可能把这一职责分散到取指缓冲区和流水线寄存器,而不是包含一个字面意义上的架构“IR”。

程序计数器回答“从哪里取指”,译码阶段则回答“取回的位是什么意思”。教学 CPU 通常会在二者之间放置指令寄存器:一组触发器在控制逻辑分析期间保存当前指令。

这个模型很有用,但必须明确边界。在大多数指令集中,指令寄存器不是程序员可见的寄存器;高性能实现还可能同时在取指块、队列、流水线寄存器或已译码操作缓存中保存许多指令。“IR”描述的是教学数据通路中的一项职责,而不是所有处理器都具有的通用物理元件。

教学数据通路

小型多周期 CPU 可以画成:

PC → instruction memory → instruction register → field extraction → control

发生取指事件时:

  1. PC 提供指令地址;
  2. 指令存储器返回一个指令字;
  3. IR 在有效时钟边沿捕获该字;
  4. 组合译码逻辑解释已经保存的位。

只有需要接收新指令时,IR 写使能才会置位。发生停顿时,IR 保持旧值。复位或流水线冲刷时,控制器会把已保存的指令标记为无效,或用无害操作替换它。

有些多周期数据通路会在内存与 IR 之间放置内存数据寄存器,另一些则直接捕获内存输出。不存在通用的唯一顺序;所选内存接口的时序与握手机制决定实际需要哪些寄存器。

一个小型编码示例

假设某个 8 位教学指令集定义以下格式:

7             5 4             2 1             0
┌──────────────┬───────────────┬───────────────┐
│ opcode [2:0] │ register [2:0]│ mode / small  │
│              │               │ immediate     │
└──────────────┴───────────────┴───────────────┘

具体字段由指令集设计决定。8 位无法同时编码很大的操作码空间、两个独立寄存器操作数和一个实用立即数,因此真实教学指令集往往使用多种格式。例如:

  • 寄存器指令可以把位分配给源地址和目标地址;
  • 加载立即数指令可以把大部分低位用作常量;
  • 分支指令可以把同样的低位解释为偏移量。

译码必须先确定格式,再按该格式解释剩余位。给每一位永久贴上同一个标签会得到错误电路。

固定位置字段:RISC-V 示例

基础 RISC-V 指令集使用 32 位指令格式。对 R 型指令:

31         25 24    20 19    15 14   12 11      7 6        0
┌────────────┬────────┬────────┬───────┬─────────┬──────────┐
│   funct7   │   rs2  │   rs1  │ funct3│    rd   │  opcode  │
└────────────┴────────┴────────┴───────┴─────────┴──────────┘

在基础格式中,寄存器字段占据一致的位置:

opcode = IR[6:0]
rd     = IR[11:7]
funct3 = IR[14:12]
rs1    = IR[19:15]
rs2    = IR[24:20]
funct7 = IR[31:25]

这些切片只是接线,不是算术。rs1 与 rs2 送入寄存器堆读地址,rd 标识一个可能的目标寄存器,操作码和功能位共同确定操作类别与变体。

立即数需要更谨慎地处理。在多种 RISC-V 格式中,立即数字段有意分散在指令不同位置。硬件必须按照规定顺序将其拼接,并从架构指定的符号位完成符号扩展。这仍然是简单组合接线,却不总是一个连续切片。

译码不只是操作码译码器

对于仅有八种完整操作的玩具指令集,一个 3 线到 8 线译码器可以产生独热信号:

操作码教学操作独热结果
000空操作is_nop
001加载is_load
010存储is_store
011加法is_add
100减法is_sub
101跳转is_jump
110零分支is_branch_z
111停机is_halt

随后可以从这些信号推导控制方程:

RegWrite=is_load∨is_add∨is_sub\text{RegWrite} = \text{is\_load} \lor \text{is\_add} \lor \text{is\_sub} MemWrite=is_store\text{MemWrite} = \text{is\_store}

这是学习译码器和积之和逻辑的实用入门,但它并不代表真实译码器的一般结构。

在更大的指令集中:

  • 主操作码通常选择格式或指令族,而不是单个操作;
  • 次级功能字段区分同一族中的各个成员;
  • 模式或前缀状态可以改变操作数大小与解释;
  • 某些位模式被保留或非法;
  • 特权级与扩展可用性会影响合法性;
  • 控制输出通常采用编码,而不是为每条指令专设一根导线。

正确译码器必须拒绝不受支持的编码,不能误把它们当成相邻的合法指令。

译码会产生什么

与其把输出视为几十根彼此无关的导线,不如把它看成一个控制信号束。教学信号束可以包含:

字段含义
alu_op选择加法、减法、逻辑运算、比较或直通
src_a / src_b选择寄存器、PC、立即数或常量操作数
reg_write允许在正确的后续阶段写寄存器堆
mem_read / mem_write请求数据存储器操作
result_source选择 ALU、内存、PC 相对值或其他回写来源
branch_kind选择不分支、条件分支、直接跳转或间接跳转
immediate重新组装并扩展后的常量
legal指示当前机器状态是否支持该编码

这些信号必须由流水线有效性和阶段时序限定。译码阶段组合地产生 reg_write = 1,并不允许立即更新寄存器堆;只有指令未被冲刷或触发故障,并到达指定阶段时,写入才发生。

定长与变长指令

指令长度会改变译码问题的前端部分。

定长示例:Arm A64

Arm A64 指令宽度为 32 位。取指提供一个对齐字后,硬件可以直接检查字段位置。固定长度简化了指令边界识别,但不会让每一种操作或立即数编码都变得简单。

带压缩指令的 RISC-V

基础 RISC-V 指令为 32 位。可选压缩扩展增加 16 位指令,架构还为更长编码保留了一套长度编码方案。取指必须先确定下一条指令占两个、四个还是其他受支持字节数,再推进 PC 并对齐下一条指令。

变长示例:x86

x86 指令长度为 1 至 15 字节,可以包含传统或现代前缀编码、操作码字节、ModR/M 和 SIB 寻址字节、位移量与立即数。具体包含哪些部分,取决于此前出现的字节。

因此,现代 x86 前端需要在语义译码之前或同时完成指令边界与长度译码。不同实现并不相同:不能声称每款处理器都取回一个固定大小的块、使用相同队列布局,或让每条指令都经过同一条转换路径。Intel 架构手册定义软件可见编码,具体流水线则由微架构决定。

定长与变长并不是 RISC 与 CISC 的同义词,它们只描述编码的一项属性。

硬连线控制与顺序控制

小型 CPU 可以用布尔逻辑直接生成控制信号。多周期控制器则可以使用状态机或控制存储器,其中各个条目描述多个内部步骤。真实处理器可能把快速译码路径与更复杂操作的顺序处理结合起来。

有用的区别是:

  • 组合译码把当前字段映射为初始控制表示。
  • 顺序控制随时间发出多个内部动作。

两种方法都不天然等于“RISC”或“CISC”,微码实现也不必然很慢。性能取决于完整实现与工作负载。

流水线控制:停顿、冲刷与有效性

多条指令开始重叠后,指令位只是状态的一部分。每个流水线寄存器还需要控制元数据。

停顿

如果下一阶段无法接收指令,当前取指/译码寄存器必须保持数据,或参与就绪/有效握手。如果位继续前进而有效状态停住,指令与控制之间的对应关系就会错位。

冲刷

分支预测错误、异常或复位都可能使较年轻的指令无效。只清除指令位不如清除有效位稳健,因为全零编码在某些指令集中本身可能合法。

相关性处理

译码可以识别用于冒险检查的源寄存器和目标寄存器。响应可能是停顿、转发选择、寄存器重命名或其他与实现相关的操作。字段提取只提供标识符,后续控制才决定如何处理它们。

常见设计错误

  • 假设内存始终需要 MDR。 应根据真实内存时序与接口增加寄存器,而不是照搬一张通用图。
  • 当前指令仍有效时改变 IR。 使用写使能或握手,让译码始终看到稳定的位。
  • 只译码主操作码。 还可能需要功能字段、模式、扩展和合法性检查。
  • 按照图中的数字排列顺序拼接立即数。 应严格遵循指令集位映射,再从指定符号位完成扩展。
  • 用未经限定的译码直接驱动副作用。 写操作必须受流水线有效性、阶段时序和异常/冲刷状态门控。
  • 复位到任意指令模式。 应优先使用有效位或控制器复位状态,不能假设全零位就是空操作。
  • 把教学波形当成物理时序证明。 模型可以展示逻辑顺序,却不能证明硅片建立/保持时序已经收敛。

构建并验证教学译码器

打开顺序指令执行器,找出:

  1. 指令存储元件;
  2. 组成各字段的导线;
  3. 受各字段影响的控制输出;
  4. 这些输出可以改变架构状态的位置。

然后构建一张紧凑的测试表:

测试类别改变内容预期结果
每个合法操作码主操作码位正确的操作族与副作用使能
功能变体次级字段正确的 ALU 或寻址变体
寄存器地址rs / rd 字段正确寄存器端口,操作保持不变
立即数极值零、最大正数、最小负数正确拼接与扩展
保留编码不支持的位模式非法指令路径,不发生意外写入
停顿让译码保持若干时钟指令与控制保持对齐
冲刷使飞行中的指令无效不产生寄存器或内存副作用

手动切换一个示例只能起到演示作用,不足以完成验证。应覆盖每一种合法操作类别以及有代表性的非法编码。

它在 CPU 中的位置

程序计数器提供取指地址。译码识别操作数和控制意图。CPU 标志寄存器是基于标志的指令集中分支条件的一种来源;其他指令集直接比较寄存器。控制单元安排教学数据通路的顺序并限定副作用。

这些元件共同解释小型处理器的控制流,但其中任何一个都不是“完整控制平面”。在更大的机器中,缓存、预测、异常、特权、冒险与流水线状态还会增加更多控制。

主要参考资料

接下来阅读取指—译码—执行深入解析,然后修改教学 CPU 中的一种编码,并同步更新译码器、测试与文档。

将想法变成真正可运行的电路。