1.3 超标量处理器的流水线
1.3.0 超标量处理器的概述
A. 什么是超标量处理器?
如果每周期可取出多条指令(eg: 超过一条)送到流水线中执行,并使用硬件来对指令进行调度(eg: 靠硬件自身来决定哪些指令可以并行执行)的处理器,就可称为超标量处理器;
B. 超标量处理器与VLIW处理器
不是每周期可执行多条指令的处理器都是超标量处理器,在 VLIW(超长指令字,Very Long Instruction Word)结构的处理器中,每周期也可执行多条指令,但VLIW处理器与超标量处理器有本质上的有差别:
VLIW处理器 | 超标量处理器 | |
---|---|---|
使用什么来对指令进行调度? | 靠硬件自身来决定哪些指令可以并行地执行; | 靠编译器和程序员自身来决定哪些指令可以并行执行; |
通用处理器所必须具有的特性之一:程序员可以抛开底层硬件的实现细节,专注于软件本身的功能,而且这个程序可以运行在任何支持该指令集的处理器上; | 对于通用处理器来说,超标量结构是必需的 | VLIW处理器无法实现这个功能,但是由于需要编译器和程序员自身来调度指令的执行顺序,这种处理器在硬件实现上是很简单的,在功能比较专一的专用处理器领域可以大有一番作为,例如: DSP处理器; |
C. 超量与超标量、顺序与乱序
标量 | 超标量 | |
---|---|---|
顺序(in-order) | 3 | 2 |
乱序(out-of-order) | x | 1 |
标量与超标量
标量:指处理器在一个时钟周期内获取、执行和提交一条指令;
超标量:指处理器在一个时钟周期内获取、执行和提交多条指令,与标量对应;
顺序与乱序
顺序:"顺序发射、顺序执行",指处理器按照指令原始顺序逐条发射、逐条执行;
乱序:"乱序发射、乱序执行",与顺序对应;
"超标量"一般和"乱序"搭配,"标量"一般和"顺序"搭配;
D. 顺序执行和乱序执行的超标量处理器的特点
Fetch(取指) | Decode(译码) | Issue(发射) | Execute & Memory(执行 & 访存) | Write Back(写回) | Commit(提交) | ||
---|---|---|---|---|---|---|---|
顺序执行 | in-order | in-order | in-order | - | in-order | in-order | |
乱序执行 | in-order | in-order | out-of-order | - | out-of-order | in-order |
Fetch 和 Decode 阶段很难实现乱序,事实上就算实现了也没意义;
Issue 表示将指令送到对应的功能单元(Function Unit,FU)中执行;
这里可乱序执行(out-of-order),因只要指令的源操作数准备好了,就可以将其先于其他指令而执行;
Write back 表示将指令的结果写到目的寄存器中,
可在处理器内使用寄存器重命名(Register Renaming)将指令集(Instruction Set,IS)中定义的逻辑寄存器(Architecture Register File,ARF)动态地转化为处理器内部实际使用的物理寄存器(Physical Register File,PRF),从而实现乱序方式(out-of-order)的写回寄存器;
Commit 表示一条指令被允许更改处理器的状态(Architecture state,例如D-Cache等),为了保证程序按照原来的意图得到执行,并实现精确异常,这个阶段需要顺序执行,这样才能够保证从处理器外部看起来,程序是串行执行的;
精确异常:因为不希望异常处理进程破坏掉原程序的正常执行,所以流水线上没有执行完的指令必须记住它处于流水线的哪一阶段,且必须知道哪条指令发生的异常,当发生异常指令之后,所有指令都不能改变处理器状态所以处理完异以便异常处理结束后能精确恢复执行,这便是精确异常。
1.3.1 顺序执行(in-order)
A. 顺序执行的超标量处理器的流水线
在顺序执行(in-order)的超标量处理器中,指令的执行必须遵循程序中指定的顺序;
B. 粗略介绍 "顺序执行的超标量处理器" 的流水线中关键阶段
假设上图的流水线是2-way超标量处理器的,则每周期可以从I-Cache中取出两条指令来执行:
对于执行乘法操作指令的第三个FU来说,只有当指令到达1时,才可将它的结果进行旁路(by-pass);
举例一个典型的 Scoreboard (如下图),记录了指令集中定义的每个逻辑寄存器(R0~R31)的执行情况;
在流水线的 Issue 阶段,会将指令的信息写到 ScoreBoard 中,同时,这条指令会查询 ScoreBoard 来获知自己的源操作数是否都准备好了,在这条指令被送到 FU 中执行之后的每个周期,都会将这个值右移一位,这样使用这个值就可以表达出指令在FU中执行到哪个阶段;
对于执行ALU类型指令的第一个FU来说,当指令到达3时,就可将它的结果进行旁路(by-pass),而
在更复杂的处理器中,ScoreBoard 中还会有其他的内容;
第一个FU用来执行ALU类型的指令;
第二个FU用来执行访问存储器类型的指令;
第三个FU用来执行乘法操作的指令
Issue:在指令经过 Decode 阶段之后,处理器会根据指令的类型,从 Issue Queue (发射队列) 中选择合适的指令发送到对应的 FU (Function Unit) 中执行,这个过程称为 lssue,若将 Issue 的过程放到指令的 Decode 阶段,会严重影响处理器的周期时间,因此将发射的过程单独使用一个流水段;
Execute:如上图,Execute 阶段是使用了三个 FU (如下),因为要保证流水线的 Write Back 阶段是顺序执行的,因此所有 FU 都需要经历同样周期数的流水线 —— 此例子中,乘法运算需要的时间最长,因此第三个FU使用了三级流水线,其他的FU也需要跟随着使用三级流水线,即使它们在有些流水段啥事情都没有做;
Scoreboard:是用来记录流水线中每条指令的执行情况,例如一条指令在哪个FU中执行,在什么时候这条指令可以将结果计算出来等,并可协助流水线的旁路(by-pass)工作;
C. 粗略介绍 "顺序执行的超标量处理器" 的流水线中的执行情况
下图为上面流水线中的执行情况(情况有一定简化)
RAW (先写后读):
WAR (先读后写) 和 WAW (先写后写):
在所有的处理器中(不论顺序执行还是乱序执行的处理器),RAW (先写后读) 相关性都是不可以绕开的,如果一个程序中存在过多的RAW相关性,那么这个程序就不能够在处理器中被有效地执行;
处理器需要在先前的写操作完成之后才能保证正确的读取数据,因此不论处理器是顺序执行还是乱序执行,都需要考虑和处理RAW相关性;
由于上图例子中顺序执行的处理器只有一个统一的 Write Back 阶段,而且这个阶段位于流水线的最后一级,因此WAR和WAW这两种相关性都不会对流水线产生影响;
假设在流水线的 Write Back 阶段才可以对计算结果进行 by-pass,由于这是一个顺序执行(in-order)的处理器,很多指令在流水线都会由于前面指令的阻塞而不能够继续执行;
指令F,它和前面的指令都是不相关的,但由于这是一个顺序执行(in-order)的处理器,所以这条指令只有等到前面所有的指令都已经发射(issue)了,它才可以送到FU中执行 —— 降低了处理器性能;
每条指令都可以从旁路网络(bypassing network)获得操作数,不需要等待源寄存器的值被 Write Back 到通用寄存器中,由于指令需要按照顺序的方式执行,所以指令在很多时候都处于等待的状态 —— 按照图中例子,程序在一个2-way顺序执行的超标量处理器中需要12个周期才可以执行完毕 —— 降低了处理器性能;
指令之间的相关性:
1.3.2 乱序执行(out-of-order)
A. 乱序执行的超标量处理器的流水线
在乱序执行(out-of-order)的超标量处理器中,指令的执行不再遵循程序中指定的顺序 —— 某条指令的操作数一旦准备好,就可以将其送到 FU 中执行;
B. 粗略介绍 "乱序执行的超标量处理器" 的流水线中各个阶段
Fetch(取指)
I-Cache:负责存储最近常用的指令;
分支预测器:用来决定下条指令的PC值;
负责从 I-Cache 中取指令,主要由两个部件构成:
Decode(解码)
Decode 这部分的设计和指令集是息息相关的:
对于RISC指令集来说,例如MIPS, 由于比较简洁,所以Decode部分也就相对比较简单 —— 但在超标量处理器中,仍旧需要对一些特殊的指令进行处理,这些内容额外增加了Decode部分的设计复杂度。
对于CISC指令集来说,例如x86,由于比较复杂,所以Decode部分需要更多的逻辑电路来对这些指令进行识别;
用来识别出指令的类型、指令需要的操作数、指令的一些控制信号等;
Register Renaming(寄存器重命名)
在进行寄存器重命名时,通常使用一个表格来存储当前逻辑寄存器到物理寄存器之间的对应关系,同时在该表格中还存储着哪些物理寄存器还没有被使用等信息,使用一些电路来分析当前周期被重命名的指令之间的RAW相关性,将那些存在RAW相关性的指令加以标记,这些指令会通过后续的旁路网络(bypassing network)来解决它们之间存在的“真相关性”。
由于寄存器重命名阶段需要的时间比较长, 现实当中的处理器都会将其单独使用一级流水线,而不是和Decode阶段放在一起(当然头铁也可以放一起^^)。
在流水线的 Decode 阶段,可以得到指令的源寄存器和目的寄存器,这些寄存器都是逻辑寄存器,是在指令集中定义好的寄存器(ARF),为了解决WAR和WAW这两种“伪相关性”,需要使用寄存器重命名的方法,将指令集中定义好的逻辑寄存器(ARF,Architecture Register File)重命名为处理器内部使用的物理寄存器(PRF,Physical Register File),物理寄存器(PRF)的个数需要多于逻辑寄存器(ARF)的个数,通过寄存器重命名,处理器可以调度更多可并行执行的指令。
Dispatch(分发)
如果在这些部件中没有空闲的空间可以容纳当前的指令,那么这些指令就需要在流水线的寄存器重命名阶段进行等待,这就相当于暂停了寄存器重命名以及之前的所有流水线,直到这些部件中有空闲的空间为止;
Dispatch阶段可以和寄存器重命名阶段放在一起,在一些对周期时间要求比较紧的处理器中,也可以将这个部分单独使用一个流水段;
经过流水线的 Dispatch阶段后,指令会被写到了 Issue Queue (发射队列) 部件中;
在这个阶段,被重命名之后的指令会按照程序中规定的顺序,写到发射队列(lssue Queue)、重排序缓存(ROB)和 Store Buffer 等部件中;
Issue(发射) —— 是流水线从 in-order 到 out-of-order 的分界点
这个仲裁(select)电路可繁可简:
对于乱序执行的处理器,Issue 阶段是顺序执行(in-order)到乱序执行(out-of-order)的分界点,指令在 Issue 阶段后,都是按照乱序执行(out-of-order)的,直到流水线的 Commit 阶段,才会重新变为顺序执行(in-order)的状态。
在lssue Queue中还存在唤醒(wake-up)电路,它可将 lssue Queue 中对应的源操作数置为有效的状态;
仲裁电路和唤醒电路互相配合进行工作,是超标量处理器中的关键路径;
对于顺序发射(in-order issue)的情况,只需要判断发射队列中最旧的那条指令是否准备好就可以了;
对于乱序发射(out-of-order issue)的情况,则仲裁电路会变得比较复杂,它需要对lssue Queue中所有的指令进行判断,并从所有准备好的指令中找出最合适的那条指令,送到FU中执行;
仲裁(select)电路会从这个 Issue Queue (发射队列) 部件中选择合适的指令发送到对应的 FU (Function Unit) 中执行,这个过程称为 lssue;
Register File Read(读取寄存器)
分情况,能不能从PRF中得到操作数:
事实上很大一部分指令都是通过旁路网络(bypassing network)获得操作数的,这也为减少PRF的读端口提供了可能;
由于超标量处理器每周期需要执行好几条指令,PRF所需要的端口个数也是比较多的,多端口寄存器堆的访问速度一般都不会很快,因此在现实世界的处理器中,这个阶段都会单独使用一个流水段。
一般情况下,被仲裁电路选中的指令可以从PRF中得到源操作数;
不一般情况下,被仲裁电路选中的指令不能从PRF中得到操作数, 但却可在送到FU中执行之前,从旁路网络(bypassing network)中得到操作数;
被仲裁电路选中的指令需要从 PRF (物理寄存器堆,Physical Register File) 中读取操作数 —— 指令得到它所需要的操作数:
Source Drive
Execute(执行)
在超标量处理器中,Execute 阶段通常有很多个不同类型的 FU,例如负责普通运算的 FU、负责乘累加运算的FU、负责分支指令运算的FU、负责load/store指令执行的 FU 等;
现代的处理器还会加入一些多媒体运算的 FU,例如进行单指令多数据(SIMD)运算的 FU;
每个FU都有自己的流水线级数,如执行ALU类型指令的FU需要一个周期就可以计算出结果,则不再需要像顺序执行的处理器那样被拉长到和乘法FU一样的周期数;
为什么PRF中的结果需要写到ARF中?
PRF(Processing Register File)是用于保存指令执行的临时结果的寄存器文件;
ARF(Architectural Register File)是用于在程序执行期间保存程序状态的寄存器文件;
因为PRF中的结果是临时保存的,在程序的不同阶段可能会被覆盖或者丢失,所以需要将其写入到ARF中,以便长期保存和使用。
当PRF中的结果写入ARF时,它们就成为了程序执行的一部分,可以被其他指令访问和使用,从而更新程序执行的当前状态。
因此,将PRF中的结果写入ARF中是程序正确执行所必需的步骤。
在这种流水线中,由于每个FU的执行周期数都不相同,所以指令在流水线的Write Back 阶段是乱序的,在 Write Back 阶段,一条指令只要计算完毕, 就会将结果写到PRF中;
由于分支预测失败(mis-prediction)或者异常(exception)的存在,PRF中的结果未必都会写到ARF中,因此也将PRF称为Future File;
指令得到它所需要的操作数后,马上就可以送到对应的FU中执行了:
Write Back(写回)
在现代的处理器中,旁路网络是影响速度的关键因素,因为这部分电路需要大量的布线,而随着硅工艺尺寸的减少,连线的延迟甚至超过了门电路的延迟,因此旁路网络会严重影响处理器的周期时间;
为了解决上述的问题,很多处理器都使用了Cluster的结构,将 FU 分成不同的组:
在一个组内的FU,布局布线时会被紧挨在一起,这样在这个组内的旁路网络,由于经过的路径比较短,一般都可以在一个周期内完成;
当旁路网络跨越不同的组时,就需要两个甚至多个周期了,这种Cluster的结构是一种的折中方案。
Write Back 阶段:1. 会将FU计算的结果写到PRF(物理寄存器堆)中;2. 同时也可通过旁路网络(bypassing network)将这个FU计算的结果送到需要的地方,一般都是送到所有FU的输入端,由FU输入端的控制电路来决定最终需要的数据;
Commit(提交)
精确异常:因为不希望异常处理进程破坏掉原程序的正常执行,所以流水线上没有执行完的指令必须记住它处于流水线的哪一阶段,且必须知道哪条指令发生的异常,当发生异常指令之后,所有指令都不能改变处理器状态所以处理完异以便异常处理结束后能精确恢复执行,这便是精确异常。
一条指令在 Retire 之前,都可以从流水线中被清除,但是一旦它顺利地 Retire 而离开流水线,它的生命周期也就结束了,不能够再返回到以前的状态 —— 这对于store指令会带来额外的麻烦:因为store指令需要写存储器,如果在流水线的 Write Back 阶段就将store指令的结果写入到存储器中,那么一旦由于分支预测失败或者异常等原因,需要将这条store指令从流水线中抹掉时,就没有办法将存储器的状态进行恢复了,因为存储器中原来的值已经被覆盖了 —— 于是需使用一个缓存,称为 Store Buffer (SB),来存储store指令没有 Retire 之前的结果;
store指令在流水线的 Write Back 阶段,会将它的结果写入到SB中,只有一条 store 指令真的从流水线中 Retire 时,才可以将它的值从SB写到存储器中。
使用 SB 这个部件之后,Load指令此时除了从D-Cache中寻找数据,还需要从Store Buffer中进行查找,这样在一定程度上增加了设计的复杂度。
一条指令在 Commit 阶段,会将它的结果从PRF搬移到ARF中,同时ROB也会配合完成对exception(异常)的处理,如果不存在异常,那么这条指令就可以顺利地离开流水线, 并对处理器的状态进行更改,此时称这条指令退休 (Retire) 了,一条指令一旦退休,它就再也不可能回到之前的状态了。
在ROB中,如果一条指令之前的指令还没有执行完,那么即使这条指令已经执行完了,它也不能离开ROB,必须等待它之前的所有指令都执行完成这条指令才能离开ROB —— 一条指令一旦从 ROB 中离开而 Retire(退休),那么就对处理器的状态进行了修改,再也无法返回到之前的状态了;
之所以能够完成这样的任务,是因为:指令在流水线的 dispatch 阶段,按照程序中规定的顺序(in-order)写到了 ROB 中;
程序在处理器中表现出来的结果总是串行的,如果在程序中先向寄存器R1写数据,然后向寄存器R2写数据,那么处理器表现出来的执行结果一定是先写R1再写R2,也就是说,处理器执行的结果要和程序中原始的顺序是一样的;
在超标量处理器中,虽然指令可按照乱序执行(out-of-order),但是最后需要这样一个阶段(Commit 阶段),将这些乱序执行的指令变回到程序规定的原始顺序;
程序在处理器中表现出来的结果总是串行的,为了保证程序的串行结果,指令需要按照程序中规定的顺序更新处理器的状态,这需要使用一个称为 ROB (重排序缓存) 的部件来配合,流水线中的所有指令都按照程序中规定的顺序存储在 ROB (重排序缓存) 中,使用 ROB 来实现程序对处理器状态的顺序更新,这阶段称为Commit;
Commit 阶段起主要作用的部件是 ROB (重排序缓存),它会将乱序执行的指令拉回到程序中规定的顺序;
指令退休 (Retire) :
Store Buffer (SB) :
在 Commit 阶段也会对指令产生的exception(异常)进行处理,指令在流水线的很多阶段都可能发生exception(异常),但是所有的exception(异常)都必须等到指令到达流水线的 Commit 阶段时才能进行处理,这样可以保证异常处理是按照程序中规定的顺序进行,并且能够实现精确异常;
C. 粗略介绍 "乱序执行的超标量处理器" 的流水线中的执行情况
下图为上面流水线中的执行情况(情况有一定简化)
D将"Decode"和"寄存器重命名"这两个过程放到了同一个流水段;
r 表示指令已经计算完成,在 ROB 中等待 Retire;
C表示一条指令经过了流水线的 Commit 阶段,离开流水线而 Retire了,这个过程是按照程序中规定的顺序执行(in-order)的;
D、r、C:
一条指令只有等到它之前的所有指令都离开 ROB 了,才允许它离开ROB而从流水线中 Retire;
该程序只要9个周期就可以完成,快于之前顺序执行程序的处理器(12个周期),这是由于乱序执行提高了流水线的执行效率 —— 当需要执行的指令个数更多时,乱序执行的优势就会更加明显。
1.3.3 处理器的状态恢复
现代的处理器在很多地方使用了预测技术,因为超标量处理器的流水线一般比较深,所以不使用预测技术是没有办法获得高性能的;
一般情况下,预测能够有效工作的前提就是:有规律可循,一个很明显的例子就是分支预测,分支指令在执行过程中表现出的规律性,使分支预测成为了可能。
但是,只要是预测,就会存在失败的可能,这时候就需要一种方法,将处理器恢复到正确的状态,这就是恢复电路的工作,它不但要将错误的指令从流水线中抹掉,还需要将这些错误指令在流水线中造成的“痕迹”进行消除,例如这些错误的指令可能已经修改了重命名映射表, 或者已经将结果写到了物理寄存器中等,这都需要被修正过来。
恢复电路和预测技术是天生的一对,只要有预测,就必然有状态恢复,激进的预测技术会提高处理器的性能,但是代价就是更复杂的恢复电路。
在超标量处理器中,对异常的处理由于需要抹掉流水线中的指令, 因此也需要使用恢复电路来使处理器恢复到正确的状态,这些内容将在本书详细地展开介绍。
审核编辑:刘清
-
处理器
+关注
关注
68文章
19259浏览量
229651 -
dsp
+关注
关注
553文章
7987浏览量
348745 -
寄存器
+关注
关注
31文章
5336浏览量
120230 -
Cache
+关注
关注
0文章
129浏览量
28330 -
ArF
+关注
关注
0文章
3浏览量
1054
原文标题:一文入门 | 什么是超标量处理器的流水线?
文章出处:【微信号:处芯积律,微信公众号:处芯积律】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论