微处理器设计/冒险
冒险是指微控制器操作中出现的错误,由流水线处理器中多个阶段同时执行导致。
冒险有三种类型:数据冒险、控制冒险和结构冒险。
数据冒险是由尝试同时访问或修改数据引起的。在MIPS设计中,结果在同一时钟周期内被写回寄存器文件,而另一个指令解码阶段正在读取寄存器文件。数据冒险有三种基本类型
- 写后读 (RAW)
- 在这些冒险中,读取过程发生在写入过程之后,尽管这两个过程发生在同一个时钟周期内。如果写入过程需要很长时间,则在读取发生时可能无法完成,这将产生不正确的数据。
- 读后写 (WAR)
- 在WAR冒险中,先前指令的写入将不会在后续读取指令之前完成。这意味着下一个读取的值将是先前的值,而不是正确的值。
- 写后写 (WAW)
- 当两个进程试图同时写入数据存储单元时,就会发生WAW冒险。如果这发生在一个时钟周期内,则中间值之间没有时间读取。如果指令乱序执行,则寄存器中可能保留了不正确的值。
如果未明确考虑数据冒险,则可能会出现竞争条件,其中处理器的正确执行取决于时序。如果事件按正确的顺序和时间发生,则可能不会出现问题。但是,在竞争条件下,事件很可能发生乱序或在不同的时间间隔内发生,这将导致问题。
控制冒险发生在处理分支指令时。当分支指令在流水线中传输时,指令获取模块将继续从指令存储器中读取顺序指令。问题在于,由于分支的存在,后续指令可能会乱序执行,这会导致问题。
当两个单独的指令试图同时访问特定的硬件模块时,就会发生结构冒险。
有许多方法可以避免或消除冒险。
停顿,或流水线中的“气泡”,发生在控制单元检测到将发生冒险时。发生这种情况时,控制单元会停止指令获取机制,并改为将NOP放入流水线。这样,敏感指令将被迫单独执行,而不会有任何其他指令同时被处理。
在这张图片中,我们可以看到在数据冒险发生的地方绘制了“气泡”。气泡表示指令在流水线中停顿,直到先前的指令完成。一旦先前的指令完成,停顿的指令将继续移动。
请注意,在这张图片中,黄色指令在ID阶段停止了2个周期,而红色指令继续执行。
当一条指令的结果要作为下一条指令的ALU输入时,我们可以使用转发将数据直接从ALU输出移动到下一个周期的ALU输入,在该数据写入寄存器之前。这样,我们就可以避免在这些情况下需要停顿,但代价是需要添加额外的转发单元来控制这种机制。
寄存器可以重命名或重新编号,而不是使用固定的寄存器编号。考虑以下ADD指令
add R1, R2, R1
我们正在将R1和R2中的值相加,并将结果存储回R1。如果名称“R1”指向两个不同的物理存储区域,即值从一个位置“旧R1”读取,并写入一个新的存储区域“新R1”。
寄存器重命名可用于防止由乱序执行(OOOE)引起的冒险。
在分支期间,通常可以“猜测”分支的结果。通过猜测目标,可以推测性地执行指令。如果猜测错误,则需要清空流水线,这与停顿花费的时间相同。但是,如果猜测正确,则不会浪费时间,并且处理器将照常继续运行。
猜测分支将采取哪种方式的过程是一个复杂的话题,超出了本书当前的范围。
分支延迟是在分支之后在汇编源代码中编写的指令,旨在无论分支是否被执行都执行。如果没有可以不依赖于分支执行的指令,则应插入NOP。某些汇编器能够以这种方式重新排列代码,尽管其他使用此技术的汇编器需要程序员手动处理分支延迟。
在分支预测方案中,ISA中的所有指令或大多数指令可以根据某些条件有条件地执行。换句话说,指令将从内存中加载,解码,然后处理器将确定是否执行它。例如,在分支的情况下,如果分支朝另一个方向进行,则可以关闭分支之后的流水线中的指令。分支预测与推测执行密切相关。
分支预测是对分支指令将采取的方向进行猜测的行为。通常,分支预测器根据寄存器值和过去的分支历史来做出这些决定。例如,在一个大型循环中,特定的程序可能会多次分支回循环顶部,然后循环终止。考虑此高级伪代码
while(condition) do this end
大致转换为此汇编伪代码
top of loop: compare condition and 0. branch to end of loop if equal do this branch to top of loop bottom of loop:
此循环将持续重复,直到条件标志为0。这段代码可能会循环很多次,才会在最后一次退出。在这样的 while 结构中,它每次都会执行分支,除了最后一次,并且它只在最后一次不执行分支。因此,假设我们遇到的每个分支都会被执行,这可以提高我们的推测执行的准确性。
示例:循环优化
在现代处理器中,分支预测会经常查看最近分支的历史记录,以确定如何猜测未来分支的结果。考虑以下具有嵌套条件的循环结构
while(loop condition) if(branch condition) do this else do that end
如果我们从统计上知道分支条件将有 90% 的时间为假 (0),并且循环条件将有近 100% 的时间为真 (1)。我们可以将其分解成汇编伪代码
1) compare loop condition and 0 2) branch to end of loop if equal 3) compare branch condition and 0 4) branch to branch true if not equal 5) do that 6) branch to end of if 7) branch true 8) do this 9) end of if 10)branch to top of loop
如果我们查看此循环结构,我们可以看到第 10 行的分支在大多数情况下都会被执行。我们还可以看到,第 4 行的分支仅在分支条件为 1 时才会发生。我们知道分支条件仅有 10% 的时间为真,因此此循环的分支预测效果不佳。在这种情况下,更好的循环应该是
while(loop condition) if(not branch condition) do this else do that end
以便条件中的分支有 90% 的时间被执行,从而使分支预测器更准确。
分支预测器通常像一个计数器一样工作。每次执行分支时,计数器都会递增;每次不执行分支时,计数器都会递减。考虑一个 2 位预测器。如果预测器为 0 或 1,则不执行分支;但如果预测器为 2 或 3,则执行分支。
我们可以像上图所示那样,将分支预测器视为一个有限状态机 (FSM)。此 FSM 有 4 个阶段,对应以下“猜测”
- q0:强执行
- q1:弱执行
- q2:弱不执行
- q3:强不执行
此图中的零表示未执行分支,而 1 表示执行分支。如果执行了许多分支,则状态会向右移动。如果不执行分支,则状态会向左移动。