ISA
- 指令集架构(ISA)是连接计算机软件与硬件的一座桥梁,定义了程序如何与底层硬件进行交互。
- ISA的设计覆盖从应用程序到物理芯片的整个软硬件协作过程。
- 即
Application → Compiler → OS → ISA → CPU → Circuit → Chip - ISA之上:专注于程序设计和指令序列的执行。
- ISA之下:关注指令的高效执行和硬件设计指导。
Y86-64
- 相比X86-64,数据类型、指令和寻址方式都要少一些。
- 字节级编码较为简单,机器代码相比不够紧凑。
- 每条指令都会读取或修改处理器状态的某些部分,称为程序员可见(
写程序的人或者编译器)。
Y86-64的状态
- 15个程序寄存器:
%rdi,%rsi,%rax,%rbx,%rcx,%rdx,%rbp,%rsp,%r8~%r14,比x86-64少一个。 - 它们并称为寄存器堆(
Register File)。 - 每个程序寄存器存储一个
64位的字,%rsp的用法与x86-64相同。 - 三个条件码:
SF,ZF和OF。 - 程序计数器(
PC/%rip):存放当前执行指令的地址。 - 程序用虚拟地址引用内存位置,硬件和OS联合起来翻译为物理地址。
- 状态码(Stat):表明程序运行的整体状态。
- 内存:动态随机存储器,采用小端法存储。
Y86-64指令
- 每条指令从
1个字节到10个字节不等。 - 只包含
8字节整数操作,寻址方式较少。 - 在
Y86-64中,将8字节称为字。 - 内存寻址只包含
基址和偏移量形式,不支持第二变址寄存器和寄存器值伸缩。 - 同样,不允许内存与内存之间传送,以及不允许立即数传送到内存。
halt:字节编码为一字节00,停止程序执行。nop:字节编码为一字节10,占位操作。rrmovq rA,rB:字节编码为两字节20 rArB,寄存器之间数据传送。cmovXX rA,rB:字节编码为两字节2fn rArB,寄存器之间条件传送。irmovq V,rB:字节编码为十字节30 FrB V,立即数传送到寄存器。rmmovq rA,D(rB):字节编码为十字节40 rArB D,寄存器传送到内存。mrmovq D(rB),rA:字节编码为十字节50 rArB D,内存传送到寄存器。OPq rA,rB:字节编码为两字节6fn rArB,进行整数操作,只在寄存器之间进行。jXX Dest:字节编码为九字节7fn Dest,跳转到某个地址。call Dest:字节编码为九字节80 Dest,调用某个地址的函数。ret:字节编码为一字节90,返回上级函数。pushq rA:字节编码为两字节A0 rAF,将寄存器中的值压入栈顶。popq rB:字节编码为两字节B0 rAF,将栈顶的值弹出到寄存器中。- 注意到
cmovXX和rrmovq极为相像,因此下文会加以区分。 - 注意到被寻址的永远为
rB,但是字节的存储方式没有改变。 - 注意到
Y86-64没有testq和cmpq语句,因此最近一次算术/逻辑运算结构设置条件码。 OPQ的fn:0:表示addq1:表示subq2:表示andq3:表示xorq
JXX的fn:0:表示jmp1:表示jle2:表示jl3:表示je4:表示jne5:表示jge6:表示jg
cmovXX的fn:0:表示rrmovq1:表示cmovle2:表示cmovl3:表示cmove4:表示cmovne5:表示cmovge6:表示cmovg
- 寄存器的字节表示
0:%rax1:%rcx2:%rdx3:%rbx4:%rsp5:%rbp6:%rsi7:%rdi8:%r89:%r910:%r1011:%r1112:%r1213:%r1314:%r1415:无寄存器/F
- Y86-64符合指令集的重要性质:字节编码必须有唯一的解释,要么唯一解释要么非法。
- 地址是基于字节编码偏移的。
Y86-64状态码
- 用程序状态码
Stat表示 1:AOK,表示运行正常。2:HLT,表示遇到halt指令。3:ADR,表示试图读写非负地址。4:INS,表示指令非法。- 处理器会停止,或调用异常处理指令。
RISC和CISC指令集的比较
CISC指令数量多,RISC指令数量少。CISC有长延迟指令,RISC无长延迟指令。CISC编码长度可变,RISC编码固定长度。CISC寻址方式复杂,RISC寻址方式简单。CISC可对内存和立即数进行算术逻辑运算,RISC只能对寄存器进行算术逻辑计算。CISC细节对机器级程序不可见,RISC细节对机器级程序可见。CISC有条件码,RISC无条件码。CISC过程链接是栈密集的,RISC过程链接是寄存器密集的。CISC过程频繁访问内存,RISC过程频繁访问寄存器。CISC- 早期内存昂贵需要短指令
- 编译器能力有限需要复杂指令
- 处理器设计复杂,功耗高,价格昂贵
RISC- 更适合流水线操作
- 简化指令集便于并行处理
- 示例:
RISC/ARM- 基于寄存器的指令集,典型设计包含32个通用寄存器。
- 仅加载和存储指令(Load/Store)访问内存。
- 不使用条件码,测试结果存储于寄存器中。
- 现代指令集一般两者特点兼备。
逻辑设计
逻辑门与组合逻辑
- 数字电路的基本单位
- 基本逻辑门:与门(And)、或门(Or)、非门(Not)
- 只对单个位的数进行操作,常见是 \(n\) 路操作。
- 输入高/低电平,输出对输入持续响应(有微小延迟)。
- 组合逻辑:由逻辑门网络组成的电路,例如比较器、加法器等。
- 每个逻辑门的输入必须连到
- 一个系统输入/主输入
- 某个存储器单元的输出
- 某个逻辑门的输出
- 注意,两个或多个逻辑门的输出不能直接连接,组合逻辑也不能存在环。
- HCL(Hardware Control Language)
- 类似C语言逻辑表达式,用于描述硬件控制逻辑。
- 不同于C语言的短路逻辑,HCL表达式不会省略任何分支。
- 多路复用器(MUX)
bool re = (s && a) || (!s && b)
- 情况表达式(Case Expression)
T out = [select1 : Expr1;select2 : Expr2;
]
- 算术/逻辑单元(ALU)
- 抽象模型为两个数据输入与一个控制输入
- 控制输入与字节编码中功能相同,选择计算功能并改变条件码
- 堆叠多个运算黑箱,例如加法、减法、与、异或
时序逻辑与存储器
- 时序逻辑中的寄存器不同于上文提到的通用寄存器,暂称其为时钟寄存器。
- 其行为受时钟信号(同一个时钟)控制。
- 其由逻辑门组成的触发器堆叠而成。
- 时钟到达上升沿时,加载输入,状态与输出均改变。
- 时钟下降时,保持输出和状态稳定。
- 配备时钟寄存器后的时序电路不会出现震荡,可以实现反馈和循环。
- 震荡(Oscillation)
- 电路输出在短时间内反复切换(高电平和低电平之间快速变化)的现象。
- 这种现象通常是由于电路设计不当或信号反馈路径不稳定引起的。
- 使得无法稳定在预期的逻辑电平,从而电路无法正常工作。
寄存器堆与RAM
- 属于随机访问存储器,通过地址选择该读或该写哪个字。
- 包含通用寄存器等用于存取数据的寄存器。
- 有两个读端口,一个写端口,允许同时进行多个读写操作(经典寄存器堆,不代表最终品质)。
- 每个端口有一个地址输入,表示该选择哪个寄存器。
- 另外还有一个数据输出或对应该寄存器的输入值。
- 读端口有地址输入
srcA和srcB,数据输出valA和valB。 - 写端口有地址输入
dstW,数据输入valW。 - 写入由时钟信号控制,类似将值加载到时钟寄存器。
SEQ/顺序硬件架构
SEQ的主要状态单元
- PC(Program Counter)寄存器:存储下一条指令的地址。
- CC(Condition Code)寄存器:存储条件码,用于记录算术/逻辑单元(ALU)的结果状态。
- 寄存器堆(Register File):用于存储临时变量,由程序显式管理的寄存器集合,具有两个读端口和两个写端口。
- Memories:包括指令内存(Instruction Memory)和数据内存(Data Memory)。
- 从不回读原则:不为了完成一条指令的执行而去读由该指令更新的状态。
SEQ的阶段
取指(Fetch)
- 操作:从内存中读取指令字节,地址为程序计数器(PC)的值。
- 读出的指令由以下部分组成:
icode:指明指令类型,指令字节低4位。ifun:指令功能,指令字节高4位。rA:第一个源操作数寄存器,通过need_regids控制。rB:第二个源操作数寄存器,通过need_regids控制。valC:立即数或操作地址,常量,通过need_valC控制。- 若指令地址不合法,由
imem_error指明。
- 生成PC的下一条指令地址
valP。

译码(Decode)
- 操作:从寄存器堆读入最多两个操作数,得到
valA和valB。 - 通常读入
rA和rB对应寄存器,部分指令读入%rsp。 - 解析操作码:从指令中提取操作码和寄存器序号
srcA和srcB。 - 确定操作类型:根据操作码识别指令类型(只使用icode)。
- 确定写回指向:根据操作码确定
dstE和dstM。 - 读取操作数:从寄存器堆中提取源操作数
valA和valB。

执行(Execute)
- 操作:算术/逻辑单元(ALU)进行运算。
- 运算包含以下情况
- 执行指令指明的操作(
OPq) - 计算内存引用的地址(
rmmovq mrmovq) - 增加/减少栈指针(
pushqpopq) - 一般而言运算为加法运算,除非
ifun特别指定。
- 执行指令指明的操作(
- ALU操作:将源操作数
valA、valB输入ALU,执行指定运算,输出运算结果valE。 - ALU输入分为两个部分
aluA由valA、valC以及+8、-8等常数决定。aluB由valB决定。
- 设置条件码:
OPq设置CC中的条件码。 - 条件判断:
cmovXX与JXX根据CC内容和ifun计算Cnd,决定是否跳转。 - 注意到,没有一条指令又设置
CC又使用CC,这是Arch Lab优化的关键! - 对于无需计算的指令,将寄存器编号于
srcA中,提取出valA,valB置零,ALU输出的valE即为valA的值。 - 对于地址传送指令,寄存器提取出
valB,aluA选择8或-8执行。 - 对于条件传送指令,根据
Cnd决定是否将dstE设为F。

访存(Memory)
- 操作:将数据写入内存,或从内存读出数据。
- 仅在需要访问内存的指令中进行,即
load/store,Y86-64中为mrmovq和rmmovq。 - 读取内存:从
valA(popq,ret)或valE(mrmovq)中读出valM。 - 写入内存:向
valE(rmmovq pushq call)中写入valA(rmmovq pushq)或valP(call)。

写回(Write Back)
- 操作:最多可以写两个结果到寄存器文件。
- 更新寄存器堆:将ALU或内存操作的结果存储到目标寄存器。
- 更新PC:根据
valP、valC(JXX/CALL)或者valM(ret)为下一条指令准备好新的PC。 - 寄存器堆的更新只在时钟上升沿执行,保持写入稳定性。

SEQ实现示例
OPq rA,rB
- 取指
- 以
PC为索引,从指令内存读取指令的操作码OPq。 - 计算下一条指令的PC值(
PC+2),存放到valP。
- 以
- 译码
- 解析操作码得到
srcA和srcB。 - 根据
ifun确定操作类型。 - 选择
dstE为rB。 - 从寄存器堆中提取源操作数
valA和valB。
- 解析操作码得到
- 执行
- 根据
ifun使用ALU对valA和valB进行运算,结果存放到valE。 - 根据结果更新CC寄存器。
- 根据
- 访存
- 无访存操作,跳过该阶段。
- 写回
- 将
valE写回寄存器堆的rB寄存器。 - PC更新为
valP。
- 将
rrmovq rA,rB
- 取指
- 以
PC为索引,从指令内存读取指令的操作码rrmovq。 - 计算下一条指令的PC值(
PC+2),存放到valP。
- 以
- 译码
- 解析操作码得到
srcA。 - 设置
dstE为rB。 - 从寄存器堆中提取源操作数
valA。
- 解析操作码得到
- 执行
- 因为不计算只取原值,将
valB置零。 - 使用ALU计算
valA+valB,结果存放到valE。
- 因为不计算只取原值,将
- 访存
- 无访存操作,跳过该阶段。
- 写回
- 将
valE写回寄存器堆的rB寄存器。 - PC更新为
valP。
- 将
irmovq V,rB
- 取指
- 以
PC为索引,从指令内存读取指令的操作码irmovq。 - 取出立即数,存放到
valC。 - 计算下一条指令的PC值(
PC+10),存放到valP。
- 以
- 译码
- 设置
dstE为rB。
- 设置
- 执行
- 因为不计算只取原值,将
valB置零。 - 使用ALU计算
valC+valB,结果存放到valE。
- 因为不计算只取原值,将
- 访存
- 无访存操作,跳过该阶段。
- 写回
- 将
valE写回寄存器堆的rB寄存器。 - PC更新为
valP。
- 将
rmmovq rA,D(rB)
- 取指
- 以
PC为索引,从指令内存读取指令的操作码rmmovq。 - 取出偏移量,存放到
valC。 - 计算下一条指令的PC值(
PC+10),存放到valP。
- 以
- 译码
- 解析操作码得到
srcA和srcB。 - 从寄存器堆中提取源操作数
valA和valB。
- 解析操作码得到
- 执行
- 使用ALU对
valB+valC进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valE为地址,将valA写入。
- 以
- 写回
- PC更新为
valP。
- PC更新为
mrmovq D(rB),rA
- 取指
- 以
PC为索引,从指令内存读取指令的操作码mrmovq。 - 取出偏移量,存放到
valC。 - 计算下一条指令的PC值(
PC+10),存放到valP。
- 以
- 译码
- 解析操作码得到
srcB。 - 设置
dstM为rA。 - 从寄存器堆中提取源操作数
valB。
- 解析操作码得到
- 执行
- 使用ALU对
valB+valC进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valE为地址,取出valM。
- 以
- 写回
- 将
valM写回寄存器堆的rA寄存器。 - PC更新为
valP。
- 将
pushq rA
- 取指
- 以
PC为索引,从指令内存读取指令的操作码pushq。 - 计算下一条指令的PC值(
PC+2),存放到valP。
- 以
- 译码
- 解析操作码得到
srcA,srcB默认为%rsp。 - 设置
dstE为%rsp。 - 从寄存器堆中提取源操作数
valA和valB。
- 解析操作码得到
- 执行
- 使用ALU对
valB+(-8)进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valE为地址,将valA写入。
- 以
- 写回
- 将
valE写回寄存器堆的%rsp寄存器。 - PC更新为
valP。
- 将
popq rA
- 取指
- 以
PC为索引,从指令内存读取指令的操作码popq。 - 计算下一条指令的PC值(
PC+2),存放到valP。
- 以
- 译码
srcA,srcB默认均为%rsp。- 设置
dstE和dstM为popq。 - 从寄存器堆中提取源操作数
valA和valB。
- 执行
- 使用ALU对
valB+8进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valA为地址,取出valM。
- 以
- 写回
- 将
valE写回寄存器堆的%rsp寄存器。 - 将
valM写回寄存器堆的rA寄存器。 - PC更新为
valP。
- 将
jXX Dest
- 取指
- 以
PC为索引,从指令内存读取指令的操作码jXX Dest。 - 取出跳转地址,存放到
valC。 - 计算下一条指令的PC值(
PC+9),存放到valP。
- 以
- 译码
- 无译码操作。
- 执行
- 根据
CC寄存器内容和指令类型ifun计算cond,判断条件是否满足。
- 根据
- 访存
- 无访存过程。
- 写回
- 若
cond为真,PC更新为valC;反之更新为valP。
- 若
Call Dest
- 取指
- 以
PC为索引,从指令内存读取指令的操作码Call Dest。 - 取出调用地址,存放到
valC。 - 计算下一条指令的PC值(
PC+9),存放到valP。
- 以
- 译码
srcB默认为%rsp。- 设置
dstE为%rsp。 - 从寄存器堆中提取源操作数
valB。
- 执行
- 使用ALU对
valB+(-8)进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valE为地址,将valP写入。
- 以
- 写回
- 将
valE写回寄存器堆的%rsp寄存器。 - PC更新为
valC。
- 将
ret
- 取指
- 以
PC为索引,从指令内存读取指令的操作码ret。 - 计算下一条指令的PC值(
PC+1),存放到valP。
- 以
- 译码
srcA和srcB默认为%rsp。- 设置
dstE为ret。 - 从寄存器堆中提取源操作数
valA和valB。
- 执行
- 使用ALU对
valB+8进行运算,结果存放到valE。
- 使用ALU对
- 访存
- 以
valA为地址,取出valM。
- 以
- 写回
- 将
valE写回寄存器堆的%rsp寄存器。 - PC更新为
valM。
- 将
comvXX rA,rB
- 取指
- 以
PC为索引,从指令内存读取指令的操作码cmovXX。 - 计算下一条指令的PC值(
PC+2),存放到valP。
- 以
- 译码
- 解析操作码得到
srcA。 - 从寄存器堆中提取源操作数
valA和valB。
- 解析操作码得到
- 执行
- 由于不计算只取原值,将
valB置零。 - 使用ALU对
valA+valB进行运算,结果存放到valE。 - 根据
CC寄存器内容和指令类型ifun计算cond,若不满足,将rB设置为F。
- 由于不计算只取原值,将
- 访存
- 无访存过程。
- 写回
- 将
valE写回寄存器堆的rB寄存器,若为F则不写回。 - PC更新为
valP。
- 将
最后,得到了SEQ的整体设计:

- SEQ的缺点:时钟周期必须非常长,使信号能在一个周期内传播所有的阶段。
SEQ+硬件架构
- 相比SEQ架构,每个时钟周期都可以取出下一条指令的地址,更新PC在一个时钟周期开始时执行,而非结束时执行。
- 没有硬件寄存器来存放程序计数器,依据前一条指令保存下来的一些状态信息动态计算PC。


PIPE-/流水线硬件架构
概况
- 类比生活中的流水线,流水线能够提高系统吞吐量,同时轻微增加延迟。
- 它讲顺序执行的指令流划分为多个阶段,每个阶段由不同的硬件单元执行,允许多条指令并行处理。
- 吞吐量:单位时间内完成的指令数量。
- 单位:每秒千兆指令(GIPS,10^9 instructions per second,即10^{-9} s 执行多少指令)。
- 运行时钟的速率是由最慢阶段的延迟限制的。
- 流水线寄存器(即组合逻辑拆分后各阶段的时钟寄存器)在时钟上升沿写入数据,在时钟稳定期间保持稳定输入输出。
- 流水线过深:会导致寄存器延迟占总延迟的比例逐渐升高,边际效益递减,收益下降,同时处理冒险、分支错误和清除流水线的成本也增加。
- 反馈路径:由后阶段传递到前阶段的电路线,主要用于传递和指令执行相关的关键信息,如:
- 预测的程序计数器(PC)值。
- 分支信号(程序是否需要跳转)。
具体信息
-
在流水线架构中,往往需要区分不同阶段之间的数据,数据有时稳定地存储在寄存器中,有时暂时地流经组合逻辑。
-
大写前缀指的是流水线寄存器(即将由对应阶段进行处理),对应阶段开始时就是正确的值,在下一个时钟上升沿到来之前不会变化。
-
小写前缀指的是流水线阶段,对应阶段中,完成相应运算后才会是正确的值。
-
PIPE-相比PIPE架构,没有对冒险的处理和转发逻辑,由SEQ+插入流水线寄存器而来。
-
同时,增加了新模块
SelectA来选择valA的来源。 -
分支预测:由于流水线,因此每个时钟周期都要给出一个指令地址用于取指。
-
JXX指令的最简单策略:总是预测选择条件分支,即选取新PC为valC。 -
ret指令:整个流水线停转,等待它通过写回W阶段,从而得到压栈的返回值并更新PC。 -
于是,得到
PIPE-的整体设计:

流水线冒险(Hazard)
数据冒险(Data Hazard)
- 定义:下一条指令需要使用当前指令计算的结果。
- 例如,以下
Y86-64指令序列:
addq %rax, %rbx # 计算rax和rbx的和,存放到rbx中
movq %rbx, %rcx # 将rbx的值复制到rcx中
- 此时,当
movq执行到D阶段时,需要提取%rbx的值。 - 但是
addq此时处于E阶段,需要到W阶段才能写回。 - 有以下三种策略可以解决数据冒险
暂停(Stall)
- 定义:保持状态,输入清空。
- 在两条指令之间插入
nop指令,以确保后一条指令的操作数已被前一条指令写回。 - 这条指令,相当于延迟了下一条指令出现,同时它本身顺着流水线流动。
- 需要在软件层面修改汇编代码,具体如下:
addq %rax, %rbx # 此时,addq指令运行到fetch
nop # 此时,addq指令运行到decode
nop # 此时,addq指令运行到execute
nop # 此时,addq指令运行到memory
movq %rbx, %rcx # 此时,addq指令运行到writeback,movq指令运行到fetch
... # 此时,addq指令写回完毕,movq指令运行到decode,可以正确取出rbx的值
气泡(Bubble)
- 定义:保持状态,输入清空。
- 在两个指令之间插入
bubble,以确保后一条指令的操作数已被前一条指令写回。 - 这条指令,相当于延迟了下一条指令需要冒险的阶段,下一条指令及其之后的指令会被冻结在当前阶段。
bubble不随着流水线流动,在硬件层面加。- 注意:数据冒险中的
bubble与控制冒险中的bubble含义不同。 - 在PIPE-的基础上需要加入流水线控制单元,用于实现
stall和bubble。 stall即暂停,数据冒险中的bubble(因为是硬件层面)。bubble即气泡,控制冒险中的bubble,直接顶掉对应寄存器内容。

转发(Bypass)
- 定义:将结果值直接从一个流水线阶段直接传到较早阶段的技术。
- 例如,当指令进入
D阶段时,如果它需要访问的寄存器值已经在后续的E阶段或M阶段,可以通过转发路径将该值直接传递给当前指令,可以避免流水线暂停,提升流水线效率。 - 局限性:如果后续阶段的值仍然在内存中(例如执行
mrmovq或popq指令时,该值仍在M阶段),此时无法进行数据转发,称为加载/使用冒险(load/use hazard)。
加载/使用冒险(Load/Use Hazard)
- 定义:只发生在
mrmovq和popq后立即使用对应寄存器的情况。 - 探测时机:
E阶段为mrmovq或popq,且D阶段的对应寄存器为其中相关。 - 解决策略:
- 译码阶段中的指令暂停一个周期(硬件层面一般使用
bubble的stall定义)。 - 然后进行转发。
- 译码阶段中的指令暂停一个周期(硬件层面一般使用
- 示例:
mrmovq (%rdi), %rax # 取出rdi寄存器指向地址的数据,存放在rax中
andq %rax, %rax # 对rax寄存器进行按位与
控制冒险(Control Hazard)
- 定义:数据需要确定下一条指令的位置。
- 即处理器无法根据处于取指阶段的当前指令来确定下一条指令的地址。
- 通常分为以下几类:
- 不传递控制:对于没有控制转移的指令,直接将
valP设置为下一条PC值,通常不会出错。 - 无条件跳转/调用(
Jmp/Call):将valC设置为下一条PC值,总是正确的。 - 条件跳转:"总是跳转策略"将
valC设置为下一条PC值,正确性大概为60%。 - 其他策略:当跳转发生在前面时,总是执行;当跳转发生在后面时,通常不执行。
- 返回指令:处理器不进行任何预测。
- 其中,只有条件跳转和返回指令会产生控制冒险。
- 不传递控制:对于没有控制转移的指令,直接将
JXX冒险
- 定义:当
E阶段发现不应该选择分支时,已经取出了两条指令。 - 探测时机:
E阶段为条件跳转指令,且Cnd为假。 - 此时,需要清空流水线,往下两条指令所在寄存器插入两个
bubble,直接顶掉指令。

RET冒险
- 定义:检测到
ret指令后,不能再让错误指令进流水线。 - 探测时机:
RET在D、E、M阶段。 - 由于无法在
F阶段插入bubble,因此其在接下来三个指令的D阶段均插入bubble,直接顶掉指令。
组合冒险
组合A:JXX+RET冒险
- 定义:
E阶段有一条预测错误分支的JXX指令,D阶段有一条ret指令。 - 要求:
ret位于不选择分支的目标处,此时策略为取消ret指令。 - 由于跳转地址与
F寄存器无关,因此为取消ret指令,将F阶段设为暂停。

组合B:Load/Use冒险+RET冒险
- 定义:
mrmovq指令设置寄存器%rsp,ret指令用它作为源操作数。 - 此时
mrmovq会使D阶段暂停,而RET会使D中插入一个bubble。 - 定义:在同一阶段,
Stall和Bubble永远不同时为真。 - 希望只考虑针对
加载/使用冒险的动作,因此修改D_bubble的处理条件。

PIPE/流水线架构
- 综上,相比PIPE-,添加转发逻辑,就得到了PIPE架构
- 相比之下,它是我们从CEQ一步步优化而来的。


PIPE的各阶段实现
取指
- 取当前指令的
PC,若无变化则取F_predPC。 - 由于分支预测错误的存在,若
M_icode==JXX并且!Con,取M_valA。 - 因为伴随
JXX产生的valP,由D_valP→E_valA→M_valA。 - 同时,若
W_icode==RET,则取W_valM。 - 根据
imem_error判断是否取icode和ifun。 - 通过
instr_valid判断指令是否有效。 - 获取指令的状态码
f_stat。 - 判断指令是否需要寄存器和常数,得到
need_regids和need_valC。 - 预测下一个
PC值,得到f_predPC。 - 根据是否为
加载/使用冒险以及RET冒险,设置F_stall(F阶段bubble始终为假)。
译码
- 根据指令类型取出
d_srcA和d_srcB。 - 决定
E阶段计算结果和M阶段读出结果的写入寄存器d_srcE和d_srcM。 - 求出
d_valA和d_valB,此处需要满足转发逻辑,从后续E、M或W阶段转发。 - 若为跳转指令或调用指令,则
valA保存D_valP。 - 根据是否为
加载/使用冒险,设置D_stall。 - 根据是否为
JXX冒险或(RET冒险且不满足加载/使用冒险),设置D_bubble。
执行
- 选择
ALU的输入aluA,部分指令需要将aluA设为8或-8。 - 选择
ALU的输入aluB,部分指令需要将aluB设为0。 - 设置
ALU的功能alufun,除了IOPQ,其它默认为ALUADD。 - 计算是否更新条件码
set_cc,只在IOPQ和状态正常期间改变。 - 为传递
E_val给M_valA,仅设置e_valA。 - 根据是否更新条件码
set_cc,计算Cnd。 - 设置
e_dstE,除非当前指令为JXX且!Cnd,则设置为RNONE,其余设置为E_dstE。 - 根据是否为
JXX冒险或加载/使用冒险,设置E_bubble(E阶段stall始终为假)。
访存
- 选择访存地址
mem_addr。- 若为
rmmovq,pushq,call,mrmovq,则设置为m_valE。 - 若为
popq,ret,则设置为M_valA(由D_valA传递来)。
- 若为
- 决定是否读取内存
mem_read,是否写入内存mem_write。 - 根据是否访问错误地址,更新条件码
m_stat。 - 根据是否内存异常,设置
M_bubble(M阶段stall始终为假)。
写回
- 设置
E端口寄存器w_dstE,E端口值w_valE。 - 设置
M端口寄存器w_dstM,M端口值w_valM。 - 根据是否有气泡,更新条件码
Stat。 - 根据是否内存异常,设置
M_stall(M阶段bubble始终为假)。
后记
写了几天,终于写完了,算是综合了几位学长各有特色、部分有缺漏的笔记写成。
第四章的难度果然名不虚传.....
