代码放在github上
在之前实现的基础上继续增加了加载、存储指令,系统结构上增加了访存模块和RAM
之前操作的传送门:
单指令周期ori指令的实现
单指令周期CPU—–逻辑、移位操作和空指令
单指令周期CPU——移动操作指令的实现
单指令周期CPU—算术操作指令(1)—简单算术操作指令的实现
单指令周期CPU—转移指令的实现
- lb(指令码6’b100000):字节加载指令,用法:lb,rt,offset(base),作用:从内存中指定的加载地址处,读取一个字节,然后符号扩展至32位,保存到地址为rt的通用寄存器中
- lbu(指令码6’b100100):无符号字节加载指令,用法:lbu,rt,offset(base),作用:从内存中指定的加载地址处,读取一个字节,然后无符号扩展至32位,保存到地址为rt的通用寄存器中
- lh(指令码6’b100001):半字加载指令,用法:lh,rt,offset(base),作用:从内存中指定的加载地址处,读取一个半字,然后符号扩展至32位,保存到地址为rt的通用寄存器中该指令有地址对齐要求,要求计算出来的存储地址的最低两位为0
- lhu(指令码6’b100101):无符号半字加载指令,用法:lhu,rt,offset(base),作用:从内存中指定的加载地址处,读取一个半字,然后无符号扩展至32位,保存到地址为rt的通用寄存器中该指令有地址对齐要求,要求计算出来的存储地址的最低两位为0
- lw(指令码6’b100011):字加载指令,用法:lw,rt,offset(base),作用:从内存中指定的加载地址处,读取一个字,保存到地址为rt的通用寄存器中。该指令有地址对齐要求,要求加载地址的最低两位为00
- sb(指令码为6’b101000):字节存储指令,用法:sb rt,offset(base),作用:将地址为rt的通用寄存器的值存储到内存中的指定地址。
- sh(指令码为6’b101001):半字存储指令,用法:sh rt,offset(base),作用:将地址为rt的通用寄存器的值存储到内存中的指定地址。该指令有地址对齐要求,要求计算出来的存储地址的最低两位为0
- sw(指令码为6’b101011):字存储指令,用法:sw rt,offset(base),作用:将地址为rt的通用寄存器的值存储到内存中的指定地址。该指令有地址对齐要求,要求计算出来的存储地址的最低两位为00
之前因为不需要访问内存,所以在改变MIPS五级流水线线时省略了只用来传递数据的访存阶段,现在需要加上访存阶段,访存阶段需要通过执行阶段EX传递过来的信息,根据具体的加载、存储指令来对数据存储器RAM进行读/写操作
mem_sel_o宽度是4位,每一位代表一个字节是否有效,比如:
加载时mem_sel_o=4'b1000,说明加载加载地址处32位数据总线的最高字节
存储时mem_sel_o=4'b0011,说明存储要存储数据的最低两个字节
`include "defines.v"
module mem(
input wire rst,
//来自执行阶段的信息
input wire[`RegAddrBus] wd_i,
input wire wreg_i,
input wire[`RegBus] wdata_i,
input wire[`RegBus] hi_i,
input wire[`RegBus] lo_i,
input wire whilo_i,
input wire[`AluOpBus] aluop_i,
input wire[`RegBus] mem_addr_i,
input wire[`RegBus] reg2_i,
//送到回写阶段的信息
output reg[`RegAddrBus] wd_o,
output reg wreg_o,
output reg[`RegBus] wdata_o,
output reg[`RegBus] hi_o,
output reg[`RegBus] lo_o,
output reg whilo_o,
//来自memory的信息
input wire[`RegBus] mem_data_i,
//送到memory的信息
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 ;
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_SW_OP: begin
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_LW_OP: begin
mem_addr_o <= mem_addr_i;
mem_we <= `WriteDisable;
wdata_o <= mem_data_i;
mem_sel_o <= 4'b1111;
mem_ce_o <= `ChipEnable;
end
default: begin
end
endcase
end //if
end //always
endmodule
数据存储器的建立与指令存储器非常相似,7个接口分别为
接口名 | 作用 |
---|---|
ce | 数据存储器使能信号 |
clk | 时钟信号 |
data_i | 要写入的数据 |
addr | 要访问的地址 |
we | 1:写操作;0:读操作 |
sel | 字选择信号 |
data_o | 读出的数据 |
为了反汇编对数据存储器按字节寻址,使用4个8位存储器代替一个32位的存储器
`include "defines.v"
module data_ram(
input wire clk,
input wire ce,
input wire we,
input wire[`DataAddrBus] addr,
input wire[3:0] sel,
input wire[`DataBus] data_i,
output reg[`DataBus] data_o
);
reg[`ByteWidth] data_mem0[0:`DataMemNum-1];
reg[`ByteWidth] data_mem1[0:`DataMemNum-1];
reg[`ByteWidth] data_mem2[0:`DataMemNum-1];
reg[`ByteWidth] data_mem3[0:`DataMemNum-1];
always @ (posedge clk) begin
if (ce == `ChipDisable) begin
//data_o <= ZeroWord;
end else if(we == `WriteEnable) begin
if (sel[3] == 1'b1) begin
data_mem3[addr[`DataMemNumLog2+1:2]] <= data_i[31:24];
end
if (sel[2] == 1'b1) begin
data_mem2[addr[`DataMemNumLog2+1:2]] <= data_i[23:16];
end
if (sel[1] == 1'b1) begin
data_mem1[addr[`DataMemNumLog2+1:2]] <= data_i[15:8];
end
if (sel[0] == 1'b1) begin
data_mem0[addr[`DataMemNumLog2+1:2]] <= data_i[7:0];
end
end
end
always @ (*) begin
if (ce == `ChipDisable) begin
data_o <= `ZeroWord;
end else if(we == `WriteDisable) begin
data_o <= {data_mem3[addr[`DataMemNumLog2+1:2]],
data_mem2[addr[`DataMemNumLog2+1:2]],
data_mem1[addr[`DataMemNumLog2+1:2]],
data_mem0[addr[`DataMemNumLog2+1:2]]};
end else begin
data_o <= `ZeroWord;
end
end
endmodule
defines.v:
`define EXE_LB 6'b100000 //指令LB的指令码
`define EXE_LBU 6'b100100 //指令LBU的指令码
`define EXE_LH 6'b100001 //指令LH的指令码
`define EXE_LHU 6'b100101 //指令LHU的指令码
`define EXE_LW 6'b100011 //指令LW的指令码
`define EXE_SB 6'b101000 //指令SB的指令码
`define EXE_SH 6'b101001 //指令SH的指令码
`define EXE_SW 6'b101011 //指令SW的指令码
`define EXE_LB_OP 8'b11100000
`define EXE_LBU_OP 8'b11100100
`define EXE_LH_OP 8'b11100001
`define EXE_LHU_OP 8'b11100101
`define EXE_LW_OP 8'b11100011
`define EXE_SB_OP 8'b11101000
`define EXE_SH_OP 8'b11101001
`define EXE_SW_OP 8'b11101011
`define EXE_RES_LOAD_STORE 3'b111
//数据存储器data_ram
`define DataAddrBus 31:0
`define DataBus 31:0
`define DataMemNum 131071
`define DataMemNumLog2 17
`define ByteWidth 7:0
需要将指令传递到下一阶段,因为需要在执行阶段使用到指令
如果读则需要写入目的寄存器,即Load类的指令wreg_o都是WriteEnable;而且只需要知道base的值即reg1_read_o为1即可
写则不需要写入目的寄存器,即Store类指令wreg_o都是WriteDisable;而且存储要知道存储的寄存器的值,还需要知道base的值,所以reg1_read_o和reg2_read_o都为1
此类指令的操作类型为`EXE_RES_LOAD_STORE
...
assign inst_o = inst_i;
...
`EXE_LB: begin
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
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
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
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
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_SB: begin
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
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
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
...
//将指令类别传递到访存阶段,利用其确定加载、存储类型
assign aluop_o = aluop_i;
//mem_addr_o即传递到访存阶段,是加载、存储指令的地址
//reg1_i是加载存储指令中地址为base的通用寄存器的值
//inst_i[15:0]就是指令中的offset
assign mem_addr_o = reg1_i + {{16{inst_i[15]}},inst_i[15:0]};
//reg2_i是存储指令要存储的数据,或者lwl、lwr指令要加载到的目的寄存器的原始值
assign reg2_o = reg2_i;
增加了访存模块和RAM,和一些接口,需要修改相关代码,将模块与模块之间连接起来
编写测试代码:
.org 0x0
.set noat
.set noreorder
.set nomacro
.global _start
_start:
ori $3,$0,0xeeff
sb $3,0x3($0) # [0x3] = 0xff
srl $3,$3,8
sb $3,0x2($0) # [0x2] = 0xee
ori $3,$0,0xccdd
sb $3,0x1($0) # [0x1] = 0xdd
srl $3,$3,8
sb $3,0x0($0) # [0x0] = 0xcc
lb $1,0x3($0) # $1 = 0xffffffff
lbu $1,0x2($0) # $1 = 0x000000ee
nop
ori $3,$0,0xaabb
sh $3,0x4($0) # [0x4] = 0xaa, [0x5] = 0xbb
lhu $1,0x4($0) # $1 = 0x0000aabb
lh $1,0x4($0) # $1 = 0xffffaabb
ori $3,$0,0x8899
sh $3,0x6($0) # [0x6] = 0x88, [0x7] = 0x99
lh $1,0x6($0) # $1 = 0xffff8899
lhu $1,0x6($0) # $1 = 0x00008899
ori $3,$0,0x4455
sll $3,$3,0x10
ori $3,$3,0x6677
sw $3,0x8($0) # [0x8] = 0x44, [0x9]= 0x55, [0xa]= 0x66, [0xb] = 0x77
lw $1,0x8($0) # $1 = 0x44556677
_loop:
j _loop
nop