Menu Close

MIPS 寄存器文件详解 – 三十二个寄存器如何为中央处理器提供数据

MIPS 寄存器文件详解:32个寄存器如何为CPU提供数据

MIPS 寄存器文件详解 - 三十二个寄存器如何为中央处理器提供数据

中央处理器每秒可以执行数百万甚至数十亿条指令。它可以完成加法、比较、地址计算、函数调用、循环控制和数据搬运。

但在真正执行这些操作之前,CPU必须先取得操作数

这些正在被指令使用的数据,通常不会每次都直接从主内存读取。主内存容量很大,但速度远远跟不上处理器内部的执行电路。

CPU真正用来保存当前工作数据的地方,是寄存器。

在经典的32位MIPS处理器中,32个通用寄存器被组织成一个独立的硬件模块,称为:

Register File,寄存器文件。

寄存器文件不仅仅是32个寄存器的集合。它还包含读取选择、写入选择、译码器、控制信号和数据端口。

它可以同时读取两个寄存器,并在时钟边沿把一个结果写回另一个寄存器。

这就是MIPS寄存器文件最核心的结构:

两个读取端口,一个写入端口。


一、什么是寄存器?

寄存器是CPU内部的一种高速存储电路。

一个32位寄存器可以保存一个32位二进制数值。这个数值可能表示:

  • 一个整数
  • 一个内存地址
  • 一个字符
  • 一个位掩码
  • 一个状态值
  • 一次计算的中间结果

32位寄存器中的位通常从0编号到31。

  • bit 0 是最低有效位
  • bit 31 是最高有效位

每一位只能保存0或1,32个位组合起来,构成一个完整的32位数值。

单个寄存器容量非常小,只能保存一个数值。但寄存器位于CPU核心内部,距离执行电路非常近,因此访问速度非常快。

CPU需要同时保存很多正在使用的数据,所以不能只有一个寄存器,而需要把多个寄存器组织在一起。

这就形成了寄存器文件。


二、什么是Register File?

Register File中的“File”不是磁盘上的文件,而是指一组统一组织和控制的寄存器。

经典MIPS寄存器文件包含:

  • 32个通用寄存器
  • 每个寄存器32位
  • 两个读取端口
  • 一个写入端口
  • 读取地址
  • 写入地址
  • 写入数据
  • 写使能信号
  • 时钟信号

寄存器文件接收寄存器编号,然后选择对应的寄存器。

CPU需要告诉它:

  1. 第一个操作数来自哪个寄存器
  2. 第二个操作数来自哪个寄存器
  3. 计算结果应该写入哪个寄存器
  4. 当前是否允许执行写入

因此,寄存器文件不是把32个寄存器简单地放在一起,而是一个完整的多端口硬件模块。


三、32乘32的寄存器结构

经典MIPS寄存器文件通常表示为:

32 × 32 Register File

第一个32表示有32个寄存器。

第二个32表示每个寄存器宽32位。

可以把它想象成一个表格:

  • 从上到下有32行
  • 每一行对应一个寄存器
  • 每一行包含32个位

寄存器编号从0到31。

CPU提供一个寄存器编号时,寄存器文件就会选择对应的一行,并输出其中的32位数据。

执行写入时,CPU也会指定一个寄存器编号,再把新的32位数据保存到对应位置。


四、整个寄存器文件只有128字节

一个32位寄存器等于4个字节。

MIPS共有32个通用寄存器,因此总容量为:


整个通用寄存器文件只有128字节。

与其他存储设备相比,这个容量非常小。

  • 主内存通常以GB计算
  • 高速缓存可能有几十KB或几MB
  • 一个普通图片可能有几MB
  • 一个视频文件可能有几百MB甚至几十GB

但是,寄存器文件并不是为了保存大量数据。

它的目标是让CPU能够极快地取得当前最需要的操作数

不需要立即使用的数据,可以留在缓存或者主内存中。

需要频繁使用的数据,则尽量保存在寄存器中。


五、为什么MIPS有32个寄存器?

经典MIPS指令长度是32位。

在许多MIPS指令中,都有字段用于指定源寄存器和目标寄存器。

如果处理器拥有32个寄存器,就需要5位二进制数来选择其中一个。

因为:


5位二进制一共可以表示32种组合,范围从0到31。

因此,指令中的一个5位寄存器字段,可以选择寄存器0到寄存器31中的任意一个。

如果寄存器数量更多,程序就可以在CPU内部保存更多工作数据。

但是,指令中用于保存寄存器编号的字段也必须更大。

MIPS选择32个通用寄存器,是寄存器数量和指令编码空间之间的一种平衡。


六、寄存器编号和软件名称

从硬件角度看,MIPS寄存器只有编号:


但是,汇编程序员通常使用更容易理解的名称。

编号 名称 常见用途
0 $zero 永远读取为0
1 $at 汇编器临时寄存器
2–3 $v0–$v1 函数返回值
4–7 $a0–$a3 函数参数
8–15 $t0–$t7 临时寄存器
16–23 $s0–$s7 保存寄存器
24–25 $t8–$t9 临时寄存器
28 $gp 全局指针
29 $sp 栈指针
30 $fp / $s8 帧指针或保存寄存器
31 $ra 返回地址

这些名称主要来自软件调用约定。

例如:

  • $t0实际上是寄存器8
  • $s0实际上是寄存器16
  • $sp实际上是寄存器29
  • $ra实际上是寄存器31

硬件并不知道“T”代表临时,也不知道“S”代表保存。

汇编器会把这些名称转换成5位寄存器编号,CPU最终看到的只是数字。


七、为什么需要两个读取端口?

考虑下面这条MIPS指令:


它的含义是:


CPU需要完成三个动作:

  1. 读取$t1
  2. 读取$t2
  3. 把结果写入$t0

由于加法需要两个源操作数,寄存器文件必须同时提供两个数值。

因此,MIPS寄存器文件拥有两个独立的读取端口。

第一个读取端口选择$t1

第二个读取端口选择$t2

两个32位数据可以同时离开寄存器文件,并进入后面的执行电路。

这不只适用于加法。

减法、逻辑运算、比较、分支和存储指令,也经常需要同时读取两个寄存器。


八、读取端口内部如何工作?

在教学模型中,一个读取端口可以看作一个大型多路选择器。

32个寄存器的输出全部连接到一个32选1选择结构。

5位读取地址决定最终选择哪个寄存器。

例如:

  • 地址8选择寄存器8
  • 地址9选择寄存器9
  • 地址16选择寄存器16

由于寄存器文件拥有两个读取端口,因此需要两套独立的读取选择结构。


两个读取端口可以选择不同的寄存器,也可以同时读取同一个寄存器。

读取不会删除寄存器中的数据,只是把选中的数值复制到输出端。

真实处理器中的多端口寄存器文件,物理实现会比简单的多路选择器图复杂得多,但外部行为基本一致:

两个寄存器编号输入,两个32位数据输出。


九、写入端口如何工作?

寄存器文件的写入端口通常包含:

  • 5位写地址
  • 32位写数据
  • Reg Write写使能
  • 时钟信号

写地址决定哪个寄存器接收结果。

写数据提供需要保存的新数值。

Reg Write决定当前是否允许写入。

当Reg Write等于0时,不修改任何通用寄存器。

当Reg Write等于1时,目标寄存器可以在有效时钟边沿保存新数据。

可以把它概括为:


十、5到32译码器

5位写地址需要从32个寄存器中选择一个目标。

一种常见教学实现,是使用5到32译码器。

译码器接收5位二进制输入,并产生32条选择线。

正常情况下,只有一条选择线被激活。

例如:

  • 写地址为8,选择寄存器8
  • 写地址为16,选择寄存器16
  • 写地址为31,选择寄存器31

译码器的输出还需要与Reg Write信号结合。

只有当目标寄存器被选中,并且Reg Write等于1时,它才会在时钟边沿保存写数据。

虽然所有寄存器都可以连接到同一组写数据线上,但一次正常写入只会修改一个寄存器。


十一、读取是组合逻辑

在经典MIPS教学模型中,读取通常被看作组合逻辑操作。

也就是说,读取输出直接取决于当前的读取地址和寄存器中保存的数据。

当读取地址变化时,经过短暂的传播延迟,读取数据也会发生变化。

读取操作不需要等待新的时钟边沿。

例如:


如果地址改为9:


真实电路并不是完全没有延迟,但读取过程不需要通过时钟边沿触发。


十二、写入发生在时钟边沿

寄存器写入通常是时钟控制的。

在一个时钟周期中,处理器首先准备好:

  • 写地址
  • 写数据
  • Reg Write信号

目标寄存器不会随着这些信号的每一次微小变化而不断修改。

它会等待有效时钟边沿。

当时钟边沿到来,并且Reg Write等于1时,选中的寄存器才会保存新的数据。

这种设计可以让CPU内部状态只在明确的时间点发生变化。

在时钟周期的大部分时间里,组合逻辑可以读取操作数、完成计算并生成结果。

到了时钟边沿,结果再被写入目标寄存器。

因此,在经典模型中:

读取通常是组合逻辑,写入通常是时钟控制。


十三、一条add指令的完整流程

继续使用下面这条指令:


在经典MIPS R型指令中:

  • rs字段选择第一个源寄存器
  • rt字段选择第二个源寄存器
  • rd字段选择目标寄存器

对于这条指令:


执行过程如下:

  1. $t1的编号进入第一个读取地址
  2. $t2的编号进入第二个读取地址
  3. 寄存器文件输出两个32位操作数
  4. 两个操作数进入执行电路
  5. 执行电路完成加法
  6. 结果返回寄存器文件
  7. $t0成为写地址
  8. Reg Write被设置为1
  9. 在时钟边沿,结果写入$t0

这一集暂时可以把ALU看成黑盒。

两个操作数进入,一个结果输出。

下一篇再详细解释ALU内部如何完成加法、减法和逻辑运算。


十四、永远为零的寄存器

MIPS中的寄存器0非常特殊。

读取$zero时,结果永远是0。

即使程序试图向$zero写入其他数值,写入也会被忽略。

例如:


相当于:


因为任何数加0都不会改变。

零寄存器还可以用于:

  • 与0进行比较
  • 判断一个数是否为0
  • 生成固定的零值
  • 丢弃不需要的计算结果

硬件可以直接阻止对寄存器0的写入,也可以在读取时强制输出0。

无论具体电路如何实现,软件看到的规则始终相同:

$zero永远读取为0,所有写入都会被忽略。


十五、立即数指令如何使用寄存器文件?

立即数指令不一定需要读取两个通用寄存器。

例如:


它的含义是:


这条指令从寄存器文件读取$t1

第二个操作数10,不来自另一个寄存器,而是直接来自指令中的立即数字段。

寄存器文件仍然输出正常的读取数据。

后面的选择电路会决定,执行单元的第二个输入是寄存器数据,还是立即数。

这部分将在完整MIPS数据通路中继续展开。


十六、load指令如何使用寄存器文件?

考虑一条加载指令:


它的含义是:

$t1 + 4所指向的内存位置读取一个32位数据,并保存到$t0

执行过程是:

  1. 寄存器文件读取$t1
  2. $t1提供基地址
  3. 处理器把基地址与偏移量4相加
  4. 内存系统返回数据
  5. 返回的数据成为寄存器文件的Write Data
  6. $t0成为Write Address
  7. Reg Write被设置为1
  8. 数据在时钟边沿写入$t0

这里还不需要深入讨论缓存和总线。

可以暂时把内存系统看成一个黑盒。


十七、store指令为什么也需要两个读取端口?

考虑下面这条存储指令:


它的含义是:

$t0中的数据,写入$t1 + 4所指向的内存位置。

这条指令需要同时读取两个寄存器:

  • $t1提供基地址
  • $t0提供需要写入内存的数据

因此:


这再次说明了两个读取端口的重要性。

但是,store指令不会产生新的通用寄存器结果。

它修改的是内存,而不是寄存器文件。

所以执行store时:


没有通用寄存器被写入。


十八、寄存器文件不是CPU的全部状态

32个通用寄存器非常重要,但它们并不是CPU内部的全部存储状态。

经典MIPS处理器还可能包含:

  • Program Counter,程序计数器
  • HI寄存器
  • LO寄存器
  • 浮点寄存器
  • 状态寄存器
  • 异常寄存器
  • 地址转换和系统控制寄存器
  • 流水线寄存器

程序计数器通常独立于通用寄存器文件。

传统MIPS中的HI和LO寄存器,用于保存乘法和除法的特殊结果。

浮点运算单元也可能拥有独立的浮点寄存器文件。

因此,32个通用寄存器是程序员最常直接使用的一部分CPU状态,但不是处理器内部的全部寄存器。


十九、寄存器和Cache不是一回事

寄存器和高速缓存都位于CPU附近,但两者的工作方式不同。

寄存器由机器指令直接指定。

例如:


这条指令明确要求读取$t1$t2,再写入$t0

Cache则通常由硬件自动管理。

程序不会直接要求:


缓存保存的是内存中某些数据块的副本。

寄存器保存的是指令当前直接使用的数值。

寄存器文件只有128字节,而缓存可能有几十KB或几MB。

寄存器容量更小,但与指令执行的联系更加直接。


二十、总结

经典32位MIPS寄存器文件包含32个通用寄存器。

每个寄存器宽32位。

5位寄存器编号可以选择32个寄存器中的任意一个。

它拥有两个独立的读取端口,可以同时提供两个源操作数

它还拥有一个写入端口,可以把一个结果保存到目标寄存器。

读取通常由组合逻辑完成。

写入通常发生在有效时钟边沿。

5到32译码器选择目标寄存器。

Reg Write信号决定是否允许写入。

寄存器0永远输出0,并忽略所有写入操作。

加法、立即数、加载和存储指令,都以不同方式使用同一个寄存器文件。

寄存器文件本身不负责完成加法、减法或者逻辑运算。

它的任务是:

  • 保存当前操作数
  • 向执行电路提供数据
  • 接收指令执行之后产生的结果

两个操作数离开寄存器文件之后,通常会进入中央处理器中最重要的执行电路之一:

Arithmetic Logic Unit,算术逻辑单元,也就是ALU。

下一篇,我们将继续介绍ALU内部如何使用数字电路完成加法、减法、比较和按位逻辑运算。

Posted in CPU结构