1.除法器的实现
除法器是移植了chiplab中的除法器 IP/myCPU/div.v · 龙芯教育/chiplab - Gitee.com
但在移植的过程中出现了一些问题: ①run all Error: div no end in 34 clk! $finish called at time : 1745 ns : File "H:/1/div_tb.v" Line 162
出现这个问题的原因是我把reset和resetn搞混了,div.v中的端口中定义的是reset而测试文件div_tb.v中定义的是resetn,我误认为它俩是同一个变量,于是为了解决div.v和div_tb.v中的端口不一致,我把div.v中的reset全改成了resetn,导致出现这个报错。
原因是reset和resetn实际上是相反的,当reset为1/0时,resetn为0/1,因为这一点改动导致:
always @(posedge div_clk) begin //33位除法计算
if (resetn || ~div || complete_delay) begin
count <= 8'd32; //计算33次,0010 0000
tmp_r <= 33'b0;
end
当div_clk到达时,上面代码中的判断语句恒为真,count的值就恒为32,无法计数,而导致测试文件div_tb.v出现Error: div no end in 34 clk!
于是我将div.v中的resetn还复原成reset,然后在div_tb.v中调用div模块的代码中改动为:
div uut (
.div_clk(div_clk),
.reset(!resetn), //原语句为 .resetn(resetn)
.div(div),
.div_signed(div_signed),
.x(x),
.y(y),
.s(s),
.r(r),
.complete(complete)
修改完毕后成功解决Error: div no end in 34 clk!报错。
然而却又出现了下面的报错:
run all [time@ 815000]Error: x= 1206705039, y= 2033215986, signed=0, s= 0, r= 2097015289, s_ref= 0, r_ref= 1206705039, s_OK=1, r_OK=0
显示余数的结果计算错误,仔细观察波形发现上的计算结果总是比正确结果慢一个时钟出现:
也就是当complete为1也就是除法运算结束时还没有将商的结果及时赋值过去,在下一个时钟也就是complete_delay为1时才得到商的正确结果。
发现当count为8'hf0时complete_delay才为1
assign complete_delay = (count == 8'hf0); //1111 0000
assign real_complete = complete_delay || complete;
always @(posedge div_clk) begin //33位除法计算
if (reset || ~div || complete_delay) begin
count <= 8'd32; //计算33次,0010 0000
tmp_r <= 33'b0;
end
else if (~(count[7])) begin //0010 0000 -> 0001 1111 -> ... ->0000 0000 ->1111 1111
if (tmp_d[32]) begin //tmp_d为负数
UnsignS <= {UnsignS[31:0], 1'b0}; //tmp_d为负数,即相减结果为负,不够减,商0
tmp_r <= result_r;
end
else begin
UnsignS <= {UnsignS[31:0], 1'b1}; //tmp_d为正数,即相减结果为正,够减,商1
tmp_r <= tmp_d;
end
count <= count - 8'd1;
end
else begin //计算完33位除法后,将此时的tmp_r(余数)复制给UnsignR
UnsignR <= tmp_r;
count <= 8'hf0; //完成33位除法运算complete signal only maintain one clock
end
end
assign result_r = {tmp_r[31:0], UnsignX[count]};
通过complete_delay发现问题出在了UnsignR的赋值上,即运算结束后tmp_r的值还没有赋给UnsignR,当count的值从0010 0000减为1111 1111时才执行UnsignR <= tmp_r,此时才得到了商的绝对值,而除法运算在count从0010 0000的值减到0000 0000时就已经完成,故慢了一个时钟周期。且发现实际上complete_delay根本没起到任何作用可以直接不用。修改代码如下:
//assign complete_delay = (count == 8'hf0); //注释掉,用不到complete_delay
assign real_complete = complete; //删掉complete_delay
always @(posedge div_clk) begin //33位除法计算
if (reset || ~div ) begin //删掉complete_delay
count <= 8'd32; //计算33次,0010 0000
UnsignR <= 33'b0; //将tmp_r改为UnsignR
end
else if (~(count[7])) begin //0010 0000 -> 0001 1111 -> ... ->0000 0000 ->1111 1111
if (tmp_d[32]) begin //tmp_d为负数
UnsignS <= {UnsignS[31:0], 1'b0}; //tmp_d为负数,即相减结果为负,不够减,商0
UnsignR <= result_r; //将tmp_r改为UnsignR
end
else begin
UnsignS <= {UnsignS[31:0], 1'b1}; //tmp_d为正数,即相减结果为正,够减,商1
UnsignR <= tmp_d; //将tmp_r改为UnsignR
end
count <= count - 8'd1;
end
/*删掉下面的代码
else begin //计算完33位除法后,将此时的tmp_r(余数)复制给UnsignR
UnsignR <= tmp_r;
count <= 8'hf0; //完成33位除法运算complete signal only maintain one clock
end
*/
end
assign result_r = {UnsignR[31:0], UnsignX[count]}; //将tmp_r改为UnsignR
修改后成功解决报错!
2.乘法器
乘法器是直接用的chiplab的乘法器…
3.添加指令
①添加ADD、ADDI、SUB指令
ADD复用ADDU的数据通路,ADDI复用ADDIU的数据通路,SUB复用SUBU的数据通路
id_stage.v
wire inst_add; //新增add指令
wire inst_addi; //新增addi指令
wire inst_sub; //新增sub指令
assign inst_add = op_d[6'h00] & func_d[6'h20] & sa_d[5'h00]; //新增add
assign inst_addi = op_d[6'h08]; //新增
assign inst_sub = op_d[6'h00] & func_d[6'h22] & sa_d[5'h00]; //新增sub
//inst_add、inst_addi,分别复用add和addiu在执行、访存、写回阶段的数据通路
assign alu_op[ 0] = inst_addu | inst_addiu | inst_lw | inst_sw | inst_jal | inst_add | inst_addi;
//sub复用subu在执行、访存、写回阶段的数据通路
assign alu_op[ 1] = inst_subu | inst_sub; //添加inst_sub
//addi复用addiu在译码级的数据通路
assign src2_is_imm = inst_addiu | inst_lui | inst_lw | inst_sw | inst_slti | inst_sltiu | inst_addi;
assign dst_is_rt = inst_addiu | inst_lui | inst_lw | inst_slti | inst_sltiu | inst_andi | inst_ori | inst_xori | inst_addi;
//添加 复用译码级数据通路 inst_need_rs和inst_need_rt是和前递相关的变量
assign inst_need_rs =inst_add | //rd,rs,rt 新增
inst_addi | //rt,rs,imm 新增
inst_sub ; //rd,rs,rt 新增
assign inst_need_rt =inst_add | //rd,rs,rt 新增
inst_addi | //rt,rs,imm 新增
inst_sub ; //rd,rs,rt 新增
②SLTI、SLTIU指令
SLTI与SLT对于两个源操作数的比较运算是完全一致的,且SLTI与ADDIU的操作数来源和目的寄存器号来源一致。
所以SLTI复用SLT指令在执行和访存阶段的数据通路,复用ADDIU在译码和写回阶段(即写回的寄存器地址)的数据通路。
SLTIU与SLTU对于两个源操作数的比较运算是完全一致的,且SLTIU与ADDIU的操作数来源和目的寄存器号来源一致。
SLTIU复用SLTU指令在执行和访存阶段的数据通路,复用ADDIU在译码和写回阶段的数据通路。
id_stage.v
wire inst_slti; //新增slti指令
wire inst_sltiu; //新增sltiu指令
assign inst_slti = op_d[6'h0a]; //新增
assign inst_sltiu = op_d[6'h0b]; //新增
//slti复用slt在执行和访存阶段的数据通路
assign alu_op[ 2] = inst_slt | inst_slti; //添加inst_slti
//sltiu复用sltu在执行和访存阶段的数据通路
assign alu_op[ 3] = inst_sltu | inst_sltiu; //添加sltiu
//inst_slti、inst_sltiu复用addiu在译码和写回阶段的数据通路
assign src2_is_imm = inst_addiu | inst_lui | inst_lw | inst_sw | inst_slti | inst_sltiu | inst_addi;
//slti和sltiu复用addiu在译码和写回阶段的数据通路
assign dst_is_rt = inst_addiu | inst_lui | inst_lw | inst_slti | inst_sltiu | inst_andi | inst_ori | inst_xori | inst_addi;//添加slti、sltiu、inst_andi、inst_ori、inst_xori
//添加 复用译码级数据通路 inst_need_rs和inst_need_rt是和前递相关的变量
assign inst_need_rs = inst_slti | //rt,rs,imm 新增
inst_sltiu ; //rt,rs,imm 新增
assign inst_need_rt = inst_slti | //rt,rs,imm 新增
inst_sltiu ; //rt,rs,imm 新增
③ANDI、ORI、XORI指令
ANDI、ORI、XORI与AND、OR、XOR进行的逻辑运算一样,且和ADDIU一样均是将结果写入rt寄存器
所以ANDI、ORI、XORI复用AND、OR、XOR在执行和访存阶段的数据通路,以及复用ADDIU指令在写回阶段的数据通路。
不过,ANDI、ORI、XORI和ADDIU的第二个源操作数不一样,所以在译码阶段不能完全复用ADDIU的数据通路,第一个源操作数可以复用ADDIU的数据通路(即默认的数据通路,把rs寄存器的值当做第一个源操作数),而第二个源操作数是将”指令码的低16位立即数零扩展至32位“,没有可以复用的数据通路,所以第二个源操作数的产生需要新增一个数据通路,并将信号传递到执行级,最终在执行级实现第二个源操作数的产生。
id_stage.v
wire inst_andi; //新增
wire inst_ori; //新增
wire inst_xori; //新增
wire src2_is_imm_zero; //立即数0扩展。新增
assign inst_andi = op_d[6'h0c]; //新增
assign inst_ori = op_d[6'h0d]; //新增
assign inst_xori = op_d[6'h0e]; //新增
//andi、ori、xori复用and、or、xor在执行和访存阶段的数据通路
assign alu_op[ 4] = inst_and | inst_andi; //新增andi
assign alu_op[ 6] = inst_or | inst_ori; //新增ori
assign alu_op[ 7] = inst_xor | inst_xori; //新增xori
//andi、ori、xori复用addiu在写回阶段的数据通路(就是写回的是那个寄存器)
assign dst_is_rt = inst_addiu | inst_lui | inst_lw | inst_slti | inst_sltiu | inst_andi | inst_ori | inst_xori | inst_addi;//添加slti、sltiu、inst_andi、inst_ori、inst_xori
//添加 复用译码级数据通路 inst_need_rs和inst_need_rt是和前递相关的变量
assign inst_need_rs = inst_andi | //rt,rs,imm 新增
inst_ori | //rt,rs,imm 新增
inst_xori ; //rt,rs,imm 新增
assign inst_need_rt = inst_andi | //rt,rs,imm 新增
inst_ori | //rt,rs,imm 新增
inst_xori ; //rt,rs,imm 新增
//andi、ori、xori第二个源操作数的产生
assign src2_is_imm_zero = inst_andi | inst_ori | inst_xori;//立即数0扩展。新增
//通过ds_to_es_bus将andi、ori、xori第二个源操作数的产生的信号传递到执行级
assign ds_to_es_bus = { src2_is_imm_zero} //立即数0扩展。新增
对于第二个源操作数的产生需要在执行级实现:
exe_stage.v
wire es_src2_is_imm_zero; //立即数0扩展。新增
assign {es_src2_is_imm_zero} = ds_to_es_bus_r; //执行级接受译码级第二个源操作数的产生信号
//产生第二个源操作数
assign es_alu_src2 = es_src2_is_imm_zero ? {16'b0, es_imm[15:0]} : //新增 立即数零扩展
es_src2_is_imm ? {{16{es_imm[15]}}, es_imm[15:0]} : //立即数符号扩展
es_src2_is_8 ? 32'd8 :
es_rt_value;
④SLLV、SRLV、SRAV指令
SLLV、SRLV、SRAV复用SLL、SRL、SRA在执行、访存、写回阶段的数据通路,复用ADDU在译码阶段的数据通路。
wire inst_sllv; //新增
wire inst_srlv; //新增
wire inst_srav; //新增
assign inst_sllv = op_d[6'h00] & func_d[6'h04] & sa_d[5'h00]; //新增
assign inst_srlv = op_d[6'h00] & func_d[6'h06] & sa_d[5'h00]; //新增
assign inst_srav = op_d[6'h00] & func_d[6'h07] & sa_d[5'h00]; //新增
//sllv复用sll在执行、访存、写回阶段的数据通路
assign alu_op[ 8] = inst_sll | inst_sllv; //新增inst_sllv
//slrv复用srl在执行、访存、写回阶段的数据通路
assign alu_op[ 9] = inst_srl | inst_srlv; //新增inst_srlv
//srav复用sra在执行、访存、写回阶段的数据通路
assign alu_op[10] = inst_sra | inst_srav; //新增inst_srav
//至于SLLV、SRLV、SRAV复用ADDU在译码阶段的数据通路。不需要采用改动,因为默认的就是rd,rs,rt
//使用默认的数据通路即可
//添加 复用译码级数据通路 inst_need_rs和inst_need_rt是和前递相关的变量
assign inst_need_rs = inst_andi | //rt,rs,imm 新增
inst_ori | //rt,rs,imm 新增
inst_xori ; //rt,rs,imm 新增
assign inst_need_rt = inst_andi | //rt,rs,imm 新增
inst_ori | //rt,rs,imm 新增
inst_xori ; //rt,rs,imm 新增
⑤MULT、MULTU、DIV、DIVU指令
乘除法指令的两个源操作数来自rs和rt寄存器,与ADDU等指令相同,故源操作数的读出可复用ADDU数据通路(即不用做改动,采用默认的通路即可)乘、除法的结果要写入HI、LO寄存器。
id_stage.v
wire inst_mult; //新增有符号乘法
wire inst_multu; //新增无符号乘法
wire inst_div; //新增有符号除法
wire inst_divu; //新增无符号除法
wire [1:0] mul_div_op; //新增乘除法信号
wire mul_div_sign; //新增乘除法符号信号
assign inst_mult = op_d[6'h00] & func_d[6'h18] & rd_d[5'h00] & sa_d[5'h00]; //新增
assign inst_multu = op_d[6'h00] & func_d[6'h19] & rd_d[5'h00] & sa_d[5'h00]; //新增
assign inst_div = op_d[6'h00] & func_d[6'h1a] & rd_d[5'h00] & sa_d[5'h00]; //新增
assign inst_divu = op_d[6'h00] & func_d[6'h1b] & rd_d[5'h00] & sa_d[5'h00]; //新增
//乘除法
assign mul_div_op[0] = inst_mult | inst_multu; //新增 mul_div_op需传递到EXE级
assign mul_div_op[1] = inst_div | inst_divu; //新增
//mul_div_sign是有符号乘、除法的控制信号
assign mul_div_sign = inst_mult | inst_div ; //新增 并传递到EXE级
//将mul_div_op与mul_div_sign传递到exe级
assign ds_to_es_bus = {mul_div_op , //143:142 新增
mul_div_sign, //141:141 新增
}
//添加 复用译码级数据通路 inst_need_rs和inst_need_rt是和前递相关的变量
assign inst_need_rs = inst_mult | //rs,rt 新增
inst_multu | //rs,rt 新增
inst_div | //rs,rt 新增
inst_divu ; //rs,rt 新增
assign inst_need_rt = inst_mult | //rs,rt 新增
inst_multu | //rs,rt 新增
inst_div | //rs,rt 新增
inst_divu ; //rs,rt 新增
exe_stage.v
由于用的chiplab的booth两位乘+华莱士树的乘法器,一个时钟周期内就可以得到乘法结果,由于乘法没有完成信号,不知道乘法什么时候完成,如果在EXE级将乘除法结果写入HI、LO寄存器会导致写入的结果与真实值差一个时钟周期,故将乘除法结果写入HI、LO寄存器放到访存级,这样到访存级时刚好可以得到乘法的正确结果。
//将乘除法结果通过接口传到访存级
module exe_stage(
output [63:0] mul_result, //新增
output [31:0] div_result, //新增
output [31:0] mod_result //新增
);
wire [1:0] es_mul_div_op; //新增乘除法信号
wire es_mul_div_sign; //新增乘除法符号信号
wire div_stall; //新增
wire es_div_enable; //新增
wire div_complete; //新增
wire es_mul_enable; //新增
//接受从译码级传来的乘除法信号
assign {
es_mul_div_op , //143:142 新增
es_mul_div_sign //141:141 新增
} = ds_to_es_bus_r;
//将乘、除法信号传递到访存级用于写HI、LO寄存器
assign es_to_ms_bus = {
es_mul_div_op //76:75 新增
};
//如果在执行乘法指令便阻塞流水线等待乘法执行完成,与前递相关
assign dep_need_stall = es_load_op | es_valid | es_mul_enable;
//等待除法执行完毕
assign div_stall = es_div_enable & ~div_complete;
//等待除法完成
assign es_ready_go = 1'b1 && !div_stall; //新增div_stall
assign es_div_enable = es_mul_div_op[1] & es_valid;
assign es_mul_enable = es_mul_div_op[0];
//调用乘除法模块
div u_div(
.div_clk (clk ),
.reset (reset ),
.div (es_div_enable ),
.div_signed (es_mul_div_sign),
.x (es_alu_src1 ),
.y (es_alu_src2 ),
.s (div_result ),
.r (mod_result ),
.complete (div_complete )
);
mul u_mul(
.mul_clk (clk ),
.reset (reset ),
.mul_signed (es_mul_div_sign),
.x (es_alu_src1 ),
.y (es_alu_src2 ),
.result (mul_result )
);
mem_stage.v
//接受来自执行级传递过来的乘除法结果
module mem_stage(
input [63:0] mul_result, //新增
input [31:0] div_result, //新增
input [31:0] mod_result //新增
);
wire [1:0] es_mul_div_op; //新增乘除法信号
//接受来自执行级的乘、除法信号
assign { es_mul_div_op } = es_to_ms_bus_r;
//乘除法器
reg [31:0] hi; //HI用于存放乘法结果的高32位和除法的余数
reg [31:0] lo; //LO用于存放乘法结果的低32位和除法的商
wire hi_write; //新增 hi写使能
wire lo_write; //新增 lo写使能
assign hi_write = es_mul_div_op[1] | es_mul_div_op[0] ; //当执行除法、乘法时,触发写hi寄存器
assign lo_write = es_mul_div_op[1] | es_mul_div_op[0] ; //当执行除法、乘法时,触发写lo寄存器
//将乘、除法结果写入HI、lo寄存器
//-----{HI/LO寄存器}begin
//要写入HI的数据
always @(posedge clk)
begin
if (hi_write) //
begin
if (es_mul_div_op[0] )
begin
hi <= mul_result[63:32];
end
else if (es_mul_div_op[1])
begin
hi <= mod_result;
end
end
end
//要写入LO的数据
always @(posedge clk)
begin
if (lo_write) //
begin
if (es_mul_div_op[0])
begin
lo <= mul_result[31:0];
end
else if (es_mul_div_op[1])
begin
lo <= div_result; //
end
end
end
//-----{HI/LO寄存器}end
//*******************************************************************************
⑥MTHI、MTlO、MFHI、MFLO指令
MTHI、MTLO读通用寄存器rs的源操作数,写入HI、LO寄存器
MTHI、MTLO读通用寄存器rs的源操作数,这与ADDU等指令类似,只需要将rs寄存器的值写入HI、LO寄存器即可。
MFHI、MFLO读HI、LO寄存器的值,写入通用寄存器
MFHI、MFLO像其他写通用寄存器的指令一样,当读取到HI、LO寄存器的之后随着流水线逐级前进到写回级完成写通用寄存器
因为HI、LO寄存器定义在访存级所以下面要将mthi、mtlo、mfhi、mflo信号传递到访存级
id_stage.v
wire mfhi; //新增
wire mflo; //新增
wire mthi; //新增
wire mtlo; //新增 };
assign mfhi = inst_mfhi; //新增 传递到EXE级
assign mflo = inst_mflo; //新增 传递到EXE级
assign mthi = inst_mthi; //新增 传递到EXE级
assign mtlo = inst_mtlo; //新增 传递到EXE级
//将译码级的mfhi、mflo、mthi、mtlo信号传递到EXE级
assign ds_to_es_bus = {
mfhi , //140:140 新增
mflo , //139:139 新增
mthi , //138:138 新增
mtlo //137:137 新增
//添加 复用译码级数据通路 inst_need_rs是和前递相关的变量
assign inst_need_rs = inst_mthi | //rs 新增
inst_mtlo ; //rs 新增
exe_stage.v
wire mfhi; //新增
wire mflo; //新增
wire mthi; //新增
wire mtlo; //新增
assign {
mfhi , //140:140 新增
mflo , //139:139 新增
mthi , //138:138 新增
mtlo //137:137 新增
} = ds_to_es_bus_r;
assign es_to_ms_bus = {
es_rs_value , //108:77 新增 用于mthi、mtlo指令读rs寄存器的值
mfhi , //74:74 新增
mflo , //73:73 新增
mthi , //72:72 新增
mtlo //71:71 新增
};
mem_stage.v
wire mfhi; //新增
wire mflo; //新增
wire mthi; //新增
wire mtlo; //新增
wire [31:0] ms_rs_value;
assign {
ms_rs_value , //108:77 新增 用于mthi、mtlo指令读rs寄存器的值
mfhi , //74:74 新增
mflo , //73:73 新增
mthi , //72:72 新增
mtlo //71:71 新增
} = es_to_ms_bus_r;
//修改乘除法器
reg [31:0] hi; //HI用于存放乘法结果的高32位和除法的余数
reg [31:0] lo; //LO用于存放乘法结果的低32位和除法的商
wire hi_write; //新增
wire lo_write; //新增
assign hi_write = es_mul_div_op[1] | es_mul_div_op[0] | mthi; //当执行除法、乘法、mthi时,触发写hi寄存器
assign lo_write = es_mul_div_op[1] | es_mul_div_op[0] | mtlo; //当执行除法、乘法、mtlo时,触发写lo寄存器
//-----{HI/LO寄存器}begin
//要写入HI的数据
always @(posedge clk)
begin
if (hi_write) //
begin
if (es_mul_div_op[0] )
begin
hi <= mul_result[63:32];
end
else if (es_mul_div_op[1])
begin
hi <= mod_result;
end
else if (mthi)
begin
hi <= ms_rs_value;
end
end
end
//要写入LO的数据
always @(posedge clk)
begin
if (lo_write) //
begin
if (es_mul_div_op[0])
begin
lo <= mul_result[31:0];
end
else if (es_mul_div_op[1])
begin
lo <= div_result; //
end
else if (mtlo)
begin
lo <= ms_rs_value;
end
end
end
//-----{HI/LO寄存器}end
//mfhi.mflo指令读取到hi、lo寄存器的值后通过复用ms_final_result的数据通路传递到写回级,写入通用寄存器。
assign ms_final_result = mfhi ? hi :
mflo ? lo :
ms_res_from_mem ? mem_result
: ms_alu_result;
至此第五章 5.4任务与实践所有指令添加完成