1.9 流水线
1.9.1 流水线的概念与原理
处理器按照一系列步骤来执行每一条指令,典型的步骤如下。
(1)从存储器读取指令(fetch)。
(2)译码以鉴别它属于哪一条指令(decode)。
(3)从指令中提取指令的操作数(这些操作数往往存在于寄存器reg中)。
(4)将操作数进行组合以得到结果或存储器地址(ALU)。
(5)如果需要,则访问存储器以存储数据(mem)。
(6)将结果写回到寄存器堆(res)。
并不是所有的指令都需要上述每一个步骤,但是,多数指令需要其中的多个步骤。这些步骤往往使用不同的硬件功能,如ALU可能只在第4步中用到。因此,如果一条指令不是在前一条指令结束之前就开始,那么在每一步骤内处理器只有少部分的硬件在使用。
有一种方法可以明显改善硬件资源的使用率和处理器的吞吐量,这就是在当前一条指令结束之前就开始执行下一条指令,即通常所说的流水线(Pipeline)技术。流水线是RISC处理器执行指令时采用的机制。使用流水线,可在取下一条指令的同时译码和执行其他指令,从而加快执行的速度。可以把流水线看做是汽车生产线,每个阶段只完成专门的处理器任务。
采用上述操作顺序,处理器可以这样来组织:当一条指令刚刚执行完步骤(1)并转向步骤(2)时,下一条指令就开始执行步骤(1)。从原理上说,这样的流水线应该比没有重叠的指令执行快6倍,但由于硬件结构本身的一些限制,实际情况会比理想状态差一些。
1.9.2 流水线的分类
1.3级流水线ARM组织
到ARM7为止的ARM处理器使用简单的3级流水线,它包括下列流水线级。
(1)取指令(fetch):从寄存器装载一条指令。
(2)译码(decode):识别被执行的指令,并为下一个周期准备数据通路的控制信号。在这一级,指令占有译码逻辑,不占用数据通路。
(3)执行(execute):处理指令并将结果写回寄存器。
图1-4所示为3级流水线指令的执行过程。
当处理器执行简单的数据处理指令时,流水线使得平均每个时钟周期能完成1条指令。但1条指令需要3个时钟周期来完成,因此,有3个时钟周期的延时(latency),但吞吐率(throughput)是每个周期1条指令。
图1-4 3级流水线
2.5级流水线ARM组织
所有的处理器都要满足对高性能的要求,直到ARM7为止,在ARM核中使用的3级流水线的性价比是很高的。但是,为了得到更高的性能,需要重新考虑处理器的组织结构。有两种方法来提高性能。
(1)提高时钟频率。时钟频率的提高,必然引起指令执行周期的缩短,所以要求简化流水线每一级的逻辑,流水线的级数就要增加。
(2)减少每条指令的平均指令周期数CPI。这就要求重新考虑3级流水线ARM中多于1个流水线周期的实现方法,以便使其占有较少的周期,或者减少因指令相关造成的流水线停顿,也可以将两者结合起来。
ARM的指令流水线
3级流水线ARM核在每一个时钟周期都访问存储器,或者取指令,或者传输数据。只是抓紧存储器不用的几个周期来改善系统性能,效果并不明显。为了改善CPI,存储器系统必须在每个时钟周期中给出多于一个的数据。方法是在每个时钟周期从单个存储器中给出多于32位数据,或者为指令或数据分别设置存储器。
基于以上原因,较高性能的ARM核使用了5级流水线,而且具有分开的指令和数据存储器。把指令的执行分割为5部分而不是3部分,进而可以使用更高的时钟频率,分开的指令和数据存储器使核的CPI明显减少。
在ARM9TDMI中使用了典型的5级流水线,5级流水线包括下面的流水线级。
(1)取指令(fetch):从存储器中取出指令,并将其放入指令流水线。
(2)译码(decode):指令被译码,从寄存器堆中读取寄存器操作数。在寄存器堆中有3个操作数读端口,因此,大多数ARM指令能在1个周期内读取其操作数。
(3)执行(execute):将其中1个操作数移位,并在ALU中产生结果。如果指令是Load或Store指令,则在ALU中计算存储器的地址。
(4)缓冲/数据(buffer/data):如果需要则访问数据存储器,否则ALU只是简单地缓冲1个时钟周期。
(5)回写(write-back):将指令的结果回写到寄存器堆,包括任何从寄存器读出的数据。
图1-5 5级流水线
图1-5所示列出了5级流水线指令的执行过程。
在程序执行过程中,PC值是基于3级流水线操作特性的。5级流水线中提前1级来读取指令操作数,得到的值是不同的(PC + 4而不是PC + 8)。这里产生代码不兼容是不容许的。但5级流水线ARM完全仿真3级流水线的行为。在取指级增加的PC值被直接送到译码级的寄存器,穿过两级之间的流水线寄存器。下一条指令的PC + 4等于当前指令的PC + 8,因此,未使用额外的硬件便得到了正确的R15。
3.13级流水线
在Cortex-A8中有一条13级的流水线,但是由于ARM公司没有对其中的技术公开任何相关的细节,这里只能简单介绍一下,从经典ARM系列到现在的Cortex系列,ARM处理器的结构在向复杂的阶段发展,但没改变的是CPU的取指指令和地址关系,不管是几级流水线,都可以按照最初的3级流水线的操作特性来判断其当前的PC位置。这样做主要还是为了软件兼容性上的考虑,由此可以判断的是,后面ARM所推出的处理核心都想满足这一特点,感兴趣的读者可以自行查阅相关资料。
1.9.3 影响流水线性能的因素
1.互锁
在典型的程序处理过程中,经常会遇到这样的情形,即一条指令的结果被用做下一条指令的操作数。例如,有如下指令序列:
LDR R0,[R0,#0]
ADD R0,R0,R1 ;在5级流水线上产生互锁
从例子可以看出,流水线的操作产生中断,因为第1条指令的结果在第2条指令取数时还没有产生。第2条指令必须停止,直到结果产生为止。
2.跳转指令
跳转指令也会破坏流水线的行为,因为后续指令的取指步骤受到跳转目标计算的影响,因而必须推迟。但是,当跳转指令被译码时,在它被确认是跳转指令之前,后续的取指操作已经发生。这样一来,已经被预取进入流水线的指令不得不被丢弃。如果跳转目标的计算是在ALU阶段完成的,那么在得到跳转目标之前已经有两条指令按原有指令流读取。
显然,只有当所有指令都依照相似的步骤执行时,流水线的效率达到最高。如果处理器的指令非常复杂,每一条指令的行为都与下一条指令不同,那么就很难用流水线实现。