自己动手写CPU_step4_逻辑运算|移位指令

上一篇中我们解决了流水线的数据相关问题,本篇将添加多条逻辑运算和移位运算指令,这些指令的格式严格按照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

 
仿真结果


由仿真结果可知,写回寄存器的值都是符合我们实现计算结果预期的。

验证了这些指令的正确性。

下一篇进行移动指令的设计!

欢迎对次有兴趣的友友积极交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值