首页 > 代码库 > 自己动手写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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是add指令中的rs,默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是slt指令中的rs。默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit。正是mult指令中的rs。默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是mul指令中的rs,默认通过Regfile模块读port2读取的寄存器地址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模块读port1读取的寄存器地址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


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


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