乱序执行
1. 设计原则
以下介绍QuSim用到的主要的设计原则。其中,有一些设计原则是贯穿始终的(例如乱序执行以提高ILP的原则),另外一些设计原则可能只是应用于具体的部件(例如前端异步流水线原则)。在提及具体部件的设计时,我们还会引用这些原则。
1.1. 乱序执行
QuSim被设计成支持乱序执行以提高ILP(Instruction Level Parallelism)。具体而言,为了在乱序执行的同时保证对精确异常和中断的支持,QuSim采用了Tomasulo with Reorder Buffer算法,通过“顺序发射、乱序执行、顺序提交”,允许异常和中断的精确处理。 乱序执行的硬件设计远比顺序流水线结构复杂,会带来一系列设计上的挑战,同时会遇到顺序流水线中不存在的冲突问题。例如:
访存时的内存访问顺序与内存重命名(Memory Disambiguation)。
内存模型。一些Load操作必须是强顺序的。
乱序执行中的全局状态,例如CSR寄存器。
复杂的逻辑带来较大的门延迟,限制主频进一步提升。
我们会在各个部件的设计中提及如何解决这些问题。
1.2. Load Speculation
警告
读取预测和重发射尚未被实现。
Load Speculation允许我们在Store的地址尚未确定的时候就执行Load操作,从而所有的内存Load操作都不会遇到阻塞;然而这样的Load Speculation可能会失败,此时我们需要回滚已有的操作。简单的策略是视为分支预测失败并Flush ROB,但是会带来严重的效率损失。我们采用一种“重发射”的方法,对受到影响的指令进行精确重放。
指令数据依赖关系:假如后一条指令需要前一条指令的计算结果,那么我们称后一条指令依赖于前一条指令。我们可以发现,一条指令需要被重新计算,当且仅当这条指令间接依赖了错误的Load。
指令数据依赖关系的确定和存储:我们使用一个Mask来表示ROB表项之间的依赖关系。一条指令的Target的Mask等于指令的Operands的Mask之或(将Mask同时存储在重命名表里可以降低查找ROB的延迟)。当ROB表项正确提交时,依赖关系消失,所有的Mask抹去依赖关系中的这一位。
保留站中包含当前指令的Mask:需要被取消的计算会被取消。(I/O operation一定没有执行,所以可以被安全取消。)
LQ/SQ中所有元素被记为无效。
重命名站被清空。
ROB从错误的Load指令开始,要求Dispatcher重新分发每一条指令:对于所有的指令,重命名站和Mask会被重新设置,LQ/SQ会被重新设置为有效;但是当遇到不需要重新发射的指令时,保留站不做任何操作,LQ/SQ不需要清空操作。
当最后一条指令被分发完毕时,重发射状态结束,继续正常指令发射。
预测读取失败:当我们发现一条Load指令失败时,我们可以确定需要重新发射的指令的集合。此时,ROB进入一种“重发射”的状态:
重发射状态可以被重入:在重发射时,假如再次发现Load Speculation Fail,则再次从开始重新发射。
重发射状态可以被打断:清空ROB的条件不变。
1.3. AXI总线协议
QuSim使用AXI总线协议,对内存和外设进行操作。 AXI总线协议的特点:
通过若干个Channel(读地址、读数据、写地址、写数据、写完成)提供以Burst为单位的外设访问。完整的Slave实现较为可能复杂,但Master实现比较简单。
与Xilinx系列IP核高度兼容(例如可以直接由AXI协议访问Block RAM/ROM、PS控制的DRAM、封装的GPIO等)。
Xilinx提供AXI Interconnect以实现总线复用等功能。
基于以上原因,我们使用AXI总线协议与外设进行交互。
QuSim使用了以下的Master:
L1 DCache/ICache,可以用总线读取数据或者向总线写回数据。
Page Table Walker,硬件直接从总线访问页表。
QuSim需要使用以下的Slave:
GPIO,控制状态指示灯和开关等。
SRAM。尽管理论上SRAM可以在一个周期内完成读或写操作,但是由于需要等待状态稳定,SRAM也可以通过Burst操作来减少状态转移带来的效率损失(2周期读1次->9周期读8次)。
串口。
1.4. 前端:异步流水线
QuSim的前端流水线由PC、TLB、访存、分支预测、指令队列等部分组成。这些流水线级具有以下特点:
需要的周期数长于1周期且不定:TLB段可能需要多次访存以查询页表,访存段可能遇到Cache Miss,指令发射可能会被重发射阻塞等。
可能会被打断,但是不能简单重置:例如,可能在访存段访总线的时候发现分支预测失败,但是AXI协议不支持操作的取消。
操作可以认为是无副作用的:分支预测(尤其是RAS的维护)可以回滚,Cache的误操作和TLB的误读取不会造成本质问题。
受到AXI协议的启发,QuSim要求前端的各个流水段作为“异步流水线”:
流水段支持的接口:clk, rst, in_ready, in_valid, din, out_ready, out_valid, cancel。
流水段之间的数据通信必须经过类似AXI Channel的握手才能进行。这允许“后面的流水段阻塞反馈到前面的流水段”的类似功能。
流水段总会完成一整个操作,但是假如流水段在完成这个操作的中间收到了一个取消信号(例如,分支预测失败清空前端流水线),那么它将不会向下一个流水段发射运算结果。
这样的异步流水线的行为可以用以下的状态转移模型来描述:
异步流水线内除了计算单元,还包括一个和dout等大的dout_store和一个cancelled寄存器。
闲置态:当流水段被Reset时进入这个状态。此时in_ready=1,out_valid=0。当in_valid为1时,进入计算态,cancelled设为0;否则停留在闲置态。
计算态:试图完成流水线需要的计算。计算可能只需要一个周期,也可能需要多个周期。
为了节约周期数,在计算的最后一个周期,直接将out_valid设为1:假如此时out_ready也为1,则将in_ready也设为1,下一个周期直接进行下一轮计算。 在计算态的任何周期内,假如cancel被设为1,则cancelled置1,之后out_valid不再设为1,并且允许将in_ready置为1(如同本轮计算结果被下一级流水线吸收了)尽早开始下一轮计算。 假如在计算的最后一个周期,这轮操作没有被取消而out_ready为0,则将计算结果存入dout_store,并且进入到输出态。
输出态:dout=dout_store,out_valid=1,in_ready=in_valid。假如out_ready=1时,in_valid为1,则进入计算态进行下一轮计算;否则,进入闲置态。
在输出态的任何周期内,假如cancel被设为1,则out_valid不再设为1(同上),并且允许尽早开始下一轮计算。
计算单元应该具有“寄存器输出接入组合逻辑”的结构,例如,BRAM的输入线路可以直接作为din,因为BRAM可以被看成“一个寄存器的输出接到了一组数据选择器上,输出所选择的值”。
1.5. 内存模型
QuSim使用一个比RVWMO还要弱的内存模型。
所有的内存读操作是乱序的,所有的内存写操作是顺序的,允许forwarding。因为是单核情况,所以不考虑同地址读取的乱序问题。
所有的IO操作都是强制顺序的:一条指令必须在其能被立即提交时才能开始IO操作,并且这条指令的提交不能被中断或者异常打断。
Fence指令只支持fence rwio, rwio,不提供更细粒度的内存围栏支持。
Fence.I指令会导致Cache的写回。
CSR寄存器强制顺序执行:只有当ROB为空的时候,才发射他们,且它们的提交不能被异常或中断打断(除非操作本身导致了异常)。
Memory attribute:0x000000080000000开始的8M物理地址空间属于RAM,IO空间分布在物理地址空间中,剩余的物理地址空间为空白。