首页 > 代码库 > 自己动手写CPU之第九阶段(5)——实现加载存储指令3(修改访存阶段)

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

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

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


9.3.3 修改访存阶段

      访存阶段主要是修改MEM模块,参考图9-19可知,需要为其添加对数据存储器RAM的访问接口,添加的接口描述如表9-5所示。


      从图9-19可知,表9-5后面的几个新增接口mem_data_imem_addr_omem_we_omem_sel_omem_data_omem_ce_o都是与数据存储器相连的,其中大部分接口的作用都很好理解,此处只对mem_sel_o做进一步说明,分加载、存储两种操作分别说明。

      (1)对于加载操作,MIPS32指令集架构中定义的加载指令可以加载字节、半字、字,但是数据总线的宽度是32位,占4个字节。如果执行加载字节指令lblbu,那么就要知道通过数据总线输入的4个字节中,哪个字节是要读取的数据;如果执行加载半字指令lhlhu,那么就要知道哪个半字是要读取的数据,mem_sel_o的作用就是指出哪一部分是有效数据。mem_sel_o宽度为4,分别对应数据总线的4个字节,比如:使用加载指令lb读取数据存储器地址0x1处的字节,那么可以设置mem_sel_o4‘b0100,意思就是,希望外部存储器在输出数据时,将地址0x1处的字节放在32位数据总线的次高字节,也就是16-23bit的位置,当数据送到处理器时,处理器就取出其中16-23bit对应的字节,作为数据存储器地址0x1处的值。

      (2)对于存储操作,MIPS32指令集架构中定义的存储指令可以存储字节、半字、字,但是数据总线的宽度是32位,占4个字节,如果执行字节存储指令sb、半字存储指令sh,那么外部数据存储器就要知道通过数据总线传递过来的4个字节中,哪个字节、哪个半字是要存储的数据,mem_sel_o的作用就是指出哪一部分是要存储的有效数据。比如:使用存储指令sh向地址0x2处存储0x8281,那么可以设置mem_data_o0x82818281、设置mem_sel_o4’b0011,这样外部存储器就知道要存储的数据是0x82818281的最低两个字节,正是0x8281

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

module mem(

  ......

   //新增接口,来自执行阶段的信息
  input wire[`AluOpBus]        aluop_i,
  input wire[`RegBus]          mem_addr_i,
  input wire[`RegBus]          reg2_i,
	
  //新增接口,来自外部数据存储器RAM的信息
  input wire[`RegBus]          mem_data_i,
	
  ......
	
  //新增接口,送到外部数据存储器RAM的信息
  output reg[`RegBus]          mem_addr_o,
  output wire		 	             mem_we_o,
  output reg[3:0]              mem_sel_o,
  output reg[`RegBus]          mem_data_o,
  output reg                   mem_ce_o
	
);

  wire[`RegBus] zero32;
  reg           mem_we;

  assign mem_we_o = mem_we ;     //外部数据存储器RAM的读、写信号
  assign zero32   = `ZeroWord;

  always @ (*) begin
    if(rst == `RstEnable) begin
      wd_o       <= `NOPRegAddr;
      wreg_o     <= `WriteDisable;
      wdata_o    <= `ZeroWord;
      hi_o       <= `ZeroWord;
      lo_o       <= `ZeroWord;
      whilo_o    <= `WriteDisable;
      mem_addr_o <= `ZeroWord;
      mem_we     <= `WriteDisable;
      mem_sel_o  <= 4'b0000;
      mem_data_o <= `ZeroWord;
      mem_ce_o   <= `ChipDisable;	
  end else begin
      wd_o       <= wd_i;
      wreg_o     <= wreg_i;
      wdata_o    <= wdata_i;
      hi_o       <= hi_i;
      lo_o       <= lo_i;
      whilo_o    <= whilo_i;
      mem_we     <= `WriteDisable;
      mem_addr_o <= `ZeroWord;
      mem_sel_o  <= 4'b1111;
      mem_ce_o   <= `ChipDisable;
      case (aluop_i)
        `EXE_LB_OP:		begin                  //lb指令
           mem_addr_o <= mem_addr_i;
           mem_we     <= `WriteDisable;
           mem_ce_o   <= `ChipEnable;
           case (mem_addr_i[1:0])
             2'b00:	begin
               wdata_o   <= {{24{mem_data_i[31]}},mem_data_i[31:24]};
               mem_sel_o <= 4'b1000;
             end
             2'b01:	begin
               wdata_o   <= {{24{mem_data_i[23]}},mem_data_i[23:16]};
               mem_sel_o <= 4'b0100;
             end
             2'b10:	begin
               wdata_o   <= {{24{mem_data_i[15]}},mem_data_i[15:8]};
               mem_sel_o <= 4'b0010;
             end
             2'b11:	begin
               wdata_o   <= {{24{mem_data_i[7]}},mem_data_i[7:0]};
               mem_sel_o <= 4'b0001;
             end
             default:	begin
               wdata_o   <= `ZeroWord;
             end
           endcase
        end
       `EXE_LBU_OP:		begin            //lbu指令
           mem_addr_o <= mem_addr_i;
           mem_we     <= `WriteDisable;
           mem_ce_o   <= `ChipEnable;
           case (mem_addr_i[1:0])
             2'b00:	begin
                wdata_o   <= {{24{1'b0}},mem_data_i[31:24]};
                mem_sel_o <= 4'b1000;
             end
             2'b01:	begin
                wdata_o   <= {{24{1'b0}},mem_data_i[23:16]};
                mem_sel_o <= 4'b0100;
             end
             2'b10:	begin
                wdata_o   <= {{24{1'b0}},mem_data_i[15:8]};
                mem_sel_o <= 4'b0010;
             end
             2'b11:	begin
                wdata_o   <= {{24{1'b0}},mem_data_i[7:0]};
                mem_sel_o <= 4'b0001;
             end
             default:	begin
                wdata_o   <= `ZeroWord;
             end
          endcase
        end
       `EXE_LH_OP:		begin                  //lh指令
	        mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteDisable;
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
             2'b00:	begin
               wdata_o   <= {{16{mem_data_i[31]}},mem_data_i[31:16]};
               mem_sel_o <= 4'b1100;
             end
             2'b10:	begin
               wdata_o   <= {{16{mem_data_i[15]}},mem_data_i[15:0]};
               mem_sel_o <= 4'b0011;
             end
             default:	begin
               wdata_o   <= `ZeroWord;
             end
          endcase
        end
       `EXE_LHU_OP:		begin            //lhu指令
          mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteDisable;
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00:	begin
               wdata_o   <= {{16{1'b0}},mem_data_i[31:16]};
               mem_sel_o <= 4'b1100;
            end
            2'b10:	begin
               wdata_o   <= {{16{1'b0}},mem_data_i[15:0]};
               mem_sel_o <= 4'b0011;
            end
            default:	begin
               wdata_o   <= `ZeroWord;
            end
          endcase
        end
       `EXE_LW_OP:		begin                   //lw指令
          mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteDisable;
          wdata_o    <= mem_data_i;
          mem_sel_o  <= 4'b1111;
          mem_ce_o   <= `ChipEnable;
       end
       `EXE_LWL_OP:		begin            //lwl指令
	        mem_addr_o <= {mem_addr_i[31:2], 2'b00};
          mem_we     <= `WriteDisable;
          mem_sel_o  <= 4'b1111;
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00:	begin
                wdata_o <= mem_data_i[31:0];
            end
            2'b01:	begin
                wdata_o <= {mem_data_i[23:0],reg2_i[7:0]};
            end
            2'b10:	begin
                wdata_o <= {mem_data_i[15:0],reg2_i[15:0]};
            end
            2'b11:	begin
                wdata_o <= {mem_data_i[7:0],reg2_i[23:0]};
            end
            default:	begin
                wdata_o <= `ZeroWord;
            end
          endcase
       end
      `EXE_LWR_OP:		begin             //lwr指令
          mem_addr_o <= {mem_addr_i[31:2], 2'b00};
          mem_we     <= `WriteDisable;
          mem_sel_o  <= 4'b1111;
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00: begin
                wdata_o <= {reg2_i[31:8],mem_data_i[31:24]};
            end
            2'b01: begin
                wdata_o <= {reg2_i[31:16],mem_data_i[31:16]};
            end
            2'b10: begin
                wdata_o <= {reg2_i[31:24],mem_data_i[31:8]};
            end
            2'b11: begin
                wdata_o <= mem_data_i;
            end
            default: begin
                wdata_o <= `ZeroWord;
            end
          endcase
       end
      `EXE_SB_OP:		begin             //sb指令
          mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteEnable;
          mem_data_o <= {reg2_i[7:0],reg2_i[7:0],
                          reg2_i[7:0],reg2_i[7:0]};
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00: begin
               mem_sel_o <= 4'b1000;
            end
            2'b01: begin
               mem_sel_o <= 4'b0100;
            end
            2'b10: begin
               mem_sel_o <= 4'b0010;
            end
            2'b11: begin
               mem_sel_o <= 4'b0001;	
            end
            default: begin
               mem_sel_o <= 4'b0000;
            end
          endcase
       end
      `EXE_SH_OP:		begin             //sh指令
          mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteEnable;
          mem_data_o <= {reg2_i[15:0],reg2_i[15:0]};
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00: begin
               mem_sel_o <= 4'b1100;
            end
            2'b10: begin
               mem_sel_o <= 4'b0011;
            end
            default: begin
               mem_sel_o <= 4'b0000;
            end
          endcase
       end
      `EXE_SW_OP:		begin             //sw指令
          mem_addr_o <= mem_addr_i;
          mem_we     <= `WriteEnable;
          mem_data_o <= reg2_i;
          mem_sel_o  <= 4'b1111;
          mem_ce_o   <= `ChipEnable;
       end
      `EXE_SWL_OP:		begin             //swl指令
          mem_addr_o <= {mem_addr_i[31:2], 2'b00};
          mem_we     <= `WriteEnable;
          mem_ce_o   <= `ChipEnable;
          case (mem_addr_i[1:0])
            2'b00: begin 
              mem_sel_o <= 4'b1111;
              mem_data_o <= reg2_i;
            end
            2'b01: begin
              mem_sel_o <= 4'b0111;
              mem_data_o <= {zero32[7:0],reg2_i[31:8]};
            end
            2'b10: begin
              mem_sel_o <= 4'b0011;
              mem_data_o <= {zero32[15:0],reg2_i[31:16]};
            end
            2'b11: begin
              mem_sel_o <= 4'b0001;
              mem_data_o <= {zero32[23:0],reg2_i[31:24]};
            end
            default: begin
              mem_sel_o <= 4'b0000;
            end
         endcase
      end
      `EXE_SWR_OP:		begin             //swr指令
         mem_addr_o <= {mem_addr_i[31:2], 2'b00};
         mem_we     <= `WriteEnable;
         mem_ce_o   <= `ChipEnable;
            case (mem_addr_i[1:0])
              2'b00: begin 
                 mem_sel_o  <= 4'b1000;
                 mem_data_o <= {reg2_i[7:0],zero32[23:0]};
              end
              2'b01: begin
                 mem_sel_o  <= 4'b1100;
                 mem_data_o <= {reg2_i[15:0],zero32[15:0]};
              end
              2'b10: begin
                 mem_sel_o  <= 4'b1110;
                 mem_data_o <= {reg2_i[23:0],zero32[7:0]};
              end
              2'b11: begin
                 mem_sel_o  <= 4'b1111;
                 mem_data_o <= reg2_i[31:0];
              end
              default:	begin
                 mem_sel_o  <= 4'b0000;
              end
            endcase
      end 
      default:		begin
       //do nothing
      end
    endcase
  end
 end

endmodule

      上面的代码虽然很长,但结构很清晰,作用也很明确,就是依据不同的加载、存储指令类型,给出mem_addr_omem_we_omem_sel_omem_data_owdata_o等接口的值。下面对其中几个典型指令的访存过程进行解释。

      1lb指令的访存过程

      (1)因为要访问数据存储器,所以设置mem_ce_oChipEnable

      (2)因为是加载操作,所以设置mem_we_oWriteDisable

      (3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i

      (4)依据mem_addr_i的最后两位,确定mem_sel_o的值,并据此从数据存储器的输入数据mem_data_i中获得要读取的字节,进行符号扩展。比如:如果mem_addr_i的最后两位是01,那么设置mem_sel_o4‘b0100,表示希望数据存储器给出的数据的第16-23bit就是要读取的字节,也就是mem_data_i[23:16],将其最高位进行符号扩展,得到最终的结果wdata_o,作为要写入目标寄存器的数据,读者如果忘记了wdata_o的作用,可以参考4.2.7ori指令实现过程访存阶段的说明。

      有些读者可能会感到疑惑,为何不直接设置mem_sel_o4‘b0001,表示希望数据存储器给出的数据的第0-7bit就是要读取的字节,而不考虑mem_addr_i的最后两位为何值,这样不是更简单吗?的确,这样做是更简单了,但是这里确定mem_sel_o值的过程实际上参考了Wishbone总线的相关规范,为了是在后期给OpenMIPS添加Wishbone总线接口的时候容易一些。在本章,读者可以简单的认为:外部的数据存储器并没有依据mem_addr_o地址读取数据,而是将mem_addr_o地址的最后两位修改为0,依据修改后的地址读取数据,所以OpenMIPS需要依据mem_addr_o最后两位的值,确定要读取的字节。如图9-21所示。

      lbulhlhulw指令的访存过程与lb指令类似,可以对照理解。

      2lwl指令的访存过程

      (1)因为要访问数据存储器,所以设置mem_ce_oChipEnable

      (2)因为是加载操作,所以设置mem_we_oWriteDisable

      (3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,但最后两位要设置为0,因为lwl指令要从RAM中读出一个字,所以需要将地址对齐,同时设置mem_sel_o4‘b1111

      (4)依据mem_addr_i的最后两位,将从数据存储器读取的数据mem_data_i与目的寄存器的原始值reg2_i进行组合,得到最终要写入目的寄存器的值wdata_o,组合过程可以参考图9-8

      lwr指令的访存过程与lwl指令类似,可以对照理解。

      3sb指令的访存过程

      (1)因为要访问数据存储器,所以设置mem_ce_oChipEnable

      (2)因为是存储操作,所以设置mem_we_oWriteEnable

      (3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i

      (4sb指令要写入的数据是寄存器的最低字节,将该字节复制到mem_data_o的其余部分,然后依据mem_addr_i的最后两位,确定mem_sel_o的值。比如:如果mem_addr_i的最后两位是01,那么设置mem_sel_o4’b0100,表示第16-23bit就是要写入的字节,即mem_data_o[23:16]

      读者可能又会有疑惑,为何不直接设置mem_sel_o的值为4‘b0001,而不用考虑mem_addr_o最低两位的值,不用将最低字节复制到mem_data_o的其余部分呢?理由与lb指令一样,此处确定mem_sel_o值的过程实际上参考了Wishbone总线的相关规范,为了是后期给OpenMIPS添加Wishbone总线接口的时候容易一些。现在,大家可以简单的认为,外部的数据存储器并没有依据mem_addr_o存储数据,而是将mem_addr_o的最后两位修改为0,依据修改后的地址存储数据,所以OpenMIPS需要依据mem_addr_o最后两位的值,确定mem_sel_o的值,同时将最低字节复制到mem_data_o的其余部分,这样保证无论mem_sel_o为何值,写入字节始终是寄存器的最低字节。如图9-22所示。



      shsw指令的访存过程与sb指令类似,可以对照理解。

      4swl指令的访存过程

      (1)要访问数据存储器,所以设置mem_ce_oChipEnable

      (2)因为是存储操作,所以设置mem_we_oWriteEnable

      (3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,但最后两位要设置为0,因为swl指令最多可能需要向数据存储器写入一个字,所以这里将地址对齐。

      (4)依据mem_addr_i的最后两位,确定最终要写入数据存储器的数据是读出的寄存器值reg2_i的哪一部分,从而给出mem_sel_o的值,这一确定过程可以参考图9-14

      swr指令的访存过程与swl指令类似,可以对照理解。


未完待续,下一步将修改顶层模块。


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