序
上一篇中我们解决了流水线的数据相关问题,本篇将添加多条逻辑运算和移位运算指令,这些指令的格式严格按照MIPS的指令格式设计。
MIPS指令格式
由于本人也是处于学习的一个过程,如有不对之处,还请大牛指正。
就逻辑运算和移位运算两中类型的指令而言,我将他们分为两类,一类是由OP操作码(高6位)决定的指令;另一类是由FUNCTION功能字段(低6位)决定的;如果是由FUNCTION决定的指令,其OP字段必为全0;按照这个规律设计将它们定义出来。
指令定义如下
//R型指令通过OP=6'd0标识 `define EXE_SPECIAL 6'b000000 //R型指令 //I型指令通过OP字段区分 `define EXE_ANDI 6'b001100 //与指令(立即数参与) rs and imm -> rt `define EXE_ORI 6'b001101 //或指令(立即数参与) rs or imm -> rd `define EXE_XORI 6'b001110 //异或指令(立即数参与) rs xor imm -> rt `define EXE_LUI 6'b001111 //立即数扩展指令 {imm,16'h0000} -> rt //R型(三寄存器)指令的OP字段(高6位)为全0,用func字段(低6位)区分不同指令 `define EXE_AND 6'b100100 //与指令 rs and rt -> rd `define EXE_OR 6'b100101 //或指令 rs or rt -> rd `define EXE_XOR 6'b100110 //异或指令 rs xor rt -> rd `define EXE_NOR 6'b100111 //或非指令 rs nor rt -> rd `define EXE_SLL 6'b000000 //逻辑左移 rt << shmat -> rd `define EXE_SLLV 6'b000100 //逻辑左移 rt << rs -> rd `define EXE_SRL 6'b000010 //逻辑右移 rt >> shmat ->rd `define EXE_SRLV 6'b000110 //逻辑右移 rt >> rs -> rd `define EXE_SRA 6'b000011 //算数右移 rt >> shmat -> rd `define EXE_SRAV 6'b000111 //算术右移 rt >> rs -> rd //aluop EX阶段输入的操作码通过增加一位来判断是通过OP还是FUNC来决定指令类型 `define EXE_ORI_OP 7'b1_001101 //首位为1标识使用OP决定指令类型 `define EXE_ANDI_OP 7'b1_001100 `define EXE_XORI_OP 7'b1_001110 `define EXE_LUI_OP 7'b1_001111 `define EXE_AND_FUNC 7'b0_100100 //首位为0标识OP段全0使用FUNC字段决定指令类型 `define EXE_OR_FUNC 7'b0_100101 `define EXE_XOR_FUNC 7'b0_100110 `define EXE_NOR_FUNC 7'b0_100111 `define EXE_SLL_FUNC 7'b0_000000 `define EXE_SLLV_FUNC 7'b0_000100 `define EXE_SRL_FUNC 7'b0_000010 `define EXE_SRLV_FUNC 7'b0_000110 `define EXE_SRA_FUNC 7'b0_000011 `define EXE_SRAV_FUNC 7'b0_000111
本次需要设计的指令如上所示。由图可知,我们使用增加一位的方式将这两种类型的指令统一起来传送到执行阶段,这样使得执行阶段能够通过一个统一的接口来实现不同的指令内容。
接下来只需要修改译码部分和执行部分。
译码阶段
//译码阶段 对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 ); /* ORI指令 31:26 25:21 20:16 15:0 op rs rt imm */ 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 <= {1'b1,inst[31:26]}; //高6位为操作码 //操作类型 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 default: aluop <= 7'd0; 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 aluop <= `EXE_LUI_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
上图代码主要修改了指令解析部分,若是OP字段全0的指令,说明需要使用三个寄存器,reg1和reg2寄存器的值都需要读取,并且默认目的寄存器就是都是统一的,唯一不同的就是部分移位指令由指令shmat字段给出移位位数而不读取寄存器获取,此时将shmat字段赋给imm,不都寄存器时默认读imm。由OP字段决定的指令和之前篇中的ORI指令实现类似,不再赘述。
执行阶段
//执行阶段,根据译码阶段得到的操作码和操作数进行运算,得到结果 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 //写回数据到目的寄存器 ); always @ (*) begin if (rst) begin reg_wb_o <= 1'd0; reg_wb_addr_o <= 5'd0; reg_wb_data <= 32'd0; end else begin reg_wb_o <= reg_wb_i; reg_wb_addr_o <= reg_wb_addr_i; 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 default: reg_wb_data <= 32'd0; endcase end end endmodule
仿真测试
inst1: 3401ffff (001101)ORI imm | reg0 => reg1; res:0000_ffff
inst2: 3c02ffff (001101)LUI op(imm) => reg2; res:ffff_0000
inst3: 3443ffff (001100)ANDI imm & reg2 => reg3; res:ffff_ffff
inst4: 00222024 (100100)AND reg1 & reg2 => reg4; res:0000_0000
inst5: 00222825 (100101)OR reg1 | reg2 => reg5; res:ffff_ffff
inst6: 00853026 (100110)XOR reg4 & reg5 => reg6; res:ffff_ffff
inst7: 00853827 (100111)NOR ~(reg4 | reg5) => reg7; res:0000_0000
inst8: 00054200 (000000)SLL reg5 << 8 => reg8; res:ffff_ff00
inst9: 00054e02 (000010)SRL reg5 >> 24 => reg9; res:0000_00ff
inst10: 00085103 (000011)SRA reg8 >> 4 => reg10; res:ffff_fff0
inst11: 00095903 (000011)SRA reg9 >> 4 => reg11; res:0000_000f
inst12: 01656004 (000100)SLLV reg5 << reg11 => reg12; res:ffff_8000
inst13: 01656806 (000110)SRLV reg5 >> reg11 => reg13; res:0001_ffff
inst14: 01617007 (000111)SRAV reg1 >>> reg11 => reg14; res:0000_0001
inst15: 01627807 (000111)SRAV reg2 >>> reg11 => reg15; res:ffff_fffe
inst16: 01cf8024 (100100)AND reg14 & reg15 => reg16; res:0000_0000
仿真结果
由仿真结果可知,写回寄存器的值都是符合我们实现计算结果预期的。
验证了这些指令的正确性。
下一篇进行移动指令的设计!
欢迎对次有兴趣的友友积极交流!