IF -> ID -> EX -> MEM -> WB
。(https://en.wikipedia.org/wiki/Classic_RISC_pipeline)。首先,让我们看一下那些每个阶段通常需要一个时钟周期的指令(例如SW(将字存储到内存)、BNEZ(非零分支)和ADD(将两个寄存器相加并存储到寄存器中))。并不是所有这些指令在所有流水线阶段都有有用的工作。例如,SW在WB阶段没有工作要做,BNEZ可以在ID阶段尽早完成(这是计算目标地址的最早时间),而ADD在MEM阶段没有工作要做。
无论如何,每个指令都会经过流水线的每个阶段,即使它们在某些阶段没有工作。指令将占用给定的阶段,但不会执行任何实际工作(例如,SW指令的WB阶段不会向寄存器写入结果)。换句话说,在这种情况下,不会出现停顿。
转向更复杂的指令,其EX阶段可能需要数十个周期,例如MUL或DIV。这里变得更加棘手。现在,即使按顺序获取指令(意味着WAW hazards现在是可能的),指令也可以以无序完成。请看以下示例:
MUL R1, R10, R11
ADD R2, R5, R6
MUL首先被获取并在ADD之前到达EX阶段,然而ADD将会在MUL的EX阶段运行超过10个时钟周期之前就完成。但是,在这个序列中不存在风险,因此管道不会在任何时候停顿-既不可能发生RAW风险也不可能发生WAW风险。再举一个例子:
MUL R1, R10, R11
ADD R1, R5, R6
实际上比你想象的更加复杂。
首先,CPU 不执行指令,而是执行 uops,其次,它可以乱序执行 uops。
uops
简单的指令转换为单个 uop,复杂的指令分成多个 uop。CPU 有一个 uop 缓存,保留最近的(例如1024)几个 uop。与完整指令相比,uop 更相似,因此在流水线中更容易匹配。
乱序执行
如果 CPU 需要等待计算结果,则查找不依赖于先前指令的 uops 并执行它们。
为了允许乱序执行,CPU 拥有一个寄存器文件,其中有比程序员可用的寄存器多得多的寄存器(例如256个通用寄存器)。它可以将其用作临时存储中间结果的工作区。
所有执行的指令都进入退役缓冲区,在原始顺序中输出结果。
缓冲区
除此之外,缓冲区解决了停顿问题。
指令被推测性地获取,并在缓冲区中等待解码。
nop
,只有停顿。 - Ped7g