• 自己动手写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所看到的。


          当中涉及的宏定义例如以下,正是图7-13中各个指令的功能码。在本书附带光盘CodeChapter7_2文件夹下的defines.v文件里能够找到这些定义。


    `define EXE_MADD   6'b000000
    `define EXE_MADDU  6'b000001
    `define EXE_MSUB   6'b000100
    `define EXE_MSUBU  6'b000101
    
          译码阶段的ID模块主要改动内容例如以下。完整代码请參考本书光盘CodeChapter7_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模块读port1读取的寄存器地址reg1_addr_o的值是指令的21-25bit,正是指令中的rs,默认通过Regfile模块读port2读取的寄存器地址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所看到的。


          EX模块的代码主要改动例如以下。完整代码请參考本书附带光盘CodeChapter7_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所看到的。

          EX/MEM模块的代码主要改动例如以下。完整代码位于本书附带光盘CodeChapter7_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所看到的,详细代码不在书中列出。读者能够參考本书附带光盘CodeChapter7_2文件夹下的openmips.v文件。


    代码下载地址http://download.csdn.net/detail/leishangwen/7858701

  • 相关阅读:
    PHP mysqli扩展库 预处理技术
    libevent入门(1)
    你的项目需要升级成64bit程序吗?
    [敏捷个人]个人知识管理
    [转]职业规划中的“我想要”和“我需要”
    《领导力》读书笔记
    http 传输原理及格式
    转载:Fiddler 教程
    Nginx 配置文件详解
    Wireshark
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5244887.html
Copyright © 2020-2023  润新知