参考整理:
https://www.cnblogs.com/lyc-seu?page=1
第 1 章:超标量处理器概述
1.1 为什么需要超标量
如何加快处理器执行程序的速度:
- 减少程序中指令的数量
- 减少每条指令在处理器中执行所需要的周期数
- 减少处理器的周期时间
超标量处理器靠硬件实现指令并行,VLIW 靠编译器和程序员实现指令并行。处理器设计要根据应用领域和场合来决定设计思路,进行折衷(tradeoff)。
1.2 普通处理器的流水线
处理器的级数在考虑能效比的情况下有一个理论最优解,但现实情况往往大不相同。五级流水线包括取指、译码、执行、访存和写回,可以通过“合”和“拆”进一步优化流水线。指令之间存在 RAW、WAR 和 WAW 三种相关。
1.3 超标量处理器的流水线
超标量处理器每周期可以取出多于一条的指令送到流水线中执行,并且使用硬件来对指令进行调度。顺序执行的超标量处理器中,计分板用来记录流水线中每条指令的执行情况,为旁路网络提供信息。乱序执行的超标量处理器中,需要增加 PRF 进行寄存器重命名来解决 WAW 和 WAR 两种相关性。发射阶段是流水线从顺序执行到乱序执行的分界点,重排序缓冲实现指令对处理器状态的顺序更新。乱序执行的超标量处理器流水线包括:取指、译码、重命名、分发、发射、读寄存器、执行、写回、提交。预测和恢复电路也是超标量处理器的关键。
第 2 章:Cache
2.1 Cache 的一般设计
时间相关性和空间相关性是 Cache 存在的必要基础,内存墙是 Cache 诞生的原因。在超标量设计时,ICache 需要每周期取出多条指令,DCache 也需要支持每周期多条 load/store 指令的访问。
Cache 由 Tag 部分和 Data 部分组成,分为直接映射、组相连、全相连三种组织方式。Cache 缺失分为强制缺失、容量缺失和冲突缺失。Cache 中 Tag SRAM 和 Data SRAM 的访问可以是并行的,也可以是串行的。Cache 的写入分为写穿和写回,非写分配和写分配。Cache 的替换策略有树状伪 LRU 算法,随机替换算法等。
2.2 提高 Cache 的性能
写缓存可以隐藏写入下级存储的延迟。流水线可以降低处理器的周期时间,需要注意流水线内部 store 对 load 的前递。多级结构中 Inclusive 类型的 Cache 相比 Exclusive 类型,虽然浪费了一定的资源,但简化了一致性设计。Victim Cache 和 Cache 是互斥的关系。可以将数据先重填至 Filter Cache,防止偶尔被使用的数据占据 Cache。预取可以减少强制缺失,将预取的数据放在 Stream Buffer 中,但错误的预取会浪费功耗和性能。
2.3 多端口 Cache
实际的处理器当中,一般不会直接使用多端口的 SRAM 来设计多端口 Cache,也不会使用多个 SRAM 拷贝来设计多端口 Cache。Multi-Banking 是现实中被广泛使用的方法,但 Tag SRAM 仍需要多个端口或进行复制。
2.4 超标量处理器的取指令
ICache 可以在每周期将一个数据块全部输出。可以考虑将 Cache 行的大小翻倍,并将一个 Cache 行分为两个 SRAM 行进行存储,取出指令后进行重排序。
第 3 章:虚拟存储器
3.1 概述
负责地址转换的部件一般称为内存管理单元(MMU),虚拟存储器可以实现保护和共享。
3.2 地址转换
目前最通用的是基于分页的虚拟存储器,典型的页大小是 4KB,虚拟地址到物理地址的转换实际是虚拟页号到物理帧号的转换。
页表是存储从虚拟地址到物理地址的对应关系的表格,页表的起始地址存储在页表寄存器(PTR)中。多级页表可以减少页表对于物理存储空间的占用。一个页表中的表项简称为 PTE,操作系统创建进程时,在物理内存中为这个进程找到一块连续的 4KB 空间存放该进程的第一级页表,运行时按需创建第二级页表。
虚拟存储器可以让每个程序都有独立的地址空间,为物理内存的管理带来了方便,为多个进程分配的虚拟内存之和可以大于实际可用的物理内存,也方便管理每一个页的访问权限。
一旦虚拟地址在访问页表时,发现对应的 PTE 还没有保存相应的映射关系,那么此时硬件就会产生一个 Page Fault 类型的异常。
3.3 程序保护
可以通过页表中的权限位控制内核和用户对内存的访问。
3.4 加入 TLB 和 Cache
TLB 的设计
缓存 PTE 的部件称为 TLB,现代处理器多采用两级 TLB:一级 TLB 为哈弗结构,全相联;二级 TLB 是共用的,组相联。现代的处理器还支持大小可变的页,以充分利用 TLB。解决 TLB 缺失的过程称为 Page Table Walker,可以是软件实现,也可以是硬件实现。在发生 Page Fault 需要替换物理内存中的某一页时,TLB 中存在映射的物理页是不能被替换的。为方便软件对 TLB 的管理,需要实现 TLB 管理指令:将所有表项置为无效,将某个 ASID 对应的表项置为无效,将某个虚拟页号对应的表项置为无效。
Cache 的设计
同义问题(重名)指多个不同的名字对应相同的物理地址,可以通过物理 tag 和分 bank 的方式解决该问题。同名问题指相同的名字对应不同的物理位置,可以使用 ASID 和 Global 位来解决该问题。软件对 Cache 的操作包括将 ICache 的 Cache 行置为无效,将 DCache 的 Cache 行写回,将 DCache 的 Cache 行写回并置为无效。
将 TLB 和 Cache 放入流水线
PIPT 的 Cache 会增加访存的延迟,真实的处理器中很少被使用。VIPT 的 Cache 是最广泛使用的,当单路 Cache 的容量小于等于页大小时,和物理 Cache 没有区别;当单路 Cache 的容量大于页大小时,会存在同名问题,可以使用 L2 Cache 解决该问题。将虚拟 Index 中位于虚拟页号的几位存于 L2 Cache 的表中,就可以让 L2 Cache 保证相同物理地址的多个虚拟地址不会同时存在于 L1 Cache 中。VIVT 的 Cache 则需要存储整个 VA 在相应的表中。
第 4 章:分支预测
4.1 概述
分支预测对超标量处理器的性能至关重要,分支指令包含方向和目标地址两个要素。动态分支预测直观的方法是将 ICache 中取出的指令进行快速解码,然后将分支指令的 PC 送入分支预测器进行预测,但这通常需要多个时钟周期,降低了分支预测的效率;从 L2 Cache 写入 ICache 时进行预解码可以略微缓解过长的延迟。更佳的解决方案是根据 PC 的值预测本周期的指令组是否存在分支指令以及分支指令的方向和目标地址。
4.2 分支指令的方向预测
基于两位饱和计数器的分支预测
基于两位饱和计数器进行分支预测是最基础的方法,其状态迁移图还可以根据实际情况进行改进。PHT 是两位饱和计数器表,使用 PC 的低位进行寻址。两条分支指令映射到同一 PHT 表项称为别名问题,哈希可以缓解别名问题。如何选取更新 PHT 的时间是一个问题,可选更新 PHT 的时间包括:取指阶段,执行阶段和提交阶段。
基于局部历史的分支预测
使用 BHR 记录一条分支指令在过去的历史状态,使用 BHR 去索引 PHT。n 位的 BHR 可以预测循环周期小于等于 n 位的序列。将所有分支指令的 BHR 组合在一起称为 BHT,一般使用分支指令 PC 的一部分来分别索引 BHT 和 PHTs,同样地,使用哈希可以降低别名带来的影响。
基于全局历史的分支预测
GHR 是全局历史寄存器,使用 PC 的哈希值索引 PHTs,使用 GHR 索引对应的 PHT。也可以结合拼接或异或的方式进行索引。
竞争的分支预测
使用 CPHT 选择全局预测器和局部预测器,结合 GHR 和 PC 来索引 CPHT 可以获得更好的效果。
分支预测的更新
更新历史寄存器和两位饱和计数器的时机会影响分支预测的准确度。在流水线的取指阶段根据预测的结果推测地更新 GHR 可以获得较好性能,不过需要 GHR 的恢复机制。可以使用 Retired GHR 进行修复,并可以使用 checkpoint 增加效率。BHR 和 PHT 一般在退休时再更新。
4.3 分支指令的目标地址预测
直接跳转类型的分支预测
使用 BTB 记录分支指令的目标地址,BTB 中有 tag 和目标地址,可以是直接相联或组相联。可以通过减少 tag 的长度来降低开销。当 BTB 缺失时,一般需要停止流水线执行直到算出目标地址。
间接跳转类型的分支预测
使用返回地址栈 RAS 处理 call 和 return 指令。RAS 需要在取指阶段就知道哪些指令是 call 和 return 指令,所以需要 BTB 存储分支指令的类型。RAS 在满时应该覆盖旧的表项,并增加计数器来预测递归调用。
对于其他间接跳转类型的分支指令,使用 BHR 和 PC 来索引 Target Buffer。
4.4 分支预测失败时的恢复
可以基于 ROB 进行分支预测失败的恢复,在预测错误的分支指令到达 ROB 表头时清空流水线,但这效率较低。使用 checkpoint 进行恢复需要消耗更多的硬件资源,还需要对每一条分支指令进行编号。可以使用空闲编号列表来实现编号逻辑。使用 PTAB 存储预测跳转的分支指令的目标地址,用于执行阶段进行正确性检查。
4.5 超标量处理器的分支预测
如果要求每周期取出的指令对齐,就可以利用指令组的公共地址进行分支预测,此时需要 BTB 存储指令组中分支指令的偏移。若不要求对齐,则需要 BTB、BHT 和 PHT 都要有多个读端口;多端口 BTB 很难被接受,故使用预译码的方式直接得到目标地址更为实际;BHT 和 PHT 可以通过交叠的方式模拟多端口。
第 5 章:指令集体系
指令集体系包括基本数据类型、指令、寄存器、寻址模式、存储体系、中断和异常、外部 IO 等内容。对一个指令集体系的硬件实现方式称为微架构。
5.1 复杂指令集和精简指令集
指令集从本质上可以分为复杂指令集和精简指令集两种。
5.2 精简指令集概述
MIPS 指令有三种类型:I-type,J-type 和 R-type。ARM 指令有三种类型:DP、DT 和 BR,DP 类型的立即数通过循环右移得到,只有少数 32 位数是合法的立即数。
5.3 Load/Store 指令
ARM 指令集支持前/后变址的寻址方式,支持多寄存器传送指令 LDM/STM。
5.4 计算指令
MIPS 中的加法和减法指令可以产生溢出异常,ARM 中可以选择将结果的状态保存到状态寄存器中。ARM 将移位指令和运算指令集成到了一条指令当中。MIPS 通过 Hi 和 Lo 两个特殊寄存器保存乘法结果,ARM 则使用通用寄存器。
5.5 分支指令
MIPS 中直接比较寄存器的值来决定分支指令是否执行,ARM 中使用状态寄存器来决定。ARM 指令集的任何指令都可以条件执行。
5.6 杂项指令
SYSCAL、TRAP 和 ERET 指令。
5.7 异常
处理器的外部事件引起的异常,虚拟地址到物理地址的转换引起的异常,指令自身引起的错误,指令自身产生的异常等。
第 6 章:指令解码
6.1 指令队列
因为取指宽度不等于解码宽度,需要指令队列暂存取出的指令,指令队列本质是一个多端口的 FIFO。
6.2 一般情况
对于 ARM 中的特殊指令,可以考虑用单独的重命名表和物理寄存器来进行 CPSR 寄存器的重命名,使用指令拆分来实现多个目的寄存器的指令的重命名工作。解码阶段要判断指令的类型,操作和数据来源。
6.3 特殊情况
分支指令的处理
为了减少分支编号分配电路的复杂度,需要限制每周期进行解码的分支指令个数,这可以通过控制指令队列的读指针来实现。解码阶段还需要对分支预测是否正确进行初步的检查。
乘累加/乘法指令的处理
将寄存器 Hi 和 Lo 分配为 MIPS 处理器的第 33 和第 34 个通用寄存器,并将乘法/乘累加指令拆为两条指令。在指令分发阶段,两条指令在 ROB 中占据两个连续的空间,但在发射队列中仍是一个。
在解码阶段,需要限制每周期可以解码的指令个数,例如一旦发现乘累加指令,只会解码它拆分后的第一条指令和其之前的指令,如此拆分后的指令数便不会超过解码宽度。
前/后变址指令的处理
基本和乘累加指令的处理方式相同。
LDM/STM 指令的处理
LDM/STM 指令使用 16 位的寄存器列表来对应指令集中定义的 16 个通用寄存器,若每周期拆分出 2 个 load/store 指令,则需要寻找 16 位的寄存器列表中的最低位开始的两个 1。
可以将 16 位的向量拆分为四个 4 位的向量。先对 4 位向量进行分析,得到其中有无 1、第一个 1 的位置。然后将两个 4 位向量的信息进行结合,再将两个 8 位向量的信息进行结合,通过这样的树状结构得到第一个 1 的位置。
寻找第二个 1 的过程更复杂,在结合时需要考察低位向量没有 1,有一个 1 和多于一个 1 的情况。
条件执行指令的处理
单独的寄存器重命名。
第 7 章:寄存器重命名
7.1 概述
可以将相关性分为下面的几类:1. 数据相关性:WAW,WAR,RAW 2. 存储器数据的相关性 3. 控制相关性 4. 结构相关性
通过寄存器重命名可以解决 WAW 和 WAR 相关,因为它们产生的原因是有限个数的寄存器、程序中的循环体和代码重用。处理器内部实际存在的寄存器称为物理寄存器,指令集中定义的寄存器称为逻辑寄存器。寄存器重命名映射表(RAT)用来保存已经存在的映射关系,空闲寄存器列表(Free Register List)用来记录哪些物理寄存器是空闲的。
7.2 寄存器重命名的方式
使用 ROB 进行寄存器重命名
需要重命名映射表来指示每个逻辑寄存器的值是位于 ROB 中还是位于 ARF 中。该方法实现简单,但很多指令并没有目的寄存器,造成物理寄存器的浪费,而且 ROB 需要的读端口需求很高。
将 ARF 扩展进行寄存器重命名
PRF 的作用和上述的 ROB 的结果域是相似的,但是没有目的寄存器的指令不需要占用 PRF 的表项。
使用统一的 PRF 进行寄存器重命名
使用一个空闲列表来记录这个 PRF 中哪些寄存器处于空闲状态,其可以用 FIFO 来实现。当一条指令和后面的某条指令都写到同一个目的寄存器时,则后面的指令退休的时候,前面指令对应的物理寄存器就已经没有用处了,所以在 ROB 中,除了记录逻辑寄存器当前对应的物理寄存器之外,还需要存储它之前对应的物理寄存器。
7.3 重命名映射表
RAT 在物理层面上有两种实现方式,一是基于 SRAM,二是基于 CAM。sRAT 的深度是逻辑寄存器个数,使用逻辑寄存器的编号进行寻址,每个表项中存放着对应的物理寄存器的编号。cRAT 的深度是物理寄存器的个数,在每个表项中存放逻辑寄存器的编号和有效位,表项在 cRAT 中的索引是对应的物理寄存器的编号。对 cRAT 进行 Checkpoint 只需要保存状态位。
基于 SRAM 的重命名映射表
当需要对分支指令的状态进行 Checkpoint 保存时,需要将整个 sRAT 都保存起来。只对误预测率较高的分支指令进行 Checkpoint 可以减少 Checkpoint 的个数。为一条指令的目的寄存器找到一个空闲的物理寄存器时,sRAT 中旧的对应关系需要保存在指令的 ROB 中,供释放物理寄存器和 RAT 修复使用。
基于 CAM 的重命名映射表
每次对 cRAT 进行 Checkpoint,只需要保存有效位即可,需要注意一个物理寄存器对应的有效位即使是 0,也并不能表示它已经变为了空闲状态。
7.4 超标量处理器的寄存器重命名
对于一条指令来说,RAT 需要三个读端口,一个写端口。超标量处理器的寄存器重命名过程中需要考虑 RAW 和 WAW 两种相关性。
需要将每条指令的源寄存器编号和它前面所有指令的目的寄存器编号进行比较,如果存在一个相等的项,那么这个源寄存器对应的物理寄存器就不是来自于 RAT 的输出,而是来自于当前周期从空闲列表输出的对应值;如果存在多个相等的项,那么就使用最新的那条指令所对应的物理寄存器。
如果存在多条指令的目的寄存器都相等的情况,那么只有最新的那条指令的映射关系才允许写入 RAT 当中。如果在一个周期内进行寄存器重命名的几条指令中,有两条指令的目的寄存器相等,那么比较新的这条指令对应的旧的物理寄存器就直接来自于比较旧的那条指令。
7.5 寄存器重命名过程的恢复
使用 Checkpoint
将 Checkpoint 简称为 GC,分为随机访问的 GC 和串行访问的 GC。对于 RAT 来说,可以在 SRAM 的每个最小存储单元(MBC)周围都加入同样的存储单元(CBC)。
使用 WALK
可以按照 ROB 中的信息,把所有更改 RAT 的指令按照相反的顺序,将 RAT 进行恢复。
使用 Architecture State
将 aRAT 的内容在指令退休阶段直接恢复到重命名阶段的 RAT 中。
7.6 分发
流水线的分发阶段是顺序执行和乱序执行的分界点。分发阶段将寄存器重命名之后的指令写到发射队列和重排序缓存中。
第 8 章:发射
8.1 概述
- 发射队列:用来存储已经被寄存器重命名但还没有执行的指令
- 分配电路:从发射队列中找到空闲的空间,将寄存器重命名之后的指令存储到其中
- 选择电路:在多条准备好的指令中选出最合适的指令送到 FU 执行
- 唤醒电路:将 FU 执行得到的结果通知给发射队列中所有等待这个数据的指令
集中式 VS 分布式
所有的 FU 共用一个发射队列,称为集中式的发射队列;每个 FU 都有一个单独的发射队列,称为分布式的发射队列。
数据捕捉 VS 非数据捕捉
在流水线的发射阶段之前读取寄存器,称为数据捕捉的结构,发射队列中存储操作数的地方称为 payload RAM。当一条指令从发射队列中被选中时,它会将目的寄存器的编号值进行广播,发射队列中其他的指令可以捕捉 FU 的结果。
在流水线的发射阶段之后读取物理寄存器堆,称为非数据捕捉结构,该方法可以减少发射队列的面积,但需要更多的物理寄存器读端口。
压缩 VS 非压缩
压缩的发射队列中,每当一条指令被选中而离开发射队列时,会出现一个“空隙”,这条指令上面所有的指令都会下移一格,填上“空隙”。该方法可以保证空闲的空间都处于发射队列的上部,而且队列中的指令按新旧顺序排列。该方法的分配电路和选择电路简单,但面积和功耗不容乐观。
非压缩的发射队列中,空闲空间在发射队列中的分布是没有规律的,指令的位置信息和年龄信息也没有关联。但功耗和布线较为友好。
8.2 发射过程的流水线
非数据捕捉结构的流水线
发射过程被分为了仲裁和唤醒两个流水线阶段,在仲裁阶段,会使用仲裁电路从发射队列中选择一条最合适的指令送到 FU 中;在唤醒阶段,即将发往 FU 的指令的目的寄存器会将发射队列中的相关的寄存器置为准备好的状态。唤醒的时机早于实际指令执行完成,以实现背靠背执行两条存在 RAW 相关的指令,这需要仲裁和唤醒两个操作放在一个周期之内完成,还需要旁路网络的支持。
数据捕捉结构的流水线
多端口的 payload RAM 的延迟较大,可以将仲裁和唤醒放在一个周期内完成,将 payload RAM 的读取放在下一个周期完成;将执行和旁路也分为两个流水级实现。
8.3 分配
当采用非压缩的方式来设计发射队列时,需要分配电路能够找到四个空闲的表项。可以采用树状结构来寻找空闲,也可以采用 FIFO 结构的空闲列表。更激进者,可以将发射队列进行拆分。
8.4 仲裁
1-of-M 的仲裁电路
采用非压缩方式设计的发射队列中,可以使用每条指令在 ROB 中的位置作为这条指令的年龄信息,并在 ROB 的地址前面再加入一位回绕值。根据年龄值,通过树状的比较电路即可选出最旧的指令。
为屏蔽掉发射队列中没有准备好的指令,可以添加 ready 位,最终选出最老的指令在发射队列中的索引。
N-of-M 的仲裁电路
完美的 N-of-M 仲裁电路的延迟过大,可以采用折中的方案,对每一条指令指定一个 FU,实现 N 个 1-of-M 的仲裁电路,但这不能实现完美的 oldest-first。
8.5 唤醒
单周期指令的唤醒
被仲裁电路选中的指令会将它的目的寄存器的编号送到对应的总线上,每一条总线上的编号会和发射队列中所有的源寄存器的编号进行比较,如果发现相等,它就可以将对应的源寄存器置为准备好的状态。
多周期指令的唤醒
延迟广播的方法可能导致广播总线的冲突,可以使用一个表格记录每条指令的执行周期数,否决可能出现总线冲突的指令的仲裁请求。延迟唤醒的方法可以使用移位寄存器来实现。
推测唤醒
可以假设 D-Cache 是一直命中的,以此来进行唤醒,预测错误的情况需要进行状态恢复。load 指令被仲裁电路选中之后,需要等待两个周期才可以将相关的指令进行唤醒,这两个周期称为 IW;从 load 指令开始将相关的指令进行唤醒,直到它明确自身 D-Cache 的命中情况,中间间隔的周期称为 SW。缺失时 SW 窗口中的指令需要 replay。
基于 Issue Queue 的 replay 中,所有的指令在被仲裁电路选中的时候,都不要马上离开发射队列,只有当这条指令确认自己可以正确执行的时候,才会离开发射队列。考虑到 store/load 违例和 load/load 违例的情况,指令要在退休时才能离开发射队列,这大大浪费了发射队列的空间。作为折中,可以将 load/load 违例和 store/load 违例的情况通过重新取指解决。但我们仍需要在 D-Cache 缺失时,将 load 指令和之后的指令抹去。无论是抹去之后的所有指令,还是只抹去 SW 窗口中的指令,都需要识别其与 load 指令的相关性。
可以通过对每个物理寄存器添加 load-vector 的值来识别这种相关性,向量的长度等于 in-flight 的 load 指令的数量,表格的容量等于物理寄存器的数量,这种方法的开销较大。另一种较好的方法是,对于每条 load 指令都使用一个 5 位的值,表示这条 load 指令处于流水线的哪个位置,这个值称为 LPV。所有被唤醒的源寄存器都会得到这条 load 指令的 LPV 值,发射队列中所有源寄存器的 LPV 每周期逻辑右移一位。load 指令到达流水线的 Data 阶段时,在发射队列中那些 LPV 的最低位是 1 的所有源寄存器,都会和这条 load 指令存在直接或间接的相关性。若 LPV 的值随指令在流水线中流动,还可以精确地抹掉相关的指令。
基于 Replay Queue 的 replay 中,指令被仲裁电路选中之后,会马上离开发射队列,进入 RQ。一般地,非数据捕捉结构的发射队列来说,需要较长时间确定 load 指令的执行状态,采用该方式能降低发射队列的压力。
第 9 章:执行
9.1 概述
RISC 指令集一般包括算术运算、访问存储器的操作、控制程序流的操作和特殊的指令。执行阶段的另一个重要的部分是旁路网络。
9.2 FU 的类型
ALU 负责对整数类型的数据进行计算。AGU 用来计算地址。BRU 负责处理程序控制流类型的指令,即计算分支指令的目标地址,并判断分支条件是否成立。BRU 的另一功能是进行分支预测的正确性检查。
MIPS 处理器使用了在分支指令执行的同时判断条件的方法,而 ARM 和 PowerPC 等处理器在每条指令的编码中都加入了条件码。条件码的存在对寄存器重命名造成困难,预测一条指令总是会执行,在错误时再刷新流水线,会导致处理器的执行效率很低;Intel 提出了使用硬件插入额外的指令(select-uOP 指令)的解决方案。
9.3 旁路网络
将 FU 的输出端数据送到 FU 的输入端、物理寄存器堆、payload RAM 等的通路,称为旁路网络。为降低延迟,可在读取寄存器后增加 Source Drive 阶段,在执行后增加 Result Drive 阶段。
当 FU 的数量增加时,旁路网络的复杂度迅速增加。多个 FU 可以共享前递总线,因此仲裁电路在进行选择时需要判断 FU 当前的资源是否可以使用。当需要增加流水级时,旁路网络也会变得更复杂。
9.4 操作数的选择
需要 ScoreBoard 记录所有物理寄存器的信息,包括物理寄存器会在哪个 FU 中被计算出来,这些值是否已被写回 PRF 中。执行的过程需要先读取 ScoreBoard 来判断操作数的来源。更简单的方案是,在将一条指令的计算结果进行广播时,将这条指令的目的寄存器编号也进行广播。通过源寄存器和广播的目的寄存器的比较结果控制源寄存器的多路选择器。
9.5 Cluster
将发射队列采用 Cluster 结构可以减少发射队列的端口个数,加快每个仲裁电路的速度,但跨 Cluster 的唤醒会经过较长的走线,增加一个 bubble。在数据捕捉的结构中,payload RAM 可以分 Cluster 实现;在非数据捕捉的结构中,物理寄存器堆可以分 Cluster 实现。
在旁路网络上引入 Cluster 结构,可以显著降低复杂度,但只有当两条连续的相关指令使用同一 Cluster 时,才可以背靠背执行。
9.6 存储器指令的加速
Memory Disambiguation
在部分的乱序执行中,store 指令是按序执行的,处于两个 store 指令之间的所有 load 指令可以乱序执行。将已经被仲裁电路选中但还没有退休的指令存储在 Store Buffer 中。通过 Store Buffer 进行前递需要判断 load 和 store 指令的相对顺序,要对它们进行编号。
在完全的乱序执行中,load 指令的操作数只要准备好了,就可以参与仲裁的过程。若提前执行的 load 指令得到了错误的结果,则需要抹掉这条 load 指令和与它有直接或间接相关关系的指令,这称为 store/load 违例。需要精确的 load/store 相关性的预测来提升乱序执行访存的性能。Intel 提出了编译器和指令集配合解决 load/store 相关性的方案。
非阻塞 Cache
非阻塞的 Cache 允许处理器在发生 D-Cache 缺失的时候继续执行新的 load/store 指令。支持非阻塞的操作方式需要将产生 D-Cache 缺失的 load/store 指令保存起来,这个结构称为 MSHR。MSHR 主要由两部分组成,本体用来保存所有产生首次缺失的 load/store 指令的信息;load/store Table 存储所有发生缺失的 load 和 store 指令的信息。
关键字优先
从缺失的字开始读取。
提前开始
缺失的字被取出后马上恢复流水线。
第 10 章:提交
10.1 概述
只有在重排序缓存中最旧的那条指令变为已完成的状态时,这条指令才允许离开流水线,并使用它的结果对处理器的状态进行更新,此时称这条指令退休了。当一条指令没有退休之前,它的状态都是推测的。
10.2 重排序缓存
ROB 本质上是一个 FIFO,记录指令是否完成、逻辑寄存器号、物理寄存器号、旧的物理寄存器号、PC、异常状态和类型等信息。ROB 的读写端口需求很大。
10.3 管理处理器的状态
指令集定义的状态称为 Architecture State,超标量处理器内部的状态称为 Speculative State,后者是程序员不可见的。可以使用 ROB 管理指令集定义的状态,也可以使用物理寄存器管理指令集定义的状态。
10.4 特殊情况的处理
分支预测失败的处理
前端的状态恢复比较简单,只需要将流水线中重命名阶段之前的所有指令都抹掉,将分支预测器中的历史状态表进行恢复,并使用正确的地址来取指令即可。后端的状态恢复比较复杂,但其可以和新的指令在前端流动的过程并行进行。
后端的状态恢复主要是对 RAT 的恢复。基于 ROB 的寄存器重命名方式,只要将 RAT 中所有的内容都标记为 ARF 状态。基于统一的 PRF 进行重命名的结构中,可以将后端 RAT 全部复制到寄存器重命名阶段使用的 RAT 中。以上的方法称为 Recovery at Retire,有分支预测失败惩罚过大的问题。使用 Checkpoint 和 WALK 的方式可以加快恢复进程。
异常的处理
精确的异常指处理器能够知道哪条指令发生了异常,并且这条发生异常的指令之后的所有指令都不允许改变处理器的状态。对于 Free Register Pool,恢复的过程只需要修改读指针;对于 Busy Table,可以在 WALK 过程中逐个修改。
中断的处理
中断是异步的,可以立即处理,也可以延迟处理。
Store 指令的处理
可以让 Store 指令在真正成功写入 D-Cache 之前就离开 ROB,只在 Store Buffer 中标记为 retired 状态,由 Store Buffer 将数据写回 D-Cache。
指令离开流水线的限制
由于资源的限制,每周期退休的同一类型的指令数量可能有限制。