超标量处理器设计9-执行
1. 简介
执行阶段负责指令的执行。然后将这个执行的结果写入到寄存器,或者是存储器中。指令类型主要有ALU,负责算术运算,逻辑运算以及移位等操作;访问存储器的操作,load/store指令;控制程序流的操作,branch/jump/子程序调用CALL, 子程序返回return指令;特殊的指令,例如操作TLB的指令,存储器隔离指令;浮点运算指令。
2. ALU
1cycle alu运算,包括加法运算/减法运算/逻辑运算/移位运算
多cycle的乘法和除法运算也属于算术运算
3. BRU
处理程序控制类型指令,包括分支指令,跳转指令,子程序调用指令,子程序返回指令。
对每条指令使用条件执行的好处是可以降低分支指令使用的频率。但缺点是条件码占据了指令编码的一部分,导致指令中分配给通用寄存器的部分变少了。同时,条件执行指令还会引入一个问题,就是我们的重命名过程会更加复杂,因为条件执行会由于不执行导致后续的寄存器出现错误。
最简单的解决方式是碰到条件执行的指令时发生stall,暂停流水线;或者我们推测执行,等到发生错误时再进行flush。但由于经常发生预测错误,导致处理器的执行效率不会很高。
还有一种解决方案,在条件执行指令后面插入一条select-uop指令。
4. AGU
处理访存指令,并完成虚拟地址到物理地址的转化,以及从物理地址到数据的过程(D-cache访问), 这个过程直接决定了处理器的性能,尤其对于load/store指令采用乱序执行的处理器,需要一系列复杂的硬件来检测各种违例的情况。
5. 旁路网络
为了提高处理器的执行效率,我们不可能等指令将结果写到register file后,再从register file读数据,因此我们需要从fu的输出端到输入端之间架起一个通路,也即旁路网络,当如register file, payload RAM也需要这些数据
source drive: 数据从fprf中读出来
result drive: 数据从bypass网路中过来
5.1 简单的旁路网络设计
每个mux中的输入包括所有的其它EU, 以及自身的EU和fprf
5.2 复杂的旁路网络设计
数据的result drive 既可以是wb1, 也可以是wb2/wb3因为数据写到fprf还需要1个cycle, 计算周期越长,bypass网络越复杂。
5.3 操作数的选择
也即produced where, 操作数从哪个EU,那一拍计算得到, 通过一个scoreboard来记录,指令在select阶段,将它在哪个FU中执行的信息写到scoreboard中,并在流水线的wb阶段,对scoreboard进行更新。由于这个方案中读取scoreboard的过程发生在流水线的Execute阶段,这会对处理器的周期时间产生负面影响,因此为了提高频率,可以将读取scoreboard的过程放到流水线的RF read阶段
5.4 cluster IQ
优点:
- 可以减少每个分布式发射队列的端口个数
- 每个分布式发射队列的仲裁电路只需要从少量的指令中进行选择,可以加快每个仲裁电路的速度
- 由于分布式发射队列的容量较少,它其中的指令唤醒速度也会比较快
缺点:
跨cluster的唤醒需要经过更长的走线,可能需要在这个中间添加一级流水线,这样当两条存在相关性的相邻指令属于不同的cluster时,就不能背靠背地执行了,而是在流水线中引入了一个气泡(bubble)
5.5 load/store 指令的处理
前面介绍的寄存器之间存在的相关性,即先写后读(RAW), 先写后写(WAW), 和先读后写(WAR), 都是在流水线的解码阶段可以发现并通过rename可以部分解决的。但是对于访问存储器的指令,访问的地址只有在执行阶段才能被计算出来,才能知道他们是否存在相关性,同时地址的可能性是非常多的,对这些地址进行重命名是非常难的事情。因此对于load/store指令,有三种执行方式, 完全的顺序执行,部分的乱序执行,完全的乱序执行
5.5.1 完全的顺序执行
在超标量处理器设计中一般不会采用这种方法,效率比较低
5.5.2 部分的乱序执行
store指令仍然按照顺序执行,但是处于两条store指令之间的所有load指令可以乱序执行。这个方法的本质是当一条store指令所携带的地址被计算出来之后,在它之后进入到流水线的load指令可以去判断先写后读的相关性了(RAW),每条load指令将它携带的地址计算出来之后,需要和前面所有已经执行的store指令携带的地址进行比较,为实现这一功能,需要缓存(store buff)所有已经被pick出来的store指令。如果load指令在store buffer中发现了地址相等的store指令, 则说明存在 (RAW)相关性, 我们需要记录store指令的新老关系,如果用rid,那么比较逻辑较长,位宽较宽,可以为所有的store指令分配sid来记录新老关系。
5.5.3 完全的乱序执行
在这种方法中,store指令仍然会按照程序中指定的顺序来执行,但是load指令将不再受限于它前面的store指令,只要load指令的操作数准备好了,就可以pick. store buffer由于是顺序pick, 所以可以用一个FIFO来实现,这样可以减少pick的逻辑。load指令是乱序执行的,要根据src ready和新老关系来综合判断。
6. load/store执行单元
6.1 非阻塞Cache
I-cache 由于只需要读取,而且取指令要求串行的顺序,所以对它的处理是比较简单的。只有访问存储器的指令(load/store)才可以访问D-Cache, 对于load/store指令来说,这两种类型都有可能引起D-Cache发生缺失(miss)
- 对于load指令,它需要的数据不在D-cache就发生了缺失,就需要从下一级存储器中拿数据,并在D-CACHE中按照某种算法,找到一个cacheline 进行写入。如果这条cache line是dirty状态,还需要将这个line的数据块先写回到物理内存中。
- 对于store指令,如果携带的地址不在D-CACHE, 那么首先需要从物理内存中找到这个地址的数据块,将其读取出来,和store指令所携带的数据进行合并, 并从D-Cache中按照某种算法,找到一个cache line,并合并之后的数据写入到这个cache line;如果被替换的这个Cache line已经标记为dirty, 那么在写入之前,还需要将这个被覆盖的数据块写回到物理内存中。
为了提高效率,即使发生了cache miss, 我们仍然会处理后续的多条load指令,这样会造成拿回来的data不知道属于哪一条load指令的,因此我们需要记录发生缺失的load/store指令。
6.2 关键字优先
当执行一条访问存储器的指令而发生D-Cache缺失时,如果等到数据块所有的数据都写到D-Cache之后才将所需要的数据送给cpu,这可能会让cpu等待一段时间,为了提高效率,如果指令访问的数据位于数据块中的第6个字,那么可以使下一级存储器的读取从第6个字开始,当读到数据末尾时,在从头开始将剩下的第0-5个字读取出来,这样做的好处是当下级存储器返回第一个字的时候,CPU就可以得到所需要的数据而继续执行了,而D-Cache会继续完成其他数据的填充工作,这相当于将cpu的执行和cache的填充进行了并行。
6.3 提前开始
关键字优先的方法由于需要改变数据读取的顺序,因此要额外增加硬件才能实现这一功能。提前开始的意思是当指令所需要的数据也就是第6个字被从下一级存储器中取出来时,就可以让CPU恢复执行了,cache继续填充数据。和关键字优先的区别时,下一级存储器的读取并不是从第6个字开始的。
7. 小结
执行这一部分比较难的包括三部分,一个是执行算法部分,加减乘除,开方等,然后load/store指令中替换算法,数据合并等;另一个是数据通路bypass的设计,主要是时序和面积的考虑