直接映射
先看懂cache的映射原理,根据cache大小与主存大小来计算各个信号线的位数
各个信号线位数
主存地址在逻辑上分为区号、块号、块内地址
Cache结构
Cache访问原理
基本过程
状态机:“三段式”实现 6.3 Verilog 状态机 | 菜鸟教程 (runoob.com)
// TODO: 编写状态机现态的更新逻辑
// TODO: 编写状态机的状态转移逻辑
// TODO: 生成状态机的输出信号
状态机设计
再分析并确定各个状态下的输入输出,完善状态机。
编写状态机更新逻辑
这个类似模板了
// TODO: 编写状态机现态的更新逻辑always @(posedge cpu_clk or posedge cpu_rst) beginif(cpu_rst) beginsta <= 2'b0;endelse beginsta <= nex_sta;endend
编写状态机状态转移信号
需要以下变量:新的访问请求信号、命中信号、缺失(不命中)、主存数据已返回
// TODO: 编写状态机的状态转移逻辑always@(*) begin if(cpu_rst) beginnex_sta = IDLE;endelse begincase(sta) IDLE: begin if(inst_rreq == 1'b1) begin nex_sta = TAG_CHECK;endelse begin nex_sta = IDLE;endendTAG_CHECK:begin if(hit) beginnex_sta = IDLE;endelse begin nex_sta = REFILL;endendREFILL: beginif(mem_rvalid) beginnex_sta = TAG_CHECK;endelse begin nex_sta = REFILL;endenddefault: nex_sta = IDLE;endcaseendend
编写状态机输出信号
分为向主存输出和向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 CPUinput 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 Businput 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]; // 主存地址的TAGwire [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 @(*) beginif(hit & hit_n) begin inst_valid = 1'b1;inst_out = data_out[31:0];/* TODO: 根据字偏移,选择Cache行中的某个32位字输出指令 */endelse begin inst_valid = 1'b0;inst_out = 32'b0;endendreg hit_n ;always@(posedge cpu_clk) begin hit_n <= ~hit;endreg 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) beginif(cpu_rst) beginsta <= 2'b0;endelse beginsta <= nex_sta;endend// TODO: 编写状态机的状态转移逻辑always@(*) begin if(cpu_rst) beginnex_sta = IDLE;endelse begincase(sta) IDLE: begin if(inst_rreq == 1'b1) begin nex_sta = TAG_CHECK;endelse begin nex_sta = IDLE;endendTAG_CHECK:begin if(hit) beginnex_sta = IDLE;endelse begin nex_sta = REFILL;endendREFILL: beginif(mem_rvalid) beginnex_sta = TAG_CHECK;endelse begin nex_sta = REFILL;endenddefault: nex_sta = IDLE;endcaseendend// 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;endelse if(sta == REFILL) begin if(mem_rrdy && !mem_ren_pulse) begin mem_ren_pulse <= 1'b1;endend else beginmem_ren_pulse <= 1'b0;end end // TODO: 生成状态机的输出信号always @(*) beginif(cpu_rst) begin mem_ren = 4'b0;mem_raddr = 32'b0;// mem_rrdy_n <= 1'b1;endelse begin case(sta) IDLE : beginmem_ren = 4'b0;mem_raddr = 32'b0;// mem_rrdy_n <= 1'b1;endTAG_CHECK : begin mem_ren = 4'b0;mem_raddr = 32'b0;// mem_rrdy_n <= 1'b1;endREFILL: beginif(mem_rrdy && !mem_ren_pulse) begin mem_ren = 4'b1111;mem_raddr = {inst_addr[31:4],4'b0000}; // mem_rrdy_n <= 1'b0;endelse begin mem_ren = 4'b0;mem_raddr = 32'b0;end enddefault: begin mem_ren = 4'b0;mem_raddr = 32'b0;// mem_rrdy_n <= 1'b1;endendcaseendend/******** 不要修改以下代码 ********/
`elselocalparam IDLE = 2'b00;localparam STAT0 = 2'b01;localparam STAT1 = 2'b11;reg [1:0] state, nstat;always @(posedge cpu_clk or posedge cpu_rst) beginstate <= cpu_rst ? IDLE : nstat;endalways @(*) begincase (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;endcaseendalways @(posedge cpu_clk or posedge cpu_rst) beginif (cpu_rst) begininst_valid <= 1'b0;mem_ren <= 4'h0;end else begincase (state)IDLE: begininst_valid <= 1'b0;mem_ren <= (inst_rreq & mem_rrdy) ? 4'hF : 4'h0;mem_raddr <= inst_rreq ? inst_addr : 32'h0;endSTAT0: beginmem_ren <= mem_rrdy ? 4'hF : 4'h0;endSTAT1: beginmem_ren <= 4'h0;inst_valid <= mem_rvalid ? 1'b1 : 1'b0;inst_out <= mem_rvalid ? mem_rdata[31:0] : 32'h0;enddefault: begininst_valid <= 1'b0;mem_ren <= 4'h0;endendcaseendend`endifendmodule