写在前面
前面执行模块对于除法计算只做流水线控制和参数传递,具体的除法计算由DIV除法模块来完成。该模块作者使用试商法实现,所以在开始前先了解下试商法怎么进行除法计算。
有VIP的同学可以看一下这篇文章,可能会讲解得更加详细:https://blog.csdn.net/m0_71078397/article/details/126610918试商法是一种用于除法运算的算法,特别适用于硬件实现(如FPGA、CPU等)。它的核心思想是通过试探和修正的方式逐位确定商的值。
基本公式:
被除数 ÷ 除数 = 商 … 余数基本思想:从最高位开始,每次确定商的一位。
1、猜测当前位的商值
2、用猜测值乘以除数
3、比较乘积与被除数(或当前余数)
4、根据比较结果调整商值十进制举例(100 / 7):
二进制举例(100 / 7):
call in:作者为什么说除法至少需要33个时钟周期。这里试商法对于32位数的除法而言需要迭代处理32次,即每一位都需要迭代一次。
1、RISC-V32位除法指令
DIV:有符号除法(商) DIVU:无符号除法(商) REM:有符号取余(余数) REMU:无符号取余(余数)2、除法模块接口定义
input wire clk,input wire rst,// from exinput wire[`REG_BUS]dividend_i,// 被除数input wire[`REG_BUS]divisor_i,// 除数input wire start_i,// 开始信号,运算期间这个信号需要一直保持有效input wire[2:0]op_i,// 具体是哪一条指令input wire[`REG_ADDR_BUS]reg_waddr_i,// 运算结束后需要写的寄存器// to exoutput reg[`REG_BUS]result_o,// 除法结果,高32位是余数,低32位是商output reg ready_o,// 运算结束信号output reg busy_o,// 正在运算信号output reg[`REG_ADDR_BUS]reg_waddr_o// 运算结束后需要写的寄存器该模块只和执行模块交换数据,所以接口只用定义与执行模块数据交换即可。
3、除法器状态机状态定义
// 状态定义localparam STATE_IDLE=4'b0001;//空闲localparam STATE_START=4'b0010;//开始localparam STATE_CALC=4'b0100;//计算中localparam STATE_END=4'b1000;//结束4、中间运算变量
reg[`REG_BUS]dividend_r;//被除数reg[`REG_BUS]divisor_r;//除数reg[2:0]op_r;//指令reg[3:0]state;//状态机状态reg[31:0]count;reg[`REG_BUS]div_result;//除法结果reg[`REG_BUS]div_remain;//除法结果余数reg[`REG_BUS]minuend;//余数reg invert_result;//结果的补码形式wire op_div=(op_r==`INST_DIV);wire op_divu=(op_r==`INST_DIVU);wire op_rem=(op_r==`INST_REM);wire op_remu=(op_r==`INST_REMU);5、试商法除法的核心计算部分
wire[31:0]dividend_invert=(-dividend_r);//等价于dividend_invert = ~dividend_r + 32'b1wire[31:0]divisor_invert=(-divisor_r);//取二进制补码wire minuend_ge_divisor=minuend>=divisor_r;//比较部分余数 minuend 是否大于等于除数 divisor_rwire[31:0]minuend_sub_res=minuend-divisor_r;//wire[31:0]div_result_tmp=minuend_ge_divisor?({div_result[30:0],1'b1}): ({div_result[30:0], 1'b0});//若部分余数大于等于除数,则余数更新为余数减除数的差值,否则余数不变wire[31:0]minuend_tmp=minuend_ge_divisor?minuend_sub_res[30:0]:minuend[30:0];div_result_tmp:在余数大于等于除数时,在对应位上商1,否则商0。
6、除法状态机
1. 复位
if(rst==`RESET_EN)begin state<=STATE_IDLE;ready_o<=`DIV_RESULT_NOT_READY;//除法结果是否完成result_o<=`ZERO_WORD;//存储最终的除法结果(商或余数)div_result<=`ZERO_WORD;//存储计算过程中的商div_remain<=`ZERO_WORD;op_r<=3'h0;reg_waddr_o<=`ZERO_WORD;//除法结果-写地址dividend_r<=`ZERO_WORD;divisor_r<=`ZERO_WORD;minuend<=`ZERO_WORD;//存储当前的部分余数(被减数)invert_result<=1'b0;// 结果取反标志busy_o<=`FALSE;//除法器是否正在工作count<=`ZERO_WORD;//控制32次迭代的计数器end2. 空闲状态
STATE_IDLE:beginif(start_i==`DIV_START)begin op_r<=op_i;dividend_r<=dividend_i;divisor_r<=divisor_i;reg_waddr_o<=reg_waddr_i;state<=STATE_START;busy_o<=`TRUE;endelsebegin op_r<=3'h0;reg_waddr_o<=`ZERO_WORD;dividend_r<=`ZERO_WORD;divisor_r<=`ZERO_WORD;ready_o<=`DIV_RESULT_NOT_READY;result_o<=`ZERO_WORD;busy_o<=`FALSE;end end当接收到开始信号时,将参与运算的变量进行初始化,并对外发出“忙”信号(busy_o),后将状态机状态切换位开始状态。
3、开始状态
STATE_START:beginif(start_i==`DIV_START)begin// 除数为0if(divisor_r==`ZERO_WORD)beginif(op_div|op_divu)begin result_o<=32'hffffffff;//若指令为除法运算则返回全1--无限大endelsebegin result_o<=dividend_r;//若为取余数运算则返回取余本身end ready_o<=`DIV_RESULT_READY;//完成计算state<=STATE_IDLE;busy_o<=`FALSE;// 除数不为0endelsebegin busy_o<=`TRUE;count<=32'h40000000;//计数器初始化--第31位为1,右移32位后为0state<=STATE_CALC;//状态机切换为计算div_result<=`ZERO_WORD;div_remain<=`ZERO_WORD;// DIV和REM这两条指令是有符号数运算指令if(op_div|op_rem)begin// 被除数求补码if(dividend_r[31]==1'b1)begin//被除数为负数时取二进制补码dividend_r<=dividend_invert;minuend<=dividend_invert[31];endelsebegin minuend<=dividend_r[31];end// 除数求补码if(divisor_r[31]==1'b1)begin divisor_r<=divisor_invert;end endelsebegin minuend<=dividend_r[31];end// 运算结束后是否要对结果取补码if((op_div&&(dividend_r[31]^divisor_r[31]==1'b1))//异或运算,若除数与被除数符号不同则为1,标记结果符号为负号||(op_rem&&(dividend_r[31]==1'b1)))begin invert_result<=1'b1;endelsebegin invert_result<=1'b0;end end endelsebegin state<=STATE_IDLE;result_o<=`ZERO_WORD;ready_o<=`DIV_RESULT_NOT_READY;busy_o<=`FALSE;end end执行操作:
1、处理除数为0的情况(除法计算返回全1,取余运算返回本身)。
2、除数不为0,初始化迭代计数器(count <= 32’h40000000),将状态切换为计算状态。
3、对于操作数中存在负数情况时,对其取补码。
4、判断运算结果的符号,并标记(invert_result )。
4、计算状态
STATE_CALC:beginif(start_i==`DIV_START)begin dividend_r<={dividend_r[30:0],1'b0};//被除数左移一位div_result<=div_result_tmp;//保存运算中间结果count<={1'b0,count[31:1]};//计数器右移if(|count)begin minuend<={minuend_tmp[30:0],dividend_r[30]};endelsebegin state<=STATE_END;if(minuend_ge_divisor)begin div_remain<=minuend_sub_res;endelsebegin div_remain<=minuend;end end endelsebegin state<=STATE_IDLE;result_o<=`ZERO_WORD;ready_o<=`DIV_RESULT_NOT_READY;busy_o<=`FALSE;end end结合计算部分迭代32次,完成计算。
5、完成状态
STATE_END:beginif(start_i==`DIV_START)begin ready_o<=`DIV_RESULT_READY;state<=STATE_IDLE;busy_o<=`FALSE;if(op_div|op_divu)beginif(invert_result)begin result_o<=(-div_result);endelsebegin result_o<=div_result;end endelsebeginif(invert_result)begin result_o<=(-div_remain);endelsebegin result_o<=div_remain;end end endelsebegin state<=STATE_IDLE;result_o<=`ZERO_WORD;ready_o<=`DIV_RESULT_NOT_READY;busy_o<=`FALSE;end end结合符号标记(invert_result)和指令为结果输出赋值,并向执行模块发送完成信号。
https://gitee.com/liangkangnan/tinyriscv