本文主要是介绍自己动手写CPU_step6_算术运算指令,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
序
接上篇,本篇开始实现算数运算指令,包括加减乘除,加减比较好实现,乘除则需要考虑指令周期与其他指令的周期长度不一致问题,可能会导致流水线效率下降,本篇先实现简单的算术运算。
指令定义
`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 32boutput reg [`RegDataBus] reg2, //源操作数2 32boutput 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 @ (*) beginif (rst) beginreg1_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 beginreg1_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) beginreg1_rden <= 1'd1;reg2_rden <= 1'd1;reg_wb <= 1'd1;case (func) `EXE_AND: beginaluop <= `EXE_AND_FUNC;end `EXE_OR: beginaluop <= `EXE_OR_FUNC;end`EXE_XOR: beginaluop <= `EXE_XOR_FUNC;end`EXE_NOR: beginaluop <= `EXE_NOR_FUNC;end`EXE_SLLV: beginaluop <= `EXE_SLL_FUNC;end`EXE_SRLV: beginaluop <= `EXE_SRLV_FUNC;end`EXE_SRAV: beginaluop <= `EXE_SRAV_FUNC;end`EXE_SLL: beginreg1_rden <= 1'd0;imm[4:0] <= shmat;aluop <= `EXE_SLL_FUNC;end`EXE_SRL: beginreg1_rden <= 1'd0;imm[4:0] <= shmat;aluop <= `EXE_SRL_FUNC;end`EXE_SRA: beginreg1_rden <= 1'd0;imm[4:0] <= shmat;aluop <= `EXE_SRA_FUNC;end`EXE_MOVN: beginif (reg2 == 32'd0) beginreg_wb <= 1'b0;end else beginreg_wb <= 1'b1;aluop <= `EXE_MOVN_FUNC;endend`EXE_MOVZ: beginif (reg2 == 32'd0) beginreg_wb <= 1'b1;aluop <= `EXE_MOVZ_FUNC;end else beginreg_wb <= 1'b0;endend`EXE_MFHI: beginreg1_rden <= 1'b0;reg2_rden <= 1'b0;aluop <= `EXE_MFHI_FUNC;end`EXE_MFLO: beginreg1_rden <= 1'b0;reg2_rden <= 1'b0;aluop <= `EXE_MFLO_FUNC; end`EXE_MTHI: beginreg2_rden <= 1'b0;reg_wb <= 1'b0;aluop <= `EXE_MTHI_FUNC;end`EXE_MTLO: beginreg2_rden <= 1'b0;reg_wb <= 1'b0;aluop <= `EXE_MTLO_FUNC;end`EXE_ADD: beginaluop <= `EXE_ADD_FUNC;end`EXE_ADDU: beginaluop <= `EXE_ADDU_FUNC;end`EXE_SUB: beginaluop <= `EXE_SUB_FUNC;end`EXE_SUBU: beginaluop <= `EXE_SUBU_FUNC;enddefault: aluop <= 7'd0;endcaseend else if (op == `EXE_SPECIAL2) begin //由FUNC字段决定,操作码为011100的指令reg1_rden <= 1'd1;reg2_rden <= 1'd1;reg_wb <= 1'd1;case (op)default: beginreg1_rden <= 1'd0;reg2_rden <= 1'd0;reg_wb <= 1'd0;endendcaseend else beginreg1_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: beginaluop <= `EXE_ANDI_OP;end `EXE_XORI: beginaluop <= `EXE_XORI_OP;end`EXE_LUI: beginreg1_rden <= 1'b0;aluop <= `EXE_LUI_OP;end`EXE_ADDI: beginaluop <= `EXE_ADDI_OP;end`EXE_ADDIU: beginaluop <= `EXE_ADDIU_OP;enddefault:aluop <= 7'd0; endcaseendend endalways @ (*) beginif (rst) beginreg1 <= 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 beginreg1 <= 32'd0;end endalways @ (*) beginif (rst) beginreg2 <= 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 beginreg2 <= 32'd0;end endendmodule
本篇,我们只添加实现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 @ (*) beginif (rst) beginreg_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 beginreg_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: beginreg_wb_data <= reg1 | reg2;end`EXE_ANDI_OP,`EXE_AND_FUNC: beginreg_wb_data <= reg1 & reg2;end`EXE_XORI_OP,`EXE_XOR_FUNC: beginreg_wb_data <= reg1 ^ reg2;end`EXE_LUI_OP: beginreg_wb_data <= {reg2[15:0],reg2[31:16]};end`EXE_NOR_FUNC: beginreg_wb_data <= ~(reg1 | reg2);end`EXE_SLL_FUNC,`EXE_SLLV_FUNC: beginreg_wb_data <= reg2 << reg1[4:0];end`EXE_SRL_FUNC,`EXE_SRLV_FUNC: beginreg_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: beginreg_wb_data <= reg1;end`EXE_MFHI_FUNC: beginif (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 beginreg_wb_data <= hi_reg_i; //正常读取HI寄存器endend`EXE_MFLO_FUNC: beginif (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;endend`EXE_MTHI_FUNC: beginhi_wren <= 1'b1;hi_reg_o <= reg1;lo_reg_o <= lo_reg_i;end`EXE_MTLO_FUNC: beginlo_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) beginreg_wb_o <= 1'd0;end else begin reg_wb_o <= 1'd1;endend`EXE_ADDU_FUNC,`EXE_SUBU_FUNC,`EXE_ADDIU_OP: begin //无符号数无需判断溢出,直接截断保存reg_wb_data <= res;enddefault: beginreg_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;endendcaseendendendmodule
有符号加减时需要判断溢出,如果溢出的话,就不讲结果写回目的寄存器;无符号加减则不需要考虑溢出问题,直接高位截断即可。
仿真测试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)补码有测试结果可知,这两条减法指令也是正确无误。
加减指令都测试无误,下一篇将介绍其他指令的设计与测试!
这篇关于自己动手写CPU_step6_算术运算指令的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!