直接映射
先看懂cache的映射原理,根据cache大小与主存大小来计算各个信号线的位数
各个信号线位数
主存地址在逻辑上分为区号、块号、块内地址
Cache结构
Cache访问原理
基本过程
状态机:“三段式”实现 6.3 Verilog 状态机 | 菜鸟教程 (runoob.com)
// TODO: 编写状态机现态的更新逻辑
// TODO: 编写状态机的状态转移逻辑
// TODO: 生成状态机的输出信号
状态机设计
再分析并确定各个状态下的输入输出,完善状态机。
编写状态机更新逻辑
这个类似模板了
// TODO: 编写状态机现态的更新逻辑
always @(posedge cpu_clk or posedge cpu_rst) begin
if(cpu_rst) begin
sta <= 2'b0;
end
else begin
sta <= nex_sta;
end
end
编写状态机状态转移信号
需要以下变量:新的访问请求信号、命中信号、缺失(不命中)、主存数据已返回
// TODO: 编写状态机的状态转移逻辑
always@(*) begin
if(cpu_rst) begin
nex_sta = IDLE;
end
else begin
case(sta)
IDLE: begin
if(inst_rreq == 1'b1) begin
nex_sta = TAG_CHECK;
end
else begin
nex_sta = IDLE;
end
end
TAG_CHECK:begin
if(hit) begin
nex_sta = IDLE;
end
else begin
nex_sta = REFILL;
end
end
REFILL: begin
if(mem_rvalid) begin
nex_sta = TAG_CHECK;
end
else begin
nex_sta = REFILL;
end
end
default: nex_sta = IDLE;
endcase
end
end
编写状态机输出信号
分为向主存输出和向cpu输出
IDLE状态下是赋默认值
TAG_CHECK状态下是看否命中,来确定输出(若未命中,要从主存里读东西出来,放到cache里面,再从cahce里来一次是否命中的判断)
REFILL状态下,如果主存准备信号(mem_rrdy)为真,则可以发送主存读所需要的地址和使能信号(注意只提供一个时钟周期)
错误总结:
- 一直在思考如何让发送给主存的信号只有一个时钟周期,由于有 mem_rrdy 的限制,以及不符合打两拍的场景,所以打两拍操作最后是不行的(打两拍可参照inst_valid和inst_out单周期有效的方法,打两拍的应用场景:要在单位信号从0到1,的属于1的那个周期里,发送一个周期的有效信号)
- 还有,块是整块取,块的取址地址要是模四为零的。
- 忘了mem_addr的有效时间只有一个周期,在后面也使用到了它,哭哭。
贴一个完整代码
`timescale 1ns / 1ps
`define BLK_LEN 4
`define BLK_SIZE (`BLK_LEN*32)
module ICache(
input wire cpu_clk,
input wire cpu_rst, // high active
// Interface to CPU
input wire inst_rreq, // 来自CPU的取指请求
input wire [31:0] inst_addr, // 来自CPU的取指地址
output reg inst_valid, // 输出给CPU的指令有效信号(读指令命中)
output reg [31:0] inst_out, // 输出给CPU的指令
// Interface to Read Bus
input wire mem_rrdy, // 主存就绪信号(高电平表示主存可接收ICache的读请求)
output reg [ 3:0] mem_ren, // 输出给主存的读使能信号
output reg [31:0] mem_raddr, // 输出给主存的读地址
input wire mem_rvalid, // 来自主存的数据有效信号
input wire [`BLK_SIZE-1:0] mem_rdata // 来自主存的读数据
);
`ifdef ENABLE_ICACHE /******** 不要修改此行代码 ********/
wire [4:0] tag_from_cpu = inst_addr[14:10]; // 主存地址的TAG
wire [3:0] offset = inst_addr[3:0]; // 32位字偏移量
wire valid_bit = cache_line_r[`BLK_SIZE + 5]; // Cache行的有效位
wire [4:0] tag_from_cache = cache_line_r[`BLK_SIZE + 4 : `BLK_SIZE]; // Cache行的TAG
// TODO: 定义ICache状态机的状态变量
parameter IDLE = 2'b00;
parameter TAG_CHECK = 2'b01;
parameter REFILL = 2'b10;
reg [1:0] sta, nex_sta;
wire hit = (sta == TAG_CHECK) ? (valid_bit && (tag_from_cache == tag_from_cpu)) : 1'b0;
wire[6:0] offset_bit = {offset,3'b000};
wire[`BLK_SIZE + 5:0] data_out = cache_line_r >> offset_bit;
always @(*) begin
if(hit & hit_n) begin
inst_valid = 1'b1;
inst_out = data_out[31:0];
/* TODO: 根据字偏移,选择Cache行中的某个32位字输出指令 */
end
else begin
inst_valid = 1'b0;
inst_out = 32'b0;
end
end
reg hit_n ;
always@(posedge cpu_clk) begin
hit_n <= ~hit;
end
reg inst_addr_reg;
wire cache_we = mem_rvalid; // ICache存储体的写使能信号
wire [5:0] cache_index = inst_addr[9:4]; // 主存地址的Cache索引 / ICache存储体的地址
wire [`BLK_SIZE + 5:0] cache_line_w = {1'b1,inst_addr[14:10],mem_rdata}; // 待写入ICache的Cache行
wire [`BLK_SIZE + 5:0] cache_line_r; // 从ICache读出的Cache行
//135 = 1(有效位) + 7(TAG的位数) + 128(数据位数)
// ICache存储体:Block MEM IP核
blk_mem_gen_1 U_isram (
.clka (cpu_clk),
.wea (cache_we),
.addra (cache_index),
.dina (cache_line_w),
.douta (cache_line_r)
);
// TODO: 编写状态机现态的更新逻辑
always @(posedge cpu_clk or posedge cpu_rst) begin
if(cpu_rst) begin
sta <= 2'b0;
end
else begin
sta <= nex_sta;
end
end
// TODO: 编写状态机的状态转移逻辑
always@(*) begin
if(cpu_rst) begin
nex_sta = IDLE;
end
else begin
case(sta)
IDLE: begin
if(inst_rreq == 1'b1) begin
nex_sta = TAG_CHECK;
end
else begin
nex_sta = IDLE;
end
end
TAG_CHECK:begin
if(hit) begin
nex_sta = IDLE;
end
else begin
nex_sta = REFILL;
end
end
REFILL: begin
if(mem_rvalid) begin
nex_sta = TAG_CHECK;
end
else begin
nex_sta = REFILL;
end
end
default: nex_sta = IDLE;
endcase
end
end
// reg mem_rrdy_n;
reg mem_ren_pulse;
always @(posedge cpu_clk or posedge cpu_rst) begin
if(cpu_rst) begin
mem_ren_pulse <= 1'b0;
end
else if(sta == REFILL) begin
if(mem_rrdy && !mem_ren_pulse) begin
mem_ren_pulse <= 1'b1;
end
end
else begin
mem_ren_pulse <= 1'b0;
end
end
// TODO: 生成状态机的输出信号
always @(*) begin
if(cpu_rst) begin
mem_ren = 4'b0;
mem_raddr = 32'b0;
// mem_rrdy_n <= 1'b1;
end
else begin
case(sta)
IDLE : begin
mem_ren = 4'b0;
mem_raddr = 32'b0;
// mem_rrdy_n <= 1'b1;
end
TAG_CHECK : begin
mem_ren = 4'b0;
mem_raddr = 32'b0;
// mem_rrdy_n <= 1'b1;
end
REFILL: begin
if(mem_rrdy && !mem_ren_pulse) begin
mem_ren = 4'b1111;
mem_raddr = {inst_addr[31:4],4'b0000};
// mem_rrdy_n <= 1'b0;
end
else begin
mem_ren = 4'b0;
mem_raddr = 32'b0;
end
end
default: begin
mem_ren = 4'b0;
mem_raddr = 32'b0;
// mem_rrdy_n <= 1'b1;
end
endcase
end
end
/******** 不要修改以下代码 ********/
`else
localparam IDLE = 2'b00;
localparam STAT0 = 2'b01;
localparam STAT1 = 2'b11;
reg [1:0] state, nstat;
always @(posedge cpu_clk or posedge cpu_rst) begin
state <= cpu_rst ? IDLE : nstat;
end
always @(*) begin
case (state)
IDLE: nstat = inst_rreq ? (mem_rrdy ? STAT1 : STAT0) : IDLE;
STAT0: nstat = mem_rrdy ? STAT1 : STAT0;
STAT1: nstat = mem_rvalid ? IDLE : STAT1;
default: nstat = IDLE;
endcase
end
always @(posedge cpu_clk or posedge cpu_rst) begin
if (cpu_rst) begin
inst_valid <= 1'b0;
mem_ren <= 4'h0;
end else begin
case (state)
IDLE: begin
inst_valid <= 1'b0;
mem_ren <= (inst_rreq & mem_rrdy) ? 4'hF : 4'h0;
mem_raddr <= inst_rreq ? inst_addr : 32'h0;
end
STAT0: begin
mem_ren <= mem_rrdy ? 4'hF : 4'h0;
end
STAT1: begin
mem_ren <= 4'h0;
inst_valid <= mem_rvalid ? 1'b1 : 1'b0;
inst_out <= mem_rvalid ? mem_rdata[31:0] : 32'h0;
end
default: begin
inst_valid <= 1'b0;
mem_ren <= 4'h0;
end
endcase
end
end
`endif
endmodule