P6支持30指令的五级全速流水线

指令分析

29条指令

  • 计算类指令:add sub and or slt sltu lui addi andi ori nop
  • 访存类指令:lb lh lw sb sh sw
  • 乘除类指令:mult multu div divu mfhi mflo mthi mtlo
  • 分支类指令:beq bne
  • 跳转类指令: jal jr

数据冒险

需要回写的有16条指令:add sub and or slt sltu lui addi andi ori lb lh lw mfhi mflo jal

转发

阻塞

Tuse : 在D级用数据为0,E级为1,M级为2,用不到数据为max(在编写时,max = 3)

add sub and or slt sltu lui addi andi ori nop
Tuse_RS 1 1 1 1 1 1 max 1 1 1 max
Tuse_RT 1 1 1 1 1 1 max max max max max
lb lh lw sb sh sw beq bne jal jr
Tuse_RS 1 1 1 1 1 1 0 0 max 0
Tuse_RT max max max 2 2 2 0 0 max max
mult multu div divu mfhi mflo mthi mtlo
Tuse_RS 1 1 1 1 max max 1 1
Tuse_RT 1 1 1 1 max max max max

Tnew:在D级产生数据并存入DEreg为1,E级为2,M级为3,不产生数据为min(在编写时,min = 0)

add sub and or slt sltu lui addi andi ori nop
Tnew 2 2 2 2 2 2 1 2 2 2 min
lb lh lw sb sh sw beq bne jal jr
Tnew 3 3 3 min min min min min 1 min
mult multu div divu mfhi mflo mthi mtlo
Tnew min min min min 2 2 min min

乘除法实现

我们首先回顾一下乘除法的实现过程:

对于乘法指令来说,当mult指令进入D级时,Start信号置1,维持一个周期。

这个信号会跟随该乘法指令传到E级进入乘除模块。

一个周期以后,start指令置0,同时busy指令置1,并维持五个周期。

五个周期以后,busy指令置0,结果写入hilo寄存器。

除法只需要将上述“维持五个周期”改为“维持十个周期”即可。

需要指出的是,当busy信号或者start信号为1的时候,后续与乘除相关的8条指令都会被阻塞在D流水级。

Stall信号为1时会发生什么

D级中的指令不变,同时清空DEreg(E流水级),给D级下游形成一种D级的指令是空抛的效果。

思考题

1、为什么需要有单独的乘除法部件而不是整合进 ALU?为何需要有独立的 HI、LO 寄存器?

因为乘除需要多个周期,分开设计更简洁,降低模块内部复杂度,也降低了功能之间的耦合度。由于乘除法的结果需要被存下来,所以需要hilo寄存器。

2、真实的流水线 CPU 是如何使用实现乘除法的?请查阅相关资料进行简单说明。

乘法(移位相加法):CPU 将被乘数和乘数表示为二进制数,然后从最低位开始,将被乘数的每一位与乘数进行相与运算,并将结果向左移位相应的位数,最后将所有移位后的结果相加,得到最终的乘积。

除法(移位相减法):CPU 将被除数和除数表示为二进制数,然后从被除数的最高位开始,将被除数与除数进行比较。如果被除数大于等于除数,则将被除数减去除数,并将结果向左移位一位,同时商的相应位设置为1;如果被除数小于除数,则将被除数向左移位一位,同时商的相应位设置为0。重复这个过程,直到被除数小于除数为止,此时得到的商就是最终的结果。

3、请结合自己的实现分析,你是如何处理 Busy 信号带来的周期阻塞的?

busy传至位于D级的阻塞处理单元Hazard。当busy信号为1时代表正在处理乘除指令。若D级此时又出现一条新的乘除法指令,则必须阻塞。

4、请问采用字节使能信号的方式处理写指令有什么好处?

更加规范清晰。

5、请思考,我们在按字节读和按字节写时,实际从DM获得的数据和向DM写入的数据是否是一字节?在什么情况下我们按字节读和按字节写的效率会高于按字读和按字写呢?

  • 对于按字节写入(sb):

由于我们需要将GRF[rt]的低8位写入内存,所以我们只需将它的低8位重复4次构成m_data_wdata。再通过设置m_data_byteen(写使能)来控制具体写入地址的哪8位。

  • 按半字写入(sh)和按字写入(sw)是同样的道理。

  • 对于按字节读出(lb):

    我们将CPU外部存储器返回给我们的m_data_rdata传入LEXT模块,这个模块是专门用来处理读出来的原始数据的。

    我们取m_data_rdata中的对应8位(由op决定),并对它做符号拓展,作为最终结果WD

  • 按半字读出(lh)和按字读出(lw)是同样的道理。

    所以实际从DM获得的数据和向DM写入的数据不是一字节。

6、为了对抗复杂性你采取了哪些抽象和规范手段?这些手段在译码和处理数据冲突的时候有什么样的特点与帮助?

建立了编码和标签的映射关系,例如:

1
wire ori  = (opcode_DEin == 6'b001101) ? 1 : 0;

这样做更具有可读性。

7、在本实验中你遇到了哪些不同指令类型组合产生的冲突?你又是如何解决的?相应的测试样例是什么样的?

mflo mfhi等指令和其他需要使用相应的操作数的指令的冲突。通过给mfhi mflo设置对应的Tnew统一解决。