第四章 处理器体系结构
一个处理器支持的指令和指令的字节级编码称为它的ISA(instruction-set architecture,指令集体系结构)。不同家族处理器有不同的ISA。ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层,编译器编写者只需要知道允许哪些指令,以及他们是如何编码的;而处理器设计者,必须建造出执行这些指令的处理器。
ISA模型看上去是顺序执行的,实际上同时处理多条指令的不同部分,可以提升性能。
本章自定义设计一个Y86处理器,它的指令集称为Y86指令集。
4.1 Y86指令集体系结构
我们设计的Y86处理器,类似于IA32,包含下面四部分:
八个程序寄存器:每个寄存器存储一个字,寄存器%esp被入栈,出栈调用和返回指令作为栈指针,其他寄存器没有固定含义或固定值。
三个一位的条件码:它们保存最近的运算的相关信息。
程序计数器(PC):存放着当前正在执行指令的地址。
存储器:保存程序和数据,可以看成很大的数组。Y86用虚拟地址来引用存储器位置。硬件和软件操作系统协作将虚拟地址转换成物理地址。
下图是我们设计的Y86指令集描述,这个就是我们要设计的处理器要实现的目标。左边是汇编表示,右边是字节编码。我们使用小端法。
指令的字节编码最多6个字节(0xff是8位,一个字节)
第0字节:称为指令指示符。fn字段表示是某个整数操作(OP1)或分支条件(jXX)。
我们把IA32的movl拆分成4个指令,显示地指明源和目的的类型,i立即数,r寄存器,m存储器。
opl:是4个整数操作指令addl,subl,andl,xorl。它们只对寄存器数据进行操作,这些指令会设置条件码。
jXX:跳转指令jmp,jle,jl,je,jne,jg,jge。根据分支指令类型和条件码选择分支。dest是绝对地址。
call指令将返回地址入栈,然后调到目的地址。dest是绝对地址。
ret指令从call指令的调用中返回。
pushl和popl实现入栈和出栈。
halt:停止执行指令。
fn指令字节码
如下图所示,8个寄存器都有相应的寄存器标识符。程序寄存器被存放在cpu的一个寄存器文件中,这个寄存器文件就是一个小的,以寄存器ID作为地址的随机访问存储器。图4.2中的rA,rB就对应这8个寄存器。当操作数不是寄存器时,就用ID为8的表示,图4.2中的8就是这个意思。
通过上面这些规则,我们可以把汇编语言转换成十六进制字节码,反之亦然。
例如
rmmovl %esp 0x12345(%edx)的转换过程如下:
rmmovl %esp 0x12345(%edx) -->404245230100
rmmovl = 40
%esp = rA = ID 4
%edx = rB = ID 2
0X12345 = D = 0x12345补齐8位=0x00012345 = 小端452301100
指令集的一个重要特性就是字节编码必须有唯一解释 。任何一个字节序列只能对应唯一的指令。
4.2 逻辑设计和硬件控制语言HCL
4.2.1 逻辑门
下图是3个逻辑门的标准符号,下面是对应的HCL表达式。逻辑门只对位进行操作。
4.2.2 组合电路和HCL布尔表达式
将多个逻辑门组合起来,可以得到组合电路,有两个基本原则:
1. 逻辑门的输出不可以连到一起,因为会相互影响。
2. 这个网必须是无环的,因为该组合电路的计算函数有歧义。
两个简单的组合电路
4.2.3 字级的组合电路和HCL整数表达式
通常需要字级操作,这就需要更复杂的组合电路,下面列了几个组合电路的符号图和HCL表达式
1. 字等于电路
2. 字等于的HCL表达式
bool Eq = (A == B);
字级多路复用HCL表达式
int Out = [
S: A;
1: B;
];
3. 多路选择的多路复用
HCL表达式:
int Out4 = [
!S1 && !S0: A;
!S1: B;
!S0: C;
1: D;
];
4. 取最小值
HCL表达式:
int Min3 = [
A < B && A < C: A;
B < A && B < C: B;
1: C;
];
算术逻辑运算单元ALU是非常重要的组合电路,其抽象如下图
A和B是数据输入,上方的0、1、2、3是控制输入,对应Y86指令集的四个整数操作addl,subl,andl,xorl。
4.2.4 集合关系(Set Membership)
处理器设计中,经常需要判断一个信号是否属于一组信号中的一个,使用表达式
iexpr in {iexpr1, iexpr2,...iexpr10}
4.2.5 存储器和时钟控制
为了产生时序电路(sequential circuit),也就是有状态,并且在这个状态上的系统,需要引入安位存储信息的设备,考虑如下两类:
时钟寄存器:简称寄存器,存储单个位或字。时钟信号控制寄存器加载输入值。
随机访问存储器:简称存储器,存储多个字,用地址来选择读写哪个字。常见的有处理器的虚拟存储器系统和寄存器文件。
硬件通过时钟控制化寄存器的值,如高电平保持,低电平变化。
下图是一个寄存器文件,虽然不是硬件组合电路,但原理是一样的,当向src*写入一个值后,过一段时间,就可以在val*上得到对应的值。例如向srcA设置为3,就会读%ebx的值,然后这个值就会出现在valA上。
4.3 Y86的顺序(sequential)实现
SEQ:顺序处理器
4.3.1 将处理组织成阶段
取址(fetch):从存储器读入指令,地址为PC的值。从指令中抽取出指令指示符字节两个4位部分,称为icode指令代码和ifun指令功能。取的值有多种可能:
1. 一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。
2. 四字节常数valC。
人理解L取出的内容是4.1中的字节码,可以转换成汇编语言后理解。
它按顺序的方式计算下一条指令的地址valP,valP等于PC的值加上已取出的指令的长度。
解码(decode):从寄存器文件读入最多两个操作数,得到valA或/和valB。通常它读入指令rA和rB字段指明的寄存器,也有些指令是读寄存器%esp的。
执行(execute):ALU进程工作,有如下几种:
1. 执行指令指明的操作(根据ifun的值),计算存储器引用的有效地址;
2. 增加或减少栈指针;
3. 设置条件码,对于跳转指令来说,这一阶段会检查条件码和(ifun)给出的分支条件,看是否选择分支。
访存(memory):将数据写入存储器或从存储器读出数据,读出的值为valM。
写回(write back):最多可以写两个结果寄存器文件。
更新PC:将PC设置成下一条指令地址。
处理器循环执行上述几个阶段,只有在遇到halt或发生错误时才会停止。
下图展示了三个机器指令在每个阶段的动作。
以这个例子具体来看
第3行指令subl
第5条指令rmmovl
第6行pushl
第8行je
第13行ret
4.3.2 SEQ硬件结构
通过下面的硬件抽象图,学习图中的硬件单元和各个阶段关联
取址:指令存储器将PC作为地址,读取一个指令大小的字节,PC增加器计算valP。
解码:从寄存器文件的A、B两端读取valA和valB。
执行:ALU根据指令类型进行相应计算valE,可能会设置条件码。
访存:数据存储器读出或写入一个存储器字。指令和数据存储器访问的是相同的存储器位置,但是用于不同的目的。
写回:寄存器文件有两个写端口,M端口用于写从存储器中读出的值,E端口用来写ALU计算出的值。
下图是一个更详细的SEQ所需硬件,简单说明下图形表示:
用带淡点的浅灰色方框:表示硬件单元。比如存储器,ALU等。
灰色圆角矩形:表示控制逻辑块。用于从一组信号源中进行选择,或用来计算布尔函数。
白色圆角方框:线路的名字。
中等粗的线:表示宽度为字长的数据连接。实际上代表一簇32根线。
比字长线细一点的线:表示宽度为字节或者更小的数据连接。根据数据类型,实际上代表一簇4或8根线。
点虚线:表示单个位的连接。代表芯片上单元与块之间传递的控制值。
4.3.3 SEQ的时序(timing)
提前总结,就是新周期开始的上升沿,会把逻辑组合的值更新到寄存器,计数器等状态设备。
以下图为例介绍,硬件如何执行第3、4条指令的。
在时钟周期3开始的时候,状态元素使第2条命令结束的状态,地址0x00c载入程序计数器中,这样就会取出和处理第3条指令。值沿着组合逻辑流动,包括读随机访问存储器,在3周期末尾,组合逻辑为条件码产生了新值000,但没更新到条件码寄存器;更新了程序寄存器%ebx,以及程序计数器的新值0x00c,此时逻辑组合已经更新了,但是状态没有更新。
在时钟周期4开始的时候,有上升沿,会更新条件码寄存器,程序计数器,寄存器文件。但组合逻辑还没对这些变化做出反应。在这个周期内会取出和执行第4条指令。
4.3.4 SEQ的阶段实现
常用的HCL如下表
取址阶段:
如下图所示,取指阶段包括指令存储器硬件单元。以PC作为第一个字节(字节0)的地址,这个单元一次从存储器读取6个字节。
第一个字节被当成指令字节,被分成icode和ifun。根据icode的值可以计算3个1位的信号(用虚线表示):
instr_valid: 用于表示这个指令是否是一个合法的Y86指令;
need_regids: 用于表示这个指令是否包含寄存器指示字节;
need_valC: 用于表示这个指令是否包括一个常数;
比如need_regids的HCL语言描述如下:
need_regids = icode in {IRRMOVL, IIRMOVL, IRMMOVL, IMRMOVL, IOPL, IPUSH, IPOPL};
剩余的5个字节是:寄存器指示符字节和常数数字的组合编码。标号称为Align的硬件单元会处理这些字节,将他们放入寄存器字段和常数字中。
当need_regids=1时,字节1被分开装入rA和rB中,否则这两个字段会被设为8。同时根据need_regids来确定1~4还是1~5字节是常数。
PC增加器硬件单元,计算valP, valP=p(PC)+r(need_regids)+4*1(need_valC)
解码和写回阶段:
下图实现解码和会写阶段的逻辑
srcA和srcB是读端口valA和valB的地址输入
dstE和dstM是写端口valE和valM的地址输入
根据指令代码icode和寄存器指示值rA和rB会产生4个不同的寄存器文件的寄存器ID.
寄存器ID srcA表明应该读哪个寄存器以产生valA,srcA的HCL语言描述如下:
int srcA = [
icode in {IRRMOVL, IRMMOVL, IOPL, IPUSHL} : rA;
icode in {IPOPL, IRET} : RESP;
1 : RNONE; #Don't need register
];
srcB的HCL描述如下:
int srcB = [
icode in {IOPL, IRMMOVL, RMRMOVL} : rB;
icode in {IPUSHL, IPOPL, ICALL, IRET} : RESP;
1 : RNONE; #Don't need register
];
寄存器ID dstE表明写端口E的目的寄存器,计算出来的值valE将放在那里。其HCL描述如下:
int dstE = [
icode in {IOPL, IRRMOVL, IIRMOVL} : rB;
icode in {IPUSHL, IPOPL, ICALL, IRET} : RESP;
1 : RNONE; #Don't need register
];
dstM的
int dstM = [
icode in {IOPL, IMRMOVL} : rA;
1 : RNONE; #Don't need register
];
执行阶段:
如下图所示,根据ALU fun信号,对ALU A和ALU B执行运算
ALU A的HCL 表达
int alua = [
icode in {IRRMOVL, IOPL} : valA;
icode in {IIRMOVL, IRMMOVL, IMRMOVL} : valC;
icode in {ICALL, IPUSHL} : -4;
icode in {IRET, IPOPL} : 4;
# other instruction don't need ALU
];
对应OPL指令,我们希望ALU使用ifun指令字段中编码的操作,所以ALU控制的HCL描述可以写成:
int alufun = [
icode == IOPL : ifun;
1 : ALUADD;
];
执行阶段包括条件码寄存器,每次运行时ALU都会产生条件码相关的信号,不过我只希望执行OPL时设置条件码寄存器,所以创建了一个信号set_cc进行控制.
bool set_cc = icode in {IOPL};
名字为bcond的硬件单元,决定一条指令是否跳转,并产生信号Bch。
访存阶段:
存储器的读写地址总是valE和valA,用HCL描述
int mem_addr = [
icode in {IRMMOVL, IPUSHL, ICALL, IMRMOVL} : valE;
icode in {IPOPL, IRET} : valA;
];
更新PC阶段:
HCL描述如下:
int new_pc = []
icode == ICALL : valC;
icode == IJXX && Bch : valC;
icode == IRET : valM;
1 : valP;
;
4.3.5 SEQ+: 重新安排计算阶段
我们把将计算PC放在时钟开始阶段,称这样的处理器为SEQ+。
4.4 流水线的通用原理
介绍流水线原理
4.4.1 计算流水线
计算流水线的效率
4.4.2 流水线操作的详细说明
4.4.3 流水线的局限性
1. 不一致的划分
2. 流水线过深,反而影响效率
4.4.4 带反馈的流水线
带反馈的流水线容易产生错误
4.5 Y86的流水线实现
设计一个流水线话的Y86处理器。以SEQ+为基础,在各个阶段添加流水线寄存器。
4.5.1 插入流水线寄存器
先设计一个初级版本PIPE-的处理器,抽象结构如下图所示
灰色方框表示流水线寄存器:
F:保存程序计数器的预测值;
D:位于取址和解码之间,它保存关于最新取出的指令的信息,即将由解码阶段进行处理;
E:位于解码和执行之间,它保存关于最新解码的指令和从寄存器文件读出的值的信息,即将由执行阶段进行处理。
M:位于执行和访存阶段之间,它保存最新执行的指令的结果,即将由访存阶段进行处理。它还保存关于用于处理条件转移的分支条件和分支目标的信息。
W:位于访存阶段和反馈路径之间,反馈路径将计算出来的值提供给寄存器文件写,而当完成ret指令时,它还要向PC选择逻辑提供返回地址。
下图用于说明代码是如何在流水线执行的,时间是从左往右增大,上面的数字表明各个阶段发生的时钟周期。
下图是一个更为详细的硬件结构,每个流水线寄存器包含多个字段(白色方块),对应于不同的指令通过流水线的相关信号。
先介绍SEQ+和PIPE-结构的差别
4.5.2 对信号进行重新排列和标号
1. 解码阶段产生的dstE和dstM会一直贯穿执行和访存阶段,直到写回阶段才送到寄存器文件, 这样可以确保寄存器文件的写端口的地址和数据都来自解码阶段的同一条指令。否则会将写回阶段的指令的值写入,而寄存器ID却来自于解码阶段的指令。作为一条通用原则,我们要保存处于一个流水线阶段中的指令的所有信息。
2. seletc A块,从寄存器文件的A端口或者流水线寄存器D的valP中选一个作为流水线寄存器E的valA。PIPE-增加这个块的目的是为了减少要携带给流水线寄存器E和M的状态数量。因为,在所有指令中,只有call在访存阶段需要valP的值,只有跳转指令在执行阶段需要valP的值,而这些指令又都不需要从寄存器文件中读取的值,因此我们合并这两个信号,将他们作为valA携带穿过流水线,从而减少流水线寄存器的状态数量。在硬件设计中,像这样仔细确认信号,然后通过合并信号来减少寄存器状态和线路的数量,是很常见的。
4.5.3 预测下一个PC
为了实现每个时钟执行一条命令,需要在取出当前指令后,马上确定下一条指令的地址。但是有些情况下(如跳转指令和ret)无法确定,这就需要进行预测。
图4.41中取指阶段的Predict PC块会从PC增加器计算出的valP和取出的指令中得到的valC中选一个存放到流水线寄存器F的predPC中,作为程序计数器的预测值。Select PC块从三个值中选择一个作为指令存储器的地址。
4.5.4 流水线冒险(hazard)
将流水线引入带反馈的系统会导致相邻指令间在发生相关时出现问题,这些相关有如下2形式:
1. 数据相关,下一条指令会用到这指令计算出的结果。
2. 控制相关,一条指令要确定下一条指令的位置。例如在执行跳转、调用或返回指令时。
这些相关可能会导致流水线产生计算错误,称为冒险。同相关一样,冒险也分为2类:数据冒险和控制冒险。
我们通过下图进一步了解流水线冒险
在周期6,此时addl指令从寄存器文件读取它的操作数。此时,第一条irmovl指令已经通过了写回阶段,因此%edx的值已经更新。第2条irmovl指令处于写回阶段,因此对%eax的写要到第7周期开始,时钟上升时才会发生。结果addl指令在解码阶段时会读出错误的%eax值,因为对该寄存器的写还没发生。 如果有3条nop指令就不会发生错误,如果nop减少,则有更多的错误。
之所以会出现这些冒险,是因为流水线化处理器,是从解码阶段从寄存器文件中读取指令的操作数,而要到3周期后,指令经过写回阶段时,才会将指令的结果写到寄存器文件。
4.5.5 用暂停(stalling)来避免数据冒险
暂停时,处理器会停止流水线中的一条或多条指令,直到冒险条件不再满足。只要一条指令的源操作数会被流水线后面某个阶段中的指令产生,处理器就会通过将指令阻塞在解码阶段来规避数据冒险。
暂停技术就是就是让一组指令阻塞在他们的阶段,而允许其他指令继续通过流水线。
在周期3中对addl指令解码之后,暂停控制逻辑发现了对两个源寄存器的数据冒险。它在执行阶段中插入一个气泡,并在周期5中重复对指令addl的解码。它再次发现对两个源寄存器的冒险,就在执行阶段中插入一个气泡,并在中期6中重复对指令addl解码。它再次发现对寄存器%eax的冒险,就在执行阶段中插入一个气泡,并在周期7中重复对指令addl的解码。实际上,集气室动态地插入3条nop指令,得到的执行流类似于prog1的流。
4.5.6 用转发(forwarding)来避免数据冒险
将结果值直接从一个流水线阶段传到较早阶段的技术称为数据转发(data forwarding或简称转发)。
如下图,周期4中,解码阶段逻辑发现在访存阶段中有对寄存器%edx为完成的写,而且执行阶段中ALU正在计算的值稍后也会写入寄存器%eax。它可以将访存极端中的值(信号M_valE)作为操作数valA,也可以将ALU的输出(信号e_valE)作为操作数valB。注意,使用ALU的输出不会造成任何同步问题。解码阶段只要在时钟周期结束之前产生信号valA和valB,这样在时钟上升开始下一个周期时,流水线寄存器E就能装载来自解码阶段的值了。而在此之前ALU的输出已经是合法的了。
上图周期4中,解码阶段逻辑发现有在访存阶段中对寄存器%edx未完成的写,还发现在执行阶段中正在计算寄存器%eax的新值。它用这些值,而不是从寄存器文件中读出的值作为valA和valB的值。
下图是PIPE的抽象结构,它是PIPE-的扩展,能通过转发处理数据冒险。我们可以看到添加了从5个转发源到解码阶段的额外的反馈路径(用深灰色表示) 。这些旁路路径(bypass path)反馈到解码阶段中一个标号为Forward的块。这个块会从寄存器文件中读出的值或转发过来的值作为源操作数valA和valB。
下图给出了PIPE硬件结构的一个更为详细的说明。与PIPE-的结构对比,我们可以看到来自5个转发源的值反馈到解码阶段中两个标号为Sel+Fwd A和Fwd B的块。
Sel+Fwd A 是PIPE-中标号为Select A的块的功能与转发逻辑的结合。它允许流水线寄存器M的valA为已增加的程序计数器值valP,从寄存器文件A端口读出的值,货值某个转发过来的值。
Fwd B实现的是源操作数valB的转发逻辑。
4.5.7 加载/使用(load/use)数据冒险
可以通过暂停和转发结合的方式处理所有可能的数据冒险。
4.5.8 PIPE各阶段的实现
来自不同流水线寄存器的信号用大写的字母加下划线开头,如D_*表明信号来自流水线寄存器D
来自各个阶段计算的信号用小写的阶段名做前缀,如
PC选择和取址阶段
这个阶段必须选择程序计数器的当前值,以及预测下一个PC值。用于从存储器中读取指令和抽取不同指令字段的硬件单元。
PC选择逻辑从3个程序计数器源中选择:
当一条预测错误的分支进入访存阶段时,会从流水线寄存器M(信号M_valA)中读出该指令valP的值(指明下一条指令的地址)。
当ret指令进入写回阶段时,会从流水线寄存器W(信号W_valM)中读取返回地址。
其他情况会使用存放在流水线寄存器F中(信号F_oredPC)的PC预测值。
HCL语言描述
int f_pc = []
# Mispredicted branch. Fetch at incremented PC
M_icode == IJXX && !M_Bch : M_valA;
# Completion of RET instruction.
W_icode == IRET : W_valM;
#Default: Use predicted value of PC
1 : F_predPC;
;
当取出的指令为函数调用或跳转时,PC预测逻辑会选择valC,否则选择valP:
int new_F_predPC = [
f_icode in { IJXX, ICALL} : f_valC;
1 : fvalP;
];
解码和写回阶段
如下图,标号为dstE,dstM,srcA,srcB块非常类似于在SEQ的实现中的相应部件。可以看到,提供给写端口的寄存器ID来自于写回阶段(信号W_dstE和W_dstM),而不是来自于解码阶段。这是因为我们希望进行写的目的寄存器是由写回阶段中 的指令指定的。
没有指令既需要valP又需要来自寄存器端口A中读出的值,因此对后面的阶段来说,这两者可以合并为信号valA。
标号为Sel+Fwd A的块执行该任务,并实现源操作数valA的转发逻辑。
标号为Fwd B的块实现源操作数valB的转发逻辑,寄存器写的位置是由来自写回阶段的dstA和dstB信号指定的,而不是来自于解码阶段,因为它要写的是当前正在写回阶段中的指令的结果。
这个阶段的复杂性主要是根转发逻辑相关。
只有call和跳转指令在后面的阶段中需要valP的值,而这些指令并不需要从寄存器文件A端口中读出的值。这个选择是由该阶段的icode信号来控制的。当信号D_icode与call或jXX的指令代码相匹配时,这个块就会选择D_valP作为它的输出。
有5个转发源,每个都有一个数据字和一个目的寄存器ID:
如果不满足任何转发条件,这个块就会选择d_rvalA(即从寄存器端口A中读出的值)作为它的输出。
综上,可以得到流水线寄存器E的valA的新值的HCL描述
int new_E_valA = [
D_icode in {ICALL, IJXX} : D_valP; # Use incremented PC
d_srcA == E_dstE : e_valE; # Forward valE from execute
d_srcA == M_dstM : m_valM; # Forward valE from memery
d_srcA == M_dstE : M_valE; # Forward valE from memery
d_srcA == W_dstM : W_valM; # Forward valM from write back
d_srcA == W_dstE : W_valE; # Forward valM from write back
1 : d_rvalA; # Use value read from register file
];
转发源的优先级非常重要,如果不对就会出错。下图示例如何设置正确的优先级。在这个程序中,前两条指令写寄存器%edx,而第三条指令用这个寄存器作为它的源操作数。当指令rmmovl在周期4到达解码阶段时,转发逻辑必须在两个都以该源寄存器为目的的值中选择一个。它应该选择哪个呢?为了设定优先级,必须考虑当依次执行一条指令时,机器语言程序的行为。
第一条irmovl指令会将寄存器%edx设为10
第二条irmovl指令会将寄存器%edx设为3,然后rrmovl指令会从%edx中读出3。
为了模拟这种行为,流水线化的实现应该总是给处于最早流水线阶段中的转发源以较高的优先级,因为它保持着程序序列中设置改寄存器的最近的指令。因此,上述HCL代码中的逻辑会首先检测执行阶段中的转发源,然后是访存极端中的,最后才是写回阶段中的。
对同在访存或写回阶段中的两个源之间的转发优先级,只对指令popl %esp有影响,因为只有这条指令能同时写两个寄存器。
执行阶段
信号e_valE和E_dstE作为转发源,指向解码阶段
访存阶段
许多从流水线寄存器M和W来的信号被传递到较早的阶段,以提供写回的结果、指令地址以及转发的结果。
4.5.9 流水线控制逻辑
现在准备创建流水线控制逻辑,完成我们的PIPE设计了。这个逻辑必须处理下面三种其他机制(转发和分支预测等)无法处理的控制情况。
处理ret:流水线必须暂停知道ret指令到达写回阶段。
加载/使用冒险:在一条从存储器中读出一个值的指令和一条使用该值的指令之间,流水线必须暂停一个周期。
预测错误的分支:在分支逻辑发现不应该选择分支之前,分支目标处的几条指令已经进入流水线。必须从流水线中去掉这些指令。
发现特殊控制条件
下图总结了需要特殊流水线控制的条件,给出的HCL表达式描述了在那些条件下会出现这三种情况。一些简单的组合逻辑块实现了这些表达式,为了在时钟上升开始下一个周期时控制流水线寄存器的活动,这些块必须在时钟周期结束之前产生出结果。在一个时钟周期内,流水线寄存器D、E、M分别保持着处于解码、执行、访存阶段中的指令的状态。在到达时钟周期末尾时,信号d_srcA和d_srcB会被设置为解码阶段中指令的源操作数的寄存器ID。当ret指令通过流水线时,要想发泄它,只要检查解码、执行、访存阶段中指令的指令码。发现加载/使用冒险要检查执行阶段中的指令类型(mrmovl或popl),并把它的目的寄存器与解码阶段中指令的源寄存器相比较。当跳转指令在执行阶段时,流水线控制逻辑应该能发现分支预测错误的,这样当指令进入访存阶段时,它就能设置从错误预测中恢复所需要的条件。当跳转指令处于执行阶段时,信号e_Bch指明是否需要选择分支。
流水线控制机制
下图给出的是一些低级机制,它们使得流水线控制逻辑能将指令阻塞在流水线寄存器中,或往流水线中插入一个气泡。
假设流水线寄存器有两个控制输入:暂停stall和气泡bubble,它们决定当时钟上升时,该如何更新流水线寄存器。
A):暂停=0,气泡=0,寄存器加载它的输入y作为新状态
B):暂停=1,气泡=0,用之前的状态x作为新状态
C):暂停=0,气泡=1,寄存器状态会设置成某个固定的复位配置,得到一个等效于nop指令的状态。一个流水线寄存器的复位配置的0、1模式是由流水线寄存器中字段的集合决定的。确定复位配置是硬件设计师在设计流水线寄存器时的任务之一。
下表给出了各个流水线寄存器在3种特殊情况下应该采取的行动。