基本逻辑
超标量处理器中,指令在完成rename后通过分发逻辑把指令分发(dispatch)到各个不同的执行
单元处理,指令之间存在寄存器上的依赖,所以指令被dispatch到执行单元后不能马上指令,
它们会被缓存在执行单元的buffer中,直到指令的依赖满足才会被发到执行单元上去执行。
如下是一个简单的示意图,其中issue q就是我们这里说的执行单元的buffer。
1 | +-------+ +----+ |
考虑这里面硬件需要解决的问题:1. issue q里需要有资源维护指令的状态,指令操作数
没有准备好时,指令不能发射,指令操作数准备好后,发射指令到执行单元执行; 2. 对于
issue q需要有仲裁逻辑挑出准备就续的指令发射执行(同一时刻可能有多条指令准备就绪);
3. EX/WB需要把指令执行的完毕这个信息反馈给依赖它的指令;4. 当拍数可控时,反馈信号
可以不等到指令执行完毕,这样可以使得前一条指令和后面依赖指令背靠背执行,流水线里
没有气泡,诸如此类的优化存在于很多地方。
issue queue结构
《超标量处理器设计》上按照不同维度描述了issue queue的可能组织形式。
按照执行单元和issue queue的对应关系,分成了集中式和分布式的issue queue,现在的CPU
里一般是两者的折中,比如几个执行单元共用一个issue queue。集中式issue queue的好处
是资源可以被有效利用,分布式issue queue里即使有空位,不同执行单元之间的指令也不能
混用issue queue。集中式issue queue的缺点是实现比较复杂。
按照指令输入数据的获取方式,issue queue可以分成捕获式和非捕获式,捕获式的issue queue,
指令在进入其中的时候,就会把物理寄存器的值保存入issue queue,非捕获式的issue queue,
在指令发射到执行单元时,才从物理寄存器堆中读取指令输入值,两者在issue queue里都要
有标记位记录指令输入的状态,当一个指令的输入都准备好时,指令可以被仲裁和反射。
按照指令在issue queue里的移动方式,issue queue被分成了压缩和非压缩两种,对于压缩
式的issue queue,当有一个指令被发射出去后,后续指令依次占据之前指令的位置,对于非
压缩式的issue queue,指令在其中占据固定的位置,指令进入issue queue时,需要找见一个
空位,指令离开issue queue时,直接离开就好。可以想象,压缩式的issue queue,指令在
其中频繁移动,功耗就会比较大。需要注意的是,虽然这里叫queue,但是其中的指令都是
操作数准备好就发射的,也就是在没有依赖关系的时候是乱序发射的。
可以看到,各个执行单元和各个issue queue的反馈逻辑是一个网状结构,一个issue queue
里等待发射的指令可能依赖别的执行单元的执行结果。
仲裁的逻辑
一般,仲裁逻辑都是取出年龄最老的指令发射执行,因为最老的指令处于依赖链条的前端,
执行最老的指令可以使得后续指令尽量提前投机执行。
唤醒的逻辑和优化
可以在写回阶段或者通过各种旁路网络做指令唤醒,所谓唤醒,就是标记后续依赖指令的输入
数据是有效状态。基本功能的逻辑很直白,但是硬件在实现的时候需要仔细计算唤醒的具体
时间点,使得前后指令可以很好的衔接起来,这里要解决的问题是:1. 不能出错;2. 性能
要高。
所以,这里的指导原则就是尽量早点唤醒,因为后续的指令可能是多拍完成的,用到依赖指令
结果的可能是后面几拍,早一点唤醒可以使得后续指令中没有依赖关系的几拍先运行,这样
依赖数据生成和实际使用无缝衔接在了一起。
但是有些指令的执行拍数不是一个确定值,比如load指令,大部分情况下load会直接命中cache,
但是也有少量cache不命中的情况。对于这种情况,可以投机的去做唤醒,这样做的基础是大
部分情况会命中cache,load拍数可控,投机的结果是对的,对于少量的投机错误的情况,因为
过早唤醒依赖load的指令拿到了错误的数据,load后的指令要被flush掉或者根据正确load到的
数据重新执行(replay)。
replay
如上已经提到,对于输入数据错误的指令,可以不必flush掉,而是重新从issue queue里使用
正确的数据重新发射指令执行,对于分支预测出错,还是要把错误指令flush掉的。指令replay
可以省去重新fetch指令以及decode/rename/dispatch的步骤,因为这个对于数据输入错误的
指令本来就是不必要的步骤,但是,要想做replay,就需要在issue queue里继续保留发射执行
的指令,这样会挤占issue queue位置,可能造成issue queue变满,反压前端的rename步骤。