首页 > 代码库 > OR1200数据Cache介绍

OR1200数据Cache介绍

以下内容摘自《步步惊芯——软核处理器内部设计分析》一书

 

上一章剖析了ICache模块。本章将剖析DCache模块,首先指出DCache模块相比ICache的特别之处,因为这些不同,所以DCache的分析相对复杂。接着分析了OR1200DCache的结构,给出了构成DCache的四个模块的关系,将这四个模块分为数据部分、控制部分,介绍了数据部分的工作过程。13.3节说明了DCache中特殊寄存器的作用与格式。13.4节指出使用到DCache7种情景,13.5节给出一个示例程序,其中涉及了DCache的大部分使用情景,之后,重点分析了在其中3种情景下DCache的工作过程。

13.1 DCache的特别之处

      DCache中缓存的是数据,而ICache中缓存的是指令,这就是DCacheICache的根本区别,由此也决定了DCache的特别之处。指令存储在指令存储器中,且指令存储器是只读的,对应只有一个取指操作,但是数据存储在数据存储器中,且数据存储器是可读、可写的,对应加载、存储两个操作。一般情况下,指令存储器、数据存储器都属于内存的一部分,有时就是内存中同样的地址空间。

      当向数据存储器中存储数据且DCache命中时,称之为写命中,可以采用两种策略:

  •   通写法(Write through):不仅把数据写入DCache中相应的块,而且也写入数据存储器中相应的块。
  •   回写法(Write back):只把数据写入DCache中相应的块,并不会立即更新数据存储器中的内容,而是等到DCache中相应的块因为某种原因需要从DCache中移出时,才更新数据存储器中的内容。

      通写法与回写法各有特点,回写法的优点是速度快,写操作能以DCache的速度进行,而且对于同一个内存块的多次写操作只需最后进行一次内存写操作,通写法的优点是易于实现,而且内存与DCache的数据总是保持一致。

      当向数据存储器中存储数据且DCache失靶时,称之为写不命中,也可以采用两种策略:

  •   按写分配法(Write allocate):先把要写数据所在的存储块调入DCache,然后再进行写入。
  •   不按写分配法(no-Write allocate):直接写数据存储器中的目的地址,而不将要写数据所在的存储块调入DCache

      一般采用通写法时使用不按写分配法,采用回写法时使用按写分配法。在OR1200中可以配置DCache使用通写法还是回写法,采用通写法时就与不按写分配法结合使用,采用回写法时就与按写分配法结合使用,所以后面在分析的时候只是说明通写法、回写法,而不再说明按写分配法、不按写分配法。

or1200_defines.v//不注释下面的宏定义,就是采用“通写法+不按写分配法”,//注释掉下面的宏定义,就是采用“回写法+按写分配法”`define OR1200_DC_WRITETHROUGH


      OR1200中关于DCache的宏定义如下,从中可知,DCache能够配置为512B4KB8KB16KB32KB,默认是8KB,本章采用默认配置,后面分析时不再重复说明。此时内存块的大小是16字节,采用直接映射,DCache目录表有512 line,因此使用地址的4-12bit作为DCache目录表的查找索引。

or1200_defines.v//`define OR1200_NO_DC            //是否有DCache,默认是注释掉,也就是有DCache//`define OR1200_DC_1W_4KB`define OR1200_DC_1W_8KB//`define OR1200_DC_1W_16KB//`define OR1200_DC_1W_32KB`ifdef OR1200_DC_1W_32KB `define OR1200_DCLS		5         //如果DCache配置为32KB,则内存块的大小是32字节`else `define OR1200_DCLS		4         //否则都是16字节`endif`define OR1200_SPRGRP_DC_ADR_WIDTH 3         //特殊寄存器对应索引的宽度//特殊寄存器DCBFR在第3组特殊寄存器中的索引是2`define OR1200_SPRGRP_DC_DCBFR		3'd2           //特殊寄存器DCBIR在第3组特殊寄存器中的索引是3  `define OR1200_SPRGRP_DC_DCBIR		3'd3             //特殊寄存器DCBWR在第3组特殊寄存器中的索引是4`define OR1200_SPRGRP_DC_DCBWR		3'd4             `ifdef OR1200_DC_1W_8KB          //如果配置DCache为8KB,那么一些宏定义如下`define OR1200_DCSIZE			13   //DCache是8KB,所以地址宽度是13`define OR1200_DCINDX			`OR1200_DCSIZE-2	     // 11`define OR1200_DCINDXH			`OR1200_DCSIZE-1	   // 12`define OR1200_DCTAGL			`OR1200_DCINDXH+1	     // 13//13位地址中的高9位是DCache目录表的索引`define	OR1200_DCTAG			`OR1200_DCSIZE-`OR1200_DCLS	//标识的宽度,包括地址的高19位、有效标志位V`define	OR1200_DCTAG_W			20                     `endif

13.2 DCache结构

      与ICache对应,OR1200中实现DCache的文件有or1200_dc_top.vor1200_dc_fsm.vor1200_dc_tag.vor1200_dc_ram.vor1200_spram.vor1200_spram_32_bw.v,分别实现了DCache模块、DC_FSM模块、DC_TAG模块、DC_RAM模块、单口RAM、可按字节写的单口RAM模块。在DCache中例化了DC_FSMDC_TAGDC_RAM模块,在DC_TAG模块中例化了单口RAM,在DC_RAM模块中例化了可按字节写的单口RAM。如图13.1所示。其中DC_TAGDC_RAM可以称为数据部分,DC_FSM可以称为控制部分,在数据部分进行查找操作,将查找结果(DCache命中或失靶)送到控制部分,由控制部分依据查找结果进行下一步的操作,如果是写操作,那么下一步的操作还需参考当前配置的写策略。本节将介绍DCache中的数据部分、控制部分。

13.2.1 DCache模块与其余模块的连接关系

      在介绍DCache数据部分、控制部分之前,先给出DCache模块与其余模块之间的连接关系,有助于后面的分析。在第3章介绍QMEM时,已经给出了DCacheQMEMSB之间的连接关系,从中可知DCache与两者之间都是通过Wishbone总线接口连接的,参考本书光盘中的or1200_top.vsd可以得到DCache模块完整的对外连接关系,如图13.2所示。

      有以下几点说明:

      (1)从图中可知DCache除了具有Wishbone总线接口外还具有特殊寄存器访问接口:spr_csspr_writespr_dat_i,与ICache一样,这说明DCache中有特殊寄存器,但是该特殊寄存器不可以读(没有spr_dat_o接口),只能写(有spr_dat_i接口),与ICache不同之处在于DCache还有spr_addr接口,说明DCache中的特殊寄存器是有地址要求的,也就是有索引,而在ICache中只有一个特殊寄存器,所以其索引可以任意,参考表12.1

      (2)相比CPUICache之间的接口,CPUDCache之间还增加了dc_no_writethroughmtspr_dc_done两个接口,其作用会在后面的分析中介绍。

      (3QMEM的输出中有接口dcqmem_ci_o,该信号实际直接来自DMMU,回忆一下DTLB中每个表项都有一个标志位CI,表示对应的页是否可以被缓存,通过QMEMdcqmem_ci_o输出到DCache中,如果要访问一个内存块的内容,但是该内存块所在页的dcqmem_ci_o值为1,那么该内存块是不可能在DCache中,无需查找DCache,直接从内存中访问,反之,如果dcqmem_ci_o的值为0,还要首先在DCache中进行查找。

      (4)相比QMEMICache之间的Wishbone总线接口,QMEMDCache之间的Wishbone总线接口多了两个输入dcqmem_dat_idcqmem_we_i,这是与存储操作有关的接口信号。

      (5DCache并没有与WB_BIU模块直接连接,而是在DCacheWB_BIU之间放置了一个SBStore Buffer)模块,后者与DCache之间的接口也符合Wishbone总线规范,该模块的作用将在下一章分析。

     (6DCacheSB之间的接口名称都是dcsb_xxx_x的形式,DCacheQMEM之间的接口名称都是dcqmem_xxx_x的形式,因此通过名称就可以知道这个接口的位置。

13.2.2 DCache中数据部分

      DCache的数据部分包括DC_TAGDC_RAM,其主体分别是单口RAM、可按字节写的单口RAM。两者共同组成了图12.2中的目录表。查找方法如图13.3所示。

      此处采用的是OR1200默认的DCache设置,即总容量是8KB,内存块大小为16字节,目录表有512 line。图13.3的解释请参考对ICache中数据部分的解释,需要注意一点:DC_TAG每个表项包含标识、VDirty,比IC_TAG多了一个Dirty标志位,该位为1表示当前line对应的缓存数据被修改了但是对应内存块没有修改,也就是缓存与内存不一致,反之表示line对应的缓存与内存一致。参考通写法、回写法的定义可知:只有在使用回写法策略时,才可能存在Dirty1的情况,在使用通写法策略时,Dirty标志位始终为0

      DC_TAG是通过单口RAM实现的,其主要代码如下,注意一点:其数据宽度是21,比ICache中的IC_TAG多了一位用来保存Dirty的值。

or1200_dc_tag.vmodule or1200_dc_tag(                        //DC_TAG	clk, rst,	addr, en, we, datain, tag_v, tag, dirty);……//OR1200_DCTAG_W为20,所以dw为21,增加的1位是Dirty位parameter dw = `OR1200_DCTAG_W + 1;       //地址宽度为9        parameter aw = `OR1200_DCTAG;                     or1200_spram #                 //例化单口RAM     (   .aw(`OR1200_DCTAG),      .dw(`OR1200_DCTAG_W + 1)  )      //从单口RAM中读出的数据doq包括标识、有效位V、是否修改位Dirty   dc_tag0                         (   .clk(clk),    .ce(en),    .we(we),    .addr(addr),             .di(datain),  .doq({tag, tag_v, dirty})   );                                       endmodule 


 

      DC_RAM也是通过单口RAM实现的,但是可按字节写的单口RAM,回忆一下OR1200中的存储指令l.sbl.sh,都是只修改一个字中的一部分,所以需要这样设计。DC_RAM的主要代码如下:

or1200_dc_ram.vmodule or1200_dc_ram(                       //DC_RAM	clk, rst,	addr, en, we, datain, dataout);parameter dw = `OR1200_OPERAND_WIDTH;              //数据宽度是32bitparameter aw = `OR1200_DCINDX;                     //地址宽度是11……   or1200_spram_32_bw #             //例化单口RAM,此处的单口RAM可以按字节写     (    .aw(`OR1200_DCINDX),  .dw(dw)   )   dc_ram                                          //注意变量we的宽度为4     (    .clk(clk),      .ce(en),    .we(we),    .addr(addr),              .di(datain),    .doq(dataout)   );endmodule 


 

      其中可以按字节写的单口RAM是使用48bit宽度的数组实现的,主要代码如下:

or1200_spram_32_bw.vmodule or1200_spram_32_bw  (   clk, ce, we, addr, di, doq   );     parameter aw = 10;   parameter dw = 32;                        //数据宽度是32      ……   input [3:0]				  we;	   // 注意此处的写使能信号we的宽度是4    reg [7:0] 				  mem0 [(1<<aw)-1:0];    //定义了4个8位数组   reg [7:0] 				  mem1 [(1<<aw)-1:0];   reg [7:0] 				  mem2 [(1<<aw)-1:0];   reg [7:0] 				  mem3 [(1<<aw)-1:0];   ……      //分别从4个8位数组中读出对应的数据组成一个//32位的数作为要读取的数   assign doq = {mem0[addr_reg], mem1[addr_reg], mem2[addr_reg], mem3[addr_reg]};                                                                 always @(posedge clk)     if (ce)       addr_reg <=  addr;                     //寄存地址信号      always @(posedge clk)     if (ce) begin       if (we[3])                   //如果we[3]为1表示要写目的地址的最高位字节         mem0[addr] <=  di[31:24];       if (we[2])                   //如果we[2]为1表示要写目的地址的次高位字节         mem1[addr] <=  di[23:16];       if (we[1])                   //如果we[1]为1表示要写目的地址的次低位字节         mem2[addr] <=  di[15:08];       if (we[0])                   //如果we[0]为1表示要写目的地址的最低位字节         mem3[addr] <=  di[07:00];     end   endmodule // or1200_spram


 

13.2.3 DCache中控制部分

      DCache会依据DC_RAMDC_TAG的查找结果,以及当前的写策略决定下一步的操作。DCache中的控制部分主要在DC_FSM模块中实现,其主体是一个状态机,有IDLECLOADSTORELOOP2LOOP3LOOP4FLUSH5INV6WAITSPRCS78个状态,定义如下:

or1200_fsm.v`define OR1200_DCFSM_IDLE	3'd0`define OR1200_DCFSM_CLOADSTORE	3'd1`define OR1200_DCFSM_LOOP2	3'd2`define OR1200_DCFSM_LOOP3	3'd3`define OR1200_DCFSM_LOOP4	3'd4`define OR1200_DCFSM_FLUSH5	3'd5`define OR1200_DCFSM_INV6	3'd6`define OR1200_DCFSM_WAITSPRCS7	3'd7


 

      由于有两种写策略,在不同的写策略下,DCache有不同的工作过程,状态转换也不同,比较复杂,所以本章将在分析DCache过程中逐步给出状态之间转换关系。

13.2.4 DCache数据部分与控制部分的对外接口

      DC_FSMDC_TAGDC_RAM模块都在DCache中例化,例化语句如下:

or1200_dc_top.v……or1200_dc_fsm or1200_dc_fsm(                       //例化DC_FSM	.clk(clk),         	.rst(rst),          	.dc_en(dc_en),            	.dcqmem_cycstb_i(dcqmem_cycstb_i),         .dirty(dirty),              .tag(tag),                  .tag_v(tag_v),                      .dcqmem_ci_i(dcqmem_ci_i),         .burst(dcfsm_burst),                  .tag_we(dcfsm_tag_we),                       .dc_addr(dc_addr),         .dcqmem_we_i(dcqmem_we_i),	          .dcqmem_sel_i(dcqmem_sel_i),                 .tagcomp_miss(tagcomp_miss),	.biudata_valid(dcsb_ack_i),	 	.biudata_error(dcsb_err_i),  	    	.lsu_addr(dcqmem_adr_i),	.dcram_we(dcram_we),	      	.biu_read(dcfsm_biu_read), 	    	.biu_write(dcfsm_biu_write),	.dcram_di_sel(dcfsm_dcram_di_sel),	     	.biu_do_sel(dcfsm_biu_do_sel),	.first_hit_ack(dcfsm_first_hit_ack),  		.first_miss_ack(dcfsm_first_miss_ack),	.first_miss_err(dcfsm_first_miss_err),    	.dc_no_writethrough(dc_no_writethrough),         .tag_valid(dcfsm_tag_valid),	                  .tag_dirty(dcfsm_tag_dirty),	         .dc_block_flush(dc_block_flush),                  .mtspr_dc_done(mtspr_dc_done),	.dc_block_writeback(dc_block_writeback),		.spr_dat_i(spr_dat_i),		.spr_cswe(spr_cs & spr_write));or1200_dc_ram or1200_dc_ram(                      //例化DC_RAM	.clk(clk),		.rst(rst),	.addr(dc_addr[`OR1200_DCINDXH:2]),		.en(dc_en),		.we(dcram_we),	.datain(to_dcram),    		.dataout(from_dcram));or1200_dc_tag or1200_dc_tag(                       //例化DC_TAG	.clk(clk),		.rst(rst),	.addr(dctag_addr),		.en(dctag_en),		.we(dctag_we),    	.tag_v(tag_v),		.tag(tag),   	.dirty(dirty)	.datain({dc_addr[31:`OR1200_DCTAGL], dctag_v, dctag_dirty}),);……

      参考上述例化语句得到图13.4,其中给出了DC_FSMDC_TAGDC_RAM模块的接口,以及各个接口连接到DCache的对应变量,每个模块的左边是输入接口,右边是输出接口,每个模块内部是接口名,外部引脚上的名称代表DCache中的对应变量。本章在后面分析DCache时需要经常参考该图,各个接口的含义也留在使用到该接口的时候再作说明。