将陆续上传新书《自己动手写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指令类似,可以对照理解。
未完待续,下一步将修改顶层模块。