自己动手写CPU之第九阶段(5)——实现加载存储指令1(修改译码阶段)

时间:2021-03-26 00:13:55

将陆续上传新书《自己动手写CPU》,今天是第41篇,我尽量每周四篇,但是最近已经很久没有实现这个目标了,一直都有事,不好意思哈。

开展晒书评送书活动,在亚马逊、京东、当当三大图书网站上,发表《自己动手写CPU》书评的前十名读者,均可获赠《步步惊芯——软核处理器内部设计分析》一书,大家踊跃参与吧!活动时间:2014-9-11至2014-10-30


9.3 修改OpenMIPS以实现加载存储指令

9.3.1 修改译码阶段

      1、修改ID模块

      参考图9-19可知,ID模块要增加接口inst_o,如表9-1所示。

自己动手写CPU之第九阶段(5)——实现加载存储指令1(修改译码阶段)

      在ID模块还要增加对加载存储指令的分析,根据图9-19-29-69-12给出的加载存储指令的格式可知,这些指令的指令码都是不同的,所以可以直接依据指令码确定是哪一种指令,确定指令的过程如图9-20所示。

自己动手写CPU之第九阶段(5)——实现加载存储指令1(修改译码阶段)

      其中涉及的宏定义如下,正是各个加载存储指令的指令码,在本书附带光盘Code\Chapter9_1目录下的defines.v文件可以找到这些定义。

`define EXE_LB   6'b100000
`define EXE_LBU  6'b100100
`define EXE_LH   6'b100001
`define EXE_LHU  6'b100101
`define EXE_LW   6'b100011
`define EXE_LWL  6'b100010
`define EXE_LWR  6'b100110
`define EXE_SB   6'b101000
`define EXE_SH   6'b101001
`define EXE_SW   6'b101011
`define EXE_SWL  6'b101010
`define EXE_SWR  6'b101110

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

module id(
  ......
	
  output wire[`RegBus]          inst_o,   // 新增加的输出接口
	
  ......
);

  ......

  assign inst_o = inst_i;    // inst_o的值就是译码阶段的指令
    
  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_LB:			begin         // lb指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LB_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;	
           reg2_read_o <= 1'b0;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
         end
        `EXE_LBU:			begin         // lbu指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LBU_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b0;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
        end
       `EXE_LH:			begin                 // lh指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LH_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b0;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
       end
      `EXE_LHU:			begin                // lhu指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LHU_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b0;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
       end
      `EXE_LW:			begin                 // lw指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LW_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b0;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
       end
      `EXE_LWL:			begin                 // lwl指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LWL_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
      end
      `EXE_LWR:			begin                // lwr指令
           wreg_o      <= `WriteEnable;
           aluop_o     <= `EXE_LWR_OP;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1;
           wd_o        <= inst_i[20:16]; 
           instvalid   <= `InstValid;
      end
     `EXE_SB:			begin                 // sb指令
           wreg_o      <= `WriteDisable;
           aluop_o     <= `EXE_SB_OP;
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1; 
           instvalid   <= `InstValid;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
      end
      `EXE_SH:			begin                 // sh指令
           wreg_o      <= `WriteDisable;
           aluop_o     <= `EXE_SH_OP;
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1; 
           instvalid   <= `InstValid;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
       end
      `EXE_SW:			begin                 // sw指令
           wreg_o      <= `WriteDisable;
           aluop_o     <= `EXE_SW_OP;
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1; 
           instvalid   <= `InstValid;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
       end
       `EXE_SWL:			begin        // swl指令
           wreg_o      <= `WriteDisable;
           aluop_o     <= `EXE_SWL_OP;
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1; 
           instvalid   <= `InstValid;
           alusel_o    <= `EXE_RES_LOAD_STORE; 
       end
      `EXE_SWR:			begin                // swr指令
           wreg_o      <= `WriteDisable;
           aluop_o     <= `EXE_SWR_OP;
           reg1_read_o <= 1'b1;
           reg2_read_o <= 1'b1; 
           instvalid   <= `InstValid;
           alusel_o    <= `EXE_RES_LOAD_STORE;
       end
......

      译码工作主要是确定要写的目的寄存器、要读取的寄存器、要执行的运算等三个方面。以下对几个有代表性的指令的译码过程进行说明。

      (1lb指令

  •  要写的目的寄存器:加载指令lb需要将加载结果写入目的寄存器,所以设置wreg_oWriteEnable,同时参考图9-1可知,要写的目的寄存器地址是指令中的16-20bit,所以设置wd_oinst_i[20:16]
  •  要读取的寄存器:参考图9-1可知,计算加载目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o1,表示通过Regfile模块的读端口1读取寄存器的值,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是lb指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。
  •  要执行的运算:设置alusel_oEXE_RES_LOAD_STORE,表示运算类型是加载存储,设置aluop_oEXE_LB_OP,表示运算子类型是字节加载lb
      lbu lh lhu lw 指令与 lb 指令的译码过程类似,只是 aluop_o 的值不同。

      (2lwl指令

  •  要写的目的寄存器:加载指令lwl需要将加载结果写入目的寄存器,所以设置wreg_oWriteEnable,同时参考图9-6可知,要写的目的寄存器地址是指令中的16-20bit,所以设置wd_oinst_i[20:16]
  •  要读取的寄存器:参考图9-6可知,计算加载目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o1,表示通过Regfile模块的读端口1读取寄存器的值,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是lwl指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。此外,由于lwl指令只是部分的修改目的寄存器,所以还需要读出目的寄存器,与lwl指令加载得到的结果进行组合,最终写入目的寄存器,因此,设置reg2_read_o也为1,表示通过Regfile模块的读端口2读取寄存器的值,默认读取的寄存器地址reg2_addr_o是指令的16-20bit,正是lwl指令中的rt。所以最终译码阶段的输出reg2_o就是地址为rt的寄存器的值。
  •  要执行的运算:设置alusel_oEXE_RES_LOAD_STORE,表示运算类型是加载存储,设置aluop_oEXE_LWL_OP,表示运算子类型是向左加载lwl

      lwr指令与lwl指令的译码过程类似,只是aluop_o的值不同。

      (3sb指令

  •  要写的目的寄存器:存储指令sb不需要写通用寄存器,所以设置wreg_oWriteDisable
  •  要读取的寄存器:参考图9-2可知,计算存储目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o1,表示通过Regfile模块的读端口1读取寄存器的值,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是sb指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。要存储的值是通用寄存器的值,所以设置reg2_read_o1,表示通过Regfile模块的读端口2读取寄存器的值,默认读取的寄存器地址reg2_addr_o是指令的16-20bit,正是sb指令中的rt。所以最终译码阶段的输出reg2_o就是地址为rt的寄存器的值。
  •  要执行的运算:设置alusel_oEXE_RES_LOAD_STORE,表示运算类型是加载存储指令,设置aluop_oEXE_SB_OP,表示运算子类型是字节存储sb

      shswswrswl指令与sb指令的译码过程类似,只是aluop_o的值不同。


      2、修改ID/EX模块

      参考图9-19可知,ID/EX模块需要增加部分接口,用于将ID模块新增加的输出信号inst_o传递到执行阶段的EX模块。如表9-2所示。

自己动手写CPU之第九阶段(5)——实现加载存储指令1(修改译码阶段)

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

module id_ex(

  ......		
  input wire[`RegBus]           id_inst,  // 来自ID模块的信号
	
  ......
  output reg[`RegBus]           ex_inst   // 传递到EX模块
	
);

  always @ (posedge clk) begin
    if (rst == `RstEnable) begin
      ......	
      ex_inst <= `ZeroWord;
    end else if(stall[2] == `Stop && stall[3] == `NoStop) begin
      ......
      ex_inst <= `ZeroWord;
    end else if(stall[2] == `NoStop) begin
      ......
      //在译码阶段没有暂停的情况下,直接将ID模块的输入通过接口ex_inst输出
      ex_inst <= id_inst;	
    end
  end
	
endmodule


下一步将修改执行阶段