处理数据危险
AP SHANTHI博士
本模块的目标是讨论一般以及在 MIPS 架构中如何处理数据危险。
我们在前面的模块中已经讨论过,真实的数据依赖会导致 RAW 危险,而名称依赖(反依赖和输出依赖)会分别导致 WAR 危险和 WAW 危险。
图 12.1 给出了具有真实数据依赖性的情况。在接下来的三个指令中使用 ADD 指令的结果会导致危险,因为直到这些指令读取寄存器之后才会写入寄存器。ADD 指令的回写仅发生在第 5 个时钟周期,而接下来的三个指令读取之前的寄存器值,因此会读取错误的数据。这会导致 RAW 危险。
处理真实数据依赖性的一种有效解决方案是转发。转发是使数据可用于 ALU 的输入以供后续指令使用的概念,即使生成指令尚未到达 WB 以写入内存或寄存器。这也称为短路或通过。如图 12.2 所示。
第一条指令已执行完毕,结果已写入 EX/MEM 缓冲区。因此,在第四个时钟周期内,当第二条指令 SUB 需要数据时,可以将数据从 EX/MEM 缓冲区转发到 ALU 的输入。类似地,对于下一条 AND 指令,第一条指令的结果现在在 MEM/WB 缓冲区中可用,并且可以从那里转发。对于 OR 指令,结果在时钟周期的前半部分写入寄存器文件,并在后半部分从那里读取数据。所以,这个指令没有问题。简而言之,数据必须从 EX/MEM 缓冲区或 MEM/WB 缓冲区转发。
图 12.3 显示了支持转发所需的硬件更改。ALU 的输入增加了。必须扩展多路复用器,以容纳来自两个缓冲区的额外输入。
图 12.4 显示了第一个指令是加载并且数据仅在第四个时钟周期后可用的情况。因此,转发将无济于事,第二条指令无论如何都会停顿一个周期。对于下一条指令 AND,数据从 MEM/WB 缓冲区转发。因此存在即使转发也可能发生停顿的情况。然而,转发有助于最大限度地减少危险,有时甚至可以完全消除它们。
避免/最小化由于真实数据相关性导致的停顿的另一种方法是重新排序代码——分离相关指令。如图 12.5 所示。显示的代码段计算 A = B + E;C = B + F; 加载后的依赖指令可以重新排序以避免在下一条指令中使用加载结果。这种重新排序有助于将执行的时钟周期数从 13 减少到 11。避免了两次停顿。
基于前面给出的讨论,我们可以将两对危险条件确定为:
1a. EX/MEM.RegisterRd = ID/EX.RegisterRs
1b. EX/MEM.RegisterRd = ID/EX.RegisterRt
2a. MEM/WB.RegisterRd = ID/EX.RegisterRs
2b. MEM/WB.RegisterRd = ID/EX.RegisterRt
这里使用的符号如下:名称的第一部分,在句点的左边,是流水线寄存器的名称,第二部分是该寄存器中字段的名称。例如,“ID/EX.RegisterRs”是指在流水线寄存器ID/EX中找到其值的一个寄存器的个数;即,来自寄存器文件的第一个读取端口的那个。我们将根据以下说明顺序讨论各种危险。
序列中的第一个危险位于寄存器 $2 上,位于子 $2,$1,$3 的结果和 $12,$2,$5 的第一个读取操作数之间。当 and 指令处于 EX 阶段且前一条指令处于 MEM 阶段时,可以检测到这种危险,因此这是危险 1a:EX/MEM.RegisterRd = ID/EX.RegisterRs = $2
sub-or 是 2b 类危险:
MEM/WB.RegisterRd = ID/EX.RegisterRt = $2
对 sub-add 的两个依赖不是危险的,因为寄存器文件在 add 的 ID 阶段提供了正确的数据。sub 和 sw 之间没有数据风险,因为 sw在之后的时钟周期读取 $2sub 写 $2。但是,由于有些指令没有写入寄存器文件,因此必须修改此规则。否则,有时它会在不必要的时候转发。一种解决方案是简单地检查 RegWrite 信号是否有效。在 EX 和 MEM 阶段检查流水线寄存器的 WB 控制字段可确定 RegWrite 是否有效。此外,MIPS 要求每次使用 $0 作为操作数都必须产生一个为零的操作数值。如果管道中的指令将 $0 作为其目的地(例如,sll $0, $1, 2),我们希望避免转发其可能非零的结果值。因此,只要我们将 EX/MEM.RegisterRd ≠ 0 添加到第一个危险条件并且 MEM/WB.RegisterRd ≠ 0 到第二个,上述条件就可以正常工作。
图 12.6 显示了添加到 MIPS 管道的转发路径。ForwardA 和 ForwardB 是附加的控制信号。这些控制信号采用 00、10 或 01 的值,具体取决于多路复用器是否将分别传递来自 ID/EX、EX/MEM 或 MEM/WB 缓冲区的数据。
检测危险的条件和解决危险的控制信号如下:
1. EX危险:
如果(EX/MEM.RegWrite
和 (EX/MEM.RegisterRd _ 0)
和 (EX/MEM.RegisterRd = ID/EX.RegisterRs)) ForwardA = 10
如果(EX/MEM.RegWrite
和 (EX/MEM.RegisterRd _ 0)
和 (EX/MEM.RegisterRd = ID/EX.RegisterRt)) ForwardB = 10
这种情况将前一条指令的结果转发到 ALU 的任一输入。如果前一条指令要写入寄存器文件并且写入寄存器编号与 ALU 输入 A 或 B 的读取寄存器编号匹配,假设它不是寄存器 0,则指示多路复用器从流水线寄存器 EX 中选择值/记忆。
2. MEM 危害:
如果 (MEM/WB.RegWrite
和 (MEM/WB.RegisterRd _ 0)
和 (MEM/WB.RegisterRd = ID/EX.RegisterRs)) ForwardA = 01
如果 (MEM/WB.RegWrite
和 (MEM/WB.RegisterRd _ 0)
和 (MEM/WB.RegisterRd = ID/EX.RegisterRt)) ForwardB = 01
如上所述,WB 阶段没有危险,因为我们假设如果 ID 阶段中的指令读取由 WB 阶段中的指令写入的相同寄存器,则寄存器文件提供正确的结果。这种寄存器文件执行另一种形式的转发,但它发生在寄存器文件中。
另一个复杂因素是WB阶段指令的结果、MEM阶段指令的结果和ALU阶段指令的源操作数之间的潜在数据危险。例如,当对单个寄存器中的数字向量求和时,一系列指令将全部读取和写入同一个寄存器,如下所示:
加$1,$1,$2
加$1,$1,$3
加$1,$1,$4
. . .
在这种情况下,结果是从 MEM 阶段转发的,因为 MEM 阶段的结果是最近的结果。因此,对 MEM 危害的控制将是(突出显示添加的)
如果 (MEM/WB.RegWrite
和 (MEM/WB.RegisterRd _ 0)
和 (EX/MEM.RegisterRd _ ID/EX.RegisterRs)
和 (MEM/WB.RegisterRd = ID/EX.RegisterRs)) ForwardA = 01
如果 (MEM/WB.RegWrite
和 (MEM/WB.RegisterRd _ 0)
和 (EX/MEM.RegisterRd _ ID/EX.RegisterRt)
和 (MEM/WB.RegisterRd = ID/EX.RegisterRt)) ForwardB = 01
图 12.7 显示了修改后的数据路径以通过转发解决危险。与已经显示的数据路径相比,增加的是 ALU 输入的多路复用器。
正如我们已经讨论过的,转发无法帮助消除危险的一种情况是,当一条指令试图在写入同一寄存器的加载指令之后读取寄存器。如图 12.8 所示。当 ALU 正在执行下一条指令的操作时,数据仍在时钟周期 4 中从内存中读取。某些东西必须停止管道以进行加载组合,然后是读取其结果的指令。因此,除了转发单元之外,我们还需要一个危险检测 单元。它在 ID 阶段运行,以便它可以在负载和使用之间插入失速。检查负载指令,危险检测单元的控制是这个单一条件:
如果(ID/EX.MemRead 和
((ID/EX.RegisterRt = IF/ID.RegisterRs) 或
(ID/EX.RegisterRt = IF/ID.RegisterRt)))
停止管道
第一行测试以查看指令是否为负载。读取数据存储器的唯一指令是加载。接下来的两行检查 EX 阶段中加载的目标寄存器字段是否与 ID 阶段中指令的任一源寄存器匹配。如果条件成立,则指令停止 1 个时钟周期。在这个 1 周期停顿之后,转发逻辑可以处理相关性并继续执行。如果没有转发,则指令将需要另一个停顿周期。
如果ID级的指令被暂停,那么IF级的指令也必须被暂停;否则,我们将丢失获取的指令。阻止这两条指令的执行只需通过阻止 PC 寄存器和 IF/ID 流水线寄存器的变化来实现。如果保留这些寄存器,IF 阶段的指令将继续使用同一台 PC 读取,ID 阶段的寄存器将继续使用 IF/ID 流水线寄存器中的相同指令字段读取。从 EX 阶段开始的流水线的后半部分必须执行无效的指令。这是通过执行nops来完成的. 在 EX、MEM 和 WB 阶段取消所有九个控制信号(将它们设置为 0)将创建“什么都不做”或 nop 指令。通过识别 ID 阶段的危险,我们可以通过将 ID/EX 流水线寄存器的 EX、MEM 和 WB 控制字段更改为 0 来向流水线中插入一个气泡。这些控制值在每个时钟周期与正确效果——如果控制值全为 0,则不写入寄存器或存储器。
图 12.9 和 12.10 显示了硬件中真正发生的情况:与 AND 指令关联的流水线执行槽变成了 NOP,所有以 AND 指令开头的指令都延迟一个周期。危险迫使 AND 和 OR 指令在时钟周期 4 中重复它们在时钟周期 3 中所做的事情:读取寄存器和解码,然后从指令存储器中重新获取 OR。这种重复的工作就是停顿的样子,但它的作用是拉长了 AND 和 OR 指令的时间并延迟了 ADD 指令的取指。就像水管中的气泡一样,失速气泡会延迟其后面的所有内容,并在每个循环中沿指令管向下推进一个阶段,直到最后退出。
图 12.11 突出显示了危险检测单元和转发单元的管道连接
如前所述,转发单元控制 ALU 多路复用器以用来自适当流水线寄存器的值替换来自通用寄存器的值。危险检测单元控制 PC 和 IF/ID 寄存器的写入以及在实际控制值和全 0 之间选择的多路复用器。如果负载-使用危险测试为真,则危险检测单元停止并取消断言控制字段。
应该注意的是,停顿会降低性能,但需要获得正确的结果。还要记住,编译器可以安排代码以避免危险和停顿,并且需要了解流水线结构才能做到这一点。对于其他类型的数据危害,即。WAR 和 WAW 危害在没有真正共享数据的情况下,可以通过寄存器重命名来解决,这可以由硬件或编译器处理。这种重命名也可能发生在内存操作数上,这更难 处理,因为很难解决与内存操作数相关的歧义。
总而言之,在本模块中,我们讨论了如何通过转发处理数据危险。这种技术需要额外的硬件路径和控制。可能无法处理所有情况,可能需要摊位。为了避免WAR和WAW危害,可以通过软件或硬件进行寄存器重命名。RAW 危害也可以通过软件或硬件重新组织代码来处理。执行期间代码的硬件重组将在后面的模块中讨论。
网页链接/支持材料
计算机组织与设计——硬件/软件接口,David A. Patterson 和 John L. Hennessy,第 4 版,Morgan Kaufmann,Elsevier,2009 年。
计算机组织,Carl Hamacher、Zvonko Vranesic 和 Safwat Zaky,第 5 版,McGraw-Hill 高等教育,2011 年。