指令相关和流水线冲突
什么是指令相关?
例如,在标准的五级流水线中,第N条指令写R1寄存器,第N+1条指令需要读R1寄存器做运算。由于五级流水线的结构,第N条指令在第五级才将数据写回R1寄存器,而第N+1条指令则在第二级(第N条指令的第三级)就需要从寄存器堆中取R1寄存器的数据,由于此时第N条指令并未写回,因此第N+1条指令只能取到旧的R1寄存器值,导致运算错误,这就是指令相关。
指令相关
指令相关可以分为三类:
- 数据相关
- 控制相关
- 结构相关
数据相关:如果两条指令访问同一个寄存器或者同一个内存单元,而且这两条指令中至少有一条是写该寄存器或内存单元,则这两条指令之间存在数据相关。
控制相关:如果两条指令中一条是转移指令且另一条指令是否被执行取决于该转移指令的执行结果,则这两条指令之间存在控制相关。
结构相关:如果两条指令使用同一功能部件(如两条都是加法指令),则这两条指令存在结构相关。
数据相关的类型
数据相关可以根据冲突访问读和写的次序分为3种。
写后读相关(Read After Write,RAW),就是后面指令要用到前面指令所写的数据,这是最常见的类型,也称真相关。
写后写相关(Write After Write,WAW),也称输出相关,即两条指令写同一个单元,在乱序执行的结构中如果后面的指令先写,前面的指令后写,就会产生错误的结果。
读后写相关(Write After Read,WAR),在乱序执行的结构或者读写指令流水线不一样时,如果后面的写指令执行的快,在前面的读指令读数之前就把目标单元原来的值覆盖掉了,导致读数指令读到了该单元“未来”的值,从而引发错误。
在经典的五级流水线中,只有写后读是真相关,会真实发生也会引起冲突。标准的非乱序执行的五级流水线中不会产生写后写和读后写冲突的情况。
解决五级静态流水线的控制相关
由于控制相关是由分支转移指令引起的。假设第N条指令为分支转移指令,在常规做法下,第N条指令的第三级流水(执行阶段)结束才能计算出下一条PC的值(PC寄存器被更新为新的值),因此通常来说,第N+1条指令的取值(IF)需要等到第N条指令的执行(EX)结束才能进行,也就是需要阻塞两个时钟周期。
现在设计一个专用PC计算部件,让第N条分支指令在译码(ID)阶段就能计算出下一条指令的PC值,那么当ID结束时,PC就会被正确更新为下一条指令的地址(PC+4或者PC+imm),那么下一条指令仅需等待一个时钟周期即可取指令。
有没有办法让N+1条指令不等待?
细想一下,第N+1条指令之所以需要等待一个时钟周期,是因为不确定第N条指令到底需不需要转移,PC到底应该加4还是加上一个其它偏移,这是我们动态运行时需要考虑的,如果没有分支指令,PC只是机械的更新为PC+4即可。但是动态运行时如果只是机械的+4,可能会导致CPU执行一条不该执行的指令,即静态的N+1条指令。如果静态的第N+1条指令的运行不会引起任何错误呢?那CPU就可以放心大胆的运行,不用等到分支结果出来才决定PC的值,因为直接运行PC+4不会引起任何错误,而第N条指令的ID结束后,PC也会被更新为正确的NPC(Next PC),此时第N+1条指令的IF运行完毕,第N+2条指令(正确的分支地址的指令)的IF可以还是运行了,就不需要阻塞流水线了。
在静态的第N条分支指令后面的指令设置为一条不影响程序计算的指令的技术叫做延迟槽,它的本质是让真正的会被分支指令决定的下一条指令延迟到分支指令计算出NPC后运行。