数据危害的数据转发如何工作,在管道图中
How does data forwarding for data hazards work, in pipeline diagrams
我在理解流水线危害方面有点困难,尤其是数据危害以及我们如何使用停顿周期和数据转发来解决这些危害。下面是一个带有数据和加载使用风险的 MIPS 汇编代码示例(已指出):
B0: sll $t2, $a0, 2
B1: add $t1, $a0, $a1
B2: lw $t0, 4($t1) #data hazard $t1
B3: slt $v0, $t1, $a1 #data hazard $t1
B4: sub $v1, $v0, $t3 #data hazard $v0
B5: addi $t0, $t0, 4
B6: lw $t1,8($t0) #data hazard $t0
B7: sll $t1, $a1, 1
B8: add $t2, $t1, $v0 #data hazard $t1
B9: lw $v0, 0($t2) #data hazard $t2
B10: lw $t3, 0($v0) #load-use hazard $v0
B11: and $v0, $t3, $a2 #data hazard $t3
B12: sll $a1, $t3, 1 #data hazard $t3
对于这段代码,我们有一个管道 "diagram",我们必须在其中填充停顿周期和数据转发。由于图表有 20
个周期,我将只显示第一个 5
个周期和那些 5
个周期中的数据转发。
Pipeline Cycle - First 5 cycles with data forwarding.
在图表中你可以看到从BO
我们在B1
中从EX
转发到EX
,但是为什么呢?由于我们在 B1
或 B2
中不需要来自 B0
的任何两个寄存器,因此不存在关于 $t2
或 $a0
的数据风险,因此不需要数据转发。
此外,我怎么知道我是否必须从 EX
或 MEM
转发,因为有时我不确定是否必须从 EX
转发到 EX
、EX
到 MEM
、MEM
到 EX
或 MEM
到 EX
。我想对此进行一些澄清。
这些流水线危害的本质是流水线是否会以其他方式尝试使用陈旧的值。流水线将单个指令的执行分散到各个阶段(每个阶段都发生在一个后续周期中),从而使每个阶段的周期时间更短。为了不影响性能,流水线处理器重叠连续指令的执行。正是这种重叠既加快了性能又引入了潜在的危险。
在指令解码期间,会查询寄存器文件以获取正在执行的指令的操作数——在单周期(非流水线)处理器中,这些值在架构上始终是 100% 正确的,即使它们最近被更新紧接在前面的说明。
类似地,在流水线处理器中,查询寄存器文件以获取正在执行的指令的操作数:这特别发生在 ID 阶段。
然而,对于任何两个背靠背指令,第一个指令的 WB 阶段 将发生在 第二个指令的 ID 阶段之后 — 这就是危险的来源。
当背靠背指令具有寄存器依赖性时,就会存在危险(ID 阶段已经读取过时值,因为最新值实际上尚未准备好然而。虽然,当背靠背指令没有依赖性时,没有危险,因为 ID 阶段已经读取了正确的和最新的值。
前向(又名旁路)用于获取正确的、最新的值,通常不会使处理器停止(如果我们要等待第一条指令的 WB 阶段在执行第二条指令的 ID 阶段之前完成——这将是两个周期的延迟)。
关键是我们有三个选择:
- 放弃重叠执行——这不是提高性能的选项
- 存在依赖性时延迟 — 对性能也不是很好
- 使用转发或旁路
forward/bypass 的工作原理是,最早可以使用正确值的时间是在 EX 阶段结束时(对于 ALU 风险),并且我们可以提供最多的最晚时间-to-date 值处于 EX 阶段的开始。因此,如果我们可以在周期之间获取从 ALU 输出到 ALU 输入的值,我们可以在不引入停顿的情况下避免危险。
转发(又名旁路)有两种机制:
- 决定何时参与
- 如何参与
最复杂的是确定两个背靠背指令之间是否存在依赖关系。无论是做停顿还是旁路,我们都需要这种复杂性,所以最好做后者,因为停顿会影响性能。
粗略地说,确定何时参与的逻辑是确定一条指令的目标与下一条指令的源之间何时存在 register/operand 依赖关系,因此硬件不断地在两者之间寻找任意两条背靠背指令。
当发现这样的依赖关系时,ID 阶段读取的陈旧值将被正确的值覆盖——在 ALU 危险的情况下,然后从 ALU 中出来(输出)。因此,旁路机制在启动时会忽略 ID 阶段提供的操作数值,并使用 ALU 输出值覆盖其中一个(或另一个或两者)。
我在理解流水线危害方面有点困难,尤其是数据危害以及我们如何使用停顿周期和数据转发来解决这些危害。下面是一个带有数据和加载使用风险的 MIPS 汇编代码示例(已指出):
B0: sll $t2, $a0, 2
B1: add $t1, $a0, $a1
B2: lw $t0, 4($t1) #data hazard $t1
B3: slt $v0, $t1, $a1 #data hazard $t1
B4: sub $v1, $v0, $t3 #data hazard $v0
B5: addi $t0, $t0, 4
B6: lw $t1,8($t0) #data hazard $t0
B7: sll $t1, $a1, 1
B8: add $t2, $t1, $v0 #data hazard $t1
B9: lw $v0, 0($t2) #data hazard $t2
B10: lw $t3, 0($v0) #load-use hazard $v0
B11: and $v0, $t3, $a2 #data hazard $t3
B12: sll $a1, $t3, 1 #data hazard $t3
对于这段代码,我们有一个管道 "diagram",我们必须在其中填充停顿周期和数据转发。由于图表有 20
个周期,我将只显示第一个 5
个周期和那些 5
个周期中的数据转发。
Pipeline Cycle - First 5 cycles with data forwarding.
在图表中你可以看到从BO
我们在B1
中从EX
转发到EX
,但是为什么呢?由于我们在 B1
或 B2
中不需要来自 B0
的任何两个寄存器,因此不存在关于 $t2
或 $a0
的数据风险,因此不需要数据转发。
此外,我怎么知道我是否必须从 EX
或 MEM
转发,因为有时我不确定是否必须从 EX
转发到 EX
、EX
到 MEM
、MEM
到 EX
或 MEM
到 EX
。我想对此进行一些澄清。
这些流水线危害的本质是流水线是否会以其他方式尝试使用陈旧的值。流水线将单个指令的执行分散到各个阶段(每个阶段都发生在一个后续周期中),从而使每个阶段的周期时间更短。为了不影响性能,流水线处理器重叠连续指令的执行。正是这种重叠既加快了性能又引入了潜在的危险。
在指令解码期间,会查询寄存器文件以获取正在执行的指令的操作数——在单周期(非流水线)处理器中,这些值在架构上始终是 100% 正确的,即使它们最近被更新紧接在前面的说明。
类似地,在流水线处理器中,查询寄存器文件以获取正在执行的指令的操作数:这特别发生在 ID 阶段。
然而,对于任何两个背靠背指令,第一个指令的 WB 阶段 将发生在 第二个指令的 ID 阶段之后 — 这就是危险的来源。
当背靠背指令具有寄存器依赖性时,就会存在危险(ID 阶段已经读取过时值,因为最新值实际上尚未准备好然而。虽然,当背靠背指令没有依赖性时,没有危险,因为 ID 阶段已经读取了正确的和最新的值。
前向(又名旁路)用于获取正确的、最新的值,通常不会使处理器停止(如果我们要等待第一条指令的 WB 阶段在执行第二条指令的 ID 阶段之前完成——这将是两个周期的延迟)。
关键是我们有三个选择:
- 放弃重叠执行——这不是提高性能的选项
- 存在依赖性时延迟 — 对性能也不是很好
- 使用转发或旁路
forward/bypass 的工作原理是,最早可以使用正确值的时间是在 EX 阶段结束时(对于 ALU 风险),并且我们可以提供最多的最晚时间-to-date 值处于 EX 阶段的开始。因此,如果我们可以在周期之间获取从 ALU 输出到 ALU 输入的值,我们可以在不引入停顿的情况下避免危险。
转发(又名旁路)有两种机制:
- 决定何时参与
- 如何参与
最复杂的是确定两个背靠背指令之间是否存在依赖关系。无论是做停顿还是旁路,我们都需要这种复杂性,所以最好做后者,因为停顿会影响性能。
粗略地说,确定何时参与的逻辑是确定一条指令的目标与下一条指令的源之间何时存在 register/operand 依赖关系,因此硬件不断地在两者之间寻找任意两条背靠背指令。
当发现这样的依赖关系时,ID 阶段读取的陈旧值将被正确的值覆盖——在 ALU 危险的情况下,然后从 ALU 中出来(输出)。因此,旁路机制在启动时会忽略 ID 阶段提供的操作数值,并使用 ALU 输出值覆盖其中一个(或另一个或两者)。