结构冒险:因缺乏硬件支持导致指令不能在预定的时钟周期内执行。
数据冒险:因无法提供指令执行所需数据导致指令不能在预定的时钟周期内执行。
控制冒险:即分支冒险,因所取到的指令不是所需要的而导致指令不能在预定的时钟周期内执行。
指令级并行ILP概念:是指存在于指令一级即指令间的并行性, 主要是指机器语言一级, 如存储器访问指令、整型指令、浮点指令之间的并行性等。
指令级并行主要特点:是并行性由处理器硬件和编译程序自动识别和利用, 不需要程序员对顺序程序作任何修改。正是由于这一优点, 使得它的发展与处理器的发展紧密相连。
相关:
流水线冲突
小结与比较
保持相关,但避免发生冲突
通过代码变换,消除相关
只有硬件技术和软件技术互相配合,才能够最大限度地挖掘出程序中存在的指令级并行。
循环展开和指令调度时要注意以下几个方面:
相关的3种类型
静态调度 V.S. 动态调度
指令顺序执行 V.S. 指令乱序执行
动态调度例子中,将ID段又分成了两个阶段:
另一个例子中,介绍了寄存器换名技术:
指令1 DIV.D F4,F0,F2
指令2 SUB.D F10,F4,F6
指令3 ADD.D F6,F12,F14
如果在指令2停顿周期中先执行指令3,会发生写后读冲突,利用寄存器换名技术,可以变成:
指令1 DIV.D F4,F0,F2
指令2 SUB.D F10,F4,S
指令3 ADD.D F6,F12,F14
消除了名相关。
小结
主要部件:
公共数据总线CDB(一条重要的数据通路)
load(读)缓冲器的作用有3个
store(写)缓冲器的作用有3个:
浮点寄存器FP
指令队列
运算部件
在Tomasulo算法中,寄存器换名是通过保留站和流出逻辑来共同完成的。
流出
执行
写结果
Tomasulo算法具有两个主要的优点:
各符号的意义
指令流出
①浮点运算指令
进入条件:有空闲保留站(设为r)
操作和状态表内容修改:
if (Qi[rs] = 0) // 检测第一操作数是否就绪
{ RS[r].Vj ← Regs[rs]; // 第一操作数就绪。把寄存器rs
// 中的操作数取到当前保留站的Vj。
RS[r].Qj ← 0 }; // 置Qj为0,表示当前保留站的Vj
// 中的操作数就绪。
else // 第一操作数没有就绪
{ RS[r].Qj ← Qi[rs] } // 进行寄存器换名,即把将产生该
// 操作数的保留站的编号放入当前保留站的Qj。
if (Qi[rt] = 0) // 检测第二操作数是否就绪
{ RS[r].Vk ← Regs[rt]; // 第二操作数就绪。把寄存器rt中的
// 操作数取到当前保留站的Vk。
RS[r].Qk ← 0 } // 置Qk为0,表示当前保留站的Vk中的
// 操作数就绪。
else //第二操作数没有就绪
{ RS[r].Qk ← Qi[rt] } //进行寄存器换名,即把将产生该操作
// 数的保留站的编号放入当前保留站的Qk。
RS[r].Busy ← yes; //置当前保留站为“忙”
RS[r].Op ← Op; //设置操作码
Qi[rd] ← r; // 把当前保留站的编号r放入rd所对应
// 的寄存器状态表项,以便rd将来接收结果。
② load和store指令
进入条件:缓冲器有空闲单元(设为r)
操作和状态表内容修改:
if (Qi[rs] = 0) // 检测第一操作数是否就绪
{RS[r].Vj ← Regs[rs]; // 第一操作数就绪,把寄存器rs中的
// 操作数取到当前缓冲器单元的Vj
RS[r].Qj ← 0 }; // 置Qj为0,表示当前缓冲器单元的Vj
// 中的操作数就绪。
else // 第一操作数没有就绪
{RS[r].Qj ← Qi[rs] } // 进行寄存器换名,即把将产生该
// 操作数的保留站的编号存入当前缓冲器单元的Qj。
RS[r].Busy ← yes; // 置当前缓冲器单元为“忙”
RS[r].A ← Imm; // 把符号位扩展后的偏移量放入
// 当前缓冲器单元的A
对于load指令:
Qi[rt] ← r; // 把当前缓冲器单元的编号r放入
// load指令的目标寄存器rt所对应的寄存器
// 状态表项,以便rt将来接收所取的数据。
对于store指令:
if (Qi[rt] = 0) // 检测要存储的数据是否就绪
{RS[r].Vk ← Regs[rt]; // 该数据就绪,把它从寄存器rt取到
// store缓冲器单元的Vk
RS[r].Qk ← 0 }; // 置Qk为0,表示当前缓冲器单元的Vk
// 中的数据就绪。
else // 该数据没有就绪
{RS[r].Qk ← Qi[rt] } // 进行寄存器换名,即把将产生该数
//据的保留站的编号放入当前缓冲器单元的Qk。
执行
① 浮点操作指令
进入条件:(RS[r].Qj = 0)且(RS[r].Qk= 0); // 两个源操作数就绪
操作和状态表内容修改:进行计算,产生结果 。
② load/store指令
进入条件:(RS[r].Qj = 0)且r成为load/store缓冲队列的头部
操作和状态表内容修改:
RS[r].A ← RS[r].Vj + RS[r].A; //计算有效地址
对于load指令,在完成有效地址计算后,还要进行:(load指令两个任务:计算访存地址,访存)
从Mem[RS[r].A]读取数据; //从存储器中读取数据(store指令在该阶段,只需要第一个操作数就绪)
写结果
① 浮点运算指令和load指令
进入条件:保留站r执行结束,且CDB就绪。
操作和状态表内容修改:
对所有x (if(Qi[x] = r) // 对于任何一个正在等该结果
// 的浮点寄存器x
{ Regs[x] ← result; // 向该寄存器写入结果
Qi[x] ← 0 }; // 把该寄存器的状态置为数据就绪
对所有x (if(RS[x].Qj = r) // 对于任何一个正在等该结果
// 作为第一操作数的保留站x
{RS[x].Vj ← result; // 向该保留站的Vj写入结果
RS[x].Qj ← 0 }; // 置Qj为0,表示该保留站的
// Vj中的操作数就绪
对所有x (if(RS[x].Qk = r) // 对于任何一个正在等该结果作为
// 第二操作数的保留站x
{RS[x].Vk ← result; // 向该保留站的Vk写入结果
RS[x].Qk ← 0 }; // 置Qk为0,表示该保留站的Vk中的
// 操作数就绪。
RS[r].Busy ← no; // 释放当前保留站,将之置为空闲状态。
② store指令
进入条件:保留站r执行结束,且RS[r].Qk = 0 (要存储的数据已就绪,该阶段需要第二个操作数就绪)
操作和状态表内容修改:
Mem[RS[r].A] ← RS[r].Vk // 数据写入存储器,地址由store
// 缓冲器单元的A字段给出。
RS[r].Busy ← no; //释放当前缓冲器单元,将之置为空闲状态。
其中,store指令的访存就是它的写结果阶段。
需要注意的地方:
动态分支预测技术:
优点:
从简单到复杂的动态转移预测技术如下:
以上预测机制的性能随着复杂性与硬件的增加而有效地提高。
在高性能流水线中,特别是多流出的处理机中,只准确地预测分支还不够,还要能够提供足够的指令流。许多现代的处理器要求每个时钟周期能提供4~8条指令。这需要尽早知道分支是否成功、尽早知道分支目标地址、尽早获得分支目标指令。
方法:分支目标缓冲
BTB表结构