注意:原创文章,如需转载请务必标明转载,并附上原文出处链接!!!侵权必究!
作者 : thundersnark
邮箱 : [email protected]
个人邮箱,不是经常查看,如有问题联系请发邮件的同时在本文末尾回复一下!
/******************************************************************************
This Source Code Form is subject to the terms of the
Open Hardware Description License, v. 1.0. If a copy
of the OHDL was not distributed with this file, You
can obtain one at http://juliusbaxter.net/ohdl/ohdl.txt
Description: Instruction cache implementation
Copyright (C) 2012-2013
Stefan Kristiansson
Stefan Wallentowitz
******************************************************************************/
// 关于这个mor1kx_icache module的整体性说明
// 1、这个icache实现是组相联cache结构,关于什么是组相联cache的结构,请自行查阅书籍或search internet
//
// 2、该cache结构中默认参数所实现的是 cache有2条way(路),way之间是全相连;每条way中分成了2^9个Block(块);所有way中处于
// 同一位置处的Block组成一个set(组);
// 举个例子:假设有2条way,每条way有3个Block;主存中那么cache的形式为:
// cache way0: way0_Block0, way0_Block1, way0_Block2;
// cache way1: way1_Block0, way1_Block1, way1_Block2;
// 3个set : set0 , set1 , set2 ;即set0由way0_Block0和way1_Block0组成,其余set类推
// 也就是说 cache中的set总数和一条way中的Block的数量是相同的;
// 一个set内的Block数和cache中的way数量是相同的
//
// 3、Block是主存和cache相映射的最小单元,即每当没有命中的时候cache需要更新(refill)一个Block;
// 每个Block的深度是2^5即32Byte,即8个word也就是8条指令为一个Block;主存与每条way中的Block是直接相连映射
//
// 4、具体映射方式可以这样理解:主存以一条way的大小来分割成一个个区域;每个区域中又按照Block的大小分割成Block
// 直接相连映射指的就是主存的每个区域中的Block只能放到cache的way中的相应位置的Block处(即只能放到特定的组);
// 全相连映射指的就是从主存中一个区域中取出的Block能够放入cache中任意一条way中相应Block的位置(可以任意放到组内的任意一个Block);
// 即way之间(也就是set内部)是全相连;way内部(也就是set之间)是直接映射;
// 举个例子:假设主存的结构被划分成下面这样:
// mem0_Block0, mem0_Block1, mem0_Block2; mem1_Block0,mem1_Block1,mem1_Block2; mem2_Block0,mem2_Block1,mem2_Block2;
// 其中mem0,mem1,mem2是将主存以每个way的空间大小划分成的区,区内又划分成Block,结合上面举的cache的例子,
// 那么mem1的Block2只能放到set2中,也就是只能放到way0的block2处或way1的block2处;至于是way1和way2则是任意的;
// 在这个例子中;way之间(set内部)是全相连的具体体现就是上述Block可以任意放到way1或way2;
// way内部(也就是set之间)是直接映射的具体体现就是上述Block只能放到way的Block2处;
// (注:怕讲不明白,所以写的有点啰嗦;此外网上有些博客上写的cache的组相联结构分了line,block,set,way等等,
// 写的很繁杂,绕来绕去;我没仔细去查阅相应的计算机架构的书籍,也不清楚他们是不是真正写对了,我这里写的这个按way
// 和block和set来进行组相联的cache结构仅仅是我根据mor1k的icache代码实现总结出来的,也不保证完全正确(至少我自己
// 现在认为没问题!)如果有什么问题可以在下面留言我看到的话会回答^_^,但平时比较忙不一定会及时哈~)
// 4、这个代码的主要状态分为三个即 1、cpu读指令 2、cpu读指令miss的情况下进行cache refill 3、写spr寄存器清空cache中的Block
//
// 5、关于cache和immu相关联的地方主要有两处 1、cpu读指令的时候地址有两个,cpu_adr_i和cpu_adr_match_i分别是虚拟地址和物理地址
// 当然,前提是immu使能了,如果immu不是能的话两个都是虚拟地址,也可以理解成两个都是物理地址;2、refill cache的wadr_i
// 从这里可以看处写cache的时候只用了一个地址,而读cache的时候用了两个地址;从fetch模块的代码中可以看出wadr_i在immu使能
// 的时候写入cache的是物理地址,而immu不是能的时候写入的是虚拟地址;这就存在一个问题,也就是说当immu使能的时候,写入icache
// 的地址时物理地址,tag mem中存放的也是物理地址的tag;而采用cpu_adr_i这个虚拟地址对tag mem和way mem进行索引,再讲索引的结果
// 与cpu_adr_match_i这个物理地址的tag进行比较;这时不合理的;为了解释这个问题,观察代码中用到cpu_adr_i的地方,可以看出,
// 对tag mem和way mem进行索引的时候只用到了cpu_adr_i[WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH]这一部分,
// 所以我猜测,不管immu怎么样,虚拟地址和物理地址的[WAY_WIDTH-1:0]这一区域都是相同的,不同的仅仅是两个地址的tag部分;
// 需要等到我看完immu指令部分的代码,再回头确认这个问题!!!
//
`include "mor1kx-defines.v"
module mor1kx_icache
#(
parameter OPTION_OPERAND_WIDTH = 32, //指令宽度
parameter OPTION_ICACHE_BLOCK_WIDTH = 5, //block(块)的宽度,5bit,即每个块由2^5即32个Byte组成
//块是cache中存放的主存数据的最小单位
parameter OPTION_ICACHE_SET_WIDTH = 9, //
parameter OPTION_ICACHE_WAYS = 2,
parameter OPTION_ICACHE_LIMIT_WIDTH = 32
)
(
input clk,
input rst,
//这里应该是指令的prefetch模块跟icache的接口
input ic_imem_err_i, //instruction memory err
input ic_access_i, //指示cpu是否请求读icache
output refill_o, //指示当前icache模块处于refill(重填)某一个way中的某一Block的状态
output refill_req_o, //需要refill icache;此信号的assert会从read不命中开始直到refill Block结束
output refill_done_o, //refill结束
output invalidate_o, //处于通过写spt寄存器使cache的set invalidate的状态
output cache_hit_o, //cache命中
// CPU Interface
// CPU对于cache的操作分为两种,一种从cache中取指令的操作,令一种是将指令写
// 入cache的操作,也就是refill cache
// 取指令操作对应的信号如下:
output cpu_ack_o,
output reg [`OR1K_INSN_WIDTH-1:0] cpu_dat_o, //输出给CPU的指令
input [OPTION_OPERAND_WIDTH-1:0] cpu_adr_i, // CPU读指令时给出的虚拟地址,这个地址用来索引icache的tag mem和way mem
input [OPTION_OPERAND_WIDTH-1:0] cpu_adr_match_i, //CPU读指令时给出的地址,如果Immu使能的话,这个是物理地址,用来进行比较
// //如果immu不使能的话,这两个地址是相同的,都是虚拟地址,也就是说都是cpu
// //直接看到的地址,同时也可以理解成都是物理地址;
input cpu_req_i, //CPU请求读指令
// 向cache中存入指令(refill cache)的操作对应的信号如下:
input [OPTION_OPERAND_WIDTH-1:0] wradr_i, //cpu输入给cache的写地址
input [`OR1K_INSN_WIDTH-1:0] wrdat_i, //cpu写入的cache的指令
input we_i, //cpu写cache使能
// SPR interface //特殊功能寄存器的接口
input [15:0] spr_bus_addr_i, //
input spr_bus_we_i,
input spr_bus_stb_i,
input [OPTION_OPERAND_WIDTH-1:0] spr_bus_dat_i,
output [OPTION_OPERAND_WIDTH-1:0] spr_bus_dat_o,
output reg spr_bus_ack_o
);
// States
localparam IDLE = 4'b0001;
localparam READ = 4'b0010;
localparam REFILL = 4'b0100;
localparam INVALIDATE = 4'b1000; // Invalidate vt/vi:使无效,使无效化
// Address space in bytes for a way
localparam WAY_WIDTH = OPTION_ICACHE_BLOCK_WIDTH + OPTION_ICACHE_SET_WIDTH;
// OPTION_ICACHE_SET_WIDTH 决定一条Way中有多少个Block(Line)
// OPTION_ICACHE_BLOCK_WIDTH 指定一个Block(Line)中有多少个Byte,1Word=4Byte
// OPTION_ICACHE_BLOCK_WIDTH 为5表示一个way中的Block包含32个Byte
// OPTION_ICACHE_SET_WIDTH为9表示一个way的深度为512,即1条way有512个Block
/*
* Tag memory layout
* +---------------------------------------------------------+
* (index) -> | LRU | wayN valid | wayN tag |...| way0 valid | way0 tag |
* +---------------------------------------------------------+
*/
// The tag is the part left of the index
localparam TAG_WIDTH = (OPTION_ICACHE_LIMIT_WIDTH - WAY_WIDTH);
// OPTION_ICACHE_LIMIT_WIDTH指定了需要用cache进行缓存的内存区域的大小
// 用OPTION_ICACHE_LIMIT_WIDTH减去WAY_WIDTH剩下的就是用来决定是否在某个Way
// 中的Tag了
// The tag memory contains entries with OPTION_ICACHE_WAYS parts of
// each TAGMEM_WAY_WIDTH. Each of those is tag and a valid flag.
localparam TAGMEM_WAY_WIDTH = TAG_WIDTH + 1;
localparam TAGMEM_WAY_VALID = TAGMEM_WAY_WIDTH - 1;
// Additionally, the tag memory entry contains an LRU value. The
// width of this is actually 0 for OPTION_ICACHE_LIMIT_WIDTH==1
// 关于这个TAG_LRU_WIDTH可以看具体的mor1kx_cache_lru模块里面关于lru的
// 算法的详细说明
localparam TAG_LRU_WIDTH = OPTION_ICACHE_WAYS*(OPTION_ICACHE_WAYS-1) >> 1;
// We have signals for the LRU which are not used for one way
// caches. To avoid signal width [-1:0] this generates [0:0]
// vectors for them, which are removed automatically then.
localparam TAG_LRU_WIDTH_BITS = (OPTION_ICACHE_WAYS >= 2) ? TAG_LRU_WIDTH : 1;
// Compute the total sum of the entry elements
localparam TAGMEM_WIDTH = TAGMEM_WAY_WIDTH * OPTION_ICACHE_WAYS + TAG_LRU_WIDTH;
// For convenience we define the position of the LRU in the tag
// memory entries
localparam TAG_LRU_MSB = TAGMEM_WIDTH - 1;
localparam TAG_LRU_LSB = TAG_LRU_MSB - TAG_LRU_WIDTH + 1;
// FSM state signals
reg [3:0] state;
wire read;
wire refill;
wire invalidate;
reg [WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH] invalidate_adr;
wire [31:0] next_refill_adr;
wire refill_done;
wire refill_hit;
reg [(1<<(OPTION_ICACHE_BLOCK_WIDTH-2))-1:0] refill_valid; //宽度为每个Block中的word数,以block width=5为例子
reg [(1<<(OPTION_ICACHE_BLOCK_WIDTH-2))-1:0] refill_valid_r; //每个block中为8个word,这两个寄存器的宽度就为[7:0];
// The index we read and write from tag memory
wire [OPTION_ICACHE_SET_WIDTH-1:0] tag_rindex; // tag memory 的读地址
wire [OPTION_ICACHE_SET_WIDTH-1:0] tag_windex; // tag memory 的写地址
// The data from the tag memory
wire [TAGMEM_WIDTH-1:0] tag_dout; // tag memory输出的数据
wire [TAG_LRU_WIDTH_BITS-1:0] tag_lru_out; // tag memory输出的Last resently used bit
wire [TAGMEM_WAY_WIDTH-1:0] tag_way_out [OPTION_ICACHE_WAYS-1:0]; // tag memory输出的每一条way的数据(tag+vld bit)
// The data to the tag memory
wire [TAGMEM_WIDTH-1:0] tag_din;
reg [TAG_LRU_WIDTH_BITS-1:0] tag_lru_in;
reg [TAGMEM_WAY_WIDTH-1:0] tag_way_in [OPTION_ICACHE_WAYS-1:0];
reg [TAGMEM_WAY_WIDTH-1:0] tag_way_save [OPTION_ICACHE_WAYS-1:0]; //???
// Whether to write to the tag memory in this cycle
reg tag_we; //tag memory的写使能信号
// This is the tag we need to write to the tag memory during refill
wire [TAG_WIDTH-1:0] tag_wtag; // refill cache的时候需要写入tag memory中的tag
// This is the tag we check against // cpu读指令的时候用于确定是否读命中,需要与tag memory中的tag进行比较
wire [TAG_WIDTH-1:0] tag_tag;
// Access to the way memories
wire [WAY_WIDTH-3:0] way_raddr[OPTION_ICACHE_WAYS-1:0]; //WAY_WIDTH-1 -2 ;这个地址将以Byte为单位转成以word为单位
wire [WAY_WIDTH-3:0] way_waddr[OPTION_ICACHE_WAYS-1:0];
wire [OPTION_OPERAND_WIDTH-1:0] way_din[OPTION_ICACHE_WAYS-1:0];
wire [OPTION_OPERAND_WIDTH-1:0] way_dout[OPTION_ICACHE_WAYS-1:0];
reg [OPTION_ICACHE_WAYS-1:0] way_we;
// Does any way hit?
wire hit;
wire [OPTION_ICACHE_WAYS-1:0] way_hit;
// This is the least recently used value before access the memory.
// Those are one hot encoded.
wire [OPTION_ICACHE_WAYS-1:0] lru;
// Register that stores the LRU value from lru
reg [OPTION_ICACHE_WAYS-1:0] tag_save_lru;
// The access vector to update the LRU history is the way that has
// a hit or is refilled. It is also one-hot encoded.
reg [OPTION_ICACHE_WAYS-1:0] access; //access记录了当前请求的指令hit的way,反馈给LRU模块用于更新LRU
// The current LRU history as read from tag memory and the update
// value after we accessed it to write back to tag memory.
wire [TAG_LRU_WIDTH_BITS-1:0] current_lru_history;
wire [TAG_LRU_WIDTH_BITS-1:0] next_lru_history;
// Intermediate signals to ease debugging
wire [TAG_WIDTH-1:0] check_way_tag [OPTION_ICACHE_WAYS-1:0];
wire check_way_match [OPTION_ICACHE_WAYS-1:0];
wire check_way_valid [OPTION_ICACHE_WAYS-1:0];
genvar i;
// Allowing (out of the cache line being refilled) accesses during refill
// exposes a bug somewhere, causing the Linux kernel to end up with a
// bus error UNHANDLED EXCEPTION.
// Until that is sorted out, disable it.
assign cpu_ack_o = (read /*| refill & ic_access_i*/) & hit | //处于读指令状态而且cache命中
refill_hit & ic_access_i; //
assign tag_rindex = cpu_adr_i[WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH];
//读tag ram的地址
/*
* The tag mem is written during reads to write the lru info and during
* refill and invalidate
*/
//当发生(读,此时需要更改lru)(refill)(invalidate)这三种情况时需要写tag mem
//tag mem的写地址
assign tag_windex = read ?
cpu_adr_match_i[WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH] : //如果是cpu读
invalidate ? invalidate_adr : //如果invalidate(使无效,使作废)
wradr_i[WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH]; //如果要refill
//cpu读指令时用于判断cache是否hit时需要进行比较的TAG
assign tag_tag = cpu_adr_match_i[OPTION_ICACHE_LIMIT_WIDTH-1:WAY_WIDTH];
//cpu写入指令(refill cache)时需要存入的指令对应的TAG
assign tag_wtag = wradr_i[OPTION_ICACHE_LIMIT_WIDTH-1:WAY_WIDTH];
generate
if (OPTION_ICACHE_WAYS >= 2) begin
// Multiplex the LRU history from and to tag memory
// 从TAG mem中读出的当前的LRU
assign current_lru_history = tag_dout[TAG_LRU_MSB:TAG_LRU_LSB];
// 将要存入TAG mem中的更新后的LRU
assign tag_din[TAG_LRU_MSB:TAG_LRU_LSB] = tag_lru_in;
// 从TAG mem中读出的当前的LRU
assign tag_lru_out = tag_dout[TAG_LRU_MSB:TAG_LRU_LSB];
end
for (i = 0; i < OPTION_ICACHE_WAYS; i=i+1) begin : ways
assign way_raddr[i] = cpu_adr_i[WAY_WIDTH-1:2]; //读指令cache mem的地址
assign way_waddr[i] = wradr_i[WAY_WIDTH-1:2]; //refill cache mem的写地址
assign way_din[i] = wrdat_i; //refill cache mem的写数据
// compare stored tag with incoming tag and check valid bit
assign check_way_tag[i] = tag_way_out[i][TAG_WIDTH-1:0];
assign check_way_match[i] = (check_way_tag[i] == tag_tag);
assign check_way_valid[i] = tag_way_out[i][TAGMEM_WAY_VALID];
assign way_hit[i] = check_way_valid[i] & check_way_match[i];
// Multiplex the way entries in the tag memory
//根据每一条way的tag,合成输入到tag mem的tag
assign tag_din[(i+1)*TAGMEM_WAY_WIDTH-1:i*TAGMEM_WAY_WIDTH] = tag_way_in[i];
//tag mem 输出的每一条way的tag
assign tag_way_out[i] = tag_dout[(i+1)*TAGMEM_WAY_WIDTH-1:i*TAGMEM_WAY_WIDTH];
end
endgenerate
assign hit = |way_hit;
assign cache_hit_o = hit;
integer w0;
always @(*) begin
cpu_dat_o = {OPTION_OPERAND_WIDTH{1'bx}};
// Put correct way on the data port
for (w0 = 0; w0 < OPTION_ICACHE_WAYS; w0 = w0 + 1) begin
// 如果cpu读指令hit(即取指hit),或者refill的时候hit了,那么输出指令给cpu
if (way_hit[w0] | (refill_hit & tag_save_lru[w0])) begin
cpu_dat_o = way_dout[w0]; //选择way memory输出的对应字段,作为输出给CPU的指令集
end
end
end
assign next_refill_adr = (OPTION_ICACHE_BLOCK_WIDTH == 5) ? //refill cache时需要写入Block中的下一个地址
{wradr_i[31:5], wradr_i[4:0] + 5'd4} : // 32 byte //(即refill的最小单位是block)
{wradr_i[31:4], wradr_i[3:0] + 4'd4}; // 16 byte
assign refill_done_o = refill_done;
assign refill_done = refill_valid[next_refill_adr[OPTION_ICACHE_BLOCK_WIDTH-1:2]]; //block中需要refill的下一个地址已经有效了,则
//说明该block的refill已经结束
assign refill_hit = refill_valid_r[cpu_adr_match_i[OPTION_ICACHE_BLOCK_WIDTH-1:2]] &
cpu_adr_match_i[OPTION_ICACHE_LIMIT_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH] ==
wradr_i[OPTION_ICACHE_LIMIT_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH] &
refill; //如果当前状态是refill,而且cpu_adr_match_i地址的对应字段与当前refill的写入地址wradr_i的对应字段相同
//而且由cpu_adr_match_i的Block内的地址所指的地址内容是有效的,即已经被refill了,所以此时尽管整个block还没有
//refill完成,但此时已经可以在cache中取该cpu_adr_match_i所指示的地址中的指令了
assign refill = (state == REFILL);
assign read = (state == READ);
assign invalidate = (state == INVALIDATE);
assign refill_o = refill;
assign refill_req_o = read & cpu_req_i & !hit | refill; //当前icache处于读指令状态,CPU发出读指令请求但是没有命中
//或者当前处于refill状态,就一直发出refill的请求,让refill_req_o有效
/*
* SPR bus interface
*/
assign invalidate_o = spr_bus_stb_i & spr_bus_we_i & //如果针对地址为`OR1K_SPR_ICBIR_ADDR的spr寄存器发起写操作
(spr_bus_addr_i == `OR1K_SPR_ICBIR_ADDR); //会让icache进入invalidate状态
/*
* Cache FSM
*/
integer w1;
always @(posedge clk `OR_ASYNC_RST) begin
refill_valid_r <= refill_valid;
spr_bus_ack_o <= 0;
//-------
case (state)
IDLE: begin
if (cpu_req_i) //如果CPU请求读指令,进入读指令状态
state <= READ;
end
READ: begin
if (ic_access_i) begin //指示cpu是否在access icache
if (hit) begin //如果hit了,仍然是读状态
state <= READ;
end
else //如果没有hit,进入refill状态
if (cpu_req_i) begin
refill_valid <= 0; //清空refill valid功能相关的两个寄存器为该Block的refill做准备
refill_valid_r <= 0; //
// Store the LRU information for correct replacement
// on refill. Always one when only one way.
tag_save_lru <= (OPTION_ICACHE_WAYS==1) | lru; //保存没有hit时的cache的lru
for (w1 = 0; w1 < OPTION_ICACHE_WAYS; w1 = w1 + 1) begin
tag_way_save[w1] <= tag_way_out[w1]; //保存没有hit时的tag memory的输出,因为refile一个Block的时候
//一个tag memory中包含了所有way对应的tag,但写的时候仅写对应way的tag
//其余way的tag不变,所以要保存一下当前tag mem中的所有tag
end
state <= REFILL; //进入refill状态
end
end
else begin
state <= IDLE; //如果cpu不access icache 的话就回到空闲状态;
end
end
REFILL: begin
if (we_i) begin //refill写使能有效的时候,根据wradr_i置位相应的refill_valid
refill_valid[wradr_i[OPTION_ICACHE_BLOCK_WIDTH-1:2]] <= 1;
if (refill_done) //每次we_i只能写一个word,只有将一个Block的word都填充的时候才refill_done
state <= IDLE; //refill_done了就进入IDLE状态
end
end
INVALIDATE: begin
if (!invalidate_o)
state <= IDLE;
spr_bus_ack_o <= 1;
end
default:
state <= IDLE;
endcase
//------- //这里可以看出invalidate的优先级是比其它几个状态高的,所以这里要有一个!refill
//例如:如果当前在refill状态,但是refill一个block还没结束,但是只要refill了block中的一个地址,整个block地址对应
//这时invalidate
if (invalidate_o & !refill) begin
invalidate_adr <= spr_bus_dat_i[WAY_WIDTH-1:OPTION_ICACHE_BLOCK_WIDTH]; //把spr接口输入的data作为invalidate_adr;
spr_bus_ack_o <= 1; //给出spr bus的响应信号
state <= INVALIDATE; //进入invalidate状态
end
if (rst)
state <= IDLE;
else if(ic_imem_err_i) //如果指令mem有错误,复位成idle
state <= IDLE;
end
//--------------------------------------------------------------------------------------------------------------------------------
integer w2;
always @(*) begin
//------------------------------------------------------------------
// Default is to keep data, don't write and don't access
tag_lru_in = tag_lru_out;
for (w2 = 0; w2 < OPTION_ICACHE_WAYS; w2 = w2 + 1) begin
tag_way_in[w2] = tag_way_out[w2]; //这里主要是考虑read状态下,仅更新lru的时候,每个way的tag不变
end //在其它状态下只要tag_we有效的时候给出正确的tag_way_in就行
//其它时候tag_way_in等于啥并没有任何影响
tag_we = 1'b0; //tag mem的写有效信号
way_we = {(OPTION_ICACHE_WAYS){1'b0}}; //way mem的写有效信号
access = {(OPTION_ICACHE_WAYS){1'b0}}; //指示当前访问(读或者refill)的way是哪一条
//
case (state)
READ: begin //当当前状态是read的时候
if (hit) begin
// We got a hit. The LRU module gets the access
// information. Depending on this we update the LRU
// history in the tag.
access = way_hit; //如果hit了,那么当前access的way就是读命中的way即way hit
// This is the updated LRU history after hit
tag_lru_in = next_lru_history; //lru管理模块根据current_lru_history和当前的access生成的新的lru,用于写入tag mem
tag_we = 1'b1; //在read状态下,之所以要写tag ram,仅仅是为了更新LRU,tag_way_in所代表的每个way的tag并没有变
//变的仅仅是LRU; tag_we是 tag ram的写使能信号
end
end
REFILL: begin
if (we_i) begin //如果cpu的写cache有效
// Write the data to the way that is replaced (which is
// the LRU)
way_we = tag_save_lru; //tag_save_lru记录的是没有hit的时候,Last resently used way,即tag_save_lru就是
//最不常使用的way,也就是当前要写入的way ; 该信号是相应的way ram的写使能信号;
// Access pattern
access = tag_save_lru; //当前要写入的way也是当前的access的way,将该值送到LRU管理模块,用于更新LRU
/* Invalidate the way on the first write */
if (refill_valid == 0) begin
for (w2 = 0; w2 < OPTION_ICACHE_WAYS; w2 = w2 + 1) begin
if (tag_save_lru[w2]) begin
tag_way_in[w2][TAGMEM_WAY_VALID] = 1'b0; //要在refill的开始就把该way的tag valid信号清零;理解是这样子
end //但是我认为其实没有必要,因为在refill状态cpu不会请求指令的
end //而且即使cpu请求,cache也不会响应,指示自顾自的进行refill
//所以这里没必要
tag_we = 1'b1; // tag ram 写使能
//-----------------
//???这里我个人感觉有点冗余,我认为在refill状态下,在we_i的时候没有必要去写tag mem,因为只要在refill done的时候把
//tag mem给更新一下就行了,在we_i阶段真正需要的也就是把way_we assert实现way mem的正确写入就行了
//而在refill_done的时候仅把tag mem写好,也就是1、更新lru 2、原来的不相关的way的tag保持 3、写入数据所在的
//way的tag写入并置位vld
end
// After refill update the tag memory entry of the
// filled way with the LRU history, the tag and set
// valid to 1.
if (refill_done) begin
for (w2 = 0; w2 < OPTION_ICACHE_WAYS; w2 = w2 + 1) begin
tag_way_in[w2] = tag_way_save[w2]; // 将refill之前保存的其他way的tag重新写回;
if (tag_save_lru[w2]) begin
tag_way_in[w2] = { 1'b1, tag_wtag }; //将新更新的way的tag写入并将vld位置1;
end
end
tag_lru_in = next_lru_history; //更新LRU
tag_we = 1'b1;
end
end
end
INVALIDATE: begin // 从这段代码可以看出invalidate的状态就是使所match所在那一行的所有way全部都无效
// Lazy invalidation, invalidate everything that matches tag address
tag_lru_in = 0; //将LRU清零,置为初始状态
for (w2 = 0; w2 < OPTION_ICACHE_WAYS; w2 = w2 + 1) begin
tag_way_in[w2] = 0; //将各个way的tag清零,将各个way的vld清零
end
tag_we = 1'b1;
end
default: begin
end
endcase
end
//---------------------------------------------------------------------------------------------------------------------------------
generate
for (i = 0; i < OPTION_ICACHE_WAYS; i=i+1) begin : way_memories
mor1kx_simple_dpram_sclk // 例化每一条way的数据memory
#(
.ADDR_WIDTH(WAY_WIDTH-2), //这个memory是以word为单位,根据cpu给出的地址选出相应的32bit
.DATA_WIDTH(OPTION_OPERAND_WIDTH),
.ENABLE_BYPASS(0)
)
way_data_ram
(/*AUTOINST*/
// Outputs
.dout (way_dout[i][OPTION_OPERAND_WIDTH-1:0]), // Templated
// Inputs
.clk (clk),
.raddr (way_raddr[i][WAY_WIDTH-3:0]), // Templated
.re (1'b1), // Templated
.waddr (way_waddr[i][WAY_WIDTH-3:0]), // Templated
.we (way_we[i]), // Templated
.din (way_din[i][31:0])); // Templated
end // block: way_memories
if (OPTION_ICACHE_WAYS >= 2) begin : gen_u_lru //只有在cache的路数不为1路的时候才需要lru
mor1kx_cache_lru
#(.NUMWAYS(OPTION_ICACHE_WAYS))
u_lru(/*AUTOINST*/ // 例化lru管理模块
// Outputs
.update (next_lru_history),
.lru_pre (lru), //根据current_lru_history所决定的将要替换的way的onehot码
.lru_post (),
// Inputs
.current (current_lru_history), // Templated
.access (access)); // Templated
end // if (OPTION_ICACHE_WAYS >= 2)
endgenerate
mor1kx_simple_dpram_sclk //同步双端口ram,用于存放tag
#(
.ADDR_WIDTH(OPTION_ICACHE_SET_WIDTH), //这个ram中存放的是所有way里同一个深度处的tag
.DATA_WIDTH(TAGMEM_WIDTH), //
.ENABLE_BYPASS(0)
)
tag_ram
(/*AUTOINST*/
// Outputs
.dout (tag_dout[TAGMEM_WIDTH-1:0]), // Templated
// Inputs
.clk (clk),
.raddr (tag_rindex), // Templated
.re (1'b1), // Templated
.waddr (tag_windex), // Templated
.we (tag_we), // Templated
.din (tag_din)); // Templated
endmodule