二十、div.v的代码阅读感受
除法模块的代码反复咀嚼,难以下咽的感觉,对于刚入门verilog的我不太友好。
除法模块,顾名思义,就是做除法运算的呗。概念很简单,接口也很明了,接收来自ex模块的除数、被除数等数据,算就是了,没啥难点。
但就是到了自己去动手实现写代码的时候,手高眼低,写不出来代码,虽然原理很简单。
人会做的除法运算,让机器来运算,这是两回事。
所以,不能眼高手低,都要自己敲一遍代码。
二十一、弄懂div代码的前提是搞懂试商法
试商法是说,每次移入一位被除数的最高位,然后和除数作差,够就商1,不够商0。
所以就有,除法运算会至少32周期,因为被除数是32位的。
对应的关键代码:
c
dividend_r <= {dividend_r[30:0], 1'b0};
minuend <= {minuend_tmp[30:0], dividend_r[30]};
wire[31:0] div_result_tmp = minuend_ge_divisor? ({div_result[30:0], 1'b1}): ({div_result[30:0], 1'b0});
div_result <= div_result_tmp;
把它们按流程串起来,就是这样:
第一步:看当前余数能不能减除数
这个判断在前面的:
verilog
minuend_ge_divisor = minuend >= divisor_r
第二步:决定这一位商是 1 还是 0
用:
verilog
div_result_tmp = ... ? {div_result[30:0], 1'b1} : {div_result[30:0], 1'b0}
第三步:把这一位商写入 div_result
用:
verilog
div_result <= div_result_tmp;
第四步:余数准备进入下一轮
先确定这一轮之后保留什么余数,再拼入被除数下一位:
verilog
minuend <= {minuend_tmp[30:0], dividend_r[30]};
第五步:被除数整体左移,为下一拍准备
用:
verilog
dividend_r <= {dividend_r[30:0], 1'b0};
二十二、minuend <= {minuend_tmp30:0, dividend_r30};为什么左移进dividend_r30,不是最高位31呢?
在 STATE_START 里有这句:
verilog
minuend <= dividend_r[31];
既然最高位 bit31 已经先放进 minuend,那进入 STATE_CALC 后,下一步自然应该接:
次高位 bit30
所以代码写成:
verilog
minuend <= {minuend_tmp[30:0], dividend_r[30]};
这样做有个前提:
c
dividend_r <= {dividend_r[30:0], 1'b0};
minuend <= {minuend_tmp[30:0], dividend_r[30]};
这两句在同一拍,在同一个时钟周期里,minuend 和 dividend_r 看到的都是"旧值"。到时钟沿到来时,它们再一起更新成新值。
所以,minuend先用旧的 dividend_r[30]。如果dividend_r 是先左移,那么minuend就要用31位了。
二十三、为什么每次除法运算至少需要33个时钟周期才能完成?不是32个
-
1 拍:
STATE_START,做初始化、判断除零、准备符号处理 -
31 拍:
STATE_CALC,逐位试商 -
1 拍:
STATE_END,把结果送到result_o并拉高ready_o
二十四、为什么 STATE_CALC 只有 31 拍?
因为最高位 31 已经在 STATE_START 用掉了,后面 STATE_CALC 只要继续处理 30:0 这 31 位。
所以是:
STATE_START:先吃掉 bit31STATE_CALC:再吃掉 bit30 到 bit0,共 31 位STATE_END:输出结果
这就是为什么总数是 33,而不是 32。