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

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

将陆续上传新书《自己动手写CPU》,今天是第48篇。


9.8 修改OpenMIPS以实现llsc指令

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表示没有异常
  input  wire                   flush,
	
  // 写操作
  input wire			   LLbit_i,
  input wire                    we,
	
  // LLbit寄存器的值
  output reg                    LLbit_o
	
);

  always @ (posedge clk) begin
     if (rst == `RstEnable) begin
        LLbit_o <= 1'b0;
     end else if((flush == 1'b1)) begin //如果异常发生,那么设置LLbit_o为0
        LLbit_o <= 1'b0;
     end else if((we == `WriteEnable)) begin
        LLbit_o <= LLbit_i;
     end
  end

endmodule

      当有异常发生时,会使得LLbit寄存器的值为0。所以此处有一个输入接口flush,当flush1时,表示有异常发生(在第11章实现异常处理的时候会详细介绍),从而设置LLbit寄存器的值为0

9.8.2 修改译码阶段的ID模块

      在译码阶段的ID模块要增加对llsc指令的译码,根据图9-28给出的llsc指令格式可得,确定llsc指令的过程如图9-31所示。


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

`define EXE_LL  6'b110000
`define EXE_SC  6'b111000

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

module id(

  .......
);

  .......
    
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_LL:	 begin                       // ll指令
        wreg_o      <= `WriteEnable;
        aluop_o     <= `EXE_LL_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_SC:	  begin                      // sc指令
        wreg_o      <= `WriteEnable;
        aluop_o     <= `EXE_SC_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; 
        alusel_o    <= `EXE_RES_LOAD_STORE;
      end				
        ......

endmodule

      译码工作主要是确定要写的目的寄存器、要读取的寄存器情况、要执行的运算等三个方面。分别介绍如下。

      (1ll指令

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

      (2sc指令

  •  要写的目的寄存器:条件存储指令sc也需要写通用寄存器,所以也设置wreg_oWriteEnable。这一点是与其余的存储指令sbshsw等的重要区别。要写的目的寄存器是指令中的16-20bit,所以设置wd_oinst_i[20:16]
  •  要读取的寄存器情况:参考图9-28可知,计算存储目标地址需要使用到地址为base的寄存器值,所以设置reg1_read_o1,表示通过Regfile模块的读端口1读取寄存器,默认读取的寄存器地址reg1_addr_o是指令的21-25bit,正是sc指令中的base。所以最终译码阶段的输出reg1_o就是地址为base的寄存器的值。另外,要存储的值是地址为rt的通用寄存器的值,所以设置reg2_read_o1,表示通过Regfile模块的读端口2读取寄存器,默认读取的寄存器地址reg2_addr_o是指令的16-20bit,正是sc指令中的rt。所以最终译码阶段的输出reg2_o就是地址为rt的寄存器的值。
  •  要执行的运算:设置alusel_oEXE_RES_LOAD_STORE,表示运算类型是加载存储,设置aluop_oEXE_SC_OP,表示运算子类型是sc

9.8.3 修改访存阶段

      1、修改MEM模块

      参考图9-30可知,访存阶段的MEM模块要新增部分接口,新增接口的描述如表9-9所示。


      MEM模块主要修改的代码如下,完整代码请参考本书附带光盘Code\Chapter9_2目录下的mem.v文件。

module mem(

  ......

  // 新增的输入接口
  input wire                  LLbit_i,
  input wire                  wb_LLbit_we_i,
  input wire                  wb_LLbit_value_i,
	
  ......

  // 新增的输出接口
  output reg                  LLbit_we_o,
  output reg                  LLbit_value_o,
	
  ......
	
);

  reg LLbit;         // 保存LLbit寄存器的最新值
  ......

  // 获取LLbit寄存器的最新值,如果回写阶段的指令要写LLbit,那么回写阶段要写入的
  // 值就是LLbit寄存器的最新值,反之,LLbit模块给出的值LLbit_i是最新值
always @ (*) begin
  if(rst == `RstEnable) begin
    LLbit <= 1'b0;
  end else begin
    if(wb_LLbit_we_i == 1'b1) begin
       LLbit <= wb_LLbit_value_i;     // 回写阶段的指令要写LLbit
    end else begin
       LLbit <= LLbit_i;
    end
  end
end
	
always @ (*) begin
  if(rst == `RstEnable) begin
	  ......	
    LLbit_we_o    <= 1'b0;
    LLbit_value_o <= 1'b0; 
  end else begin
    ......
    LLbit_we_o    <= 1'b0;
    LLbit_value_o <= 1'b0;、
    mem_ce_o <= `ChipDisable;
    mem_we <= `WriteDisable;
    case (aluop_i) 
      ......
      `EXE_LL_OP:	begin               // ll指令的访存输出
          mem_addr_o    <= mem_addr_i;
          mem_we        <= `WriteDisable;
          wdata_o       <= mem_data_i;
          LLbit_we_o    <= 1'b1;
          LLbit_value_o <= 1'b1;
          mem_sel_o     <= 4'b1111;
          mem_ce_o      <= `ChipEnable;
       end
      ......
     `EXE_SC_OP:	 begin              // sc指令的访存输出
          if(LLbit == 1'b1) begin
            LLbit_we_o    <= 1'b1;
            LLbit_value_o <= 1'b0;
            mem_addr_o    <= mem_addr_i;
            mem_we        <= `WriteEnable;
            mem_data_o    <= reg2_i;
            wdata_o       <= 32'b1;
            mem_sel_o     <= 4'b1111;
            mem_ce_o      <= `ChipEnable;
         end else begin
            wdata_o       <= 32'b0;
		     end
    end
  ......
	
endmodule

   MEM模块的代码增加了一个过程,以获得LLbit寄存器的最新值,然后针对llsc指令分别给出了对数据存储器的访问信息。

   (1ll指令

  •  给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,参考9.3.2节。
  •  因为是加载操作,所以设置mem_we_oWriteDisable
  •  因为要访问数据存储器,所以设置mem_ce_oChipEnable
  •  因为是加载一个字,所以设置mem_sel_o4‘b1111。
  •  要写入通用寄存器rt的值就是从数据存储器加载到的数据mem_data_i,所以设置wdata_omem_data_i
  •  要置LLbit寄存器为1,所以设置LLbit_we_o1,表示要写LLbit寄存器,同时,设置LLbit_value_o1,表示要写入LLbit寄存器的值为1

   (2sc指令

   如果LLbit寄存器的值为1,表示之前已执行过ll指令,并且在ll指令执行后、当前sc指令执行前的这段时间内,没有异常发生,此时,sc指令的访存信息如下。

  •  给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,参考9.3.2节。
  •  因为是存储操作,所以设置mem_we_oWriteEnable
  •  因为要访问数据存储器,所以设置mem_ce_oChipEnable
  •  因为是存储一个字,所以设置mem_sel_o4‘b1111。
  •  要存储的数据是reg2_i,是从译码阶段传递过来的,其值就是地址为rt的通用寄存器的值。
  •  要置地址为rt的通用寄存器为1,所以设置wdata_o1
  •  要置LLbit寄存器为0,所以设置LLbit_we_o1,表示要写LLbit寄存器,同时,设置LLbit_value_o0,表示要写入LLbit寄存器的值为0
      反之,如果LLbit的值为0,表示之前没有执行过ll指令,或者在ll指令执行后、当前sc指令执行前的这段时间内,有异常发生,此时,sc指令的访存信息如下。

  • 不修改数据存储器,所以mem_we_o保持默认值WriteDisablemem_ce_o保持默认值ChipDisable
  • 不修改LLbit寄存器的值,所以LLbit_we_o保持默认值0
  • 要置地址为rt的通用寄存器为0,所以设置wdata_o0

   2、修改MEM/WB模块

   从图9-30可知,MEM/WB模块要新增部分接口,新增接口的描述如表9-10所示。



      MEM/WB模块要修改的代码如下,作用很直白:在访存阶段没有暂停时,简单地将MEM给出的对LLbit寄存器的写信息传递到访存阶段,完整代码请读者参考本书附带光盘Code\Chapter9_2目录下的mem_wb.v文件。

module mem_wb(

  ......
	
  input wire                  mem_LLbit_we,
  input wire                  mem_LLbit_value,

  ......
	
  output reg                  wb_LLbit_we,
  output reg                  wb_LLbit_value
	
);


  always @ (posedge clk) begin
    if(rst == `RstEnable) begin
      ......
      wb_LLbit_we    <= 1'b0;
      wb_LLbit_value <= 1'b0;
    end else if(stall[4] == `Stop && stall[5] == `NoStop) begin
      ......
      wb_LLbit_we    <= 1'b0;
      wb_LLbit_value <= 1'b0; 
    end else if(stall[4] == `NoStop) begin  // 判断访存阶段是否暂停
      ......
      wb_LLbit_we    <= mem_LLbit_we;
      wb_LLbit_value <= mem_LLbit_value;
   end
 end

endmodule

9.8.4 修改OpenMIPS模块

      因为一些模块增加了接口,所以要修改顶层模块OpenMIPS,以将这些新增加的接口按照图9-30所示的关系连接起来。具体修改代码不再给出,读者可以参考本书附带光盘Code\Chapter9_2目录下的openmips.v文件。

      注意一点,因为目前还没有实现异常处理,所以可以直接设置LLbit模块的输入接口flush0,表示没有异常发生,当后续章节实现异常处理后,再将其连接到正确的模块。


下一篇将验证OpenMIPS的ll、sc指令是否实现正确。

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