第四章 计算机体系结构
流水线
1. 处理指令的阶段
-
取指
将地址为pc的指令从内存中读取出来。可能取出一个或两个寄存器操作数指示符rA和rB。
还可能取出一个常数字valc 下一条指令**的地址\(valp = pc+valc\) -
译码
从寄存器文件中读入指令rA,rB指明的寄存器中的操作数:valA,valB -
执行
算数逻辑单元(ALU)执行指令 -
访存
将数据写入内存,或者从内存读出数据
- 写回
写回阶段最多可以写两个结果到寄存器文件
- 更新
将pc设置成下一条指令的地址
2. 非流水线化
假设组合逻辑需要300ps,加载寄存器需要20ps
在这个实现中,在开始下一条指令之前必须完成上一个。
这个系统的吞吐量是(单位时间执行的指令数)
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访存结束后,再使用数据转发
4. 避免控制冒险
当处理器无法根据处于取值阶段的当前指令来确定下一条指令的地址时,就会出现控制冒险
- 当取出指令是ret时
下一条指令的地址需要从栈中读出,所以必须等到访存操作结束后才能知道下一条指令的地址。
如图,在ret后插入三个气泡,等到ret执行完访存阶段后才取出下一条指令。
- 当取出指令是跳转指令
我们可以预测是跳转还是不跳转。
对于这段代码,我们简单的假设只要遇到分支指令则必然跳转
当执行跳转后按照跳转顺序继续执行指令。当跳转指令进行完执行阶段发现预测错误,就会在原先偷跑的指令中插入气泡将这两条偷跑的指令剔除。
同时取出跳转指令后面的指令。