为什么需要超标量
程序执行时间 = 指令数(算法) * CPI(微架构) * 时钟周期(工艺)
Superscalar
每周期执行多条指令,但有区别于VLIW,超标量处理器依靠硬件自身来完成指令并行,而不是交给编译器和程序员,这样做可以兼容任何指令集,因此通用处理器的多发射离不开超标量
架构设计的经典tradeoff
精准的分支预测(性能提升)->电路复杂->不能连续取指->性能下降
流水线拆细(主频提高)->流水线加深->预测失败代价大->高频低能
普通处理器的流水线
追求Cost/Performance,一般折中是最优解
好的流水线划分
每一级的delay大致相等(木桶效应)
对每条指令来说,每一级的操作都需要重复执行(访存和ALU指令是特例)
每一级相互独立(需要处理RAW相关)
MIPS的五级流水线
Fetch -> Decode & Reg Read(准备好源操作数)-> Execute(ALU计算&访存地址计算)-> Mem -> Write Back(写回寄存器或者Forward给后续的指令)
平衡流水线的delay
合:每一级delay增加,主频下降,适合低功耗嵌入式处理器
拆:每一级delay减小,主频提高,适合高性能处理器,流水线加深、RegFile和Mem端口数增加以适配不同阶段的读写
数据相关
先写后读(RAW),真相关,当前指令操作数来自之前的指令,必须等待其结果写回(或转发)
先读后写(WAR),某个Reg即将被写但是之前的指令还未读走,可通过暂时写到其他Reg避免
先写后写(WAW),某个Reg即将被写但是之前的指令还未写回,可通过暂时写到其他Reg避免
控制相关
分支指令是否跳转、跳转地址依赖之前的指令结果,结果未写回暂时只能按照分支预测取指令,一旦预测失败流水线需要flush,重新执行
超标量处理器的流水线
顺序执行
每Cycle取2条指令执行(2-way),根据自身类型送到对应的FU(Function Unit)执行
为保证顺序执行和写回,所有FU的流水线深度必须相等
ScoreBoard记录每条指令对应Reg的占用情况,方便实现旁路转发,解决RAW相关
因为统一顺序写回,WAW和WAR相关无影响
乱序执行
顺序取指、译码和提交,乱序发射、执行和写回,只要操作数就绪即可进入对应的FU
为解决WAW和WAR,需要寄存器重命名,物理寄存器(PRF)必须多于逻辑寄存器(ARF)
因为乱序执行,每个FU流水线深度可以不同(乱序的优势所在)
为保证执行结果串行,指令顺序在ROB(Reorder Buffer)中保存,乱序写回,顺序提交(Commit,也即从ROB中退出)
分支预测失败或者发生异常,需要恢复现场,为避免此前Store指令已经写过Mem不可逆,需要将Store结果先写入SB(Store Buffer),直到提交时从SB写入Mem,引入SB的情况下Load指令不仅要读Mem还需要读SB,(个人理解)因此SB其实也解决了乱序执行引发的存储器层面的WAW和WAR相关
乱序流水线的各阶段
Fetch:从I-Cache取指令,分支预测器决定下一条指令的PC(后续展开)
Decode:识别指令类型
Reg Rename:根据src和dest编辑重命名表,分析RAW相关性
Dispatch:重命名后的指令顺序进入Issue Queue、ROB和SB,这些部件没有空间则暂停
Issue:操作数就绪的指令从Issue Queue被唤醒,准备进入FU
RegFile Read:从PRF或者旁路网络(bypass network)获得操作数
Execute:在FU进行数据或地址计算
Write Back:指令结果写回Reg或者旁路转发
Commit:指令顺序退出ROB