自己动手写CPU(6)流水线暂停、乘累加减与除法器的实现

自己动手写CPU(6)流水线暂停、乘累加减与除法器的实现

流水线暂停

因为OpenMIPS设计乘累加、乘累减、除法指令在流水线执行阶段占用多个时钟周期,因此需要暂停流水线,以等待这些多周期指令执行完毕。OpenMIPS采用的是一种改进的方法:假如位于流水线第n阶段的指令需要多个时钟周期,进而请求流水线暂停,那么需保持取指令地址PC的值不变,同时保持流水线第n阶段、第n阶段之前的各个阶段的寄存器不变,而第n阶段后面的指令继续运行。

为实现CPU的流水线暂停功能,我们设计添加了CTRL模块,其作用是接收各阶段传递过来的流水线暂停请求信号,从而控制流水线各阶段的运行。CTRL模块对译码和执行阶段的暂停请求信号进行判断,然后输出流水线暂停信号stall。下图是为了实现流水线暂停机制而对系统结构所做的修改。具体代码修改之处见文末的项目链接。

1.jpg

指令介绍

乘累加、乘累减指令共有4条,包括: madd、maddu、msub、msubu,各指令的格式如下图所示。从图中可知,这4条指令的指令码都是SPECIAL2,第6~15bit都为0,可以依据第0~5bit的功能码确定是哪一种指令。

2.jpg

  • 当功能码是6’b000000时,表示是madd指令,有符号乘累加运算。指令用法为:madd rs, rt。指令作用为:{HI, LO}<-{HI, LO}+ rs x rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为有符号数进行乘法运算,运算结果与{HI, LO}相加,相加的结果保存到{HI, LO}中。此处{HI, LO}表示 HI、LO寄存器连接形成的64位数,HI是高32位,LO是低32位。
  • 当功能码是6’b000001时,表示是 maddu指令,无符号乘累加运算。指令用法为:maddu rs, rt。指令作用为: {HI, LO} <- {HI, LO} + rs × rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为无符号数进行乘法运算,运算结果与{HI, LO}相加,相加的结果保存到{HI,LO}中。
  • 当功能码是6’b000100时,表示是msub指令,有符号乘累减运算。指令用法为:msub rs, rt。指令作用为:{HI, LO}<-{HI,LO} - rs × rt,将地址为rs 的通用寄存器的值与地址为rt的通用寄存器的值作为有符号数进行乘法运算。然后使用{HI,LO}减去乘法结果,相减的结果保存到{HI,LO}中。
  • 当功能码是6’b000101时,表示是 msubu指令,无符号乘累减运算。指令用法为:msubu rs, rt。指令作用为:{HI, LO} <-{HI, LO} - rs × rt,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为无符号数进行乘法运算。然后使用{HI,LO}减去乘法结果,相减的结果保存到{HI,LO}中。

修改之处

实现思路

乘累加与乘累减指令计划在流水线阶段采用两个时钟周期完成运算,所以必须要保存两个信息:(1)、当前是第几个时钟周期;(2)、乘法结果。所以OpenMIPS通过在EX/MEM模块添加两个寄存器cnt、hilo,分别保存上述信息。修改系统结构框图如下所示:

3.jpg

1.修改译码阶段的ID模块

译码阶段的ID模块要添加对乘累加、乘累减指令的分析,根据给出的指令格式可知,这4条指令都是 SPECIAL2类指令,可以依据功能码确定是哪一种指令。确定指令的过程如下图所示。

自己动手写CPU(6)流水线暂停、乘累加减与除法器的实现_第1张图片

涉及的宏定义如下(因为不涉及对通用寄存器的回写,所以无需“_OP”的宏定义)

`define EXE_MADD            6'b000000
`define EXE_MADDU           6'b000001
`define EXE_MSUB            6'b000100
`define EXE_MSUBU           6'b000101

这4条指令的译码过程都是相似的,简单说明如下。

  1. 因为最终结果都是写入HI、LO寄存器,而不是写入通用寄存器,所以设置wreg_o为WriteDisable。
  2. 因为都要读取两个寄存器的值,所以设置regl_read_o、reg2_read_o为1‘b1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的第2125bit,正是指令中的rs;默认通过Regfile模块读端口⒉读取的寄存器地址reg2_addr_o的值是指令的第1620bit,正是指令中的rt;所以最终译码阶段的输出reg1_o就是地址为rs 的寄存器的值,reg2_o就是地址为rt的寄存器的值。
  3. 运算类型alusel_o的值都设置为EXE_RES_MUL,不过由于没有要写的通用寄存器。所以此处alusel_o的值并没有作用,也可以设置为EXE_RES_NOP。
  4. 运算子类型aluop _o的值设置为与具体的指令对应。

2.修改执行阶段的EX模块

  1. 计算从通用寄存器中读出的两个寄存器的乘法结果,保存在mulres 中。
  2. 以乘累加指令为例进行讲解。乘累减指令与此类似。如果 cnt_i为2’b00,表示是乘累加指令的第一个执行周期,此时将乘法结果mulres通过接口 hilo_temp_o输出到EX/MEM模块,以便在下一个时钟周期使用。同时,设置变量stallreq_for_madd_msub为Stop,表示乘累加指令请求流水线暂停;如果 cnt _i为 2’b01,表示是乘累加指令的第二个执行周期,此时EX模块的输入hilo_temp_i就是上一个时钟周期得到的乘法结果,所以将 hilo_temp_i与 HI、LO寄存器的值相加,得到最终的运算结果,保存到变量 hilo_temp1中。同时,设置变量stallreq_for_madd_msub为NoStop,表示乘累加指令执行结束,不再请求流水线暂停。最后,设置cnt_o为2’b10,而不是直接设置为2’b00,目的是:如果因其他原因导致流水线保持暂停,那么由于cnt_o为2’b10,所以EX阶段不再计算,从而防止乘累加指令重复运行。
  3. 给出信号stallreq的值,目前只有乘累加、乘累减指令会导致流水线暂停,所以stallreq就直接等于变量stallreq for madd msub的值。
  4. 由于乘累加、乘累减指令要将最终结果写入 HI、LO寄存器,所以在第四段给出了对HI、LO寄存器的写信息。

3.修改EX/MEM模块

增加两个输入接口hilo_i,cnt_i,增加两个输出接口hilo_o,cnt_o,在流水线执行阶段暂停的时候,将输入信号hilo_i通过输出接口hilo_o送出,输入信号cnt i通过输出接口cnt_o送出。其余时刻,hilo_o为0,cnt_o也为0。

除法器

采用"试商法"实现除法运算,对于32位的除法,至少需要32个时钟周期才能得到除法结果。

实现思路

新建一个模块 DIV,在其中实现采用试商法的32位除法运算。当流水线执行阶段的EX模块发现当前指令是除法指令时,首先暂停流水线,然后将被除数、除数等信息送到DIV模块,开始除法运算。DIV模块在除法运算结束后,通知EX模块,并将除法结果送到EX模块,后者依据除法结果设置HI、LO寄存器的写信息,同时取消暂停流水线。

修改后的系统部分框图如下

修改后的系统框图.png

DIV模块的主要部分是一个状态机,共有四个状态

  • DivFree:除法模块空闲;
  • DivByZero:除数是0;
  • DivOn:除法运算进行中;
  • DivEnd:除法运算结束;

状态转移图.png

作者使用的是一段式的状态机,里面阻塞和非阻塞赋值交替使用,对于我这个硬件小白来说还是难度太高,我还是老老实实用三段式重新实现了,中间过程还算有点复杂,输出的逻辑既有组合逻辑也有时序逻辑,好在调试了一会之后总算通过了。

仿真结果

乘累加、乘累减测试指令

   .org 0x0
   .set noat
   .global _start
_start:
   ori  $1,$0,0xffff                  
   sll  $1,$1,16
   ori  $1,$1,0xfffb           # $1 = -5
   ori  $2,$0,6                # $2 = 6

   mult $1,$2                  # hi = 0xffffffff
                               # lo = 0xffffffe2

   madd $1,$2                  # hi = 0xffffffff
                               # lo = 0xffffffc4

   maddu $1,$2                 # hi = 0x5
                               # lo = 0xffffffa6

   msub $1,$2                  # hi = 0x5
                               # lo = 0xffffffc4   

   msubu $1,$2                 # hi = 0xffffffff
                               # lo = 0xffffffe2 

自己动手写CPU(6)流水线暂停、乘累加减与除法器的实现_第2张图片

由波形图可见,乘累加、乘累减指令实现正确,同时观察到乘累加、乘累减指令都需要两个时钟周期来完成,且两个时钟周期内的PC值都保持不变。

除法模块测试指令

   .org 0x0
   .set noat
   .global _start
_start:
   ori  $2,$0,0xffff
   sll  $2,$2,16
   ori  $2,$2,0xfff1          # $2 = -15
   ori  $3,$0,0x11            # $3 = 17

   div  $zero,$2,$3           # hi = 0xfffffff1,lo = 0x0
   divu $zero,$2,$3           # hi = 0x00000003,lo = 0x0f0f0f0e
   div  $zero,$3,$2           # hi = 2,lo = 0xffffffff

自己动手写CPU(6)流水线暂停、乘累加减与除法器的实现_第3张图片

实验心得

执行阶段的EX模块对HI、LO寄存器的写操作为什么不引入cnt_i来进行判断呢?即cnt_i=2’b01(执行阶段的第二个周期)再加上功能码的定义来进行对HI、LO寄存器是否写入的决策呢?

联系前后模块发现,EX模块发出暂停流水线的请求后,EX_MEM模块收到来自EX模块的暂停流水线请求后就把HI、LO寄存器的写使能关闭了,也就是说我们在乘累加、乘累减的第一阶段的写指令是传不到HILO寄存器堆的,所以就不用加上cnt_i这个冗余指令的判断过程了。

三段式状态机还需多多训练,多注意时序上的问题,哪些变量在上升沿赋值,哪些变量是组合逻辑赋值。

乘累加、乘累减项目
除法运算项目

你可能感兴趣的:(自己动手写CPU,CPU,verilog)