流水线
1. 处理指令的阶段
-
取指
将地址为pc的指令从内存中读取出来。可能取出一个或两个寄存器操作数指示符rA和rB。
还可能取出一个常数字valc 下一条指令**的地址\(valp = pc+valc\) -
译码
从寄存器文件中读入指令rA,rB指明的寄存器中的操作数:valA,valB -
执行
算数逻辑单元(ALU)执行指令 -
访存
将数据写入内存,或者从内存读出数据
- 写回
写回阶段最多可以写两个结果到寄存器文件
- 更新
将pc设置成下一条指令的地址
2. 非流水线化
假设组合逻辑需要300ps,加载寄存器需要20ps
在这个实现中,在开始下一条指令之前必须完成上一个。
这个系统的吞吐量是(单位时间执行的指令数)
\[1/(300+20) = 3.12GIPS \]3. 流水线化
将一条指令分为abc三部分,指令从开始到结束需要三个时钟周期
每经过一个120ps的周期,每条指令就行进一个阶段
且每个阶段都会有一个流水线寄存器,比如a阶段的输出在第一个流水线寄存器,b阶段在第二个......
吞吐量与非流水线化相比明显提高
追踪时刻240-360之间的电路活动。
- 240之前,指令I2的值已经到达第一个流水线寄存器(该寄存器此时还保持着I1在A阶段计算的值)的输入。指令I1在b阶段的输入到达第二个流水线寄存器的输入
- 到达240,时钟信号上升时,这些输入被加载到对应的流水线寄存器中,成为寄存器的输出
- 信号以不同的速率通过各个不同部分,在360之前,结果值到达流水线寄存器的输入
4. 数据冒险
1. 什么是冒险
相邻指令间存在相关
- 数据相关
下一条指令会用到这一条指令的计算结果 - 控制相关
一条指令要确定下一条指令的位置,例如在执行跳转,返回指令时
这些相关可能会导致流水线产生计算错误,称为冒险
2. 冒险实例
F D E M W分别代表取值,译码,执行,访存,写回阶段。
左边的四条指令按照右边的流水线执行。
我们重点来看一下第四个时钟周期。
指令3此时正处于译码阶段,理论上它将要从rdx和rax中取出10和3,但实际上取出的都是0
因为指令2此时正处于执行阶段,指令1正处于访存阶段,都还没有将立即数写回寄存器。
所以在第四个时钟周期,rdx和rax里面还是默认值0
在这个实例中
由于指令间存在数据相关,导致了流水线产生错误的计算结果,称之为冒险,或者冲突。
3. 避免数据冒险
-
最先能想到的是让指令addq等待前两条指令都写回后再执行
通过插入气泡,让addq等待一直到第七个时钟周期才开始进行第二阶段。但是这样的流水线性能并不高
- 但其实movq在访存阶段并没有执行任何操作,那么可以直接把运算结果传给addq,而不用再等待写回。
可以添加一个信号线,将指令movq经过ALU的执行结果直接传送到指令addq的译码阶段
这种实现技术称为数据转发,也称旁路
- 等待和转发一起
指令1要先访问内存,但读内存的操作发生在访存阶段,所以即使才用数据转发也无法将值送回过去的时间。
可以让addq在译码阶段等待一个时钟周期,等到movq访存结束后,再使用数据转发