首页 > 代码库 > 自己动手写CPU之第七阶段(2)——简单算术操作指令实现过程

自己动手写CPU之第七阶段(2)——简单算术操作指令实现过程

将陆续上传本人写的新书《自己动手写CPU》,今天是第25篇,我尽量每周四篇

亚马逊的预售地址如下,欢迎大家围观呵!

http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4

China-pub的预售地址如下:

http://product.china-pub.com/3804025


7.2 简单算术操作指令实现思路

      虽然简单算术操作指令的数目比较多,有15条,但实现方式都是相似的,与前几章逻辑、移位操作指令的实现方式也很类似,不需要增加新的模块、新的接口,只需要修改流水线译码阶段的ID模块、执行阶段的EX模块即可。实现思路如下。

      (1)修改流水线译码阶段的ID模块,添加对上述简单算术操作指令的译码,给出运算类型alusel_o、运算子类型aluop_o、要写入的目的寄存器地址wd_o等信息,同时根据需要读取地址为rsrt的通用寄存器的值。

      (2)修改流水线执行阶段的EX模块,依据传入的信息,进行运算,得到运算结果,确定最终要写目的寄存器的信息(包含:是否写、写入的目的寄存器地址、写入的值),并将这些信息传递到访存阶段。

      (3)上述信息会一直传递到回写阶段,最后修改目的寄存器。

7.3 修改OpenMIPS以实现简单算术操作指令

7.3.1 修改译码阶段的ID模块 

      在译码阶段要增加对简单算术操作指令的分析,分析的前提是能判断出指令种类,根据图7-17-4可以给出如图7-5所示的确定指令种类的过程。


      其中涉及的宏定义如下,正是图7-5中各个指令的指令码或功能码。在本书附带光盘Code\Chapter7_1目录下的defines.v文件中可以找到这些宏定义。

`define EXE_SLT   6'b101010
`define EXE_SLTU  6'b101011
`define EXE_SLTI  6'b001010
`define EXE_SLTIU 6'b001011
`define EXE_ADD   6'b100000
`define EXE_ADDU  6'b100001
`define EXE_SUB   6'b100010
`define EXE_SUBU  6'b100011
`define EXE_ADDI  6'b001000
`define EXE_ADDIU 6'b001001
`define EXE_CLZ   6'b100000
`define EXE_CLO   6'b100001

`define EXE_MULT  6'b011000
`define EXE_MULTU 6'b011001
`define EXE_MUL   6'b000010
......
`define EXE_SPECIAL_INST  6'b000000
`define EXE_REGIMM_INST   6'b000001
`define EXE_SPECIAL2_INST 6'b011100

      修改ID模块的代码如下,完整代码位于本书附带光盘Code\Chapter7_1目录下的id.v文件。

module id(
  ......
);
......
always @ (*) begin	
if (rst == `RstEnable) begin
	  ......
end else begin
  aluop_o     <= `EXE_NOP_OP;
  alusel_o    <= `EXE_RES_NOP;
  wd_o        <= inst_i[15:11];           // 默认目的寄存器地址wd_o
  wreg_o      <= `WriteDisable;
  instvalid   <= `InstInvalid;
  reg1_read_o <= 1'b0;
  reg2_read_o <= 1'b0;
  reg1_addr_o <= inst_i[25:21];           // 默认的reg1_addr_o
  reg2_addr_o <= inst_i[20:16];           // 默认的reg2_addr_o
  imm         <= `ZeroWord;
  case (op)
    `EXE_SPECIAL_INST: begin              // op等于SPECIAL
      case (op2)                        
        5'b00000: begin               // op2等于5'b00000
          case (op3)
            ......
           `EXE_SLT: begin                       // slt指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_SLT_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
           `EXE_SLTU: begin                      // sltu指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_SLTU_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
            `EXE_ADD: begin                       // add指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_ADD_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
            `EXE_ADDU: begin                      // addu指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_ADDU_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
           `EXE_SUB: begin                        // sub指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_SUB_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
           `EXE_SUBU: begin                      // subu指令
              wreg_o      <= `WriteEnable;
              aluop_o     <= `EXE_SUBU_OP;
              alusel_o    <= `EXE_RES_ARITHMETIC;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1;
              instvalid   <= `InstValid;
            end
           `EXE_MULT: begin                      // mult指令
              wreg_o      <= `WriteDisable;
              aluop_o     <= `EXE_MULT_OP;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1; 
              instvalid   <= `InstValid;
            end
           `EXE_MULTU: begin                      // multu指令
              wreg_o      <= `WriteDisable;
              aluop_o     <= `EXE_MULTU_OP;
              reg1_read_o <= 1'b1;
              reg2_read_o <= 1'b1; 
              instvalid   <= `InstValid;
            end 
            default:	begin
            end
         endcase  // end case op3
        end
     default: begin
     end
    endcase	    // end case op2
  end	 
      ......
     `EXE_SLTI: begin                        // slti指令
        wreg_o      <= `WriteEnable;
        aluop_o     <= `EXE_SLT_OP;
        alusel_o    <= `EXE_RES_ARITHMETIC; 
        reg1_read_o <= 1'b1;	
        reg2_read_o <= 1'b0;
        imm         <= {{16{inst_i[15]}}, inst_i[15:0]};
        wd_o        <= inst_i[20:16];
        instvalid   <= `InstValid;
      end
    `EXE_SLTIU:	begin                        // sltiu指令
        wreg_o      <= `WriteEnable;
        aluop_o     <= `EXE_SLTU_OP;
        alusel_o    <= `EXE_RES_ARITHMETIC; 
        reg1_read_o <= 1'b1;
        reg2_read_o <= 1'b0;
        imm         <= {{16{inst_i[15]}}, inst_i[15:0]};
        wd_o        <= inst_i[20:16];
        instvalid   <= `InstValid;
      end
    `EXE_ADDI: begin                         // addi指令
        wreg_o      <= `WriteEnable;
        aluop_o     <= `EXE_ADDI_OP;
        alusel_o    <= `EXE_RES_ARITHMETIC; 
        reg1_read_o <= 1'b1;
        reg2_read_o <= 1'b0;
        imm         <= {{16{inst_i[15]}}, inst_i[15:0]};
        wd_o        <= inst_i[20:16];	
        instvalid   <= `InstValid;
      end
     `EXE_ADDIU:	begin                       // addiu指令
         wreg_o      <= `WriteEnable;
         aluop_o     <= `EXE_ADDIU_OP;
         alusel_o    <= `EXE_RES_ARITHMETIC; 
         reg1_read_o <= 1'b1;	reg2_read_o <= 1'b0;
         imm         <= {{16{inst_i[15]}}, inst_i[15:0]};
         wd_o        <= inst_i[20:16];
         instvalid   <= `InstValid;
      end
     `EXE_SPECIAL2_INST:		begin          // op等于SPECIAL2
         case ( op3 )
          `EXE_CLZ: begin                          // clz指令
             wreg_o      <= `WriteEnable;	
             aluop_o     <= `EXE_CLZ_OP;
             alusel_o    <= `EXE_RES_ARITHMETIC; 
             reg1_read_o <= 1'b1;
             reg2_read_o <= 1'b0;
             instvalid   <= `InstValid;	
           end
          `EXE_CLO: begin                         // clo指令
             wreg_o      <= `WriteEnable;
             aluop_o     <= `EXE_CLO_OP;
             alusel_o    <= `EXE_RES_ARITHMETIC; 
             reg1_read_o <= 1'b1;
             reg2_read_o <= 1'b0;
             instvalid   <= `InstValid;
           end
          `EXE_MUL: begin                         // mul指令
             wreg_o      <= `WriteEnable;
             aluop_o     <= `EXE_MUL_OP;
             alusel_o    <= `EXE_RES_MUL; 
             reg1_read_o <= 1'b1;
             reg2_read_o <= 1'b1;
             instvalid   <= `InstValid;
           end
          default:	begin
          end
        endcase      //EXE_SPECIAL_INST2 case
       end	
     default: begin
     end
endcase		  //case op
		  
......

endmodule

      对任一条指令而言,译码工作的主要内容是:确定要读取的寄存器情况、要执行的运算、要写的目的寄存器等三个方面的信息。下面对其中几个典型指令的译码过程进行解释。

1add指令的译码过程

      add指令译码需要设置的三个方面内容如下,addusubsubu指令的译码过程可以参考add指令。

      (1)要读取的寄存器情况:add指令需要读取rsrt寄存器的值,所以设置reg1_read_oreg2_read_o1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是add指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是add指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。

      (2)要执行的运算:add指令是算术运算中的加法操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETICaluop_o赋值为EXE_ADD_OP

      (3)要写入的目的寄存器:add指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是add指令中的rd

2addi指令的译码过程

      addi指令译码需要设置的三个方面内容如下,addiusubisubiu指令的译码过程可以参考addi指令。

      (1)要读取的寄存器情况:addi指令只需要读取rs寄存器的值,所以设置reg1_read_o1reg2_read_o0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是addi指令中的rs。设置reg2_read_o0,表示使用立即数作为参与运算的第二个操作数。imm就是指令中的立即数进行符号扩展后的值。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是imm的值。

      (2)要执行的运算:addi指令是算术运算中的加法操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETICaluop_o赋值为EXE_ADDI_OP

      (3)要写入的目的寄存器:addi指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置要写入的目的寄存器地址wd_o是指令中16-20bit的值,正是addi指令中的rt

3slt指令的译码过程

      slt指令译码需要设置的三个方面内容如下,sltu指令的译码过程可以参考slt指令。

      (1)要读取的寄存器情况:slt指令需要读取rsrt寄存器的值,所以设置reg1_read_oreg2_read_o1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slt指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是slt指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。

      (2)要执行的运算:slt指令是算术运算中的比较操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETICaluop_o赋值为EXE_SLT_OP

      (3)要写入的目的寄存器:slt指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令11-15bit的值,正是slt指令中的rd

4slti指令的译码过程

      slti指令译码需要设置的三个方面内容如下,sltiu指令的译码过程可以参考slti指令。

      (1)要读取的寄存器情况:slti指令只需要读取rs寄存器的值,所以设置reg1_read_o1reg2_read_o0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slti指令中的rs。设置reg2_read_o0,表示使用立即数作为运算的第二个操作数。imm就是指令中的立即数进行符号扩展后的值。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是imm的值。

      (2)要执行的运算:slti指令是算术运算中的比较操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETICaluop_o赋值为EXE_SLT_OP

      (3)要写入的目的寄存器:slti指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置要写入的目的寄存器地址wd_o是指令中16-20bit的值,正是slti指令中的rt

5mult指令的译码过程

      mult指令译码需要设置的三个方面内容如下,multu指令的译码过程可以参考mult指令。

      (1)要读取的寄存器情况:mult指令需要读取rsrt寄存器的值,所以设置reg1_read_oreg2_read_o1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mult指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是mult指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。

      (2)要执行的运算:mult指令是乘法操作,并且乘法结果不需要写入通用寄存器,而是写入HILO寄存器,所以此处将alusel_o保持为默认值EXE_RES_NOPaluop_o赋值为EXE_MULT_OP

      (3)要写入的目的寄存器:mult指令不需要写通用寄存器,所以设置wreg_oWriteDisable

6mul指令的译码过程

      mul指令译码需要设置的三个方面内容如下。 

      (1)要读取的寄存器情况:mul指令需要读取rsrt寄存器的值,所以设置reg1_read_oreg2_read_o1。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mul指令中的rs,默认通过Regfile模块读端口2读取的寄存器地址reg2_addr_o的值是指令的16-20bit,正是mul指令中的rt。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值,reg2_o就是地址为rt的寄存器的值。

      (2)要执行的运算:mul指令是乘法操作,并且乘法结果是写入通用寄存器,所以此处将alusel_o赋值为EXE_RES_MULaluop_o赋值为EXE_MUL_OP

      (3)要写入的目的寄存器:mul指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是mul指令中的rd

7clo指令的译码过程

      clo指令译码需要设置的三个方面内容如下。clz指令的译码过程可以参考clo指令。

      (1)要读取的寄存器情况:clo指令只需要读取rs寄存器的值,所以设置reg1_read_o1reg2_read_o0。默认通过Regfile模块读端口1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是clo指令中的rs。所以最终译码阶段的输出reg1_o就是地址为rs的寄存器的值。

      (2)要执行的运算:clo指令是算术运算中的计数操作,所以此处将alusel_o赋值为EXE_RES_ARITHMETICaluop_o赋值为EXE_CLO_OP

      (3)要写入的目的寄存器:clo指令需要将结果写入目的寄存器,所以设置wreg_oWriteEnable,设置wd_o为要写入的目的寄存器地址,默认是指令字的11-15bit,正是clo指令中的rd


为了实现简单算术指令,今天修改了译码阶段,下一次将修改执行阶段,敬请关注!