《手把手教你设计CPU——RISC-V处理器篇》读书笔记(四)
四、蜂鸟E203 CPU的硬件结构
E203的单元结构
一共有三种冒险,数据冒险(正确值还没写入寄存器就会被读取),结构冒险(哈佛结构令存储和数据分离,解决了这个冒险),控制冒险(下一条指令还没决定就继续)。流水线的数据冲突。

XAY相关性的结果是,如果X比Y早,则Y或者操作失败。
前两者可以寄存器重命名来解决,即替换同一寄存器为对两个不同的寄存器操作,消除相关性。
后者因为必须对同一数据进行操作,所以不可以寄存器重命名来解决问题。一旦相关,必须使得流水线停顿。
相关性问题引发的后果是,后一指令的操作源(寄存器)与前一指令的操作对象(寄存器)相同,所以必须等到操作源更新才能解除RAW相关性引起的数据冒险。
流水线前递
作为一种替代方案,可以通过前递或称旁路来解决相关性问题,即令操作源提前更新,提前被读取(尽量靠前,最早由ALU引出旁路)与被写入(尽量靠后,将旁路接到ALU的输入端)到ALU计算单元中。
前推的路径有两种,由访存输出引出旁路或者由ALU输出引出旁路。由ALU引出旁路的情况,是前后两条指令有相关性的情况。由访存输出引出旁路的情况,是前后三条指令有相关性的情况,此时最前一条指令的写回尚未执行,因此还是得进行前递,可以提前一个周期得到更新后的操作源。
超过三条指令的操作数,不构成数据冒险。
流水线暂停
如果是由访存指令写入数据的情况,单纯的前递无法解决问题。这是因为,流水线最多能提前一个阶段告知下一条指令更新后的操作源。虽然考试出结果以前可以提前知道成绩,但是提前的幅度是有限的,在前一条指令是访存的情况下,这个有限的幅度不足以令后一条指令取得上一个时钟周期算出的结果,最快也只能是上上个时钟周期。
为了解决这种情况下的数据冒险,需要使用流水线暂停技术,使得发生冒险的指令能够等待一个周期,此时取值和译码阶段都暂停,取指计数不增加,译码可反复进行,相应寄存器被stall不更新。
如何消除控制冒险?
控制冒险指,分支条件还未判断出结果,就不得不执行后续的指令。此时可以进行分指预测,假设分支预测不跳转,可以把相应的指令直接排在分支指令后面,这些指令不会出错。
假设分支预测跳转,则说明分支预测错误,后续的相应指令都要被撤回。
一种最简单的方法就是,默认分指都不跳转,只实现撤回指令的机制。被撤回的指令,并不是消失了,可能随着后续的分支跳转被重新执行。
撤回指令的要点在于,及时。所以越早判断分支结果越好。如果判断分支结果的反馈迟了,迟了几个周期就要多撤回几个阶段的操作。可以在译码接段判断是否要分支跳转。那么在下一个周期,译码单元会对下一地址的指令进行译码,取值单元则根据译码单元的反馈从新的地址取得指令。
因此,这种做法的代价是浪费一个周期的指令。因此分支跳转指令的后一条指令可以被定义为分支延迟槽,由编译器在此插入与分支指令没有相关性的指令,可以做到不浪费这个周期,减少了一个气泡。
在采取提前进行判断分支结果的方法时,不得不考虑与之前指令的相关性,因为之前指令的运行结果还没有写入寄存器。
也可能需要指令停顿,增加了设计的复杂度。
如果不及时撤回指令,也可以等待分支结果写回到PC。这种情况下,分支延迟槽会很大,多于一个周期,加大了实现调度效果的难度。在若要等待分支结果写回PC,就要一直丢弃指令,把“什么都不做”的控制信号从译码阶段开始传下去。要改变当分支指令到达MEM阶段时,已经处于IF,ID,EX阶段的三条指令。
蜂鸟e203的流水线结构

IFU:取指单元
EXU:执行单元
WB:写回单元,同时也是寄存器组。
LSU:访存单元
BIU:对外部取指访存的控制单元
EAI:协处理器
e203自称实现了两级半的流水线,这是怎么回事呢?
它把ID和EX阶段合并,又把MEM和WB阶段合并,从而减少了两级流水线。
同时,ex阶段可以直接写回Regfile,略过了MEM阶段,从而减少了一级流水线,此时数据通路上的流水线只剩下两级。
tinyriscv的流水线结构

可见,tinyrisc采取了把EX阶段、MEM阶段、WB阶段合并的做法,从而使得无极流水线变为三级流水线。