序
接上篇,本篇开始实现算数运算指令,包括加减乘除,加减比较好实现,乘除则需要考虑指令周期与其他指令的周期长度不一致问题,可能会导致流水线效率下降,本篇先实现简单的算术运算。
指令定义
`define EXE_ADD 6'b100000 // rs + rt -> rd(检查溢出) `define EXE_ADDU 6'b100001 // rs + rt -> rd(不检查溢出) `define EXE_SUB 6'b100010 // rs - rt -> rd `define EXE_SUBU 6'b100011 // rs - rt -> rd `define EXE_SLT 6'b101010 // (rs < rt) -> rd(比较结果为1/0) `define EXE_SLTU 6'b101011 // (rs < rt) -> rd(1/0) `define EXE_CLZ 6'b100000 // clz(rs) -> rd(从最高位开始直到遇到1,之前所有0的个数保存至rd) `define EXE_CLO 6'b100001 // clo(rs) -> rd(从最高位开始直到遇到0,之前所有1的个数保存至rd) `define EXE_MUL 6'b000010 // rs × rt -> rd(保存结果低32位) `define EXE_MULT 6'b011000 // rs × rt -> {HI,LO} `define EXE_MULTU 6'b011001 // rs × rt -> {HI,LO} 无符号 `define EXE_ADDI 6'b001000 //立即数加法 rs add imm -> rt `define EXE_ADDIU 6'b001001 //无符号立即数加法 rs add imm -> rt `define EXE_SLTI 6'b001010 //立即数比较指令 (rs < imm) -> rt(1/0) `define EXE_SLTIU 6'b001011 //无符号立即数比较 (rs < imm) -> rt(1/0)
上面的是R型指令,下面的是I型指令
简单的算术运算指令和之前篇的额添加指令其实是一样的,只需要修改EX模块和ID模块即可。
ID模块
`include "defines.v" //译码阶段 对if_id传入的指令进行译码,分离出操作数和操作码 module id( input rst, input [`InstAddrBus] pc, input [`InstDataBus] inst, //读通用寄存器 读取 input [`RegDataBus] reg1_data, input [`RegDataBus] reg2_data, output reg reg1_rden, output reg reg2_rden, output reg [`RegAddrBus] reg1_addr, //源操作数1的地址 output reg [`RegAddrBus] reg2_addr, //源操作数2的地址 //送到ex阶段的值 output reg [`RegDataBus] reg1, //源操作数1 32b output reg [`RegDataBus] reg2, //源操作数2 32b output reg reg_wb, //写回目的寄存器标志 output reg [`RegAddrBus] reg_wb_addr,//写回目的寄存器地址 output reg [`AluOpBus] aluop, //操作码 //相邻指令的冲突,由EX阶段给出数据旁路 input ex_wr_en, //处于执行阶段的指令是否要写目的寄存器 input [`RegDataBus] ex_wr_data, input [`RegAddrBus] ex_wr_addr, //相隔一条指令的冲突,由MEM阶段给出数据旁路 input mem_wr_en, //处于访存阶段指令是否要写目的寄存器 input [`RegDataBus] mem_wr_data, input [`RegAddrBus] mem_wr_addr ); wire [5:0] op = inst[31:26]; //从指令中获取操作码 高6位 wire [5:0] func = inst[5:0]; //从指令中获取功能号确定指令类型 低6位 wire [4:0] shmat = inst[10:6]; //部分移位位数不从寄存器取值,直接由shmat给出 reg [`RegDataBus] imm; //立即数 always @ (*) begin if (rst) begin reg1_rden <= 1'd0; reg2_rden <= 1'd0; reg1_addr <= 5'd0; reg2_addr <= 5'd0; imm <= 32'd0; reg_wb <= 1'd0; reg_wb_addr <= 5'd0; aluop <= 7'd0; end else begin reg1_rden <= 1'd0; reg2_rden <= 1'd0; reg1_addr <= inst[25:21]; //默认从指令中读取操作数1地址 reg2_addr <= inst[20:16]; //默认从指令中读取操作数2地址 imm <= 32'd0; reg_wb <= 1'd0; reg_wb_addr <= inst[15:11]; //默认结果地址寄存器rd aluop <= 7'd0; //操作类型 if (op == `EXE_SPECIAL) begin reg1_rden <= 1'd1; reg2_rden <= 1'd1; reg_wb <= 1'd1; case (func) `EXE_AND: begin aluop <= `EXE_AND_FUNC; end `EXE_OR: begin aluop <= `EXE_OR_FUNC; end `EXE_XOR: begin aluop <= `EXE_XOR_FUNC; end `EXE_NOR: begin aluop <= `EXE_NOR_FUNC; end `EXE_SLLV: begin aluop <= `EXE_SLL_FUNC; end `EXE_SRLV: begin aluop <= `EXE_SRLV_FUNC; end `EXE_SRAV: begin aluop <= `EXE_SRAV_FUNC; end `EXE_SLL: begin reg1_rden <= 1'd0; imm[4:0] <= shmat; aluop <= `EXE_SLL_FUNC; end `EXE_SRL: begin reg1_rden <= 1'd0; imm[4:0] <= shmat; aluop <= `EXE_SRL_FUNC; end `EXE_SRA: begin reg1_rden <= 1'd0; imm[4:0] <= shmat; aluop <= `EXE_SRA_FUNC; end `EXE_MOVN: begin if (reg2 == 32'd0) begin reg_wb <= 1'b0; end else begin reg_wb <= 1'b1; aluop <= `EXE_MOVN_FUNC; end end `EXE_MOVZ: begin if (reg2 == 32'd0) begin reg_wb <= 1'b1; aluop <= `EXE_MOVZ_FUNC; end else begin reg_wb <= 1'b0; end end `EXE_MFHI: begin reg1_rden <= 1'b0; reg2_rden <= 1'b0; aluop <= `EXE_MFHI_FUNC; end `EXE_MFLO: begin reg1_rden <= 1'b0; reg2_rden <= 1'b0; aluop <= `EXE_MFLO_FUNC; end `EXE_MTHI: begin reg2_rden <= 1'b0; reg_wb <= 1'b0; aluop <= `EXE_MTHI_FUNC; end `EXE_MTLO: begin reg2_rden <= 1'b0; reg_wb <= 1'b0; aluop <= `EXE_MTLO_FUNC; end `EXE_ADD: begin aluop <= `EXE_ADD_FUNC; end `EXE_ADDU: begin aluop <= `EXE_ADDU_FUNC; end `EXE_SUB: begin aluop <= `EXE_SUB_FUNC; end `EXE_SUBU: begin aluop <= `EXE_SUBU_FUNC; end default: aluop <= 7'd0; endcase end else if (op == `EXE_SPECIAL2) begin //由FUNC字段决定,操作码为011100的指令 reg1_rden <= 1'd1; reg2_rden <= 1'd1; reg_wb <= 1'd1; case (op) default: begin reg1_rden <= 1'd0; reg2_rden <= 1'd0; reg_wb <= 1'd0; end endcase end else begin reg1_rden <= 1'd1; //需要读取操作数1 rs寄存器的值 reg2_rden <= 1'd0; //不需要读取操作数2 rt寄存器值, imm <= {16'h0, inst[15:0]}; reg_wb <= 1'd1; reg_wb_addr <= inst[20:16]; case (op) `EXE_ORI: begin //或指令 rs寄存器值是操作数1,imm是操作数2,结果放到rt寄存器 aluop <= `EXE_ORI_OP; end `EXE_ANDI: begin aluop <= `EXE_ANDI_OP; end `EXE_XORI: begin aluop <= `EXE_XORI_OP; end `EXE_LUI: begin reg1_rden <= 1'b0; aluop <= `EXE_LUI_OP; end `EXE_ADDI: begin aluop <= `EXE_ADDI_OP; end `EXE_ADDIU: begin aluop <= `EXE_ADDIU_OP; end default: aluop <= 7'd0; endcase end end end always @ (*) begin if (rst) begin reg1 <= 32'd0; end else if (reg1_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg1_addr) begin //执行阶段旁路 reg1 <= ex_wr_data; end else if (reg1_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg1_addr) begin //访存阶段旁路 reg1 <= mem_wr_data; end else if (reg1_rden == 1'd1) begin //从通用寄存器获取操作数 reg1 <= reg1_data; end else if (reg1_rden == 1'd0) begin //从指令中获取操作数 reg1 <= imm; end else begin reg1 <= 32'd0; end end always @ (*) begin if (rst) begin reg2 <= 32'd0; end else if (reg2_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg2_addr) begin //执行阶段旁路 reg2 <= ex_wr_data; end else if (reg2_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg2_addr) begin //访存阶段旁路 reg2 <= mem_wr_data; end else if (reg2_rden == 1'd1) begin //从通用寄存器获取操作数 reg2 <= reg2_data; end else if (reg2_rden == 1'd0) begin //从指令中获取操作数 reg2 <= imm; end else begin reg2 <= 32'd0; end end endmodule
本篇,我们只添加实现6条指令代码,分别是有符号加减、无符号加减、立即数有符号加和立即数无符号加法运算。鉴于之前给的主要是代码和结果,不太好理解,本篇开始一步一步的进行仿真,尽量把步骤详细写出来。
EX模块
`include "defines.v" //执行阶段,根据译码阶段得到的操作码和操作数进行运算,得到结果 module ex( input rst, input [`AluOpBus] aluop, input [`RegDataBus] reg1, input [`RegDataBus] reg2, input reg_wb_i, input [`RegAddrBus] reg_wb_addr_i, output reg reg_wb_o, output reg [`RegAddrBus] reg_wb_addr_o, output reg [`RegDataBus] reg_wb_data, //写回数据到目的寄存器 //HILO寄存器 input [`RegDataBus] hi_reg_i, //读取HI寄存器数据 input [`RegDataBus] lo_reg_i, //读取LO寄存器数据 output reg [`RegDataBus] hi_reg_o, //写入HI寄存器数据 output reg [`RegDataBus] lo_reg_o, //写入LO寄存器数据 output reg hi_wren, //HI寄存器写使能 output reg lo_wren, //LO寄存器写使能 // HILO寄存器旁路 input [`RegDataBus] wb_hi_i, input [`RegDataBus] wb_lo_i, input wb_hi_wren_i, //有指令写HI,从写回阶段给出旁路(隔一条指令) input wb_lo_wren_i, //有指令写LO,从写回阶段给出旁路(隔一条指令) input [`RegDataBus] mem_hi_i, input [`RegDataBus] mem_lo_i, input mem_hi_wren_i, //有指令写HI,从访存阶段给出旁路(上一条指令) input mem_lo_wren_i //有指令写LO,从访存阶段给出旁路(上一条指令) ); //需要转换成补码的指令 wire reg2_mux = ((aluop==`EXE_SUB_FUNC)||(aluop==`EXE_SUBU_FUNC)||(aluop==`EXE_SLT_FUNC)) ? (~reg2+1) : reg2; wire [31:0] res = reg1 + reg2_mux; //判断加减的结果是否溢出,减法转换成加法 //overflow flag 两个正数相加得负或两个负数相加得正则溢出 wire of = ((!reg1[31]&&!reg2[31]&&res[31])||(reg1[31]&®2[31]&&!res[31])); always @ (*) begin if (rst) begin reg_wb_o <= 1'd0; reg_wb_addr_o <= 5'd0; reg_wb_data <= 32'd0; hi_reg_o <= 32'd0; lo_reg_o <= 32'd0; hi_wren <= 1'b0; lo_wren <= 1'b0; end else begin reg_wb_o <= reg_wb_i; reg_wb_addr_o <= reg_wb_addr_i; reg_wb_data <= 32'd0; hi_wren <= 1'b0; lo_wren <= 1'b0; hi_reg_o <= 32'd0; lo_reg_o <= 32'd0; case (aluop) `EXE_ORI_OP,`EXE_OR_FUNC: begin reg_wb_data <= reg1 | reg2; end `EXE_ANDI_OP,`EXE_AND_FUNC: begin reg_wb_data <= reg1 & reg2; end `EXE_XORI_OP,`EXE_XOR_FUNC: begin reg_wb_data <= reg1 ^ reg2; end `EXE_LUI_OP: begin reg_wb_data <= {reg2[15:0],reg2[31:16]}; end `EXE_NOR_FUNC: begin reg_wb_data <= ~(reg1 | reg2); end `EXE_SLL_FUNC,`EXE_SLLV_FUNC: begin reg_wb_data <= reg2 << reg1[4:0]; end `EXE_SRL_FUNC,`EXE_SRLV_FUNC: begin reg_wb_data <= reg2 >> reg1[4:0]; end `EXE_SRA_FUNC,`EXE_SRAV_FUNC: begin //算术移位也可以直接使用>>> reg_wb_data <= ({32{reg2[31]}} << (6'd32 - {1'b0,reg1[4:0]})) | reg2 >> reg1[4:0]; end `EXE_MOVN_FUNC,`EXE_MOVZ_FUNC: begin reg_wb_data <= reg1; end `EXE_MFHI_FUNC: begin if (mem_hi_wren_i) begin //访存阶段数据旁路 reg_wb_data <= mem_hi_i; end else if (wb_hi_wren_i) begin //写回阶段数据旁路 reg_wb_data <= wb_hi_i; end else begin reg_wb_data <= hi_reg_i; //正常读取HI寄存器 end end `EXE_MFLO_FUNC: begin if (mem_lo_wren_i) begin //旁路 reg_wb_data <= mem_lo_i; end else if (wb_lo_wren_i) begin //旁路 reg_wb_data <= wb_lo_i; end else begin //正常读取LO寄存器 reg_wb_data <= lo_reg_i; end end `EXE_MTHI_FUNC: begin hi_wren <= 1'b1; hi_reg_o <= reg1; lo_reg_o <= lo_reg_i; end `EXE_MTLO_FUNC: begin lo_wren <= 1'b1; lo_reg_o <= reg1; hi_reg_o <= hi_reg_i; end `EXE_ADD_FUNC,`EXE_SUB_FUNC, `EXE_ADDI_OP: begin //加法减法都是加法实现 reg_wb_data <= res; if (of) begin reg_wb_o <= 1'd0; end else begin reg_wb_o <= 1'd1; end end `EXE_ADDU_FUNC,`EXE_SUBU_FUNC, `EXE_ADDIU_OP: begin //无符号数无需判断溢出,直接截断保存 reg_wb_data <= res; end default: begin reg_wb_o <= 1'd0; reg_wb_addr_o <= 5'd0; reg_wb_data <= 32'd0; hi_reg_o <= 32'd0; lo_reg_o <= 32'd0; hi_wren <= 1'b0; lo_wren <= 1'b0; end endcase end end endmodule
有符号加减时需要判断溢出,如果溢出的话,就不讲结果写回目的寄存器;无符号加减则不需要考虑溢出问题,直接高位截断即可。
仿真测试1
3c018000: LUI reg1=>8000,0000
3c028000: LUI reg2=>8000,0000
34210001: ORI reg1=>8000,0001
34420002: ORI reg2=>8000,0002
00221820: ADD reg3=>溢出,无结果
00222021: ADDU reg4=>0000,0003 截断有测试结果可知,ADD指令的有符号和无符号指令正确执行,在第五个时钟周期得到结果。
仿真测试2
00242822: SUB reg1 - reg3 => reg4 溢出无结果
00243023: SUBU reg1 - reg3 => reg5 无符号截断 8000,0001+(3)补码有测试结果可知,这两条减法指令也是正确无误。
加减指令都测试无误,下一篇将介绍其他指令的设计与测试!