自己动手写CPU(7)流水线暂停机制+乘累加累减+除法指令

流水线暂停机制

因为OpenMIPS设计乘累加、乘累减、除法指令在流水线执行阶段占用多个时钟周期,因此需要暂停流水线,以等待这些多周期指令执行完毕。

OpenMIPS采用的是一种改进的方法:假如位于流水线第n阶段的指令需要多个时钟周期,进而请求流水线暂停,那么需保持取指令地址PC的值不变,同时保持流水线第n阶段、第n阶段之前的各个阶段的寄存器不变,而第n阶段后面的指令继续运行。

为此设计添加了CTRL模块,其作用是接收各阶段传递过来的流水线暂停请求信号,从而控制流水线各阶段的运行。

       1.jpg

CTRL的输入来自译码、执行阶段的请求暂停信号stallreq,对于OpenMIPS而言只有译码、执行会有暂停请求,而其他阶段都没有,因为指令的读取、数据存储器的读写都可以在一个时钟周期内完成。

当CTRL收到暂停信号时会对信号进行判断,然后输出流水线暂停信号stall给各个模块

代码修改

要添加一个CTRL模块

注意暂停信号stall是一个宽度为6的信号,其含义为

stall[0]表示取址地址PC是否保持不变,为1保持不变

stall[1]表示流水线取指阶段是否暂停,为1表示暂停

stall[2]表示流水线译码阶段是否暂停,为1表示暂停

stall[3]表示流水线执行阶段是否暂停,为1表示暂停

stall[4]表示流水线访存阶段是否暂停,为1表示暂停

stall[5]表示流水线回写阶段是否暂停,为1表示暂停

乘累加累减指令

指令说明

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

指令格式

       2.jpg

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位

maddu指令 -无符号乘累加

用法:maddu rs, rt

作用: {HI, LO} <- {HI, LO} + rs × rt

将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值作为无符号数进行乘法运算,运算结果与{HI, LO}相加,相加的结果保存到{HI,LO}中

msub指令 -有符号乘累减

用法:msub rs, rt

作用:{HI, LO}<-{HI,LO} - rs × rt

将地址为rs 的通用寄存器的值与地址为rt的通用寄存器的值作为有符号数进行乘法运算。然后使用{HI,LO}减去乘法结果,相减的结果保存到{HI,LO}中

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

 EX模块的输出hilo_temp_o是乘法结果,传递到EX/MEM模块,并在下一个时钟周期送回EX模块,参与第二个时钟周期

EX模块的输出cnt_o代表当前是第几个时钟周期,传递到EX/MEM模块,并在下一个时钟周期送回EX模块,参与第二个时钟周期

修改ID模块

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

修改EX模块

1.计算乘法结果:若计算为有符号乘法且乘数或被乘数为负的时候,将该值修改为补码。修改后得出临时结果

2.乘累加、乘累减:判断cnt在执行第几个周期,若为第一个则进行乘法,第二个则为加/减法

3.暂停流水线

4.修改HI、LO寄存器的写信息

除法指令

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

指令说明

除法指令有2条,包括div、divu,从图中可知这2条指令的指令码都是SPECIAL,可根据功能码确定是哪种指令。

指令格式

         

div指令 -有符号除法

用法:div rs,rt

作用:{HI, LO} <- rs/rt

将地址为rs的通用寄存器的值,与地址为rt的通用寄存器的值作为有符号数进行除法运算,将商保存到寄存器LO,余数保存到寄存器HI     

divu指令 -无符号除法

用法:divu rs,rt

作用:{HI, LO} <- rs/rt

将地址为rs的通用寄存器的值,与地址为rt的通用寄存器的值作为无符号数进行除法运算,将商保存到寄存器LO,余数保存到寄存器HI

 试商法

试商法其实就是一个模拟除法的过程

       

 代码修改

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

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

              状态转移图.png 

  • DivFree:除法模块空闲
  • DivByZero:除数是0:直接给出输出结果,余数和商都是0
  • DivOn:除法运算进行中:annul_i为是否取消除法运算,1则取消(当发生异常时可能会取消)
  • DivEnd:除法运算结束

修改后的数据流图如下         

         

MUX:增加一个选择器,用来确定PC的值。PC在下一个周期可以是PC+4,也可以不变(流水线暂停)。后面我们会学到转移指令时也会用这个模块进行判断。 

修改后的系结构图如下

                     修改后的系统框图.png 

dividend的低32位保存的是被除数、中间结果,第k次迭代结束的时候dividend[k:0]保存到就是当前得到的中间结果,dividend[31:k+1]保存到就是除数中还没有参与运算的数据

DivFree:

除数为0

除数不为0:负数要取补码

DivByZero:

返回0,0

DivOn:

如果annul_i == 1,表示处理器取消除法运算,那么DIV模块直接回到DivFree

如果annul_i == 0,且cnt != 32, 表示试商法还没结束,此时若div_temp为负,则结果为0;若为正,则结果为1

如果annul_i == 0,且cnt ==32,表示试商法结束,如果是有符号除法且被除数、除数一正一负,那么将试商法的结果取补码,得到最终的结果,此时商、余数都要取补码。同时进入DivEnd状态

DivEnd:

结果64位,高32位存余数、低32存商

总结

作者使用的是一段式的状态机,里面阻塞和非阻塞赋值交替使用,对于小白来说可以了解一下三段式的写法,这样虽然代码长了一点,但也便于理解。

猜你喜欢

转载自blog.csdn.net/weixin_52259822/article/details/124645494