首页 > 代码库 > 自己动手写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_i、mem_addr_o、mem_we_o、mem_sel_o、mem_data_o、mem_ce_o都是与数据存储器相连的,其中大部分接口的作用都很好理解,此处只对mem_sel_o做进一步说明,分加载、存储两种操作分别说明。
(1)对于加载操作,MIPS32指令集架构中定义的加载指令可以加载字节、半字、字,但是数据总线的宽度是32位,占4个字节。如果执行加载字节指令lb、lbu,那么就要知道通过数据总线输入的4个字节中,哪个字节是要读取的数据;如果执行加载半字指令lh、lhu,那么就要知道哪个半字是要读取的数据,mem_sel_o的作用就是指出哪一部分是有效数据。mem_sel_o宽度为4,分别对应数据总线的4个字节,比如:使用加载指令lb读取数据存储器地址0x1处的字节,那么可以设置mem_sel_o为4‘b0100,意思就是,希望外部存储器在输出数据时,将地址0x1处的字节放在32位数据总线的次高字节,也就是16-23bit的位置,当数据送到处理器时,处理器就取出其中16-23bit对应的字节,作为数据存储器地址0x1处的值。
(2)对于存储操作,MIPS32指令集架构中定义的存储指令可以存储字节、半字、字,但是数据总线的宽度是32位,占4个字节,如果执行字节存储指令sb、半字存储指令sh,那么外部数据存储器就要知道通过数据总线传递过来的4个字节中,哪个字节、哪个半字是要存储的数据,mem_sel_o的作用就是指出哪一部分是要存储的有效数据。比如:使用存储指令sh向地址0x2处存储0x8281,那么可以设置mem_data_o为0x82818281、设置mem_sel_o为4’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_o、mem_we_o、mem_sel_o、mem_data_o、wdata_o等接口的值。下面对其中几个典型指令的访存过程进行解释。
1、lb指令的访存过程
(1)因为要访问数据存储器,所以设置mem_ce_o为ChipEnable。
(2)因为是加载操作,所以设置mem_we_o为WriteDisable。
(3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i。
(4)依据mem_addr_i的最后两位,确定mem_sel_o的值,并据此从数据存储器的输入数据mem_data_i中获得要读取的字节,进行符号扩展。比如:如果mem_addr_i的最后两位是01,那么设置mem_sel_o为4‘b0100,表示希望数据存储器给出的数据的第16-23bit就是要读取的字节,也就是mem_data_i[23:16],将其最高位进行符号扩展,得到最终的结果wdata_o,作为要写入目标寄存器的数据,读者如果忘记了wdata_o的作用,可以参考4.2.7节ori指令实现过程访存阶段的说明。
有些读者可能会感到疑惑,为何不直接设置mem_sel_o为4‘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所示。
lbu、lh、lhu、lw指令的访存过程与lb指令类似,可以对照理解。
2、lwl指令的访存过程
(1)因为要访问数据存储器,所以设置mem_ce_o为ChipEnable。
(2)因为是加载操作,所以设置mem_we_o为WriteDisable。
(3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i,但最后两位要设置为0,因为lwl指令要从RAM中读出一个字,所以需要将地址对齐,同时设置mem_sel_o为4‘b1111。
(4)依据mem_addr_i的最后两位,将从数据存储器读取的数据mem_data_i与目的寄存器的原始值reg2_i进行组合,得到最终要写入目的寄存器的值wdata_o,组合过程可以参考图9-8。
lwr指令的访存过程与lwl指令类似,可以对照理解。
3、sb指令的访存过程
(1)因为要访问数据存储器,所以设置mem_ce_o为ChipEnable。
(2)因为是存储操作,所以设置mem_we_o为WriteEnable。
(3)给出要访问的数据存储器地址mem_addr_o,其值就是执行阶段计算出来的地址mem_addr_i。
(4)sb指令要写入的数据是寄存器的最低字节,将该字节复制到mem_data_o的其余部分,然后依据mem_addr_i的最后两位,确定mem_sel_o的值。比如:如果mem_addr_i的最后两位是01,那么设置mem_sel_o为4’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所示。
sh、sw指令的访存过程与sb指令类似,可以对照理解。
4、swl指令的访存过程
(1)要访问数据存储器,所以设置mem_ce_o为ChipEnable。
(2)因为是存储操作,所以设置mem_we_o为WriteEnable。
(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(修改访存阶段)