MIPS32的DIV和DIVU实现(除法指令)
《自己动手写CPU》一书的7.11节到7.14节实现了DIV和DIVU指令。
书中通过“试商法”实现除法,并在原有的流水线结构之外另加了状态机进行计算。
照抄书上的实现方法需要另外添加个.v,我实在有点懒,不想在运算指令实现上再另外加个文件。
而且按照书上的实现,DIV和DIVU指令跟其他的运算指令差别很大,不符合我对结构的审美。
那么就必须想想办法自己写一个别的实现方法。
于是找到了一个结构化比较好的实现方法:
http://www.cnblogs.com/AndyJee/p/4575152.html
参照这个方法,使用verilog实现需要解决2个问题:
(1)文章是无符号数的除法,对应DIVU,而DIV是有符号数除法。
这里又要分两种情况讨论。
a、DIV指令的被除数和除数是有符号数
有符号数运算时,输入的被除数和除数都是补码,做除法时需要先将补码转成原码。
正数的补码就是原码,那么只需要在负数时转成原码即可。
b、DIV指令的商和余数正负符号判断
按照初等数学的定义,商的正负取决于被除数和除数的正负,而余数取决于除数的符号。
而计算机语言中,商的正负没有变化,余数符号取决于被除数。
该问题的讨论:
http://blog.sina.com.cn/s/blog_956c84bb0101j31f.html
DIV指令下,被除数/除数为负数,则转换成原码:
assign div_dividend = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] == 1'b1)) ? (~reg1_i + 1) : reg1_i;
assign div_divisor = ((aluop_i == `EXE_DIV_OP) && (reg2_i[31] == 1'b1)) ? (~reg2_i + 1) : reg2_i;
DIV指令下,将商和余数转回为补码:
assign div_quotient = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] ^ reg2_i[31] == 1'b1)) ? {1'b1, (~div_quo_o[30:0] + 1)} : div_quo_o;
assign div_remainder = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] == 1'b1)) ? {1'b1, (~div_rem_o[30:0] + 1)} : div_rem_o;
(2)参考的方法通过高级语言描述,需要转化成适合verilog实现的方式。
该方法的核心是通过减法来实现除法,使用中文通俗描述的步骤如下:
基本方法
a、比较被除数和除数的大小;
b、如果被除数比较大,将被除数减去除数,商加1;
c、循环步骤a和b,直到被除数比除数小,这时候被除数剩下的就是余数,商就是结果(初值为0)。
基本方法比较“笨”,效率是最低的。
加速方法
a、比较被除数和n*除数的大小;
b、如果被除数比较大,将被除数减去n*除数,商加n;
c、循环步骤a和b,直到被除数比除数小,这时候被除数剩下的就是余数,商就是结果(初值为0)。
加速方法适合使用移位来实现(verilog中,左移1位数值乘于2)。
但是上面文章中描述的方法不适合直接用verilog实现(2层while循环,verilog实现很麻烦)。
可以通过以下几个小技巧避免在verilog中出现类似2层while循环的结构:
a、考虑DIV和DIVU指令的除数位数有限,那么可以先判断最大需要移位几次进行加速方法步骤a(数一数除数第一个1之前有几个0);
b、在最大移位范围内,按照移位次数从大到小的顺序进行操作(使n*除数中的n尽量大),以尽快收敛结果。
最后实现时,在流水线的寄存器之间进行的运算如下(核心代码):
//core method
if (div_rem_i < (div_divisor << div_shift_cnt_i))
begin
div_quo_o = div_quo_i;
div_rem_o = div_rem_i;
end
else
begin
div_quo_o = div_quo_i + (1 << div_shift_cnt_i);
div_rem_o = div_rem_i + (~(div_divisor << div_shift_cnt_i)) + 1;
end
end
这样DIV和DIVU运算所需的周期受除数影响,除数越大则运算周期越短,除数越小则运算周期越长(移位操作次数多)。
例如,当除数是1时,需要31次移位操作(31个时钟);除数是10'b10_0000_0000时,需要22次移位(22个时钟)。
这种实现方法最大运算周期为32*时钟周期,而《自己动手写CPU》中给的例子对于32位的除法,至少需要32个时钟周期。
另外,这种实现方法与累乘加和累乘减(MADD/MADDU/MSUB/MSUBU)的结构类似,不需要另外再添加.v文件,符合懒人“初始结构以外,最好不做任何结构变动”的习惯。
最后,这种方法可以简单实现加速,即通过比较被除数和除数从bit31开始0的数量,可以在被除数和除数绝对值都较小的情况下节省很多运算周期。