天天看點

自己動手寫CPU之第七階段(7)——乘累加指令的實作

将陸續上傳本人寫的新書《自己動手寫CPU》,今天是第30篇,我盡量每周四篇

亞馬遜的銷售位址如下,歡迎大家圍觀呵!

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

China-pub的銷售位址如下:

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

北發的銷售位址如下:

http://book.beifabook.com/Product/BookDetail.aspx?Plucode=712123950&extra=0_s25960657

7.8 修改OpenMIPS以實作乘累加、乘累減指令

7.8.1 修改譯碼階段的ID子產品

      譯碼階段的ID子產品要添加對乘累加、乘累減指令的分析,根據圖7-11給出的指令格式可知,這4條指令都是SPECIAL2類指令,可以依據功能碼确定是哪一種指令,确定指令的過程如圖7-13所示。

自己動手寫CPU之第七階段(7)——乘累加指令的實作

      其中涉及的宏定義如下,正是圖7-13中各個指令的功能碼。在本書附帶CD光牒Code\Chapter7_2目錄下的defines.v檔案中可以找到這些定義。

`define EXE_MADD   6'b000000
`define EXE_MADDU  6'b000001
`define EXE_MSUB   6'b000100
`define EXE_MSUBU  6'b000101
           

      譯碼階段的ID子產品主要修改内容如下。完整代碼請參考本書CD光牒Code\Chapter7_2目錄下的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_SPECIAL2_INST:  begin          // SPECIAL2類指令
	           case ( op3 )
		           ......
		           `EXE_MADD:		begin       // madd指令
                 wreg_o      <= `WriteDisable;
                 aluop_o     <= `EXE_MADD_OP;
                 alusel_o    <= `EXE_RES_MUL; 
                 reg1_read_o <= 1'b1;
                 reg2_read_o <= 1'b1;
                 instvalid   <= `InstValid;
               end
               `EXE_MADDU:		begin       // maddu指令
                 wreg_o      <= `WriteDisable;
                 aluop_o     <= `EXE_MADDU_OP;
                 alusel_o    <= `EXE_RES_MUL; 
                 reg1_read_o <= 1'b1;	
                 reg2_read_o <= 1'b1;
                 instvalid   <= `InstValid;
               end
		          `EXE_MSUB:		begin       // msub指令
                 wreg_o      <= `WriteDisable;
                 aluop_o     <= `EXE_MSUB_OP;
                 alusel_o    <= `EXE_RES_MUL; 
                 reg1_read_o <= 1'b1;	
                 reg2_read_o <= 1'b1;
                 instvalid   <= `InstValid;
		           end
              `EXE_MSUBU:		begin       // msubu指令
                 wreg_o      <= `WriteDisable;
                 aluop_o     <= `EXE_MSUBU_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
	......

endmodule
           

      這4條指令的譯碼過程都是相似的。簡單說明如下。

      (1)因為最終結果都是寫入HI、LO寄存器,而不是寫入通用寄存器,是以設定wreg_o為WriteDisable。

      (2)因為都要讀取兩個寄存器的值,是以設定reg1_read_o、reg2_read_o為1'b1,預設通過Regfile子產品讀端口1讀取的寄存器位址reg1_addr_o的值是指令的21-25bit,正是指令中的rs,預設通過Regfile子產品讀端口2讀取的寄存器位址reg2_addr_o的值是指令的16-20bit,正是指令中的rt。是以最終譯碼階段的輸出reg1_o就是位址為rs的寄存器的值,reg2_o就是位址為rt的寄存器的值。

      (3)運算類型alusel_o的值都設定為EXE_RES_MUL,不過由于沒有要寫的通用寄存器,是以此處的alusel_o的值并沒有作用,也可以設定為EXE_RES_NOP。

      (4)運算子類型aluop_o的值設定為與具體指令對應。

7.8.2 修改執行階段的EX子產品

      參考圖7-12可知,EX子產品要增加4個接口,含義如表7-2所示。

自己動手寫CPU之第七階段(7)——乘累加指令的實作

      EX子產品的代碼主要修改如下。完整代碼請參考本書附帶CD光牒Code\Chapter7_2目錄下的ex.v檔案。

module ex(

  ......
       
  // 增加的輸入接口
  input wire[`DoubleRegBus]     hilo_temp_i,
  input wire[1:0]               cnt_i,

  ......

  // 增加的輸出接口
  output reg[`DoubleRegBus]     hilo_temp_o,
  output reg[1:0]               cnt_o,

  output reg	                 stallreq
	
);

  ......
  wire[`RegBus]       opdata1_mult;
  wire[`RegBus]       opdata2_mult;
  wire[`DoubleRegBus] hilo_temp;
  reg[`DoubleRegBus]  hilo_temp1;
  reg                 stallreq_for_madd_msub;

  ......

/****************************************************************
***********              第一段:計算乘法結果             *********
*****************************************************************/

//(1)取得乘法操作的被乘數,指令madd、msub都是有符号乘法,如果第一個
//    操作數reg1_i是負數,那麼取reg1_i的補碼作為被乘數,反之,直接
//    使用reg1_i作為被乘數
 assign opdata1_mult = (((aluop_i == `EXE_MUL_OP) || 
                        (aluop_i == `EXE_MULT_OP) ||
                        (aluop_i == `EXE_MADD_OP) || 
                        (aluop_i == `EXE_MSUB_OP))&& 
                        (reg1_i[31] == 1'b1)) ? (~reg1_i + 1) : reg1_i;
     
//(2)取得乘法操作的乘數,指令madd、msub是有符号乘法,如果第二個
//    操作數reg2_i是負數,那麼取reg2_i的補碼作為乘數,反之,直接
//    使用reg2_i作為乘數
 assign opdata2_mult = (((aluop_i == `EXE_MUL_OP) || 
                         (aluop_i == `EXE_MULT_OP) ||
                         (aluop_i == `EXE_MADD_OP) || 
                         (aluop_i == `EXE_MSUB_OP))&& 
                         (reg2_i[31] == 1'b1)) ? (~reg2_i + 1) : reg2_i;

//(3)得到臨時乘法結果,儲存在變量hilo_temp中
 assign hilo_temp = opdata1_mult * opdata2_mult;

//(4)對臨時乘法結果進行修正,最終的乘法結果儲存在變量mulres中,有兩種情況:
//    A、如果是有符号乘法運算madd、msub,那麼需要修正臨時乘法結果,如下:
//       A1、如果被乘數與乘數,兩者一正一負,那麼需要對臨時乘法結果
//          hilo_temp求補碼,作為最終的乘法結果,賦給變量mulres。
//       A2、如果被乘數與乘數同号,那麼hilo_temp的值就作為mulres
//          的值。
//    B、如果是無符号乘法運算maddu、msubu,那麼hilo_temp的值就作為
//      最終的乘法結果,賦給變量mulres。
  always @ (*) begin
    if(rst == `RstEnable) begin
       mulres <= {`ZeroWord,`ZeroWord};
    end else if ((aluop_i == `EXE_MULT_OP) || (aluop_i == `EXE_MUL_OP)  ||
                 (aluop_i == `EXE_MADD_OP) || (aluop_i == `EXE_MSUB_OP)) begin
      if(reg1_i[31] ^ reg2_i[31] == 1'b1) begin
         mulres <= ~hilo_temp + 1;
      end else begin
         mulres <= hilo_temp;
      end
    end else begin
      mulres <= hilo_temp;
    end
  end

/****************************************************************
***********             第二段:乘累加、乘累減             *********
*****************************************************************/

// MADD、MADDU、MSUB、MSUBU指令
 always @ (*) begin
  if(rst == `RstEnable) begin
    hilo_temp_o <= {`ZeroWord,`ZeroWord};
    cnt_o       <= 2'b00;
    stallreq_for_madd_msub <= `NoStop;
  end else begin
      case (aluop_i) 
         `EXE_MADD_OP, `EXE_MADDU_OP: begin    // madd、maddu指令
            if(cnt_i == 2'b00) begin           // 執行階段第一個時鐘周期
              hilo_temp_o <= mulres;
              cnt_o       <= 2'b01;
              hilo_temp1  <= {`ZeroWord,`ZeroWord};
              stallreq_for_madd_msub <= `Stop;
            end else if(cnt_i == 2'b01) begin // 執行階段第二個時鐘周期
              hilo_temp_o <= {`ZeroWord,`ZeroWord};					    
              cnt_o       <= 2'b10;
              hilo_temp1  <= hilo_temp_i + {HI,LO};
              stallreq_for_madd_msub <= `NoStop;
            end
          end
         `EXE_MSUB_OP, `EXE_MSUBU_OP: begin  // msub、msubu指令
            if(cnt_i == 2'b00) begin         // 執行階段第一個時鐘周期
              hilo_temp_o <=  ~mulres + 1 ;
              cnt_o       <= 2'b01;
              stallreq_for_madd_msub <= `Stop;
            end else if(cnt_i == 2'b01)begin // 執行階段第二個時鐘周期
               hilo_temp_o <= {`ZeroWord,`ZeroWord};
               cnt_o       <= 2'b10;
               hilo_temp1  <= hilo_temp_i + {HI,LO};
               stallreq_for_madd_msub <= `NoStop;
            end				
       end
       default:	begin
          hilo_temp_o <= {`ZeroWord,`ZeroWord};
          cnt_o       <= 2'b00;
          stallreq_for_madd_msub <= `NoStop;
       end
     endcase
   end
  end	

/****************************************************************
***********              第三段:暫停流水線              *********
*****************************************************************/

// 目前隻有乘累加、乘累減指令會導緻流水線暫停,是以stallreq就直接等于
// stallreq_for_madd_msub的值
  always @ (*) begin
     stallreq = stallreq_for_madd_msub;
  end

  ......

/****************************************************************
***********         第四段:修改HI、LO寄存器的寫資訊        ********
*****************************************************************/

  always @ (*) begin
    if(rst == `RstEnable) begin
      whilo_o <= `WriteDisable;
      hi_o    <= `ZeroWord;
      lo_o    <= `ZeroWord;		
    end else if((aluop_i == `EXE_MSUB_OP) || (aluop_i == `EXE_MSUBU_OP)) begin
      whilo_o <= `WriteEnable;
      hi_o    <= hilo_temp1[63:32];
      lo_o    <= hilo_temp1[31:0];			
    end else if((aluop_i == `EXE_MADD_OP) || 
      (aluop_i == `EXE_MADDU_OP)) begin
      whilo_o <= `WriteEnable;
      hi_o    <= hilo_temp1[63:32];
      lo_o    <= hilo_temp1[31:0];
      ......	

endmodule
           

      上述代碼可以分為四段了解。

      (1)第一段:計算從通用寄存器中讀出的兩個寄存器的乘法結果,儲存在mulres中。

      (2)第二段:以乘累加指令為例進行講解。乘累減指令與此類似。

  •  如果cnt_i為2'b00,表示是乘累加指令的第一個執行周期,此時将乘法結果mulres通過接口hilo_temp_o輸出到EX/MEM子產品,以便在下一個時鐘周期使用。同時,設定變量stallreq_for_madd_msub為Stop,表示乘累加指令請求流水線暫停。
  •  如果cnt_i為2'b01,表示是乘累加指令的第二個執行周期,此時EX子產品的輸入hilo_temp_i就是上一個時鐘周期得到的乘法結果,是以将hilo_temp_i與HI、LO寄存器的值相加,得到最終運算結果,儲存到變量hilo_temp1中。同時,設定變量stallreq_for_madd_msub為NoStop,表示乘累加指令執行結束,不再請求流水線暫停。最後,設定cnt_o為2'b10,而不是直接設定為2'b00,目的是:如果因其它原因導緻流水線保持暫停,那麼由于cnt_o為2'b10,是以EX階段不再計算,進而防止乘累加指令重複運作。

      (3)第三段:給出信号stallreq的值,目前隻有乘累加、乘累減指令會導緻流水線暫停,是以stallreq就直接等于變量stallreq_for_madd_msub的值。

      (4)第四段:由于乘累加、乘累減指令要将最終結果寫入HI、LO寄存器,是以在第四段給出了對HI、LO寄存器的寫資訊。

7.8.3 修改EX/MEM子產品

      參考圖7-12可知,EX/MEM子產品要增加4個接口,含義如表7-3所示。

自己動手寫CPU之第七階段(7)——乘累加指令的實作

      EX/MEM子產品的代碼主要修改如下。完整代碼位于本書附帶CD光牒Code\Chapter7_2目錄下的ex_mem.v檔案。

module ex_mem(

  ......

  // 來自控制子產品的資訊
  input wire[5:0]               stall,
	
  ......

  // 增加的輸入接口
  input wire[`DoubleRegBus]     hilo_i,
  input wire[1:0]               cnt_i,
	
  ......
	
  // 增加的輸出接口
  output reg[`DoubleRegBus]     hilo_o,
  output reg[1:0]               cnt_o
	
);

// 在流水線執行階段暫停的時候,将輸入信号hilo_i通過輸出接口hilo_o送出、
// 輸入信号cnt_i通過輸出接口cnt_o送出。其餘時刻,hilo_o為0,cnt_o
// 也為0。
  always @ (posedge clk) begin
    if(rst == `RstEnable) begin
       ......
       hilo_o <= {`ZeroWord, `ZeroWord};
       cnt_o  <= 2'b00;
    end else if(stall[3] == `Stop && stall[4] == `NoStop) begin
       ......
       hilo_o <= hilo_i;
       cnt_o  <= cnt_i;
    end else if(stall[3] == `NoStop) begin
       ......
       hilo_o <= {`ZeroWord, `ZeroWord};
       cnt_o  <= 2'b00;
    end else begin
       hilo_o <= hilo_i;
       cnt_o  <= cnt_i;
    end
  end

endmodule
           

7.8.4 修改OpenMIPS子產品

      因為上面為EX、EX/MEM子產品添加了接口,是以需要修改OpenMIPS子產品,以将這些接口連接配接起來,連接配接關系如圖7-12所示,具體代碼不在書中列出,讀者可以參考本書附帶CD光牒Code\Chapter7_2目錄下的openmips.v檔案。

代碼下載下傳位址http://download.csdn.net/detail/leishangwen/7858701

繼續閱讀