本次解析模块内容:
div
ctrl
rib
regs(简单,看原作者解释即可)
csr_regs(和regs长得同父同母)
参考文献:从零开始写RISC-V处理器 | liangkangnan的博客 (gitee.io)
参考代码:tinyriscv: 一个从零开始写的极简、非常易懂的RISC-V处理器核。 (gitee.com)
目录
div
`include "defines.v"
// 除法模块
// 试商法实现32位整数除法
// 每次除法运算至少需要33个时钟周期才能完成
//负数的补码取反+1 得出正数原码
//正数的原码取反+1 得出负数补码
module div(
input wire clk,
input wire rst,
// from ex
input wire[`RegBus] dividend_i, // 被除数
input wire[`RegBus] divisor_i, // 除数
input wire start_i, // 开始信号,运算期间这个信号需要一直保持有效
input wire[2:0] op_i, // 具体是哪一条指令
input wire[`RegAddrBus] reg_waddr_i, // 运算结束后需要写的寄存器
// to ex
output reg[`RegBus] result_o, // 除法结果,高32位是余数,低32位是商
output reg ready_o, // 运算结束信号
output reg busy_o, // 正在运算信号
output reg[`RegAddrBus] reg_waddr_o // 运算结束后需要写的寄存器
);
// 状态定义
localparam STATE_IDLE = 4'b0001;
localparam STATE_START = 4'b0010;
localparam STATE_CALC = 4'b0100;
localparam STATE_END = 4'b1000;
reg[`RegBus] dividend_r;//buffer
reg[`RegBus] divisor_r;
reg[2:0] op_r;
reg[3:0] state;
reg[31:0] count;
reg[`RegBus] div_result;
reg[`RegBus] div_remain;
reg[`RegBus] 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);
wire[31:0] dividend_invert = (-dividend_r);//被除数的相反数,这里是直接用一个负号把求补码然后取反+1的操作给忽略了,只能说RTL语言现在太简化了
wire[31:0] divisor_invert = (-divisor_r);//除数的相反数
wire minuend_ge_divisor = minuend >= divisor_r;//这是一个标志信号,表示验证的被除部分大于除数,这样就可以在当前除数位赋值为1
wire[31:0] minuend_sub_res = minuend - divisor_r;//这是判断完被除部分大于除数之后,留下的余数,这时候需要继续向下1bit 1bit读取
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];//这里是对操作的被除部分进行进位以及保留
// 状态机实现
always @ (posedge clk) begin
if (rst == `RstEnable) begin//初始化
state <= STATE_IDLE;
ready_o <= `DivResultNotReady;
result_o <= `ZeroWord;
div_result <= `ZeroWord;
div_remain <= `ZeroWord;
op_r <= 3'h0;
reg_waddr_o <= `ZeroWord;
dividend_r <= `ZeroWord;
divisor_r <= `ZeroWord;
minuend <= `ZeroWord;
invert_result <= 1'b0;
busy_o <= `False;
count <= `ZeroWord;
end else begin
case (state)
STATE_IDLE: begin
if (start_i == `DivStart) begin
op_r <= op_i;//引入opcode
dividend_r <= dividend_i;//引入被除数
divisor_r <= divisor_i;//引入除数
reg_waddr_o <= reg_waddr_i;//引入要把结果写入的寄存器地址
state <= STATE_START;
busy_o <= `True;//开启忙状态
end else begin
op_r <= 3'h0;
reg_waddr_o <= `ZeroWord;
dividend_r <= `ZeroWord;
divisor_r <= `ZeroWord;
ready_o <= `DivResultNotReady;
result_o <= `ZeroWord;
busy_o <= `False;
end
end
STATE_START: begin
if (start_i == `DivStart) begin
// 除数为0
if (divisor_r == `ZeroWord) begin
if (op_div | op_divu) begin
result_o <= 32'hffffffff;//这里表示运算不合法
end else begin
result_o <= dividend_r;//其余情况应该是与求余数,因为运算不合法,所以没有触发除法操作,余数就是被除数本身
end
ready_o <= `DivResultReady;
state <= STATE_IDLE;
busy_o <= `False;
// 除数不为0
end else begin
busy_o <= `True;//开始运算,继续忙
count <= 32'h40000000;//这个暂且不清楚⭐
state <= STATE_CALC;
div_result <= `ZeroWord;
div_remain <= `ZeroWord;
// DIV和REM这两条指令是有符号数运算指令
if (op_div | op_rem) begin
// 被除数求相反数
if (dividend_r[31] == 1'b1) begin
dividend_r <= dividend_invert;//将被除数正数化
minuend <= dividend_invert[31];//这个表示将被除数的头一位放入 被除部分 这里其实就是进行循环读取的时候了,一个一个读入,并不断判断是否大于除数,一旦出现大于的情况,马上在原地留个1,算出相减结果并替代,然后继续读
end else begin
minuend <= dividend_r[31];//这里同样是将除数正数化,因为负数很难拿来计算,算法和正数不一致,一般都是先全部化为正数
end
// 除数求相反数
if (divisor_r[31] == 1'b1) begin
divisor_r <= divisor_invert;
end
end else begin
minuend <= dividend_r[31];
end
// 运算结束后是否要对结果取补码
if ((op_div &&