tinyriscv学习记录之三

二十、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:先吃掉 bit31
  • STATE_CALC:再吃掉 bit30 到 bit0,共 31 位
  • STATE_END:输出结果

这就是为什么总数是 33,而不是 32。