指令集体系结构isa。ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层,编译器编写者只需要知道允许哪些指令,以及它们是如何编码的;而处理器设计者必须建造出执行这些指令的处理器。
Y86-64
程序员可见状态
Y86-64程序用虚拟地址来引用内存位置。
字:8字节
立即数(i)、寄存器(r)或内存(m)
第一个字母就表明了源的类型。第二个字母指明了目的的类型
内存引用方式是简单的基址和偏移量形式
OPq有addq、subq、andq和xorq。这些指令会设置3个条件码ZF、SF和OF(零、符号和溢出)
7个跳转指令(图4-2中的jXX)是jmp、jle^ jl、je、jne、jge和jg
有 6 个条件传送指令 cmovle, cmovl、cmove, cmovne、 cmovge和cmovg
call指令将返回地址入栈,然后跳到目的地址。ret指令从这样的调用中返回。
pushq和popq指令实现了入栈和出栈,就像在x86-64中一样。
halt指令停止指令的执行。将状态码设置为HLT
编码
每条指令的第一个字节表明指令的类型。高4位是代码(code)部分,低4位是功能(function)部分
没有寄存器操作 数的指令,例如分支指令和call指令,就没有寄存器指示符字节。那些只需要一个寄存器操作数的指令(irmovq> pushq和popq)将另一个寄存器指示符设为0xF。
4字节常数字.这个字能作为irmovq的立即数数据,rmmovq和mrmovq的地址指示符的偏移量,以及分支指令和调用指令的目的地址。
注意,分支指令和调用指令的目的是一个绝对地址
伪指令(directive)指明应该将代码或数据放在什么位置,以及如何对齐
.pos 0 告诉汇编器应该从地址0处开始产生代码。
irmovq stack, %rsp 初始化栈指针
YIS 指令集模拟器
pushq %rsp指令时,处理器的行为是不确定的,因为要入栈的寄存器会被同一条指令修改。
两种不同的约定:1)压入%rsp的原始值,2)压入减去8的%rsp的值。
在写之前,pushq应该先将栈指针减去8
popq应该首先读内存,然后再增加栈指针。
HCL 硬件控制语言
AND用&&表示,OR用||表示,而NOT用!表示。我们用这些符号而不用C语言中的位运算符&、|和〜,这是因为逻辑门只对单个位的数进行操作,而不是整个字。
多路复用器mux
bool out = (s && a) I| (!s && b);
情况表达式
顺序求值的,且第一个求值为1的情况会被选中。
集合关系
iexpr in {iexpr,yiexpr2 ,,,iexprk}
bool s1 = code in { 2, 3 };
bool s0 = code in { 1, 3 };
时钟寄存器(简称寄存器)
随机访问存储器(简称内存)
寄存器文件:
随机访问存储器
我们的处理器还包括一个只读存储器,用来读指令。
在大多数实际系统中,这两个存储器被合并为一个具有双端口的存储器:一个用来读指令,另一个用来读或者写数据。
Y86-64的顺序实现
每个时钟周期上,SEQ执行处理一条完整指令所需的所有步
骤。
创建这样一个框架
取指(fetch) 取指阶段从内存读取指令字节,地址为程序计数器(PC)的值。从指令中抽取出指令指示符字节的两个四位部分,称为icode(指令代码)和ifun(指令功能)。它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。它还可能取出一个四字节常数字valC。它按顺序方式计算当前指令的下一条指令的地址valPo也就是说,valP等于PC的值加上已取出指令的长度。
译码(decode):译码阶段从寄存器文件读入最多两个操作数,得到值valA和/或valB。通常,它读入指令rA和rB字段指明的寄存器,不过有些指令是读寄存器%rsp的。
执行(execute):在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据ifun的值),计算内存引用的有效地址,要么增加或减少栈指针。得到的值我们称为valE。在此,也可能设置条件码。对一条条件传送指令来说,这个阶段会检验条件码和传送条件(由ifun给出),如果条件成立,则更新目标寄存器。同样,对一条跳转指令来说,这个阶段会决定是不是应该选择分支。
访存(memory):访存阶段可以将数据写入内存,或者从内存读出数据。读出的值为 valM
写回(write back):写回阶段最多可以写两个结果到寄存器文件。
更新PC(PC update):将PC设置成下一条指令的地址。
popq rA的访存应该写错了,valE应为valM
对指令call,我们要将valP,也就是call指令后紧跟着的那条指令的地址,
压入栈中。在更新PC阶段,将PC设为valC,也就是调用的目的地。对指令ret,在更新PC阶段,我们将valM,即从栈中取出的值,赋值给PC。
取指:将程序计数器寄存器作为地址,指 令内存读取指令的字节。PC增加器(PC incre-menter)计算valP,即增加了的程序计数器。
译码:寄存器文件有两个读端口 A和B,从这两个端口同时读寄存器值valA和valB
执行:执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的。对整数操作,它要执行指令所指定的运算。对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输入传递到输出。
条件码寄存器(CC)有三个条件码位。ALU负责计算条件码的新值。当执行条件传送指令时,根据条件码和传送条件来计算决定是否更新目标寄存器。同样,当执行一条跳转指令时,会根据条件码和跳转类型来计算分支信号Cnd。
访存:在执行访存操作时,数据内存读出或写入一个内存字。指令和数据内存访问的是相同的内存位置,但是用于不同的目的。
PC更新:程序计数器的新值选择自:valP,下一条指令的地址;valC,调用指令或跳转指令指定的目标地址;valM,从内存读取的返回地址。
白色方框表示时钟寄存器。浅蓝色方框表示硬件单元。控制逻辑块用灰色圆角矩形表示。
srcA, valA的源;srcB, valB的源;dstE,写入valE的寄存器;以及dstM,写入valM的寄存器。
原则:从不回读
处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。
读操作沿着这些单元传播,就好像它们是组合逻辑,而写操作是由时钟控制的。
取指阶段
instr_valid:这个字节对应于一个合法的Y86-64指令吗?
need_regids:这个指令包括一个寄存器指示符字节吗?
need_valC:这个指令包括一个常数字吗?
译码和写回阶段
它们都要访问寄存器文件。
执行阶段
访存阶段
更新PC阶段
流水线
流水线化的一个重要特性就是提高了系统的吞吐量(throughput),也就是单位时间内服务的顾客总数,不过它也会轻微地增加延迟(latency),也就是服务一个用户所需要的时间。
每秒千兆条指令(GIPS),也就是每秒十亿条指令,为单位来描述吞吐量。
不一致的划分
流水线过深,收益反而下降
带反馈的流水线系统
数据相关
指令控制流造成的顺序相关
SEQ+
更新PC阶段在一个时钟周期开始时执行,而不是结束时才执行。
PIPE-
流水线寄存器分隔开这些阶段。
F 保存程序计数器的预测值,稍后讨论。
D 位于取指和译码阶段之间。它保存关于最新取出的指令的信息,即将由译码阶段 进行处理。
E 位于译码和执行阶段之间。它保存关于最新译码的指令和从寄存器文件读出的值的信息,即将由执行阶段进行处理。
M 位于执行和访存阶段之间。它保存最新执行的指令的结果,即将由访存阶段进行处理。它还保存关于用于处理条件转移的分支条件和分支目标的信息。
W 位于访存阶段和反馈路径之间,反馈路径将计算出来的值提供给寄存器文件写,而当完成ret指令时,它还要向PC选择逻辑提供返回地址。
译码阶段 “Select A”的块。这个块是为了减少要携带给流水线寄存器E和M的状态数量。
只有call在访存阶段需要valP的值。只有跳转指令在执行阶段(当不需要进行跳转时)需要valP的值。而这些指令又都不需要从寄存器文件中读出的值。因此我们合并这两个信号,将它们作为信号valA携带穿过流水线,从而可以减少流水线寄存器的状态数量。
分支预测
预测下一个 PC
预测PC的新值为valC。我们的设计只使用了简单的策略,即总是预测选择了条件分支,因而预测PC的新值为valC。
ret 在设计中,我们不会试图对返回地址做任何预测。只是简单地暂停处理新指令,直到ret指令通过写回阶段
数据冒险
用暂停来避免数据冒险
用转发来避免数据冒险
PIPE
加载 /使用数据冒险
有一类数据冒险不能单纯用转发来解决,因为内存读在流水线发生的比较晚。
可以将暂停和转发结合起来,避免加载/使用数据冒险
避免控制冒险
控制冒险只会发生在ret指令和跳转指令。
ret
跳转
异常处理
把导致异常的指令称为异常指令
1)halt 指令
2)有非法指令和功能码组合的指令
3)取指或数据读写试图访问一个非法地址。
基本原则是:由流水线中最深的指令引起的异常,优先级最高。
细节问题:1当首先取出一条指令,开始执行时,导致了一个异常,而后来由于分支预测错误,取消了该指令。2因为流水线化的处理器会在不同的阶段更新系统状态的不同部分。
当处于访存或写回阶段中的指令导致异常时,流水线控制逻辑必须禁止更新条件码寄存器或是数据内存
当一条预测错误的分支进入访存阶段时,会从流水线寄存器M(信号M_valA)中读出该指令valP的值(指明下一条指令的地址)当mt指令进入写回阶段时,会从流水线寄存器W(信号W_valM)中读出返回地址。其他情况会使用存放在流水线寄存器F(信号F_predPC)中的PC的预测值
pc选择逻辑
当取出的指令为函数调用或跳转时,PC预测逻辑会选择vale,否则就会选择vaM:
pc预测
译码和写回阶段
Sel + FwdA扮演两个角色。它为后面的阶段将valP信号合并到valA信号。valA的转发逻辑。
如果不满足任何转发条件,这个块就应该选择d_rvalA作为它的输出
5个转发源的优先级是非常重要的。给处于最早流水线阶段中的转发源以较高的优先级。因为它保持着程序序列中设置该寄存器的最近的指令。
执行阶段
访存阶段
加载/使用冒险:在一条从内存中读出一个值的指令和一条使用该值的指令之间,流水线必须暂停一个周期。
处理ret:流水线必须暂停直到ret指令到达写回阶段。
预测错误的分支:在分支逻辑发现不应该选择分支之前,分支目标处的几条指令已经进入流水线了。必须取消这些指令,并从跳转指令后面的那条指令开始取指。
异常:当一条指令导致异常,我们想要禁止后面的指令更新程序员可见的状态,并且异常指令到达写回阶段时,停止执行。
同时出现多个特殊情况的情形。
组合A中执行阶段中有一条不选择分支的跳转指令,而译码阶段中有一条ret指令。 出现这种组合要求ret位于不选择分支的目标处。流水线控制逻辑应该发现分支预测错误,因此要取消ret指令。
组合B包括一个加载/使用冒险,其中加载指令设置寄存器%rsp,然后ret指令用这个寄存器作为源操作数,因为它必须从栈中弹出返回地址。流水线控制逻辑应该将ret指令阻塞在译码阶段。
合并得到
这些分析表明组合B需要特殊处理。实际上,PIPE控制逻辑原来的实现并没有正确处理这种组合情况。
CPI(Cycles Per Instruction,每指令周期数)
如果这个阶段一共处理了Ci条指令和Cb个气泡,那 么处理器总共需要大约Ci+Cb个时钟周期来执行Ci条指令。
CPI = 1.0 + lp+ mp + rp
lp(load penalty,加载处罚)是当由于加载/使用冒险造成暂停时插入气泡的平均数
mp(mispredicted branch penalty,预测错误分支处罚)是当由于预测错误取消指令时插入气泡的平均数
rp(return penalty,返回处罚)是当由于mt指令造成暂停时插入气泡的平均数。
未完成的工作
多周期指令
在一个更完整的指令集中,我们还将实现一些需要更为复杂操作的指令,例如,整数乘法和除法,以及浮点运算。
采用独立于主流水线的特殊硬件功能单元来处理较为复杂的操作
与存储系统的接口
忽略了由自我修改代码造成的可能冒险,一条指令对一个存储区域进行写,而后面又从这个区域中读取指令。
处理器有两个第一层高速缓存,一个用于读指令,一个用于读和写数据。高速缓存(cache)存储器。翻译后备缓冲器(TLB):提供了从虚拟地址到物理地址的快速翻译。