自己动手写CPU之第七阶段(11)——除法指令实现过程2

时间:2021-10-10 09:08:34

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

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

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

在当当、京东、互动、北发等网上书店均有!


除法指令的实现过程有点长,分两篇博文介绍,今天是第二篇。


7.12.2 修改译码阶段的ID模块

      译码阶段的ID模块要增加对除法指令的分析,根据图7-15给出的指令格式可知,除法指令都是SPECIAL类指令,可以依据功能码确定是哪一种指令,确定指令的过程如图7-19所示。

自己动手写CPU之第七阶段(11)——除法指令实现过程2

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

`define EXE_DIV   6'b011010
`define EXE_DIVU  6'b011011

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

module id(
  ......	
);

  ......
  
  assign stallreq = `NoStop;
  
  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
            case (op2)
              5'b00000:			begin
                case (op3)
                  ......
                  `EXE_DIV: begin             //div指令
                     wreg_o      <= `WriteDisable;
                     aluop_o     <= `EXE_DIV_OP;
                     reg1_read_o <= 1'b1;
                     reg2_read_o <= 1'b1; 
                     instvalid   <= `InstValid;
                   end
                  `EXE_DIVU: begin           //divu指令
                     wreg_o      <= `WriteDisable;
                     aluop_o     <= `EXE_DIVU_OP;
                     reg1_read_o <= 1'b1;
                     reg2_read_o <= 1'b1; 
                     instvalid   <= `InstValid;
                   end 
                   ......

      这2条除法指令的译码过程都是相似的,简要说明如下。

      (1)因为最终结果是写入HI、LO寄存器,不需要写通用寄存器,所以赋值wreg_o为WriteDisable。

      (2)因为要读取两个通用寄存器的值,所以设置reg1_read_o、reg2_read_o为1'b1,读取的是图7-15中地址为rs、rt的寄存器的值。

      (3)alusel_o的值保持为默认值EXE_RES_NOP。

      (4)设置aluop_o的值与具体指令对应。

7.12.3 修改执行阶段的EX模块

      参考图7-17可知, EX模块需要增加部分接口,增加的接口如表7-6所示。

自己动手写CPU之第七阶段(11)——除法指令实现过程2

      EX模块的代码主要修改如下。完整代码位于本书附带光盘Code\Chapter7_3目录下的ex.v文件。

module ex(
   ......

   // 新增来自除法模块的输入
   input wire[`DoubleRegBus]     div_result_i,
   input wire                    div_ready_i,
	
   ......

   // 新增到除法模块的输出
   output reg[`RegBus]           div_opdata1_o,
   output reg[`RegBus]           div_opdata2_o,
   output reg                    div_start_o,
   output reg                    signed_div_o,

   output reg			      stallreq
	
);

   ......
			
   reg stallreq_for_div;         // 是否由于除法运算导致流水线暂停
			
   ......	

/****************************************************************
*******   第一段:输出DIV模块控制信息,获取DIV模块给出的结果   ******
*****************************************************************/

   always @ (*) begin
     if(rst == `RstEnable) begin
       stallreq_for_div <= `NoStop;
       div_opdata1_o    <= `ZeroWord;
       div_opdata2_o    <= `ZeroWord;
       div_start_o      <= `DivStop;
       signed_div_o     <= 1'b0;
     end else begin
       stallreq_for_div <= `NoStop;
       div_opdata1_o    <= `ZeroWord;
       div_opdata2_o    <= `ZeroWord;
       div_start_o      <= `DivStop;
       signed_div_o     <= 1'b0;	
       case (aluop_i) 
       `EXE_DIV_OP:		begin           //是div指令
          if(div_ready_i == `DivResultNotReady) begin
            div_opdata1_o    <= reg1_i;         //被除数
            div_opdata2_o    <= reg2_i;         //除数
            div_start_o      <= `DivStart;      //开始除法运算
            signed_div_o     <= 1'b1;           //有符号除法
            stallreq_for_div <= `Stop;          //请求流水线暂停
          end else if(div_ready_i == `DivResultReady) begin
            div_opdata1_o    <= reg1_i;
            div_opdata2_o    <= reg2_i;
            div_start_o      <= `DivStop;       //结束除法运算
            signed_div_o     <= 1'b1;
            stallreq_for_div <= `NoStop;        //不再请求流水线暂停
          end else begin
            div_opdata1_o    <= `ZeroWord;
            div_opdata2_o    <= `ZeroWord;
            div_start_o      <= `DivStop;
            signed_div_o     <= 1'b0;
            stallreq_for_div <= `NoStop;
          end					
        end
       `EXE_DIVU_OP:		begin           //是divu指令
          if(div_ready_i == `DivResultNotReady) begin
            div_opdata1_o    <= reg1_i;
            div_opdata2_o    <= reg2_i;
            div_start_o      <= `DivStart;
            signed_div_o     <= 1'b0;           //无符号除法
            stallreq_for_div <= `Stop;
          end else if(div_ready_i == `DivResultReady) begin
            div_opdata1_o    <= reg1_i;
            div_opdata2_o    <= reg2_i;
            div_start_o      <= `DivStop;
            signed_div_o     <= 1'b0;
            stallreq_for_div <= `NoStop;
          end else begin
            div_opdata1_o    <= `ZeroWord;
            div_opdata2_o    <= `ZeroWord;
            div_start_o      <= `DivStop;
            signed_div_o     <= 1'b0;
            stallreq_for_div <= `NoStop;
          end
         end
     default: begin
     end
    endcase
  end
end	

/****************************************************************
**********                第二段:暂停流水线               *******
*****************************************************************/

   always @ (*) begin
      stallreq = stallreq_for_madd_msub || stallreq_for_div;
   end
  
......

/****************************************************************
**********            第三段:修改HI、LO寄存器写信息        *******
*****************************************************************/

  always @ (*) begin
    if(rst == `RstEnable) begin
       whilo_o <= `WriteDisable;
       hi_o    <= `ZeroWord;
       lo_o    <= `ZeroWord;		
       ......	
    end else if((aluop_i == `EXE_DIV_OP) || (aluop_i == `EXE_DIVU_OP)) begin
       whilo_o <= `WriteEnable;
       hi_o    <= div_result_i[63:32];
       lo_o    <= div_result_i[31:0];							
       ......

      上面的代码可以分为三段理解。

      (1)第一段:如果是div指令,并且DIV模块没有声明除法结束(即div_ready_i等于DivResultNotReady),那么输出被除数、除数、除法开始信号、有符号除法等信息到DIV模块,设置div_start_o为DivStart,以指示DIV模块开始除法运算,同时,设置stallreq_for_div为Stop,表示由于除法运算请求流水线暂停。反之,如果DIV模块声明除法结束(即div_ready_i等于DivResultReady),那么设置div_start_o为DivStop,以指示DIV模块停止除法运算,同时,设置stallreq_for_div为NoStop,表示不是由于除法运算请求流水线暂停。

      divu指令的执行过程与div指令类似。

      (2)第二段:给出暂停流水线请求信号stallreq的值,目前已实现的乘累加、乘累减、除法指令都会请求流水线暂停,所以stallreq等于stallreq_for_madd_msub与stallreq_for_div进行逻辑“或”运算的结果。

      (3)第三段:由于除法指令要将最终结果写入HI、LO寄存器,所以在第三段给出了对HI、LO寄存器的写信息。其中div_result_i就是DIV模块计算出来的除法结果,高32位存储的是余数,低32位存储的是商。

7.12.4 修改OpenMIPS模块

      因为添加了DIV模块,并且修改了EX模块的接口,所以要修改OpenMIPS顶层模块,以将这些新增模块、接口连接起来,连接关系如图7-17所示。完整代码可以参考本书附带光盘Code\Chapter7_3目录下的openmips.v文件,书中只给出DIV模块的例化语句,如下。

div div0(
 .clk(clk),
 .rst(rst),
	
 .signed_div_i(signed_div),
 .opdata1_i(div_opdata1),
 .opdata2_i(div_opdata2),
 .start_i(div_start),
 .annul_i(1'b0),
	
 .result_o(div_result),
 .ready_o(div_ready)
);

      这里需要说明一点,DIV模块的输入接口annul_i在目前固定为0,表示不会有取消除法指令的情况发生,但是在后续章节,当我们实现异常处理的时候,会重新确定DIV模块的输入接口annul_i的值。


下一次将测试除法指令的实现效果。