mor1k icache源码分析详解;组相联cache结构详细分析与具体代码实现

注意:原创文章,如需转载请务必标明转载,并附上原文出处链接!!!侵权必究!
作者 : 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

你可能感兴趣的:(cache,verilog,数字IC前端,计算机组织与结构,cpu设计)