取指概述
取指特点
PC:指令在存储器中的地址
取指就是CPU核根据PC取指令的过程
取指的最终目的是快且连续,为了达到这个目的,先了解RISC指令本身
从PC是否跳转上来看:
非分支跳转指令:看PC连续不断递增的都是非跳指令,也就是按顺序读出来的指令
分支跳转指令:PC在某个指令处断档,也就是在断档指令处满足了跳转条件,就 会有PC跳转
理论上,发生分支跳转的话,CPU也要从新的PC值对应的存储器地址去拿指令
(PC值不一定对应 存储器物理地址)
从指令编码长度来看:
32位指令:顾名思义
需要注意的是:32位的PC值可能与32位地址不对齐
在某些系统中,要求数据或指令的访问 必须按照特定的边界进行对齐,例如4字节对齐。
这意味着数据 或 指令的起始地址 必须是4的倍数,否则就会发生对齐错误。
对齐要求可以提高访问效率,减少内存读取的次数。
然而,并非所有的系统都对地址进行对齐要求,这取决于具体的硬件和操作系统设计。
人话:对齐相当于一根棍子,要你从50%处向右拿到25%长度,你就需要两次二分劈开就🆗
不对齐就是让你拿16.2625%,多劈几次浪费时间
16位指令:就是长16位的指令
综述
我们知道了:
指令编码长度及32位问题
跳转与不跳转问题
快速取指对它的要求:
非跳转指令:快速顺序取指、要是下一个目的地不对齐,尽量1周期读出来完
跳转指令:快速判断跳转并取指、要是跳转目的地不对齐,尽量1周期读出来完
无非一个问题:1.取指 2.处理不对齐的情况,让它快点被取指
如何快速取值?
1.存储器本身读取延迟小
2.特殊器件:指令紧耦合存储器 ITCM
关键:1.物理距离近
2.容量小SRAM
3.存关键指令
优点:实现简单,保证实时性
缺点:采用地址区间寻址,不能映射无限大存储器空间
3.特殊器件:缓存 Cache
容量有限,不确定性高,一旦不命中就要花大时间取指;
尽量单周期:如何处理指令不对齐,让它快点被取指?
快速连续取指,尽量单周期取一个是最快的,也是最好的。
所以,对齐32位很重要,但如果真出现了让你从1读到32但是你初始地址是0,那你得读两次:0-31,32-63
然后取一部分拼接起来,浪费一个周期。
1.顺序取指遇到地址不对齐
采用剩余的buffer保存上次/这次用剩下的指令,供下次使用
类似于流水线,你第一周期可能浪费点时间,但是第二周期就快了
至于触发这个的原因,挺好理解的:
上次用剩下的/这次就用了16位,这次剩下的
2.跳转目标指令地址不对齐
采用bank化的SRAM,常见方式就是奇偶交错方法
将两个连续的32位指令分别存储在两块不同的SRAM中
再采用同时访问两块SRAM的方式同时取出两个指令并拼接 再输出
这里,我们分别处理了非分支跳转指令和分支跳转指令时遇到的不对齐和快速取值方法,
但是,我们没有处理判断分支的那一层
如何分类并处理分支指令
先清楚原理
分支指令类型
无条件跳转/分支指令
无条件 直接 跳转/分支
跳转目标地址可以直接从这个指令里面的数算出来
无条件 间接 跳转/分支
跳转目标地址需要从指令本身以及指令索引的寄存器操作数中算出来
有条件跳转/分支指令
有条件 直接
“直接”:跳转地址来自指令本身
“有条件”:和普通运算指令一样,直接使用两个整形操作数,对其进行比较,
如果条件满足就跳转。
有条件 间接
“间接”:和无条件间接一个道理
“有条件”:和上述一个道理,总之就是满足条件再执行
对于带条件跳转/分支来说:
我们知道因为流水线的缘故,取指令和执行是 前胸贴后背 的过程
但是由于延迟的缘故
流水线在 取指令阶段 无法马上知道这个指令是否条件成立
因此无法马上响应是否跳转,只有执行阶段完成才能解析出跳转结果
但是你要是让CPU去等它执行完成后再下一步继续取指,那就会浪费很多时间
我们叫它----”流水线空泡周期“
为了解决这个问题,现代处理器采用了分支预测技术
分支预测就是解决有关条件是否触发的问题:
1.预测 分支指令是否真的需要跳转?-----预测方向
2.预测 如果发生,跳转地址是什么?-----预测地址
然后根据预测方向和预测地址进行取指,我们叫它-----”预测取指“
对预测指令进行执行,我们叫它-----预测执行
分支预测----预测方向---猜猜它会不会做
分为:静态预测 和 动态预测
静态预测不依赖曾经的历史信息,来一个预测一个
最简单的静态预测就是:我总是 取指-译码之后的执行期间 猜他不会跳转,
所以我总是提前顺序取指,即使我第二个分支到了,我也猜它不会转,继续顺序
但是,一旦我第一个执行状态结束,发现它要跳转,那我铺垫的这个流水线就废了,
我要 冲刷流水线 并重新取指,再译码、执行判断,期间再预测,
我至少要浪费俩周期在重新取指预测上,偷鸡不成蚀把米。
为了弥补这俩周期,一种办法就是-----“分支延迟槽”
分支延迟槽实际上是一个规定:
规定,任何一个分支指令后边紧跟的几个指令一定会被执行
这样,分支指令后边的几个指令不受 流水线冲刷 的影响
不受结果影响,不用丢弃,不用重取
另一种预测方法就是:根据经验,汇编程序例如for循环,它的循环分支跳转往往目标PC是小的
基于这样的经验,我要是遇到跳转目标是PC减小的方向,那我就一定猜他跳
这种方法我们叫它:BTFN 预测方法
动态预测类似于时序逻辑,依赖已经执行过的指令的历史信息和分支本身相互结合
一位饱和计数器/二位饱和计数器
可以看到:四个状态,你的预测方向就根据你当前的状态
而且,你这些预测根据是根据之前的两次结果
这种二位饱和计数器使用很常见
这样做在处理一类分支指令的时候很有效,让它自适应你目前正在进行的一个汇编
但是面对使用很多分支指令的情况,它的效果就很差,
可能会出现左右预测状态相互切换的情况
最理想情况下你给每个分支指令都安排一个预测计数器,
这样在一次汇编程序中,每个指令都会按照它出现次数最多的状态来执行,效率自然高
但是指令数量太多了,对应计数器数量太多的话无法满足时序和功耗要求,成本也很大
我们采用有限个饱和计数器,组成一个表格
每条指令我们按照一定方式译码出这个表格中对应的序号/地址,用这种方式做对应
但是缺点在于:会出现多个指令指向一个计数器的情况:别名重合
所以现在针对动态预测主要就是两个问题:
1.表格多大?---------表格组织方式
2.如何将分支指令对应表格单元,尽量重合减少?---------控制别名重合问题
介绍常用的预测表格算法
1.一堆2位饱和计数器组成一维表格,采用PC的一部分作为索引
这种叫做一级预测器,因为索引仅仅取决于PC值本身,也导致了重合比较多
2.两级预测器也叫----”基于相关性的分支预测器“
针对每条分支指令:将有限个二位饱和计数器组织成----模式历史表(PHT)
采用这个分支的跳转历史作为PHT的索引,要是有n位分支历史,则有2ⁿ的PHT
分支历史也分为:
局部历史
全局历史
局部历史就是每个分支指令自己历史buffer
全局历史就是所有分支指令的历史buffer
局部分支预测器
局部分支预测器 使用 分立的局部历史buffer 来保存不同分支指令的跳转历史
每个buffer有自己对应的PHT,分别索引
全局分支预测器
全局分支预测器 使用 所有分支指令共享的全局历史buffer。
很明显的弊端在于:
1.无法区分每个分支的跳转历史
优点在于: 1.节省资源
因此,只有PHT容量非常大的时候才能体现出全局分支预测器的优势
分支预测----预测地址----预测执行之后提前计算目标地址
对于直接跳转指令,分支的目标地址由: PC 和 指令字中的立即数 进行加法运算
对于间接跳转指令,分支的目标地址由: 指令字中的立即数 和 寄存器索引中的操作数进行运算
在现代高速处理器中:这些都是不可能在一个时钟周期内完成的
为了能够不断的取指令,需要预测分支的目标地址,进一步加速:
BTB技术:也叫分支目标缓冲区技术。
1.采用有限的buffer记录最近执行的分支相关的PC值 以及其对应的目标地址
2.对于后续需要取指的每条PC值,与BTB中的各个值相比较。
如果二者匹配,就预测这是一个分支指令,并使用其存储的目标地址为预测的跳转地址
这种方法最为简单快捷,但是缺点有:
容量很小;
对间接跳转/分支指令预测效果不理想;
因为一次汇编程序中,如果遭遇for循环等等,每次计算,寄存器索引中的值可能会发生变化,
导致计算值和BTB中的值不一样,
RAS技术:也叫返回地址栈技术
间接BTB技术:就是专门为间接跳转/分支指令设计一个BTB
它与普通BTB类似,存储比较多的历史目标地址
不同之处在于索引算法比较高级,并结合了BTB和两级动态预测器技术
成功率高,但是硬件开销大