自己动手写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中各个指令的功能码。在本书附带光盘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模块主要修改内容如下。完整代码请参考本书光盘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所示。

EX模块的代码主要修改如下。完整代码请参考本书附带光盘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所示。

EX/MEM模块的代码主要修改如下。完整代码位于本书附带光盘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所示,具体代码不在书中列出,读者可以参考本书附带光盘Code\Chapter7_2目录下的openmips.v文件。

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

时间: 2024-08-19 15:15:30

自己动手写CPU之第七阶段(7)——乘累加指令的实现的相关文章

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

将陆续上传本人写的新书<自己动手写CPU>,今天是第26篇,我尽量每周四篇 China-pub的预售地址如下(有目录.内容简介.前言): http://product.china-pub.com/3804025 亚马逊的预售地址如下,欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4 为了实现简单算术指令,需要修改译码阶段的ID模块.执行阶段的EX模块,上一篇博文中已经介绍了对译码阶段ID模块的

自己动手写CPU之第九阶段(9)——修改OpenMIPS以实现ll、sc指令

将陆续上传新书<自己动手写CPU>,今天是第48篇. 9.8 修改OpenMIPS以实现ll.sc指令 9.8.1 LLbit寄存器的实现 LLbit寄存器在LLbit模块中实现,模块接口如图9-30所示,各接口描述如表9-8所示. LLbit寄存器的代码如下,源文件是本书光盘Code\Chapter9_2目录下的LLbit_reg.v文件. module LLbit_reg( input wire clk, input wire rst, // 异常是否发生,为1表示异常发生,为0表示没有异

自己动手写CPU之第九阶段(6)——修改最小SOPC

将陆续上传新书<自己动手写CPU>,今天是第45篇. 这几天事情多,好久没更新了 前几篇实现了加载存储指令,今天将修改最小SOPC,用以测试加载存储指令是否实现正确.闲话少说,进入正题. 9.4 修改最小SOPC 为了验证上一节添加的加载存储指令是否实现正确,需要修改在第4章中设计的最小SOPC,为其添加数据存储器RAM. 9.4.1 添加数据存储器RAM 数据存储器RAM的接口如图9-24所示,还是采用左边是输入接口,右边是输出接口的方式绘制,这样便于理解.接口含义如表9-7所示. 数据存储

自己动手写CPU之第四阶段(3)——MIPS编译环境的建立

将陆续上传本人写的新书<自己动手写CPU>(尚未出版).今天是第13篇.我尽量每周四篇 4.4 MIPS编译环境的建立 OpenMIPS处理器在设计的时候就计划与MIPS32指令集架构兼容,所以能够使用MIPS32架构下已有的GNU开发工具链.本节将说明怎样安装使用GNU开发工具链以及怎样制作Makefile文件.从而以更加方便.快捷.自己主动的方式对測试程序进行编译.并得到指令存储器ROM的初始化文件inst_rom.data. 4.4.1 VisualBox的安装与设置 GNU工具链要安装

《自己动手写cpu》读书笔记

本文来自<自己动手写cpu>一书的总结.原来自己看过原作者的<步步惊芯--软核处理器分析>以及其他关于or1200的书.本次粗略浏览了该书,就某些感兴趣的部分详细分析,并总结成此文. 关于5级流水的架构,可以自己去参考<计算机接口>一书.本文重点不在此. 1.如何从rom里面取地址 简化版的最基本的sopc的框图如下: module openmips( input wire clk, input wire rst, input wire[`RegBus] rom_dat

自己动手写CPU之第七阶段(4)——验证简单算术操作指令实现效果

将陆续上传本人写的新书<自己动手写CPU>,今天是第27篇,我尽量每周四篇 China-pub的预售地址如下(有目录.内容简介.前言): http://product.china-pub.com/3804025 亚马逊的预售地址如下,欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4 7.4 测试简单算术操作指令实现效果 本节通过实验来检验我们修改后的代码是否实现了简单算术操作指令,测试程序如下

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

将陆续上传本人写的新书<自己动手写CPU>,今天是第33篇,我尽量每周四篇 亚马逊的销售地址如下,欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4 在当当.京东.互动.北发等网上书店均有! 除法指令的实现过程有点长,分两篇博文介绍,今天是第二篇. 7.12.2 修改译码阶段的ID模块 译码阶段的ID模块要增加对除法指令的分析,根据图7-15给出的指令格式可知,除法指令都是SPECIAL类指令,

自己动手写CPU之第四阶段(2)——验证第一条指令ori的实现效果

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第12篇,我尽量每周四篇 书名又之前的<自己动手写处理器>改为<自己动手写CPU> 4.3 验证OpenMIPS实现效果 4.3.1指令存储器ROM的实现 本节将验证我们的OpenMIPS是否实现正确,包含:流水线是否正确.ori指令是否实现正确.在验证之前,需要首先实现指令存储器,以便OpenMIPS从中读取指令. 指令存储器模块是只读的,其接口如图4-7所示,还是采用左边是输入接口,右边是输出接口的方式绘

自己动手写CPU之第六阶段(3)——移动操作指令的实现

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第22篇,我尽量每周四篇 6.3 修改OpenMIPS以实现移动操作指令 6.3.1 HI.LO寄存器的实现 在HILO模块中实现HI.LO寄存器,HILO模块的接口描述如表6-1所示. HILO模块的代码如下,源文件是本书附带光盘Code\Chapter6目录下的hilo_reg.v.整个代码很简单:在时钟上升沿,如果复位信号无效,那么就判断输入的写使能信号we是否为WriteEnable,如果是WriteEnable,那么就将输