您好,欢迎来到意榕旅游网。
搜索
您的当前位置:首页第二章 流水线处理器

第二章 流水线处理器

来源:意榕旅游网
第二章 流水线处理器

2.1 流水线基本知识 2.2 流水线处理器设计 2.3 深度流水线处理器 2.4 总结 2.5 习题

流水是一种不需要增加大量的硬件就可以提高系统吞吐能力的强有力的实现技术。20世纪60年代早期,在设计高端的大型主机时首先引入了流水线技术。在IBM7030(又被称作可延伸的计算机)中,首先引入了指令流水线。后来,在CDC6600中,同时使用了流水线技术与多功能部件。

20世纪80年代,流水线技术成为RISC计算机的处理器设计的基础。RISC计算机中的许多技术直接或间接地与进行有效地流水处理相关。从那时起,流水线技术被有效地应用到CISC处理器中。Intel i486是IA32体系结构的第一个流水线实现。采用了流水线技术的Digital的VAX 以及Motorola 的M68K 体系结构同样成功地实现了商业化。

目前在设计指令集处理器时,广泛采用了流水线技术。本章主要讨论(标量)流水线处理器的设计。与流水线处理器设计相关的许多方法与技术,例如用于危险检测与解决的流水线互锁机制,是设计超标量处理器的基础。

目前的趋势是设计非常深的流水线。流水线的深度已经从不足10增加到多于20。为获得非常高的时钟频率,深度流水线是必须的。这已经成为一项非常有效的措施,以获得更高的处理器性能。有迹象表明,这一趋势将会继续。

2.1 流水线基本知识

这一节讲述流水线发展的动力以及基本原则。从历史上讲,有两种主要类型的流水线:算术流水线与指令流水线。本书的重点是指令流水线,我们首先来分析一个算术流水线的例子。算术流水线很容易地阐明了一系列理想化的假设,这些假设是流水线设计的基本原则。我们将这些理想化的假设称之为流水线理想。在这些理想化的假设与指令流水线的实际情形之间存在着差异,也正是研究这些差异使得流水线处理器设计变得如此有意义。

2.1.1 流水线设计

本小节介绍流水线设计的基本概念,并且介绍流水线的动力以及局限性,描述了由Peter Kogge 提出的一个从硬件设计角度优化的流水线理论模型。

2.1.1.1 动力

引入流水线的主要动力是通过增加很少的硬件来获取系统吞吐能力的提高。一个系统的吞吐能力,或者说带宽,用单位时间内完成的任务数目来度量,它标志着系统的性能。对于一个一次处理一个任务的系统,其吞吐能力P等于1/D,D是这个任务的延迟或者系统对于与这个任务性能相关的延迟。若有许多任务需要使用相同的系统,则采用流水线可以增加一个系统的吞吐能力。对于每个任务来讲,实际的延迟仍然保持不变,甚至可能稍许增加。

流水线需要将系统分作多个级,并且在各级之间增加缓存。这些级以及级间的缓存构成了流水线。由原系统进行的计算被分作k个子计算,分别由流水线的k级进行计算。只要上一个任务已经经过了第一级,一个新的任务便可以进入流水线。因此,流水线可以在每隔D/k单位时间启动一个新任务,而不是每隔D单位时间,其中k是流水线的级数,并且在流水线中对k个计算的处理是重叠进行的。假设要处理的任务的数目非常大,那么一个采用流水线技术的系统的吞吐能力有潜力达到一个未采用流水线技术的系统的k倍。通过在一个k级的流水线中简单地增加一些新的缓存,可以获得潜在的k倍的性能提高,这正是进行流水线设计的主要吸引力。图2-1给出了在一个k级流水线系统中可以潜在地获得吞吐能力的k倍增加。

图2-1 在一个k段流水线系统中吞吐能力增加的潜力为k倍

到目前为止,我们假定在级间增加缓存不会导致额外的延迟,事实并非如此。图2-2(a) 给出了被设计用在IBM360/91的Earle锁存器,它用作流水线乘法部件中进位存储加法器的级间缓存。在Earle锁存器中,当时钟C=1时,输出Z跟随输入D发生变化。当时钟变低,D的值通过锁存回路被锁存在Z,随后输出Z对D的进一步变化不敏感。在输入D处,需要适当的保持时间,以确保正确的锁存。中间的与门确保操作不受假脉冲的影响,由这个与门表示的结果项中“包含”一个潜在的危险。这个危险是一个寄生脉冲,它是由于多个信号同时发生变化产生的竞争所造成的。或门的顶部与底部的输入可能同时朝相反的方向变化,在这种情况下,若或门没有中间(冗余)的输入,一个寄生脉冲(危险)便有可能在或门的输出端出现。Earle锁存器有不受假脉冲影响的良好特性。此外,Earle锁存器能够“集成” 到逻辑功能中而不产生额外的门延

迟。图2-2(b)给出了在上一个两级的与-或组合逻辑电路中集成延迟功能,但不增加额外的门延迟。图2-2(b)中的电路完成与图2-2(a)中电路的逻辑功能相同,没有因为锁存而增加两个额外的门延迟。门的输入端数增加1会稍微增加通过这些门的延迟。

图2-2 Earle锁存器以及在不增加额外门延迟的情况下将其集成到逻辑中:(a)跟随组合逻辑的Earle锁存器;(b)与组合逻辑集成的Earle锁存器

2.1.1.2

由于流水线设计的性能的提高与其深度,即流水线的级数成比例,好像最好的设计总是使一个流水线系统的级数最大化。然而,由于时钟的制约,对一个原先的计算能够分成多细的流水线级数是有物理的。

流水线的每一级可以看作是组合逻辑F的一个片段,随后是一系列的锁存L。信号必须通过F传输,并在L处锁存。假设 TM是通过F的最大传输延迟,也就是通过最长信号路径的延迟;设 Tm是通过F的最小传输延迟,即通过最短信号路径的延迟。设TL是正确的时钟所需的额外时间。延迟TL可以包括为确保正确的锁存所必须的建立与保持时间,以及潜在的时钟偏移,也就是在不同的锁存器上时钟边沿到达时间不同的最大偏差。如果第一组信号X1 在T1时刻加载到这一段的输入端,则F的输出必须在T1TM时有效。为使L的锁存正确,F的输出端的信号必须持续有效到T1TMTL。当第二组信号X2 在T2时刻加载到F的输入端,至少需要到T2Tm时锁存器L才能感觉到这些信号。为了保证第二组信号不会影响第一组信号,需要

T2TmT1TMTL (式2-1)

它意味着X2最早到达锁存器的时间不能早于为确保X1正确锁存的时间。这个不等式可以写作

T2-T1TM-TmTL (式2-2)

其中T2-T1实际上是最小的时钟周期T。因此,时钟周期T 必须大于

TM-TmTL,并且最大的时钟频率不能超过1/T。

根据前面的分析,两个因素制约着时钟频率。一个是通过逻辑电路的最大与最小广播延迟的差别,即TM-Tm。另一个是为保证正确的时钟所需的额外时间,即TL。如果所有信号的传播路径的长度相同,则第一个因素可以去除。这个可以通过填充短路径来实现。因此,TM-Tm接近于0。第二个因素是由于要在流水段之间锁存结果引起的。正确的锁存要求信号通过一个反馈回路来传

播,并且在这个回路中信号要稳定。影响TL的另一个因素是最坏情况的时钟偏移。由于为所有的锁存器产生并分配时钟信号,时钟信号到达不同锁存器的时间会有细微差别。在一个全同步系统中,这个最坏情况的时钟偏移必须在时钟周期内解决。最终,一个同步系统的流水深度取决于锁存所需的最小时间,以及在时钟分布的网络中与延迟相关的不确定性。

2.1.1.3 折衷

时钟决定了流水深度的最终物理上限。除了这一上限之外,若考虑成本或流水的额外开销,最大的流水深度并不一定是最优设计。在一个流水线系统中,必须在成本与性能之间进行折衷。Peter Kogge 提出了一个流水线设计中的成本/性能折中模型,总结如下。成本与性能的模型均给出了。一个非流水线设计的成本记做G。这个成本可以是门的个数,晶体管数目,或者是硅的实际开销。一个k段的流水线设计的成本C是

CGkL (式2-3)

其中,k是流水线的段数,L是增加每个锁存的成本,G是原先非流水线设计的硬件成本。根据这个成本模型,流水线的成本C是流水线深度k的一个线性函数。基本上,流水线的成本随着流水线的深度线性上升。

假设非流水线系统的延迟是T,则非流水线设计的性能是1/T,即计算速率。流水线设计的性能可以模型化为1/(T/kS),其中T是原先非流水线设计的延迟,S是由于增加了锁存器所造成的延迟。假设原先的延迟T可以均匀地分成k段,(T/kS)是与每一段相关的延迟,即流水线的时钟周期。因此,

1/(T/kS)等于时钟频率,即流水线设计的吞吐率。于是,流水线设计的性能

P1TSk (式2-4)

注意到P是k的一个非线性函数。

根据以上关于成本与性能的模型,成本与性能之比的表达式是

CGkL (式2-5)

1PTSk这个表达式可以重写为

CGTLTGSLSk (式2-6) Pk图2-3画出了两族G,L,T,S的曲线。

图2-3 流水线设计的性能与成本折中模型

式2-6表明成本与性能之比是k的函数,对它求一阶导数并令其为0,得到一个k值,将产生最小的成本性能比。式2-7中给出的k值是根据其它参数得出的最优流水线深度。

koptGT (式2-7) LS根据上面关于k的最优值的表达式,一个kkopt的流水线设计可称作是“欠流水”,因为增加流水线的深度是有益的,所增加的成本可以带来性能的提高。另一方面,kkopt是一个“过流水”的设计,因为流水线成本的增加导致其性能的下降。前述的折中模型仅考虑硬件设计的问题,没有涉及流水线的动态性能,或者说没有考虑所要完成的计算。下面将讨论这些问题。

2.1.2 算术流水线的例子

有两种主要的流水线:算术流水线与指令流水线。指令流水线设计是本章的重点,我们先来看一个算术流水线的例子。算术流水线清楚地说明了流水线的有效性,而不必涉及在指令流水线设计中的一些复杂问题。这些复杂问题将在本章的后续小节中讨论。

2.1.2.1 浮点乘法

以一个浮点乘法器的流水线设计作为例子。这个“过时的”板级设计是从Shlomo Waser 与Mike Flynn的一本经典的课本中节选的。(尽管这个设计是1980年的技术,然而它仍然是说明算术流水线设计的一个有效的工具。)这个设计采用位的浮点模式,指数部分e采用8位,尾数部分m采用57位(包括一个隐藏位)。

下面是本设计中浮点乘算法的实现。

1.检查操作数中是否有为0的,若有,则立即令结果为0。

2.将两个特性(指数的物理位模式)相加,并且为超过128的偏移进行校正,即e1+(e2128)。

3.完成两个尾数m1与m2的定点乘法。

4.对尾数的乘积进行规格化,即将其左移一位并且将阶减1。(规格化表示的尾数的首位不能为0)。

5.在第一个保护位(尾数的最低有效位的右边一位)上加1,以对结果进行舍入。这样可以进行有效的舍入。若尾数上溢,则尾数必须右移一位,并且阶增加1,以保证尾数的规格化表示。

图2-4给出了浮点乘法器的非流水设计的功能框图。输入锁存器存储了将要进行乘法的两个操作数。在下一个时钟,两个数的乘积将保存在输出锁存器中。

图2-4 一个非流水的浮点乘法器

定点尾数乘法器是本设计中最复杂的模块,它由三个子模块组成,分别负责部分积的生成,部分积的化简以及最终的化简。每个子模块的复杂性,根据集成电路(IC)的数目,以及传输时延(以纳秒ns计),可以得到。

生成部分积。采用88 的硬件乘法器可以同时生成部分积。为生成所有的部分积,需要34个这样的88的乘法器。延迟是125ns。 部分积的化简。一旦所有的部分积都产生了,必须对它们进行相减或求和。一个称作(5,5,4)计数器的求和电路可以用来将两个各有5位的列简化为一个4位的和。用一个延迟为50ns的1K4ROM可以实现(5,5,4)计数器。需要三级(5,5,4)计数器来对所有的部分积进行化简。因此总共需要72个这样的1K4ROM,总的时延为150ns。

最后的化简。一旦所有的部分积被化简为两个部分积,可以采用快速超前进位加法器(CLA)来进行最后的化简,以产生最终结果。这一最终化简步骤需要16个带CLA的4位加法器芯片以及5个4位CLA单元。总共需要21个IC芯片,时延为55ns。

尾数部分需要额外的两个模块,一个是用以完成规格化的移位器(两个芯片,20ns时延),另一个是用来完成舍入的递增器(15个芯片,50ns的时延)。指数部分的加/减模块需要4个芯片,它们的延迟不重要,因为其不在关键的时延路径上。为实现输入以及输出锁存,各需要17个以及10个芯片。表2-1总结了非流水线设计的总的芯片数目以及各模块中的关键时延。

根据表2-1,浮点乘法器的非流水线设计需要175个芯片,其时钟频率可为2.5MHz,时钟周期为400ns。这表明非流水线设计可以达到2.5MFLOPS(每秒百万次浮点操作)的吞吐能力。

表2-1 非流水线浮点乘法器设计中

模块的芯片数以及关键延迟 模块 芯片数 延迟 生成部分积 34 125ns 简化部分积 72 150ns 最后的简化 21 55ns 规则化 2 20ns 圆整 15 50ns 指数部分 4 --- 输入锁存 17 --- 输出锁存 10 --- 总计 175 400ns

2.1.2.2 流水线浮点乘法器

浮点乘法器的非流水线设计可以采用流水来提高其吞吐能力。在这个例子中,我们假定在子模块中没有流水,即划分流水线段的最细粒度为子模块一级。现在来检查与关键时延路径相关的每个(子)模块的时延。这些时延在表2-1的第三列中给出。部分积化简子模块有最长的时延,150ns,这一时延决定了流水线段中的时延。关键路径上的5个(子)模块可以划分为3个比较均匀的段,其时延分别为125ns(部分积生成),150ns(部分积化简),125ns(最终化简,规格化以及舍入)。图2-5中给出了这个三段流水线设计。

在决定流水线设计的实际时钟频率时,必须考虑时钟要求。假定流水段之间采用边沿触发的寄存器进行缓存,则需要在150ns的段延迟的基础上增加17ns的时钟沿到寄存器输出端的时延,以及5ns的建立时间。这将导致最小的时钟周期为172ns。因此,新的流水线设计需要时钟频率为5.8MHz,而不是2.5MHz。中表示吞吐能力是原来的2.3倍。然而,需要注意完成每个乘法所需时间稍许增加,从400ns变为516ns。

为流水线设计所额外增加的硬件是流水线各段之间用于缓存的边沿触发芯片。在原来175个IC芯片的基础上,需要另外的82个芯片。采用芯片个数来衡量硬件复杂性,总共257个IC芯片说明硬件复杂性增加了45%。这个45%的硬件成本的增加使得性能提高了130%。显然,这个浮点乘法器的3段流水线设计优于原来非流水线设计。

这个例子采用现成的部件来实现板级设计。鉴于今天的芯片技术,全部设计可以在一块芯片上用一个小模块很容易地实现。尽管浮点乘法器的板级实现被认为过时了,上例的目的是采用一个已出版的具有实际延迟与硬件成本参数的设计来简单地表明流水线的有效性。事实上,图2-3中上面的曲线反映了本例中的参数。

图2-5 一个流水线浮点乘法器

2.1.3 流水线理想

回想一下,设计k段流水线的目的是为了获得吞吐能力增加k1倍,如图2-1所示。然而,在前例中,3段流水线的浮点乘法器的吞吐能力仅增加了1.3倍。吞吐能力没有增加2倍的主要原因是一个k段流水线设计能够使吞吐能力提高k1倍是一种理想情形,它建立在三个理想假设上,我们称之为流水线理想。对流水线理想的理解对理解流水线设计是至关重要的。在实际的流水线中偏离这个理想是不可避免的,这也使得流水线设计变得复杂。为处理这个理想与现实之间差别的解决方案构成了流水线设计中有趣的技术。流水线的三个理想是:

1.均衡的子计算:要完成的计算可以均匀地分割为延迟相同的子计算。 2.同一的计算:在一个数量很大的输入数据集上重复完成相同的计算。 3.的计算:所有相同计算的重复是相互的。

2.1.3.1 均衡的子计算

流水理想的第一点表明要流水的计算可以均匀地划分成k个延迟相同的子计算。这意味着原先的设计可以均匀地划分为k个平衡(即具有相同的延迟)的流水段。若原先计算的延迟,即非流水设计的时钟周期是T,则一个k段的流水线设计的时钟周期正好是T/k,即k段中每一段的延迟。鉴于这一假设,由于时钟频率成为原来的k倍,因此吞吐能力也成为原来的k倍。

在实际的设计中,理想的假设不一定成立。将计算划分为绝对平衡的段是不太可能的。在我们的浮点乘法器例子中,原计算的400ns的延迟被划分为3段,其延迟分别为125ns,150ns,125ns。显然原来的延迟没有被均匀地分作三个平衡的段。由于一个流水线设计的时钟周期由延迟最长的段决定,实际上,延迟较短的段的效率将会受损。在我们的例子中,第一段与第三段各有25ns处于无效,我们将这种流水段内的无效称作流水段的内部碎片。由于这种内部碎

片,完成同一计算的总的延迟将从T增加到Tf,流水线设计的时钟周期也不再是T/k而是Tf/k。在我们的例子中,完成三个子计算需要450ns,而不是原来的400ns,时钟周期也不是133ns(400/3 ns)而是150ns。

另一个隐含的假设是在流水段之间增加的缓存没有导致额外的延迟,因此不需要额外的延迟来确保流水段时钟的正确。在实际设计中,这一假设不一定成立。在我们的例子中,需要额外的22ns来保证流水段时钟的正确,这使得3段流水线设计的时钟周期变为172ns。3段流水线设计的理想时钟周期应该是133ns。172ns与133ns的不同解释了为什么吞吐能力没有达到理想的增加2倍。

流水理想的第一点假定了两件事情:(1)将原先的计算划分为多个子计算没有导致无效;(2)段间缓存的引入以及时钟要求没有导致额外的延迟。在芯片级的设计中,采用类似于Earle 锁存器的锁存器可以将为保证流水时钟的正确的额外延迟降为最小。将一个计算划分为平衡的流水段是流水线设计的第一个难题。其目标是使各段尽可能平衡,以使内部碎片最小。由于流水段的不平衡导致的内部碎片是偏离流水理想第一点的主要原因。这一偏离成为一种流水开销,并且使得一个k段流水线设计的吞吐能力不能增加理想的k-1倍。

2.1.3.2 同一的计算

流水理想的第二点表明在流水线中将处理多个重复的相同计算。在多个输入数据集上重复进行相同的计算,每一次计算要求流水段提供相同的子计算的序列。在我们的浮点乘法器例子中,这意味着许多对浮点数将进行乘法运算,并且每一对操作数将通过相同的三个流水段。这一假设表明每一次重复的计算将使用所有的流水段。在我们的例子中,这一点是成立的。

这一假设对于浮点乘法器的例子成立是由于这一流水线只完成一种功能,即浮点乘法。若一个流水线被设计来完成多个功能,这一假设可能不成立。例如,一个算术流水线可以设计来完成加法与乘法。在一个多功能流水线中,它支持的各个功能并非需要所有的流水段。可能为完成每个功能需要流水段的不同的子集,同时每个计算不一定需要所有的流水段。由于数据集的序列以同步的方式经过流水线,一些数据集不需要一些流水段,因此使得这些段被闲置。这些没有被使用或者闲置的流水段导致另一种流水无效,可被称作流水段的外部碎片。与内部碎片相同,外部碎片是一种流水开销,在多功能流水线中应该使其最小。在浮点乘法器的流水线例子中,没有外部碎片。

流水理想的第二点假设所有的流水段总是被利用。撇开其隐含了没有外部碎片,这一理想假设也表明要处理许多数据集。对于第一个数据集,其到达流

水线的最后一段需要k个周期,这些周期被称作装入时间。在最后一个数据集进入第一个流水段后,需要另外的k个周期来排空流水线。在流水线的装入与排空期间,并非所有的流水段都是忙的。假设要处理多个输入数据集是为了使装入与排空时间仅占总时间非常小的一部分。因此,流水线的各段可以认为总是在忙。事实上,流水线浮点乘法器的5.8MFLOPS的吞吐能力就是基于这一假定而得出的。

2.1.3.3 的计算

流水理想的第三点表明流水线所处理的重复计算,或简称为计算,是的。这意味着在流水段中的所有的计算是的,即在任何计算之间没有数据或控制依赖关系。这一假设允许流水线以“流”方式工作,由于后一计算不必由于它们之间的依赖关系而等待前面计算的完成。在我们的流水线浮点乘法器中,这一假设成立。若有多对操作数进行乘法运算,一对操作数的乘法不依赖于另一个乘法的结果,则这些乘法可以被流水线以流的方式进行处理。

对于一些流水线,这一点不一定成立。一个后来的计算可能需要一个先前计算的结果。所有这些计算可以同时在流水段中。若后来的计算已经进入需要结果的流水段,而先前的计算还没有到达产生所有结果的流水段,则后面的计算必须在那个流水段进行等待。这个等待被称作流水停止。若一个计算停止在一个流水段,则所有后续的计算可能会被停止。流水停止导致流水段的无效,实际上这是一种动态的外部碎片,并导致流水线吞吐能力的下降。在设计一个需要处理那些未必的计算的流水线时,其目标是使流水停止的量最小化。

2.1.4 指令流水线

流水理想的三点是关于流水线设计的三个理想假设。在很大程度上,算术流水线中实际情形与这些理想假设相差不大。然而,对于指令流水线,实际与理想之间的差别很大。正是处理这一差别使得指令流水变得有意义并且困难。在设计流水线处理器时,这三点成为主要问题。这里简要介绍这三个问题,在2.2节关于流水线处理器的设计中将会深入探讨。这三个问题也提供了跟踪流水线处理器设计技术的一个良好的路线。

2.1.4.1指令流水线设计

流水线理想的三点成为设计指令流水线所希望达到的目标。对指令的处理变为对计算的流水。必须把这个计算划分为均匀的子计算以便使流水段平衡。处理一条指令所用的时间称作指令周期;每个流水段所用的时间决定了机器周期。指令周期可以认为是处理一条指令的逻辑概念。一个有多条指令的程序涉及到对计算的重复执行。机器周期是一个物理概念,它涉及到数字逻辑电路中存储元素的时钟,它实际上是流水段的时钟周期。

图2-6 指令周期与机器周期的简单对比

我们可以认为前面的浮点乘法器是一个仅有一条指令的非常简单的处理器的例子,这条指令就是浮点乘。指令周期涉及到一个浮点乘法的执行,见图2-6(a) 。根据功能单元的不同,可以将这个计算分为以下5个子计算。

1.生成部分积(125ns)。 2.部分积的化简(150ns)。 3.最后的化简(55ns)。 4.规格化(20ns)。 5.舍入(50ns)。

考虑到流水,将后面三个子计算合并为一个子计算。这就生成了图2-6(b)所示的三个流水段。图2-6(a)中的指令周期已经映射到图2-6(b)中的三个机器周期,形成了一个三段流水线设计。我们可以将指令周期称作一个在指令系统中定义了的结构化(逻辑)原语,而机器周期是在微结构中定义了的一个机器(物理)原语。图2-6(b)中流水线设计是图2-6(a)所定义结构的实现。

指令流水的一个主要任务可以认为是将逻辑指令周期向物理机器周期的映射。换句话说,有指令周期所表示的计算必须划分为由流水段执行的一系列子计算。为使这个映射或者划分有效,必须考虑流水理想的三点。

均衡的子计算。将指令周期划分为多个机器周期可称作段的量化,必须使流水段的内部碎片最小。若段的量化考虑不周,则引起的内部碎片将会很快降低流水的效率。流水理想的第一点导致指令流水线的第一个问题,即需要使流水段平衡。流水段之间越平衡,则内部碎片越少。

同一的计算。与一个单功能的算术流水线不同,指令流水线本质上是一个多功能流水线,因为它必须处理不同类型的指令。不同的指令类型的子计算的序列不同,因此需要不同的硬件资源。指令流水线的第二个问题涉及到对不同类型的指令所需的不同资源进行有效的合并。这个流水线必须能处理所有的指令类型,同时使每种类型的指令中无用或闲置的流水段最少。这实际上就是使外部碎片最小化。

的计算。另外,与算术流水线处理一系列数不同,指令流水线所处理的指令未必彼此。因此,指令流水线必须由内置的机制来检查指令间的相关性,并且确保这种相关不被破坏。为保证指令间的相关可能会导致流水线停止。而流水线停止是一种动态的外部碎片,它降低了流水线的吞吐能力。因此,指令流水线的第三个问题是使流水线停止最小化。

2.1.4.2 指令系统的影响

在我们仔细讨论指令流水线的三个主要问题之前,先来简要介绍指令系统(ISA)对指令流水线的影响。另外,再一次对流水理想的三点进行讨论。

均衡的子计算。平衡流水段的第一个问题表明必须识别出一套均匀的子计算。在一个指令的所有子计算中,必须找出一个关键子计算,它需要最长的延迟,并且不能轻易地将其进一步划分为多个更细的子计算。在流水处理器设计中,一个这样的关键子计算就是对主存的访问。由于处理器与主存速度的不同,访问主存可成为关键子计算。为支持更有效的指令流水,涉及到访问主存的寻址方式应当最少,并且可以采用与处理器速度相当的快速cache存储器。

同一的计算。合并不同指令类型的资源需求是RISC结构发展的主要动力。通过减少不同类型指令的复杂性以及多样性,合并不同的指令类型变得容易了。复杂的寻址方式不仅需要额外的访存,而且增加了资源需求的多样性。将所有这些资源需求合并在一条指令流水线中是非常复杂的,并且对于许多资源需求不太复杂的指令来说,这个流水线效率很低。这些指令将导致外部碎片的开销,因为它们没有充分利用流水线中的段。对于一个RISC结构的流水线实现来说,将指令类型合并将产生一个有效的指令流水线,其外部碎片很少。

的计算。在流水线设计中,将指令相关所导致的流水停止最小化可能是最困难的领域。对于特定的操作,一个指令流水线必须检测出并能确保指令相关。复杂的寻址方式,特别是那些涉及到访存的寻址方式,由于采用了存储引用标识符,而使得相关检测很困难。通常,寄存器相关较容易检出,因为指令中显式地标明了寄存器。清晰而对称的指令模式有利于指令译码以及相关检测。相关检测以及保证相关可以在编译时静态地做,或者在运行时动态地做。决定编译时做什么以及运行时做什么涉及到动态静态界面(DSI)的定义。DSI的位置将导致有趣而细微的折中。这些折中突出了编译器与(微)结构之间的密切关系,以及在设计处理器时考虑这两者的重要性。

2.2 流水线处理器设计

在设计指令流水线或流水线处理器时,流水理想的三点是设计中的三个主要问题。处理这些与理想假设的偏差成为设计流水线处理器的主要任务。流水理想的三点以及相应的流水线处理器设计的三个主要问题如下:

1.均衡的子计算=>平衡流水段 2.同一的计算=>合并指令类型 3.的计算=>流水停止最小化

这三个问题将在后续小节中讨论。这三个问题为陈述指令流水线技术提供了一个良好的框架。所有的流水线处理器设计技术可以看作是为了解决这三个问题。

2.2.1平衡流水段

在流水线处理器设计中,要进行流水处理的计算是在每个指令周期中做的工作。一个典型的指令周期可以根据其功能划分为下面五类子计算.

1.取指令(IF) 2.指令译码(ID) 3.取操作数(OF) 4.执行指令(EX) 5.存操作数(OS)

一个典型的指令周期首先取下一个将要执行的指令,然后对指令进行译码,以决定这条指令将要完成的工作。通常它指定了一个或多个操作数,需要将其取出。根据所用的寻址方式,这些操作数可能在寄存器中,或者在内存中。一旦所需的操作数可用,则这条指令所指定的实际操作就可以进行了。指令周期结束时,将操作产生的结果进行存储。同样的,根据所指定的寻址方式,结果可以保存在寄存器中,或者保存到内存中。在一个顺序处理器中,子计算的整个序列在下一条指令中进行重复。在这五类子计算中,可能会引起指令执行的一些副作用。通常这些副作用以对机器状态的特定修改的形式呈现出来。之所以将这些状态的改变称作副作用,是由于指令中并没有显式地指定这些作用。根据实际采用的ISA,这五类子计算的每一类的实现复杂性以及所需时间会变化很大。

2.2.1.1 流水段的量化

为了流水,对指令周期的一个自然的划分便是依照这五类子计算。五类子计算中的每一类映射为一个流水段,产生了一个五段指令流水线,如图2-7所

示。我们将这个例子称作普通(GNR)指令流水线。在GNR流水线中,逻辑指令周期被映射为5个物理机器周期。机器周期与指令周期之比为五,这反映了流水的程度,也表明了流水段的粒度。

图2-7 五段普通(GNR) 指令流水线

流水段的量化是为了将指令周期划分为平衡的流水段,以便使流水段的内部碎片最小。流水段的量化可以首先对指令周期进行自然的功能划分,例如,分为五个普通的子计算。多个延迟短的子计算可以合并为一个新的子计算,以便使流水段更加平衡。例如,在图2-6(b)的流水线设计中,浮点乘法计算的后三个子计算-最后化简,规格化以及舍入被合并为一个子计算。同样的,一个典型指令中五类普通子计算中的一些可以合并,以使流水段更加平衡。例如,若一个指令系统的指令模式采用固定的指令长度,简单的寻址方式,并且其各段彼此,则与其它三个子计算相比,IF与ID子计算是相当直接并且相对简单的。若将这两个子计算合并为一个新的子计算,根据其所需的延迟,所产生的四个子计算将会更加平衡。基于这四个子计算,一个4段指令流水线的实现见图2-8(a)。事实上,在MIPS R2000/R3000指令流水线处理器中,将IF与ID子计算进行了组合。此法的本质是采用延迟最长的子计算作为参照,试图将其它延迟较短的子计算合并为一个新的子计算,其延迟与参照延迟相当。这将导致一个粗粒度的机器周期,并且流水程度较低。

图2-8 (a)一个4段指令流水线的例子;(b)一个11段指令流水线的例子

与合并短延迟的子计算相反,另一个方法可以用来平衡流水段。一个具有超长延迟的子计算可以进一步划分为多个具有较短延迟的子计算。此法采用延迟最短的子计算作为参照,试图将长延迟的子计算分割为多个细粒度的子计算,其延迟与参照对象相当。这将导致一个细粒度的机器周期以及高度的流水。例如,若一个ISA采用了复杂的寻址方式,对于OF与OS子计算可能涉及到访存,这两个子计算的延迟较长,因此可进一步分割为多个子计算。此外,EX子计算中完成的一些操作可能相当复杂,因此也可以进一步分割为多个子计算。图2-8(b)给出了一个11段的指令流水线设计。OF与OS子计算都被映射为三个流水段,而IF与EX子计算被映射为两个流水段。在平衡流水段时,采用ID子计算作为参照。

流水段量化的两个方法是:(1)将多个子计算合为一个;(2)把一个子计算分割为多个子计算。在设计指令流水线时,可以采用二者的组合。如前所述,指令系统对流水段的量化有显著的影响。在所有情况下,流水段量化的目的是使总的内部碎片最小。例如,假定5类普通子计算的总共延迟是280ns,则对于图2-8(a)的4段设计以及图2-8(b)的11段设计的机器周期分别为80ns与30ns。因此,对于4段流水线的总延迟是320ns(80ns×4),对于11段流水线的总延迟是330ns(30ns×11)。新的总延迟与原来的280ns的总延迟的差别代表了内部碎片。因此,4段设计的内部碎片是40ns(320ns—280ns),11段设计的内部碎片是50ns(330ns—280ns)。于是,可以得出,4段设计比11段设计更有效,因为它所产生的内部碎片较少。当然,11段设计的吞吐能力是非流水线设计的9.3倍(280ns/30ns),而4段设计的吞吐能力仅是非流水线设计的3.5倍(280ns/80ns)。从两个设计中可以看到,内部碎片使得11段流水线与4段流水线没有达到理想的吞吐能力的增加值10与3。

2.2.1.2 硬件需求

在许多实际的工程设计中,目标并不单纯是获得最好的性能,而是获得最好的性能价格比。此外,除了使指令流水线的吞吐能力(性能)最大化外,还必须考虑硬件需求(成本)。通常,从硬件需求看,较高程度的流水将导致较高的成本。显然,由于流水段之间增加了缓存导致成本上升。在2.1.1.3 节的模型中我们已经看到,超过一个特定点之后,进一步的流水将由于流水段之间缓存的开销使得收益下降。除了缓存开销,对于高度流水线设计,还有其它更显著的硬件需求。

在估算一个指令流水线的硬件需求时,首先需要记住的是,对于一个k段指令流水线,在最坏情况下,或者根据性能是最好的情况,在流水线中同时并存有k条指令。在每个流水段中都有一条指令,所有k条指令处于指令周期的不同阶段。因此,整个流水线必须有足够的硬件来支持在k个流水段中对k条指令同时进行处理。硬件需求分为三类:(1)在每个段中的控制以及数据管理的逻辑需求;(2)支持多个段同时对寄存器的访问的寄存器堆的多个端口;(3)支持多个段对存储器同时访问的多个存储器端口。

我们首先来看图2-8(a)的4段指令流水线。设想一个存/取结构,需要一个典型的寄存器-寄存器指令在第一阶段来读两个寄存器操作数,在第四阶段将结果存储到寄存器中。在第二段需要一个取指令从存储器中读,在第四段需要一个存储指令来写存储器。综合全部四个段的需求,需要寄存器堆有两个读端口与一个写端口,并且需要一个数据存储器接口,在一个机器周期内完成一次存

储器读与存储器写操作。此外,为了取指令,第一段需要从指令寄存器中读取。若采用综合(指令与数据)的存储器,则在一个机器周期内,存储器必须支持两次读访问与一次写访问。

对2-8(b)的11段指令流水线可以进行相同的硬件需求分析。为了适应慢速的指令存储器,将原先的IF子计算拆分并且映射为两个流水段,即IF1与IF2。在IF1开始取指,在IF2完成。尽管取指需要两个指令周期,但是它是流水的,当第一条指令在IF2完成取指时,第二条指令已在IF1开始取指。这意味着在每个机器周期内,指令存储器必须能够支持IF1与IF2两个流水段的并发访问。类似的,将原先的OF与OS 子计算分别映射为三个流水段,这表明在一个时刻,流水线中最多有6条指令,都要访问数据存储器。因此,在一个机器周期内,数据存储器必须能够支持6个无冲突的并发访问。这需要一个六端口的数据存储器。另外,若指令存储器与数据存储器综合在一个存储器单元中,则需要一个八端口的存储器。这种多端口的存储器的实现是非常昂贵的。略微便宜的解决方案,例如采用具有多个存储体的交叉存取存储器,其功能与真正的多端口存储器相似,但是不能在所有时候都确保无冲突的并发访问。

随着流水程度,即流水深度的增加,为支持这样一个流水线所需的硬件资源将显著增加。最显著增加的硬件资源是寄存器堆以及存储器单元的额外的端口,它们被用来提高对这些数据存储单元并行访问的程度。此外,为了适应长的存储器访问延迟,访存子计算需要进行流水。然而,对超过两个机器周期的访存操作进行流水是非常复杂的,通常需要对无冲突的并发访问进行折中。

2.2.1.3 指令流水线的例子

这里给出了两个商用流水线处理器段的划分,用以说明真正的指令流水线。MIPS R2000/R3000 RISC 处理器采用5段指令流水线,如图2-9(a)所示。MIPS 是一种存/取的结构。IF与ID子计算被合并到IF段,在每个机器周期内需要进行一次存储器(I-cache)读操作。OF子计算在RD与MEM段中进行。对于仅访问寄存器操作数的ALU指令,在RD段进行操作数读取,需要读两个寄存器。对于取指令,取操作数还需要在MEM段访问存储器(D-cache),这也是流水线中可以访问D-cache的唯一的段。OS子计算在MEM与WB段中进行。存指令必须在MEM段访问D-cache。ALU与取指令在WB段将其结果写回到寄存器堆。

图2-9 两个商用指令流水线:(a)MIPS R2000/R3000 五段流水线;(b)AMDAHL470V/7 十二段流水线

MIPS处理器通常采用的指令cache与数据cache。在每一个机器周期内,R2000/R3000流水线必须支持IF段进行的一个I-cache读以及MEM段进行的一个D-cache读(对于一个取指令)或者一个D-cache写(对于一个存指令)。注意到对于分立的cache结构,I-cache与D-cache不必是多端口的。另一方面,若指令以及数据都存储在同一个cache中,联合的cache不必采用双端口来支持这个流水线。在每一个机器周期内,寄存器堆必须提供足够的端口来支持RD段的两个寄存器读与WB段的一个寄存器写。

图2-9(b)给出了AMDAHL470V/7的12段指令流水线。IF子计算在头三个段内实现。由于需要支持复杂的寻址方式,OF子计算被映射为4个段。EX与OS段分别被分为两个流水段。在12段流水线的第一段,计算下一条顺序指令的地址。第二段初始化cache访问,以便读取指令,第三段将指令从cache装载到I-单元(指令单元)。第四段对指令进行解码。在第五段读两个通用寄存器,这些寄存器被用作地址寄存器。第六段计算一个操作数在存储器中的地址。第七段初始化cache,以读取存储器操作数,第八段将操作数从cache装载到I-单元中,并读取寄存器操作数。第九段与第十段是E-单元(执行单元)中的两个执行段。第11段对计算结果进行错误检测。第12段将最终结果存储在目的寄存器中。

在每个机器周期中,12段流水线必须支持由第五段发出的两个读寄存器以及第12段发出的一个写寄存器这些并发访问,同时还要支持在第2、3、7、8段所发出的四个读cache存储器。显然,这一流水线处理器的存储器子系统要比MIPS R2000/R3000流水线的存储器子系统复杂得多。

当前流水线设计的趋势是流水程度的增加以及流水深度的提高。这就导致了能够在较高的时钟频率下工作的细粒度流水段。在第一代流水线RISC处理器中,4或5个段是常见的,而超过10个段的指令流水线正在变得普遍。另有一个趋势采用不同数目的段来实现多个流水线。这属于超标量处理器设计的课题,将在第三章进行论述。

2.2.2 合并指令类型

流水线理想的第二点假定在流水线上重复地完成相同的计算。对于多数指令流水线,这一理想假设并不成立。由于指令流水线重复处理指令,将会涉及到不同的指令类型。尽管指令周期不断重复,指令周期的不断重复将会涉及到

处理不同的指令类型。不同的指令类型有不同的资源需求,并且其子计算的顺序也不一定相同。指令流水线必须能够支持不同的需求,并且提供一个所有指令类型所需的子计算的超集。每个指令类型并不一定需要指令流水线中所有的流水段。对于每一指令类型,其不需要的流水段成为一种开销,这种开销在2.1.3.2中称作流水线的外部碎片。从流水线理想的第二点来看,合并指令类型的目的是使所有指令类型的外部碎片最小。

2.2.2.1 指令类型的分类

为完成一个计算,计算机必须执行三个任务: 1.算术操作 2.数据移动 3.指令顺序化

这三个任务通过处理器对指令的处理来完成。算术操作涉及到对指定的操作数进行算术与逻辑操作。这是一个计算中最显著的部分,常常等同于计算本身。一个处理器能够支持大量的算术操作。数据移动任务负责在存储地址间移动操作数以及结果。显然有一个存储地址的等级,显式指令被用作在这些地址之间移动数据。第三个任务负责对指令进行顺序化。通常,在一个程序中计算由多条指令组成。计算的性能涉及到对一系列指令的处理。这个指令序列,或者说程序流,可以由指令本身显式指定。

指令集设计中一个关键的部分是如何将这些任务分配到ISA中的各种指令。一个非常复杂的指令可以被指定来完成所有三种任务。在一个典型的水平微码机器中,每一条微指令具有指定所有这三个任务的域。在被称作CISC(复杂指令集计算机)的传统指令集体系中,许多指令完成的任务多于一个。

受20世纪80年代RISC研究的影响,许多近期的指令集体系具有一些相同的属性。这些近期的体系结构包括Hewlett-Packard的Precision结构,IBM的Power结构,IBM/Motorola的PowerPC结构,以及Digital的Alpha结构。这些现代的ISA趋向于采用固定长度的指令,对称的指令模式,存/取结构,以及简单的寻址模式,这些属性中,许多与指令流水线非常一致。在很大程度上,本书的例子以及阐述中,采用这一典型的RISC结构。

在一个典型的现代RISC结构中,指令集为三种任务指定指令类型,每个指令仅执行三种任务中的一种。根据这三种任务,指令可以被分作三类:

1.ALU指令,用来完成算术以及逻辑操作; 2.存/取指令,在寄存器以及存储器之间移动数据; 3.分支指令,用以控制指令的顺序。

ALU指令完成对寄存器操作数的算术与逻辑操作。只有存取指令可以访问数据存储器。存取指令以及分支指令采用简单的寻址方式。通常,仅支持有偏移地址的间接寄存器寻址方式。对于分支指令来说,也支持与PC相关的寻址方式。在下面对这三种指令类型详细叙述中,假定采用一个指令cache(I-cache)与一个数据cache(D-cache)。

这三种指令类型的语义可以根据其完成的子计算的序列而确定。这一规范可以是从5个基本子计算(2.2.1节中),随后作进一步的细化。最终,这些子计算确定了硬件实现中寄存器移动的序列。为简便起见,ALU指令进一步分为整型与浮点指令。ALU指令的语义在表2-2中给出。

表2-2 ALU指令类型的规范

原先的子计算 ALU指令类型 整数指令 IF 取指 (访问I-cache) ID 指令译码 OF 访问寄存器堆 EX 执行ALU操作 OS 写回到寄存器堆

在一个存/取结构中,存取指令是访问数据存储器的唯一指令。一条取指令将数据从存储器中放到寄存器中,一条存指令将数据从寄存器移到存储器中。在表2-3的存取指令语义规范中,假定只有间接寄存器寻址方式具有偏移地址。这种寻址方式通过将寄存器中内容与指令立即域中所指定的偏移量相加而得到有效地址。

比较表2-2以及表2-3的规范,我们可以看出ALU以及存取指令所要求的子计算序列类似但并不是完全相同。ALU指令不需产生存储器地址。另一方面,除了必须生成有效地址外,存取指令不需要完成显式的算术或逻辑操作。它们只是在寄存器与存储器之间移动数据。即使在存指令与取指令之间也有细微的差别。对于取指令,OF子计算扩展为三个子计算,包括访问寄存器堆以得到基地址,生成有效地址以及访问存储单元。 类似的,对于存指令,OS子计算由两个子计算组成,包括生成有效地址以及将寄存器操作数存回存储器。这假定基地址与寄存器操作数都是在OF子计算中从寄存器堆中得到。

表2-3 存取指令类型规范

原先的子计算 IF 存取指令类型 取指令 取指 (访问I-cache) 存指令 取指 (访问I-cache) 浮点指令 取指 (访问I-cache) 指令译码 访问FP寄存器堆 执行FP操作 写回到FP寄存器堆 ID OF EX OS 指令译码 -访问寄存器堆 (基地址) -生成有效地址 (基址+偏移) -访问(读)存储器地址(访问D-cache) - 写回到寄存器堆 指令译码 -访问寄存器堆 (寄存器操作数以及基地址) - -生成有效地址 (基址+偏移) -访问(写)存储器地址(访问D-cache)

最后,表2-4中给出了无条件跳转指令以及条件分支指令的子计算序列。分支指令采用类似于存取指令的寻址方式。同时可以支持与PC相关的寻址方式。在这种寻址方式中,分支(或跳转)指令的目标地址通过对程序计数器的当前内容叠加一个偏移量来得到。这个偏移量可以是正或者是负,以便于前向与后向分支。

查看表2-2、2-3、2-4中的三种主要指令类型的规范,我们可以看到三种类型指令的最初子计算是非常类似的。然而,后续的子计算不同。例如,ALU指令不访问数据存储器,因此不需要生成存储器地址。另外,存取指令与分支指令在生成有效地址时具有相同的子计算。存取指令必须访问数据存储器,而分支指令必须提供目标指令的地址。我们也可以看到,除了生成有效地址外,条件分支指令还必须对分支条件进行判断。这可以是检查先前指令生成的状态位,或者需要对寄存器操作数进行算术操作以作为分支指令处理的一部分,或者是检查一个特定寄存器的内容。

表2-4 分支指令类型的规范

原先的子计算 IF ID OF 分支指令类型 跳转(无条件)指令 取指 (访问I-cache) 指令译码 -访问寄存器堆 (基地址) -生成有效地址 (基址+偏移) - 采用目标地址更新程序计数器 条件分支指令 取指 (访问I-cache) 指令译码 -访问寄存器堆 (基地址) -生成有效地址 (基址+偏移) -检查分支条件 -若条件为真,采用目标地址更新程序计数器 EX OS

根据前述的指令语义规范,可以确定三种主要指令类型的资源需求。尽管根据取指令以及指令解码,三种指令类型具有一些共同点,它们之间仍有所差别。这些指令语义的差别将导致资源需求的差别。

2.2.2.2 资源需求的合并

合并不同指令类型的问题涉及到将不同的资源需求有效地合并到一个指令流水线中,它可以适应所有的指令类型。其目的是流水线所需的总的资源最小化,同时最大程度地利用流水线中的所有资源。合并不同指令类型的程序可以非正式地称为由以下三步组成。

1.分析每一个指令类型的子计算序列,以决定相应的资源需求; 2.寻找不同类型指令之间的共同点,以将相同的子计算合并,使其共享同一流水段;

3.若在不违反指令语义的前提下,可以移动或者对子计算进行重新排列,以便于进一步的合并。

这一合并指令类型的程序可以通过将其应用到表2-2、表2-3、表2-4来展示。为简明起见,不考虑浮点指令与无条件跳转指令。图2-10中总结了ALU指令、存取指令,以及分支指令的语义规范。通过检查子计算的四种序列以及为支持它们所需的硬件资源,我们从上往下来应用这一合并程序。这一程序导致对一个指令流水线段的定义。

图2-10 将ALU、取指令、存指令、分支指令合并到一个6段指令流水线中,并记做典型(TYP)指令流水线。

对于IF与ID段,所有四种指令类型具有相同的子计算。因此,所有四种指令类型的头两个子计算可以合并,并用来定义头两个流水段,记做IF与ID,即取指令与指令译码。

在OF子计算中,所有的四种指令类型需要读寄存器堆。ALU指令访问两个寄存器操作数。取指令与分支指令访问一个寄存器以得到基地址。存指令访问一个寄存器以得到寄存器操作数,访问另一个寄存器以得到基地址。所有四种情况或者读一个,或者读两个寄存器。这些类似的子计算可以被合并为第三段,称作RD,最多可以从寄存器堆中读取两个寄存器。在每个机器周期中,寄存器堆必须能够支持两个且并发的读操作。

为完成必要的算术与逻辑操作,ALU指令需要一个ALU功能单元。对于存取指令与分支指令,它们不需进行这一操作,不需为访存而生成有效地址。可以看出,ALU功能单元可以完成地址生成任务。因此,这些子计算可以合并为流水线的第四段,称作ALU,它主要由ALU功能单元组成,以完成算术/逻辑操作或生成有效地址。

存指令与取指令都需要访问数据存储器。因此,一个流水段必须完成这一子计算。流水线的第五段,称作MEM,为此而引入。

ALU指令与取指令必须将一个结果写入寄存器堆以作为其最后一个子计算。一条ALU指令将其对寄存器操作数所进行操作的结果,写回到目的寄存器。一条取指令将从存储器取得的数据,存入目的寄存器。ALU指令不需访问存储器,因此,理论上,在ALU段后,可以立即写回到目的寄存器。然而,为了与取指令的寄存器写回子计算合并,ALU指令的寄存器写回子计算延迟一个流水段,到第六个流水段,称作WB。这将导致ALU指令在MEM流水段产生一个空闲的机器周期。这是一种外部碎片,造成流水线的浪费。

对于条件分支指令,在更新程序计数器之前,必须确定分支条件。由于ALU功能单元被用来产生有效地址,它不能用来判断分支条件。若分支条件的判断仅检查一个寄存器,看其值是否为零,或者为正或负,则仅需一个比较器。这个比较器最早可以加在ALU段内,即在RD段读取参考寄存器之后。因此,考虑到分支条件,可以根据分支目标地址对程序计数器进行更新的最早流水段是MEM段,即在ALU段已经计算目标地址并且判断分支条件后。

前述对不同指令类型的资源合并产生了图2-10右边所示6段指令流水线。这个流水线记做典型(TYP)指令流水线,并用作本章后续部分的例子。除了ALU指令中有一个空闲流水段(MEM)外,存指令与分支指令也会导致一些外部碎片。存指令与分支指令不需要写回寄存器,在WB段内空闲。总体来说,这个6段流水线是非常有效的。取指令用了流水线中所有六个段,其它三种指令用了5个段。

把不同的指令类型合并到一个指令流水线中,有三个优化目标。首先是使为支持所有指令类型所需的总资源最少。在某种程度上, 这个目标类似于求所有不同资源需求的最小公倍数。第二个目标是使不同的指令类型最大程度地利用所有流水段,换句话说,使得每种指令类型所产生的空闲段最少。空闲段将导致外部碎片,带来浪费,影响吞吐能力。第三个目标是使每一种指令类型的总体延迟最小。因此,若对于一个特定指令类型空闲段是不可避免的,并且空闲段的位置有一定的灵活性,则将其安排在流水线的末尾会更好一些。这将使得指令完成得更早一些,以减少那种类型指令的整体延迟。

2.2.2.3 指令流水线的实现

图2-10的6段指令流水线中,在任一时刻,可能有六条不同的指令同时在流水线中存在或“运行”。6条指令中的每一条正在通过一个流水段。在每一个机器周期内,寄存器堆必须支持两个读操作(由RD段中的指令发出),以及一个写操作(由WB段中的指令发出)。在每一个机器周期内,I-cache必须支持一个读操作。除非被分支指令中断,IF段连续地增加程序计数器,并且从I-cache中取出下一个顺序指令。在每一个机器周期内,D-cache必须支持一个存储器读或者存储器写操作。只有MEM段访问D-cache,因此,在任意时刻,流水线中的指令只有一条可以访问数据存储器。

图2-10中的流水线框图仅是6段TYP指令流水线的一个逻辑表示,说明了6个流水段的顺序。图2-11给出了TYP指令流水线的实际物理组织,它是TYP流水线处理器实现的功能框图。在这个框图中,流水段之间的缓存显式标出。两个流水段之间的逻辑缓存实际上包括分布在这个框图中的多个物理缓存。顺序地遍历6个流水段的单一逻辑路径,实际上包括这个框图中的多个物理路径。通过流水线的每条指令必须沿着这些物理路径前进。

图2-11 六段TYP指令流水线的物理组织

图2-11的6段TYP指令流水线的物理组织看起来比实际的要复杂得多。为了理解它,我们首先来看寄存器堆与存储器子系统的接口。假定采用分立的cache结构,即存储指令与数据分别采用的cache,则需要两个单端口的cache,一个I-cache与一个D-cache。TYP流水线的存储器子系统的接口是非常简单有效的,与大多数标量流水线处理器类似。如图2-12所示,IF段访问I-cache,MEM段访问D-cache。在每一个机器周期内,I-cache可以支持取一条指令,I-cache的缺失将导致流水线的停止。在MEM段,一条取(存)指令完成一个从(到)D-cache的读(写)操作。注意到这里假定访问D-cache与I-cache的延迟在一个机器周期内。随着cache变大,以及处理器流水深度的增加,将cache的延迟维持在每一个机器周期内将变得更加复杂。

图2-12 六段TYP指令流水线与存储器子系统的接口

图2-13给出了多端口寄存器堆的接口。只有RD段与WB段访问寄存器堆。在每一个机器周期内,寄存器堆必须支持由RD段发出的两个寄存器读操作以及WB段发出的一个寄存器写操作。因此需要一个具有两个读端口与一个

写端口的多端口寄存器堆。图2-13示出了这样的寄存器堆。它有三个地址端口,两个数据输出端口,一个数据输入端口,以支持在每个机器周期内的两个读操作与一个写操作。正在进行寄存器写的指令处于WB段,并且领先于正在进行寄存器读的指令三个机器周期,否则将发生指令相关。因此,在寄存器写地址端口有三个额外的流水段缓存,以确保寄存器写地址所指定的将要写的目的寄存器到达寄存器写地址端口的时刻,正好与将要写的数据所到达寄存器堆的输入数据端口的时间相同。然而,随着端口的数目超过三,硬件复杂性迅速增加。由于电路设计的局限,当写端口数目增加时这一点尤其突出。在一些高端的微处理器中,可以找到具有20个端口左右的多端口寄存器堆。

图2-13 六段TYP指令流水线与多端口寄存器堆的接口

我们来看图2-10所示的6段TYP指令流水线,每条指令好像是从单一的线性路径通过6个流水段。然而,不同类型的指令通过图2-11的TYP指令流水线的物理路径的不同集合。图2-11中标出了一些路径部分,以显示哪些流水段与之相关。本质上,一些流水段在物理上分布在流水线的物理组织框图中。

6段指令流水线与另外两个指令流水线非常相似,即MIPS2000/3000与John Hennessy和David Patterson合著的教材中的教学用DLX处理器。它们两个都是5段流水线。MIPS将TYP中的IF以及ID合并为一个流水段。DLX将TYP中的ID以及RD段合并为一个流水段。其它的四个段对于三个流水线来说,基本上相同。在本章的后续部分,采用TYP流水线作为一个例子。

2.2.3 使流水线停止最小化

流水线理想的第三点假定流水线所进行的计算是相互的。在任意时刻,一个k段流水线中有k个不同的计算。对于一个指令流水线,在任意时刻,流水线中可有k个不同的指令存在或正在运行。这些指令可能并非相互,事实上,这些运行的指令通常会有依赖关系。流水线中的指令相互方便了流水,即指令的流动不会导致流水线的停止。若存在指令间的相互依赖关系,则必须检出并且解决。解决这些相关需要使流水线停止。设计目标是使这样的流水线停止最少,并且所导致的吞吐能力下降最小。

2.2.3.1 程序依赖与流水危险

在ISA抽象层,一个程序定义为一系列的汇编语言指令。一个典型的指令可以定义为一个函数i:TS1 op S2,指令i的定义域是D(i){S1 , S2},值域是R(i){T},定义域到值域的映射由操作 op来定义。给定两条指令i与j,根据字典序,j在i之后,若下列三个条件之一成立,则i与j 之间可能存在数据依赖关系,或者说j 数据依赖于i ,记做i  j。

R(i)  D(j)   (EQ 2-8) R(j)  D(i)   (EQ 2-9) R(i)  R(j)   (EQ 2-10)

第一个条件表明指令j需要一个操作数,它在指令i的值域中。这被称作先写后读(RAW)或真数据相关,记做i d j。真数据相关的含义是,直到指令i完成之前,指令j不能开始执行。第二个条件表明指令i需要一个操作数,它在指令j的值域中,或者说指令j将要修改指令i的一个操作数。这被称作先读后写(WAR)或反数据相关,记做i a j。反相关的存在要求指令j不能在指令i执行之前完成,否则,指令i将得到错误的操作数。第三个条件表明指令i与j在其值域内共享一个公共变量,即它们将对同一个变量进行修改。这被称作先写后写(WAW)或输出数据相关,记做i o j。输出数据相关的存在要求指令j不能在指令i的完成之前完成,否则,在指令j之后的指令,若其定义域内有与这个共享变量相同的变量时,将接收到错误的操作数。显然,先读后读相关是指指令i与j访问同一个变量,无论其相互顺序为何,都不会造成错误。

两条指令定义域与值域的上述三种可能的重叠方式导致了两条指令之间的三种可能的数据相关,即真(RAW),反(WAR)以及输出(WAW)数据相关。由于汇编代码中指令的定义域或值域可以是处于寄存器或存储器中的变量,相关中的公共变量可能是处于寄存器或存储器中的变量。我们称之为寄存器相关与存储器相关。本章我们主要考虑寄存器相关。图2-14给出了RAW,WAR以及WAW的寄存器数据相关。

图2-14 RAW、WAR、WAW数据相关示例

除了数据相关,两条指令之间可能存在控制相关。给定指令i与j ,j在i之后,若指令j的执行与否依赖于指令i的执行结果,则j控制依赖于i,记做

i c j。控制相关是程序的控制流结构的顺序。条件分支指令导致指令顺序的不确定性。条件分支指令后的指令与分支指令可能存在控制相关。

一个汇编语言程序由一系列指令组成。程序的语义依赖于指令执行的顺序。指令的顺序列表代表了相邻指令的顺序关系。若程序列表中指令i之后是

i1,则假定先执行指令i,然后执行指令i1。若遵循这样的顺序执行,则可

以保证程序的语义正确。更准确的说,由于一个指令周期可以包括多个子计算,隐含的假设是指令i的所有子计算必须在指令i1的任一子计算开始之前进行。我们称之为程序的全顺序执行,即指令序列中的所有子计算顺序执行。

给定一个具有k个流水段的流水线处理器,在流水线中有k条指令重叠执行。一旦指令i 完成了第一个子计算,开始进行第二个子计算,指令i1立即开始执行其第一个子计算。一条特定指令的k个子计算,对应于k个流水段,与其它指令的子计算相重叠。因此,不能保证完全顺序执行。尽管完全顺序执行足以保证语义正确,它并非是语义正确的必要条件。指令的顺序列表所包含的完全顺序执行是程序语义的额外规范。确保程序语义不被破坏的基本要求是所有的指令相关不被破坏。换言之,若指令i与j之间存在相关,在程序列表中j在i之后,则指令i与j对公共变量的读/写必须按照原来的顺序进行。在流水线处理器中,若不小心,将有可能破坏程序的相关性。这种对程序相关的潜在的破坏被称之为流水线危险。为了程序的正确执行,所有的流水线危险必须检出并且解决。

2.2.3.2 流水线危险的识别

一旦所有的指令类型被合并到一个指令流水线中,并且定义了所有的流水段的功能,则可以对指令流水线进行分析,以找出流水线中可能发生的所有流水危险。流水危险是流水线结构与指令相关共同的结果。本章的重点是标量指令流水线。根据定义,标量流水线是具有采用线性顺序组织的多个流水段的一个单一流水线。指令根据程序列表的顺序进入流水线。除了流水线停止,指令按前后紧接的方式进入标量指令流水线,即每个机器周期内每条指令前进到下一个流水段。对于标量指令流水线,在流水线结构中,可以确定由于数据相关发生流水危险的必要条件。

流水危险是对程序相关性潜在的破坏。根据所涉及的程序相关性类型,可以对流水危险进行分类。WAW危险是对输出相关的潜在破坏。WAR危险是对反相关的潜在破坏。RAW危险是对真数据相关的潜在破坏。数据相关包括两条指令对一个公共变量的读与/或写。若要发生一个危险,则流水线中必须至少存在两个流水段,即两条指令同时访问一个公共变量。

图2-15给出了流水线结构中发生WAW,WAR以及RAW危险的必要条件。这些必要条件适用于由于存储器以及寄存器数据相关所造成的危险(图中仅给出了寄存器相关)。如图2-15(a),若由于输出相关i o j造成了WAW危险,则一定至少有两个流水段能够对公共变量同时进行写操作。若流水线中仅有一个段可以对那个变量进行写操作,则不会有危险产生,因为由这个流水

段对那个变量的两个写—事实上所有的写—将会按照程序列表所指定的顺序进行。图2-15(b)指出,若有一个WAR危险发生,流水线中必须至少存在两个段,一个早一些的段x以及晚一些的段y,段x可以对那个变量写,而段y可以读那个变量。若要破坏反相关i a j,指令j必须在指令i进行读之前完成写,即指令j必须在指令i到达段y之前到达段x。若这个必要条件不满足,则后续指令j不可能在指令i 完成其读之前进行写。例如,若仅存在一个流水段能够对那个变量进行写与读操作,则所有对那个变量的访问将按照原来的顺序进行,因此不会发生WAR危险。若流水线中执行读的段先于执行写的段,领先的指令i 必须在随后的指令j可能在流水线中一个稍后的段中进行写操作之前完成其读操作。另外,在这样的流水线中,不会发生WAR危险。实际上,图2-15中的必要条件也是充分条件,可以作为流水危险WAW,WAR,RAW的判断条件。

图2-15 在流水线结构上发生(a)WAW危险;(b)WAR危险;(c)RAW危险的必要条件

图2-15(c)指出,若由于真数据相关i d j而发生一个RAW危险,必须存在两个流水段x与y,在流水线中x在y之前,段x可以对公共变量进行读,而段y可以对公共变量进行写操作。在这个流水线结构中,若后续的指令j先于领先的指令i到达段y之前到达段x,则相关i d j会被破坏。类似于对WAR危险的论述,若这一必要条件不满足,则不会发生RAW危险。例如,若仅有一个段进行所有的写与读操作,实际上按照完全的顺序执行进行,不会发生危险。若在流水线中,进行读的段位于进行写的段之后,则RAW危险永远不会发生;因为先前指令进行的所有写操作将会在后续指令进行其读操作之前完成。

由于流水线危险是由于对程序相关性的潜在破坏造成的,逐个考虑每一种相关类型,可以编制一个程序,来判断在这个指令流水线中可能发生的流水危险。本章所用程序以下列顺序来检查程序相关性。

1.存储器数据相关

a. 输出相关 b.反相关 c. 真数据相关 2.寄存器数据相关

a. 输出相关 b.反相关 c. 真数据相关

3.控制相关

我们将这个程序应用到6段TYP指令流水线来进行阐述。首先考虑存储器数据相关。一个存储器数据相关涉及到一个处于存储器中的公共变量,它被两条指令访问(或读或写)。对于一个存取结构,存储器数据相关仅会发生在存取指令之间。为了判断由于存储器数据相关是否会导致流水线危险,必须检查流水线所处理的存/取指令。假定在TYP流水线中采用分立的cache设计,只有MEM段可以访问D-cache。于是,存/取指令对存储器的访问一定也只会发生在MEM段;在流水线中仅有一个段对数据存储器进行读与写。根据图2-15中的必要条件,在TYP流水线中,不会发生由于存储器数据相关所造成的流水危险。实际上,所有对数据存储器的访问按顺序进行,所有的存/取指令以完全顺序执行模式进行处理。因此,对于TYP流水线,没有由于存储器数据相关所造成的流水线危险。

下面考虑寄存器数据相关。为了判断由于寄存器数据相关所造成的流水危险,必须找出能够访问寄存器堆的所有流水段。在TYP流水线中,所有的寄存器读操作发生在RD段,所有的寄存器写操作发生在WB段。一个输出相关(WAW),记做i o j,表明一条指令i以及一条随后的指令j具有相同的目标寄存器。为了保证输出相关,指令i必须首先写那个寄存器,然后指令j才能写那个寄存器。在TYP流水线中,只有WB段可以对寄存器堆进行写。因此,所有的寄存器写操作由WB段顺序进行;根据图2-15(a)中的必要条件,在TYP流水线中,不会发生由于输出相关所造成的流水危险。

一个反相关(WAR),记做i a j,表明指令i读取一个寄存器,这个寄存器是一条后续指令j的目标寄存器。必须确保指令i在指令j写入那个寄存器之前对其读。反相关造成流水危险的唯一途径是,后续指令j可以在指令I读寄存器之前写寄存器。这在TYP流水线中是不可能的,由于所有的寄存器读发生在RD段,在流水线中它早于WB段,WB段是进行寄存器写的唯一段。因此,在TYP流水线中,图2-15(b)中的必要条件不存在。于是,在TYP流水线中,不会发生由于反相关所造成的流水危险。

在TYP流水线中,能够造成流水危险的唯一寄存器数据相关是真数据相关。在TYP流水线中,图2-15(c)的必要条件是存在的,因为在流水线中,进行寄存器读的RD段位于进行寄存器写的WB段的前面。一个真数据相关,记做

i d j,是指指令i写入一个寄存器,后续的一条指令j从相同的寄存器中读取。若指令j紧随指令i之后,则当指令j到达RD段时,指令i仍然在ALU段。因此,j直到i到达WB段时才能读那个寄存器,这是指令i的结果。为了保证这种数据相关,指令j直到指令i完成WB段之前不能进入RD段。对于真

数据相关,RAW流水危险能够发生,因为在流水线中,一条后续的指令能够在先前的指令完成寄存器写段之前到达寄存器读段。

最后,来考虑控制相关。控制相关涉及到改变控制流的指令,即条件分支指令。条件分支指令的结果决定将要取的下一条指令是下一个顺序指令还是条件分支指令的目标。本质上在一个条件分支之后有两条候选指令。在指令流水线中,按照正常的操作,取指段采用程序计数器的内容来取下一条指令,然后将程序计数器的内容增1,指向下一条顺序指令。这个任务由取指段在每个机器周期进行重复,以保证流水线填满。若取到一条分支指令,这个顺序流可能被破坏。若分支条件没有命中,则由取指令段对下一个顺序指令进行连续取是正确的。然而,若条件分支被命中,则取指段所取得的下一个顺序指令是错误的。问题在于这个二义性直到分支条件确定后才能够解决。

控制相关可被看作涉及到程序计数器(PC)的一种寄存器数据相关(RAW)。一条条件分支指令写入PC,而对下一条指令的取涉及到读PC。若命中条件分支,则条件分支指令用目标指令的地址更新PC;否则,采用下一条顺序指令的地址更新PC。在TYP流水线中,在MEM段中用目标指令地址来对PC进行更新,而IF段采用PC的内容去取下一条指令。因此,IF段对PC寄存器进行读,流水线中随后的MEM段对PC寄存器进行写。根据图2-15(c),这种IF段与MEM段的顺序满足涉及到PC寄存器的RAW发生的必要条件。因此,在TYP流水线中存在控制危险,可以被认为是一种涉及到PC的RAW危险。

2.2.3.3 流水危险的解决

对于TYP流水线的结果而言,由于数据相关所导致的流水危险仅有RAW危险一种。此外,由于控制相关所导致的流水危险能够发生。所有这些危险涉及到一条领先的指令i写入一个寄存器(或PC),而一条后续的指令j读取那个寄存器。鉴于流水危险的存在,必须提供一些机制来解决这些危险,即确保相应的数据相关不被破坏。对于TYP流水线中的每个RAW危险,必须保证对公共寄存器(或危险寄存器)的读发生在对其写之后。

为了解决RAW危险,必须在领先的指令i 已经经过其写危险寄存器的那个段之后,后续的指令j才能进入其读危险寄存器的段。这通过停止流水线中早一些的段(即所有先于进行寄存器读的段)来实现,因此防止指令j进入临界的寄存器读段。指令j必须等待的机器周期数,在最坏情况下等于流水线的两个临界段的距离,即对临界寄存器进行读以及对其进行写的段之间的距离。对于TYP流水线,若领先的指令i是一条ALU或者一条取指令,则临界的寄存器

写段是WB段,对于所有后续指令,临界的寄存器读段是RD段。两个临界段之间的距离是3;因此,最坏的损失是3个周期,如表2-5所示。若原始程序列表中,指令j紧随指令i之后,即j等于i+1,最坏的情形将发生。在这种情况下,指令j必须在ID段中停滞三个周期,三个周期后,当指令i退出WB段时,允许指令j进入RD段。若指令j不是紧随指令i之后,即两条指令间有其它指令,则损失会少于三个周期。假定中间的指令不依赖于指令i。实际损失的周期数是3-s,其中s是中间指令的数目。例如,若i与j之间有三条指令,则没有损失。在这种情形下,指令j将在指令i退出WB段时进入RD段,不需要停下来以满足RAW相关。

表2-5 TYP流水线中由于RAW危险所造成的最坏情形损失 先导指令类型:(i) ALU 取指令 分支指令 后续指令类型:(j) ALU,存取, ALU,存ALU,存取, 分支 取,分支 分支 PC 危险寄存器 整数寄存器整数寄存器(Ri) (Ri) 寄存器写段(i) WB(第六WB(第六MEM(第五段) 段) 段) 寄存器读段(j) RD(第三段) RD(第三IF(第一段) 段) RAW距离或损失 3个周期 3个周期 4个周期

对于控制相关,领先的指令i是一条分支指令,它在MEM段更新PC。在IF段需要读PC以取出后续指令j。这两个段之间的距离是4;因此,最坏的损失是4个周期。当遇到一条条件分支指令时,所有进一步的取指令被停止在IF段,直到条件分支指令完成了MEM段,其中将根据条件目标地址对PC进行更新。这需要在IF段停滞4个周期。进一步的分析表明,若条件分支被命中,这个停滞仅是必须的。若条件分支没有被命中,则IF段可能已经完成了对下一条顺序指令的取。这个特性可以被包括在流水线设计中,因此在一条条件分支指令后,取指令不要停止。事实上,流水线假定分支将不被命中。一旦分支被命中,PC根据MEM段中的分支目标进行更新,删除或清除所有驻留在早一些的段中的指令,下一条被取的指令是分支目标。采用如此设计,仅在条件分支确实被命中时,出现4个周期的损失,此外不会有损失。

类似于寄存器数据相关所引起的RAW危险,控制相关所引起的4个周期的损失可以被看作是最坏情形的损失。若可以将与指令i没有控制相关的指令插入到指令i与控制相关指令j之间,则实际损失的周期数将会减少,减少数为插入的指令数。这就是延迟分支的概念。本质上这些损失的周期被有用的指令所填充,无论条件分支是否命中,必须执行这些指令。实际损失的周期数是

4-s,其中s是在指令i与指令j之间插入的控制无关的指令数目。延迟分支或

填充由分支所造成的损失周期使得早先的技术难以实现,因为这个技术假定分支未命中,并允许IF段取下一个顺序指令。原因在于必须提供机制来区别填充指令与正常顺序的指令。若分支被命中,不需要删除填充指令,但是正常的顺序指令必须删除,因为不应该执行它们。

2.2.3.4 通过前向通道来减少损失

截至目前,我们隐含地假定现有的解决危险的途径仅是停止有依赖关系的后续指令,确保对危险寄存器写与读按照其正常的顺序进行。在实际的流水线实现中,有一种更积极的技术,它可以减少由于流水线危险所导致的损失周期。这样的一种技术涉及到将前向通道综合到流水线中。

图2-16 在TYP流水线中集成前向通道以减少ALU与取损失

对于流水危险,领先的指令i是后续指令j所依赖的指令。对于RAW危险,指令j需要指令i的结果作为其操作数。图2-16给出了对领先指令i的处理,其中i是一条ALU指令或取指令。若领先指令i是一条ALU指令,则指令j所需的结果实际上由ALU段产生,当指令i完成ALU段时,这个结果便可用了。换言之,指令j所需的操作数实际上当指令i退出ALU段时,在ALU段的输出变为可用,指令j不需要等待两个周期使指令i退出WB段。若ALU段的输出可以通过一个物理前向通道到达ALU段的输入端,则一旦先前的指令i离开ALU段,便允许后续指令j进入ALU段。这样,指令j不需要在RD段通过读寄存器堆来访问所依赖的操作数;相反,它通过访问ALU段的输出来得到所依赖的操作数。增加这个前向通道以及相应的控制逻辑,当领先的指令是一条ALU指令时,最坏情形的损失是0周期。即使后续的指令是i+1,也不需要停止,因为指令i+1可以进入ALU段,正如在正常的流水操作中指令i离开ALU段那样。

表2-6 采用前向通道时TYP流水线中

先导指令类型:(i) 后续指令类型:(j) 危险寄存器 寄存器写段(i) 由于RAW危险所造成的最坏情形的损失 ALU 取指令 分支指令 ALU,存取, 分支 整数寄存器(Ri) WB(第六段) ALU,存取, 分支 整数寄存器(Ri) WB(第六段) ALU,存取, 分支 PC MEM(第五段) 寄存器读段(j) 从那个输出而来 所到输入端 前向通道损失

RD(第三段) ALU , MEM , WB ALU 0周期 RD(第三段) MEM , WB ALU 1个周期 IF(第一段) MEM IF 4个周期 若领先的指令是一条取指令,而不是一条ALU指令,可以加入类似的前向通道,来减少由于领先的取指令与一条有依赖关系的后续指令所带来的损失周期。图2-16表明,若领先的指令是一条取指令,则这条取指令的结果,即正在被装进寄存器的存储器的内容,当取指令的MEM段结束时,已经在MEM段的输出端变为可用。另外,可以从MEM段的输出到ALU段的输入添加一条前向通道,以支持后续指令的需求。后续指令可以在先前指令结束MEM段时立即进入ALU段。这明显减少了由于一条先前的取指令所带来的最坏情形的损失,从三个周期减少到一个周期。在最坏情形下,有依赖关系的指令是i+1,即

ji1。在正常的流水处理中,当指令i处于ALU段时,指令i+1将在RD

段。当指令i前进到MEM段,必须通过停止流水线中早一些的段以使指令i+1保持在RD段。然而,在下一个周期,当指令i退出MEM段,通过从MEM段的输出到ALU段的输入的前向通道,允许指令i+1进入ALU段。事实上,指令i+1仅在RD段停滞了一个周期;因此,最坏的损失是一个周期。表2-6给出了通过添加前向通道使得RAW危险的最坏损失可以减少的数目。

一条ALU指令作为先导指令,由于RAW危险所造成的损失称作ALU损失。类似的,先导指令为取指令所造成的损失称作取损失。对于TYP流水线,添加了前向通道后,ALU损失是0周期。事实上,当前导指令是一条ALU指令时,没有产生损失。注意到前向通道的源头是ALU段的输出,这是指令i的结果可用的最早的点。前向通道的终点是ALU段的输入,这是指令j所需的从指令i而来的结果的最晚的点。从一个结果成为可用的最早的点到这个结果被一条相依赖的指令所需要的最晚的点,把这样一条前向通道称为关键前向通道,它代表了从减少危险损失角度讲,对于那种先导指令的最好结果。

除了关键前向通道,还需要额外的前向通道。例如,从MEM段以及WB段的输出到ALU段的输入的前向通道。需要这两条前向通道的原因是,相依赖的指令j可能是指令i+2或者是指令i+3。若ji2,则当指令j准备进入ALU段时,指令i将正在退出MEM段。因此,指令j所需要的指令i的结果,还没有写回到目标寄存器,目前在MEM段的输出变为可用,必须前进到ALU段的输入,以允许指令j在下一个周期进入这个段。类似的,若ji3,指令i的结果必须从WB段的输出前进到ALU段的输入。这样,尽管指令i已经完成其向目标寄存器的写,指令j却已经流过RD段,并准备进入ALU段。当

然,若ji4,无需任何前向通道,通过j正常的读寄存器堆,就可以容易地满足RAW依赖关系。当j到达RD段时,i 将已经完成了WB段。

若前导指令是一条取指令,指令i 的结果变为可用的最早点是MEM段的输出,需要这个结果的最晚点是ALU段的输入。因此,对于一条先导的取指令,关键前向通道是从MEM段的输出到ALU段的输入。这代表能得到的最好结果,即不可避免的要产生一个周期的损失。此外,当指令i 正退出WB段时,若相依赖的后续指令正准备进入ALU段,则需要另一条从WB段的输出到ALU段的输入的前向通道。

表2-6指出没有前向通道被用来减少由于分支指令所带来的损失。若先导指令i 是一条分支指令,对于TYP流水线所假定采用的寻址方式,结果可用的最早点在MEM段的输出。对于分支指令,分支目标地址以及分支条件在ALU段产生。直到MEM段,才检查分支条件,并且分支的目标地址被装入PC。因此,只有在MEM段之后,才能用PC来取分支目标指令。另一方面,在IF段的开始,PC必须可用以允许取下一条指令。因此,需要结果的最晚点是IF段的开始。因此,若分支被命中,则关键前向通道,或者说能获得的最好结果,是采用MEM段中的分支目标来更新PC以及在下一个周期开始取分支目标的当前损失路径。然而,若分支条件能够早到在ALU段产生,以允许在ALU段的末尾采用分支目标地址来更新PC,则这种情况下,分支损失可以从4个周期降至3个周期。

2.2.3.5 流水互锁的实现

通过硬件机制来解决流水危险被称作流水互锁。流水互锁硬件必须检出所有的流水危险,并确保满足所有的依赖关系。流水互锁可涉及到停止流水线的特定段以及通过前向通道来控制数据的流动。

图2-17 支持由于ALU先导指令所造成的流水线危险的前向通道

增加了前向通道后,标量流水线不再是一个简单序列的流水段,数据从第一个段流向最后一个段。前向通道可能造成从后面一些段的输出到前面一些段的反馈路径。例如,图2-17中展示了为支持在一个流水危险中涉及到一个先导指令为ALU指令时,需要三条前向通道。这些被称为ALU前向通道。随着先导指令i通过流水段,将会有多条后续指令与指令i有数据(RAW)相关。图2-17的右侧显示了在三个连续的机器周期内如何满足多条后续的相关指令。在周期t1,指令i通过标为“a”的前向通道将其结果送至相关的指令i1。在下

一个周期t2,指令i通过前向通道b将其结果送至相关的指令i2。若指令

i2也需要指令i1的结果,这个结果将由指令i1通过前向通道a送给指令

i2。在周期t3,指令i通过前向通道c将其结果送至指令i3。相应的,在

这个周期,若指令i3也需要指令i2或指令i1的结果的话,路径a或路径b同样可以被激活。

图2-18 涉及ALU先导指令的RAW危险的流水互锁的实现

图2-18给出了图2-17的逻辑框图的物理实现。采用比较器来比较连续指令中的寄存器标识符,可以检测出RAW危险。图2-18中示出了4个五位比较器(假定是32寄存器)。若后续指令j当前在RD段,即试图读其两个寄存器操作数,则左边的两个比较器检查在指令j以及正处于ALU段的指令j-1之间可能的RAW相关。这两个比较器比较j的两个源寄存器标识符以及j-1的目的寄存器标识符。同时,另两个(右边的)比较器检查j与目前正处于MEM段的 j-2之间可能的RAW相关。这两个比较器比较j的两个源寄存器标识符以及j-2的目的寄存器标识符。若检测到相关,则在下一个周期四个比较器的输出被用作激活相应的前向通道的控制信号。

若指令j与j-1之间检查出RAW相关,则前向通道a由第一对比较器激活。类似的,若指令j与指令j-2之间有相关,则由第二对比较器的输出激活前向通道b。若j与j-1以及j-2都相关,则两个路径可以被同时激活。

图2-18没有给出图2-17的前向通道c;原因是若设计多端口寄存器堆时采取适当措施,这个前向通道不是必须的。若三端口(两个读一个写)的寄存器堆在每个周期中先执行写,再执行读,则第三个通道是不必要的。实际上,当指令j通过RD段时,将读到新的正确的相关寄存器的值。换言之,寄存器堆内部进行了前移,不需要等待一个周期读相关寄存器或者从WB段的输出传向ALU段的输入端。这是一个合理的设计方案,它可以减少一个损失周期或者减少一个前向通道,并且在MIPS R2000/R3000流水线中确实实现了。

图2-19 支持由于先导取指令所造成的流水危险的前向通道

为了减少当前导指令是取指令时流水危险所造成的损失,需要另一套前向通道。图2-19显示了当前导指令是一条取指令时,其造成的流水危险所需要的两条前向通道。这些被称作是取前向通道。前向通道d将MEM段的输出传到ALU段的输入,前向通道e将WB段的输出传到ALU段的输入。当先导指令i到达ALU段时,若指令i1与指令i相关,它必须在RD段停滞一个周期。在

下一个周期,当指令i正退出MEM段,其结果通过路径d可以被传到ALU段,以允许指令i1进入ALU段。若指令i2也与指令i相关,则在下一个周期通过路径e可以将相同的结果从WB段传到ALU段,以允许指令i2前进到ALU段,而不必停滞另一个周期。此外,若多端口寄存器堆先执行写,后执行读,则前向通道e不是必须的。例如,当指令i在WB段执行写寄存器时,指令i2将同时在RD段读到指令i的结果。

图2-20 涉及ALU与取指令的RAW危险的流水互锁的实现

图2-20中给出了支持那些由于ALU指令以及取指令作为先导指令时,所造成流水危险的前向通道的物理实现。前向通道e没有给出,假定寄存器堆在每个周期内先执行写,然后执行读。注意到在图2-17以及图2-19中分别给出了ALU前向通道b以及取前向通道d,从MEM段的输出到ALU段的输入,实际上如图2-20所示,它们是两种不同的物理通道。这两条通道向第一对乘法器装载,但是根据MEM段中的先导指令是一条ALU指令还是一条取指令,只选择其中一条通道。前向通道b从含有前一个机器周期的ALU的输出的MEM段的缓存开始。前向通道d从包含有从D-cache中取来的数据的MEM段的缓存开始。

无论先导指令是一条ALU还是一条取指令,采用相同的两对比较器来检测寄存器相关。因为互锁硬件必须检测在指令i与指令i1之间以及指令i与指令

i2之间可能存在的相关,所以需要两对比较器。若设计的寄存器堆在每个周

期内先执行写,后执行读,则当指令i与指令i3各自通过WB段以及RD段时,它们之间的相关自动满足。第一对比较器的输出,以及标志先导指令是一条取指令的ID段的一个信号共同来产生一个控制信号,若指令i是一条取指令,并且检测到在指令i与指令i1之间存在相关,则用这个控制信号将流水线的前三个段停止一个周期。

图2-21 涉及分支指令的危险的流水互锁的实现

TYP流水线的流水互锁硬件也必须处理由于控制相关所导致的流水危险。图2-21给出了支持先导分支指令所造成的控制危险的互锁机制的实现。通常,IF段在每一个周期访问I-cache,取下一条指令,同时对PC进行增值以准备取下一个顺序的指令。当IF段取到一条分支指令,然后经ID段对其解码,IF段停滞直到分支指令经过ALU段,这时分支条件以及目标地址均已产生。在下一个周期,对应于分支指令的MEM段,若分支命中,则通过PC乘法器的右侧用

分支条件将分支目标地址装入PC。只要遇到一条分支指令,就会导致4个周期的损失。或者,可以假定分支指令没有被命中,IF段继续沿着原来的顺序取指令。一旦分支指令被命中,在分支指令的MEM段用分支目标对PC进行更新,在IF、ID、RD以及ALU段中的顺序指令变为无效,从流水线中取出。这样,只有当分支被确实命中时,才会导致4周期的损失。若分支没有被命中,不会造成损失。

2.2.4商用流水线处理器

流水线处理器设计已经成为一项成熟并被广泛应用的技术。RISC体系与指令流水的兼容已经广为人知并被很好地开发。流水也被成功地应用到CISC结构中。本小节着重讨论两个代表性的流水线处理器。这里把MIPS R2000/R3000流水线作为RISC流水线处理器的代表,把Intel i486作为CISC流水线处理器的代表。由Tilak Agerwala 以及John Cocke于1987年在IBM对RISC流水线处理器所做的实验数据,在这里作为标量流水线处理器的特性以及性能的代表。

2.2.4.1 RISC流水线处理器的例子

MIPS是一个具有32位指令的RISC结构。由三种不同的指令模式,如图2-22所示。

图2-22 在MIPS指令集结构中采用的指令模式

指令可以被分作4类。

·计算指令完成寄存器操作数的算术、逻辑以及移位操作。若所有的操作数以及结果均是寄存器,它们可以采用R-型模式;若一个操作数被指定为在指令的立即域,则可以采用I-型模式。

·存取指令在存储器以及寄存器之间移动数据。它们采用I-型模式。唯一的寻址模式是基址寄存器加上存于立即域中的有符号偏移量。

·跳转与分支指令改变程序的控制流。跳转指令是无条件的,采用J-型模式,它跳转到一个由26位目标以及PC高端4位所组成的绝对地址。分支指令是有条件的,采用I-型模式来指定目标地址为PC加上立即域中的16位偏移量。

·指令集的其它指令被用来执行协处理器以及其它特定系统功能的操作。协处理器0是系统控制协处理器。CP0指令负责存储器管理以及异常处理。浮点指令采用协处理器指令来实现,并由一个的浮点处理器执行。

MIPS R2000/R3000流水线是一个与TYP流水线非常类似的5段指令流水线。然而,每一个流水段被进一步分为两个分立的阶段,记做阶段1(1)与阶段2(2)。表2-7给出了5个段以及其对应的阶段所完成的功能。

这个5段流水线有一些有趣的特点。访问I-cache,需要一个完整的周期,实际上发生在IF段的2以及RD段1。用一个TLB来对I-cache与D-cache进行地址解析。在IF段的1访问TLB,以支持I-cache访问;在ALU段的2访问TLB,以支持D-cache访问,对TLB的访问发生在MEM周期。在每个机器周期,寄存器堆首先执行写(WB段的1),然后进行读(RD段的2)。为了分别支持IF段以及MEM段,这个流水线需要一个三端口(两个写一个读)的寄存器堆以及一个单端口的I-cache和一个单端口的D-cache。

表2-7 MIPS R2000/R3000 五段流水线的功能

段名 阶段 执行的功能 1.IF 1 采用TLB翻译虚拟指令地址 2 采用物理地址访问I-cache 2.RD 1 从I-cache返回指令,检查标记以及奇偶性 2 读寄存器堆,对于分支指令,生成目标地址 3.ALU 1 开始ALU操作,对于分支指令,检查分支条件 2 结束ALU操作,对于存取指令,翻译虚拟地址 4.MEM 1 访问D-cache 2 从D-cache返回数据,检查标记与奇偶性 5.WB 1 写寄存器堆 2 -

采用从ALU以及MEM段的输出到ALU段的输入的前向通道,所有的ALU先导危险不会导致周期损失。采用从MEM段的输出到ALU段的输入的前向通道,取损失(即由一个取先导危险所造成的最坏情形的损失)仅为一个周期。分支损失也是一个周期。由于R2000/R3000的几个特性,这成为可能。首先,分支指令只采用与PC相关的寻址模式。不同于必须在RD段访问寄存器,在IF段之后,PC变为可用。因此在RD段用一个的加法器可以计算分支目标地址。第二个特点是没有生成与存储明显的条件位。通过在ALU段的1比较参考寄存器的内容,来生成分支条件。通常,在ALU段(第三段)生成分支条件,在IF段(第一段)取指令,期望的损失将是2个周期。然而,在这个流水线设计中,实际上直到IF段的2才开始访问I-cache。由于分支条件在ALU段1的末尾变为可用,而直到IF段的2才开始访问I-cache,分支条件可以在IF段

中间对I-cache的访问之前,把在RD段末尾产生的分支目标地址送入PC。因此,分支指令只产生一个周期的损失。

从流水危险所造成的损失角度看,5段MIPS R2000/R3000是一个比6段TYP更好的流水线设计。两个流水线的ALU损失与取损失均分别是0周期与一个周期。然而,由于上述设计特性,对于分支损失,MIPS R2000/R3000流水线只有一个周期,而不是4个周期。受Stanford大学的RISC的影响并从中受益,MIPS R2000/R3000是一个非常简单并高效的流水线处理器。

2.2.4.2 CISC流水线处理器的例子

1978年Intel推出了第一代16位微处理器,Intel 8086。尽管在此之前Intel有8位微处理器(8080与8085),8086开创了一场,并最终形成了兼容对象编码的IA32族微处理器。Intel IA32是一个CISC结构,具有可变长指令与复杂的寻址方式,截至目前,从销售规模以及相应的应用软件支持角度看,它是最有影响的结构。1985年,推出了IA32家族的32位版本,Intel 386。19年,推出了IA32家族的第一个流水线版本,Intel 486。最初的8086芯片有不到30K个晶体管,而486芯片的晶体管超过了1M。486与IA32家族的所有先前成员兼容,采用对象编码,在20世纪90年代初,它是用于个人计算机最流行的微处理器。

486实现了5段指令流水线。表2-8给出了流水段的功能。一个指令预取单元,通过总线接口单元,将16字节的指令块取到预取队列中。在取指段,每一条指令都从32字节的预取队列中取。指令解码在两个段中完成。在指令解码时产生硬件控制信号以及微指令。执行段完成ALU操作以及访问cache。在指令解码时,完成地址翻译以及生成有效地址;在执行段完成存储器访问。因此,取存储器后,将会被立即使用,不会导致任何周期的损失;执行段的输出将传给其输入。然而,由于在指令译码时生成地址,若产生一个寄存器结果的指令后面紧跟着一条用相同寄存器来生成地址的指令,则需要损失一个周期。流水线的第五个段执行寄存器写回。浮点操作由一个在片的浮点单元完成,其执行需要多个周期。

表2-8 Intel 486 五段流水线的功能

段名 1.取指令 执行的功能 从32字节预取队列中取指令 (预取单元填充并清空预取队列) 2.指令解码-1 将指令翻译为控制信号或微码地址。 开始生成地址与访存 3.指令解码-2 访问微码寄存器 输出微指令到执行单元 4.执行 执行ALU与访存操作 5.寄存器写回 将结果写回到寄存器

采用5段指令流水线,486不需要使用微码就可以在一个周期内执行许多IA32指令。一些指令需要访问微指令,因此需要多个周期。486表明通过指令流水,可以提高性能。根据典型的指令组合以及常用的IA32指令的执行时间,Intel386可以达到的平均每条指令所用周期(CPI)是4.9。流水线Intel 486能达到的平均CPI是1.95。这表明加速因子大约是2.5。用我们的术语,5段i486达到的有效流水程度是2.5。显然,其中有明显的流水开销,主要是由于IA32指令集体系的复杂性以及确保对象编码的负担。但是,对于一个CISC结构,这个加速度已经可观了。486表明了CISC流水结构的灵活性。

2.2.4.3 标量流水线处理器的性能

一份记载1987年由Tilak Agerwala以及John Cocke在IBM对流水线RISC机器所进行的实验的报告,提供了对标量流水线RISC处理器的评估。这里列出了报告中一些要点。在这次研究中,采用分立的I-cache与D-cache。I-cache每周期能够向处理器提供一条指令。只有存取指令访问D-cache。在这个研究中,假定两个cache的命中率是100%,两个cache的延迟是一个周期。在这个研究中采用下列数据。

1.动态的指令组合

a. ALU:40%(寄存器-寄存器) b. 取指令:25% c. 存指令:15% d. 分支指令:20% 2.动态的分支指令组合

a. 无条件:33.3%(总是命中) b. 条件命中:33.3% c. 条件未命中:33.3% 3.取指调度

a. 不能被调度:25%(没有填充延迟时隙)

b. 能够回移一或两条指令:65%(填充两个延迟时隙) c. 能够回移一条指令:10%(填充一个延迟时隙) 4.分支调度

a. 无条件:100%可调度(填充一个延迟时隙) b. 条件:50%可调度(填充一个延迟时隙)

可以用每条指令所需的周期数来估计处理器的性能。一个标量流水线处理器的理想目标是达到CPI=1。这表明,平均来说,流水线在一个周期内处理或完成一条指令。IBM的研究试图量化能够达到这个理想目标的程度。起初,假定没有ALU损失,取损失与分支损失均为2个周期。用给定的动态指令组合,可以计算由这两种损失所造成的CPI开销。

·取损失开销:0.25×2=0.5 CPI

·分支损失开销:0.20×0.66×2 = 0.27 CPI ·合成的CPI:1.0 + 0.5 + 0.27 = 1.77 CPI

由于25%的动态指令是取指令,假定每条取指令产生2个周期的损失,因此CPI开销是0.5。设想流水线假定分支指令没有被命中,或者偏向于没有命中,则仅有命中分支指令的66.6%将会导致2周期的分支损失。考虑到取损失与分支损失,期望的CPI是1.77。这与理想目标的CPI=1相差甚远。

假定可以添加一个前向通道来对取指令从寄存器堆设一个旁路,则取损失可以从两个周期减少到1个周期。添加这个前向通道后,CPI降至1.0 + 0.25 + 0.27 = 1.52。

此外,可以用编译器来调度指令进入取损失时隙与分支损失时隙。采用上述统计数据,由于65%的取指令可以回移一条或两条指令,10%的取指令可以回移一条指令,共有75%的取指令可以被调度或者回移,因此将取损失减少了一个周期。对于无条件分支指令的33.3%,它们可以被调度使分支损失从两个周期降至1个周期。由于流水线偏向于分支未命中,没有被命中的条件分支的33.3%不会造成分支损失。对于剩下的被命中的33.3%的条件分支,假定它们的50%是可调度的,即它们可以被回移一条指令。因此,命中的50%的条件分支将导致一个周期的损失,另外50%将导致2个周期的损失。新的CPI开销以及合成CPI如下所示。

·取损失开销:0.250.25×1=0.0625 CPI

·分支损失开销:0.20×[0.33×1+0.33×0.5×1+0.33×0.5×2] = 0.167 CPI ·合成的CPI:1.0 + 0.063 + 0.167 = 1.23 CPI

通过调度取损失时隙与分支损失时隙,可以显著减少由于取损失与分支损失所造成的CPI开销。合成的1.23的CPI接近于理想目标CPI=1。由于分支损失所造成的CPI开销仍然明显。进一步减少这一开销的一个途径是考虑减少两个周期的分支损失的方法。在IBM的研究中,没有采用寄存器间接寻址方式,90%的分支可以被编码为与PC相关的。采用与PC相关的寻址方式,不需要访问寄存器堆,就可以生成分支目标地址。采用一个的加法器,可以与读寄

存器段并行地生成目标地址。因此,对于采用与PC相关寻址方式的分支指令,分支损失可以减少一个周期。对于33.3%的无条件分支,它们是100%可以调度的。因此,分支损失只有一个周期。若它们中的90%可以变为PC相关,从而消除分支损失,则只有剩下10%的无条件分支将导致一个周期的损失。相应的无条件分支的CPI开销是0.20×0.33×0.10×1=0.0066 CPI。

表2-9 考虑PC相关寻址与损失时隙调度的条件分支损失

PC相关寻址 是(90%) 是(90%) 否(10%) 否(10%)

采用与PC相关的寻址方式,取指段不再偏向于分支未命中。因此,无论条件分支是否命中,都可以对它们统一处理。根据一个条件分支是否可变为PC相关以及是否可以被调度,有四种可能情况。表2-9给出了条件分支的这四种可能情况的损失。

包括命中以及没有命中的,66.6%的分支是有条件的。考虑表2-9中的这些情况,条件分支的CPI开销为0.20×0.66×{[0.9×0.5×1]+[0.1×0.5×1]+[ 0.1×0.5×2]}=0.079CPI。综合无条件以及条件分支的CPI开销得到分支损失所导致的总的CPI开销为0.0066+0.079=0.0856CPI。连同原先的取损失一起,新的开销以及合成的整体CPI如下所示。

·取损失开销: 0.0625 CPI ·分支损失开销:0.0856CPI

·合成的CPI:1.0 + 0.0625 + 0.0856= 1.149 CPI

因此,进行一系列的改进后,原先的1.77的CPI被降至1.15。这非常接近于理想目标的CPI=1。解释这一点的一个方法是CPI=1代表理想的指令流水线,每一个周期有一条新指令进入流水线。这只有在流水理想的第三点是真时才成立,即所有指令是的。在实际程序中,指令之间存在相关。CPI=0.15表明,在设计可以处理指令之间相关的实际指令流水线时,只产生了15%的开销或者无效。这是非常显著的,反映了指令流水的有效性。

2.3 深度流水线处理器

可调度 是(50%) 否(50%) 是(50%) 否(50%) 分支损失 0周期 1个周期 1个周期 2个周期 为改善处理器的性能,流水是一种非常有效的手段,因此有很好的理由来采用深度流水线。一个更深的流水线增加了流水段的数目,并且减少了每个流水段中逻辑门的层数。更深的流水线的好处是可以减少机器周期,因此提高了时钟频率。在20世纪80年代,大多数流水线微处理器具有4到6个流水段。目前,高端微处理器的时钟频率高达几个GHz,其流水线的深度已经超过20个流水段。流水线不仅变得更深,而且变得更宽,例如超标量处理器。随着流水线变宽,每个流水段的复杂度增加,这将增加每个流水段的延迟。为了维持相同的时钟频率,一个更宽的流水线需要变得更深。

图2-23 增加流水线深度对ALU损失、取损失以及分支损失的影响

深度流水线有一个下降的趋势。流水线的深度越深,为解决流水危险所造成的损失将会变大。图2-23显示了当流水线变深并变宽时,ALU损失、取损失以及分支损失的变化。比较浅流水线与深流水线,我们可以发现,ALU损失从0周期增加到1个周期,取损失从一个周期增加到4个周期,最主要的是分支损失从3个周期增加到11个周期。由于流水损失的增加,平均CPI增加了。深度流水线所具有的更高的时钟频率所带来的潜在的性能提高,可以由于CPI的增加而改善。为了确保深度流水线整体性能的提高,时钟频率的增加必须超过CPI的增加。

有两种方法来减轻深度流水线中分支损失增加的负面影响,见图2-24。在三种流水损失中,分支损失是最严重的,因为它跨越了所有的流水段的前端。对于一个判断错误的分支,所有的流水段前端中的指令必须被清除。减轻流水损失的第一个方法是减少前端流水段的数目。例如,具有变长指令的CISC结构需要非常复杂的指令译码逻辑,因此需要多个流水段。使用一个RISC结构,可以降低译码复杂度,使得前端流水段减少。另一个例子是在将指令装入I-cache之前采用预译码逻辑。从I-cache中取出的经过预译码的指令需要较少的译码逻辑,因此译码段较少。

图2-24 减轻深度流水线的分支损失

另一个方法是将一些前端的复杂性移到流水线的末端,使得前端较浅,因此分支损失较小。这已成为一个研究热点。若流水线重复执行一系列指令,前端流水段重复执行相同的工作,对相同的指令进行取指、译码、分派。一些人建议,所做工作的结果可以被保存且重用,而不必重复相同的工作。例如,一块已解码的指令可以被存储在一个特定的cache中。随后取这些指令可以通过

访问这个cache来进行,负责解码的流水段可以被绕过去。除了存储这些已解码的指令外,还可以对这些指令进行另外的优化,进一步消除一些前端流水段。存储以及优化可以在流水线的末端实现,而不会对前端深度以及相应的分支损失造成影响。为了使深度流水线利用更高的时钟频率的好处,必须使流水损失控制在一定范围内。

在设计深度流水线中,有不同形式的折中。如2.1节中所指出的,相对于一个非流水设计,k段流水线潜在的吞吐能力可以增加到原来的k倍。若考虑到成本,则有一个成本与性能的折中。这个折中表明最优值k不能任意大。图2-3显示了这一点。这种折中涉及到实现流水线的硬件成本,它表明有一个流水深度,超过这个数值后,性能增加的减少不值得增加流水线额外的成本。

根据前面对深度流水对CPI的影响的分析,有另一种折中。这种折中涉及到时钟频率的增加与CPI的增加。根据处理器性能的Iron定律,性能由时钟频率与平均IPC(或频率/CPI)的乘积所决定。当流水线变深,频率与CPI都将增加。只有流水深度的增加带来了性能的净增加,则流水深度的增加是有利的。当超过某一点后,流水深度任何的增加将会带来性能很少改善,甚至没有改善。问题是,在达到这一点之前,流水线能有多深?

图2-25 增加流水线深度时性能变化

图2-25最初由Intel 的Edward Grochowski提出,这里用来分析流水深度与性能的关系,以及流水深度可以有多深并且保持有效。图中最陡的曲线表明频率与流水深度的比例关系。注意到它并非一条直线。频率的亚线性比例是由于时钟单元所增加的延迟的开销。底部的曲线是分支损失与取损失所造成的CPI开销。性能采用频率/CPI来计算,如中间的曲线所示。一开始,性能曲线显著上升,然后随着流水深度的增加变缓。在这条曲线的最右端,随着流水深度的增加,性能下降了。根据这一分析,直到流水段达到57个时,在性能增加方面,流水仍然有效。

向更高的时钟频率推进看来是不可逆转的。在这个十年末达到10GHz是非常可能的。为了维持这个频率推动,将会有更深的流水线。

2.4总结

流水是一种微结构技术,可以应用到任何的ISA。RISC结构使得流水变得容易,并且产生了更有效的流水设计。然而,流水对CISC结构同样有效。在

提高处理器性能方面,流水已被证明是一种非常强大的技术,从流水深度角度讲,仍然有很大的发展空间。我们期待更深的流水线。

流水线处理器性能的主要障碍是由于指令相关所造成的流水的停滞。由控制相关所造成的分支损失是最大的。动态分支预测可以缓解这一问题,只有当分支被错误预测时才会造成分支损失。当分支预测正确时,没有流水停滞;然而,当流水预测错误时,流水线必须被清除。

随着流水线变深,分支损失将增加并成为主要问题。一个策略是通过减少流水线前端的深度,即取指段与解决分支的段之间的距离,来降低分支损失。另一个方法是提高动态分支预测算法的准确性,从而降低分支预测错误的频率,以使产生分支危险的频率下降。本章我们没有涉及分支预测。这是一个非常重要的课题,我们在超标量处理器中来讨论分支预测,将其放在第四章。

流水线处理器设计改变了传统CPU设计的观点。传统的观点将处理器设计分为数据通道设计与控制通道设计。数据通道设计集中在设计ALU与其它功能单元以及寄存器访问。控制通道设计集中在状态机的设计,对指令进行解码并生成对数据通道进行适当控制所必须的控制信号流。这一观点不再适用。在一个流水线处理器中,这一划分不再明显。指令在解码段进行解码,解码后的指令以及相关的控制信号沿着流水线进行广播,并被后续的流水段所使用。每一个流水段使用已解码指令的相应域以及相关的控制信号。实际上已经没有由控制通道所完成的集中控制。反之,采用了通过流水段来广播控制信号的分布控制。传统的方法,通过多个控制通道状态序列来处理一条指令,现在被遍历各个流水段所替代。实际上,不仅数据通道是流水的,而且控制通道也是流水的。此外,传统的数据通道与控制通道现在集成在同一个流水线中。

2.5 习题

1.

等式2-4中理想流水线的性能与流水深度的联系,看起来非常象Amdahl定律。描述这两个等式中各项之间的联系,并给出为什么这两个等式这么相似的直观解释。

2.

利用等式2-7,采用参数G、T、L、S可以计算性价比优化的流水深度kopt。用芯片数作为成本项(G=175芯片以及L=82/2=41芯片/段间延迟),延迟T与S(T=400ns,S=22ns),计算2.1节中流水线浮点乘法器例子的kopt。kopt与推荐的流水线设计有什么不同?

3.

给出并讨论为什么等式2-4仅适用于对流水线潜在加速度进行大致估计的两个原因。

4. 假定你要向TYP指令集与流水线中添加一条立即取指令。这一指令从指令字中提取16位的立即数,将立即数进行符号扩展为32位数,并将结果存放在指令字中指定的目的寄存器中。由于提取以及符号扩展不需要ALU,你的同事建议这样的指令可以在解码段(ID)将其结果写入寄存器中。利用图2-15中描述进行危险检测的算法,分析这一变化将会带来什么额外的危险。

5. 6. 7.

忽略流水互锁硬件(在问题6中讨论),问题4 中给出的变化需要什么额外的流水资源?讨论这些资源以及其成本。

考虑问题4中的改变,重新画图2-18中显示的流水互锁硬件,以正确处理立即取指令。

假定你将向TYP指令集以及流水线中添加字节宽的ALU指令。这些指令除了其源操作数仅有一个字节宽以及目的操作数仅有一个字节宽外,其它语义与现存的字宽ALU指令相同。字节宽操作数与字宽指令存放在相同的寄存器的低位字节中,写寄存器必须只影响低位字节(即高位字节保持不变)。重画图2-18中的RAW流水线互锁检测硬件,以正确处理这些额外的ALU指令。

8. 考虑向TYP流水线中添加一条变址寻址方式的存指令。这条存指令与现有采用寄存器+立即数寻址的存指令不同之处在于,通过计算两个源寄存器之和作为其有效地址,即 stx r3, r4, r5 执行r3←MEM[r4 + r5]。描述在TYP流水线中为支持这一指令所需的额外流水资源。讨论这一指令的优缺点。

9. 考虑添加一条取更新指令,其寻址方式为寄存器+立即数与随后更新。在这种寻址方式中,通过计算寄存器+立即数作为取指令的有效地址,结果地址写回到基址寄存器。即,lwu r3, 8(r4) 执行r3←MEM[r4 + 8]; r4←r4+8。描述在TYP流水线中为支持这一指令所需的额外资源。

10. 11.

根据问题9中列出的变化,重画图2-20中的流水互锁硬件,以正确处理取更新指令。

根据2.2.4.3中给出的IBM实验,计算添加一个零级数据cache后对CPI的影响,这个cache可以在75%的时间用一个周期来提供数据。并行访问零级cache与一级cache,当零级cache缺失时,一级cache在下一个周期返回结果,产生一个取延迟时隙。假定零级取延迟时隙能否被填充是均匀分布的。给出你的结果。

12. 根据问题11中的假定,若只有在零级cache缺失时,一级cache顺序访问,导致两个取延迟时隙而不是一个,计算其对CPI的影响。给出你的结果。

13. 在一个具有数据cache的基于TYP流水线设计中,取指令将检查标志cache命中的队列,与访问数据队列以读相应的存储地址的操作并行处理。流水线向这个cache写更困难,因为在覆盖数据队列之前,处理器必须首先检查标志。此外,若cache缺失,则存指令所重写的存储器地址是错误的。设计一种方案来解决这个问题,不需要沿着管道将存指令传送两次,或者为每条存指令而停滞管道。参考图2-15,还有任何新的RAW,WAR与/或WAW存储器危险么?

14. 表2-7所示的MIPS流水线采用两段时钟机制,有效的利用一个共享的TLB,取指令在第一阶段访问TLB,取数据在第二阶段访问TLB。然而,当解决一个条件分支时,需要在ALU段的第一阶段与检查分支条件并行地解析分支目标地址以及分支失败地址,以使得在第二阶段能够从分支目标地址或者分支失败地址中取指令。这似乎需要一个双端口TLB。给出一个解决此问题的方案,避免采用双端口TLB。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- yrrf.cn 版权所有 赣ICP备2024042794号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务