以下摘录《步骤吓得核心——软-core处理器的室内设计与分析》一本书
上一章剖析了ICache模块。
本章将剖析DCache模块。首先指出DCache模块相比ICache的特别之处。由于这些不同。所以DCache的分析相对复杂。
接着分析了OR1200中DCache的结构,给出了构成DCache的四个模块的关系,将这四个模块分为数据部分、控制部分。介绍了数据部分的工作过程。13.3节说明了DCache中特殊寄存器的作用与格式。13.4节指出使用到DCache的7种情景。13.5节给出一个演示样例程序,当中涉及了DCache的大部分使用情景,之后。重点分析了在当中3种情景下DCache的工作过程。
13.1 DCache的特别之处
DCache中缓存的是数据。而ICache中缓存的是指令,这就是DCache与ICache的根本差别,由此也决定了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可以配置为512B、4KB、8KB、16KB、32KB,默认是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.v、or1200_dc_fsm.v、or1200_dc_tag.v、or1200_dc_ram.v、or1200_spram.v、or1200_spram_32_bw.v。分别实现了DCache模块、DC_FSM模块、DC_TAG模块、DC_RAM模块、单口RAM、可按字节写的单口RAM模块。在DCache中例化了DC_FSM、DC_TAG、DC_RAM模块。在DC_TAG模块中例化了单口RAM,在DC_RAM模块中例化了可按字节写的单口RAM。如图13.1所看到的。当中DC_TAG、DC_RAM能够称为数据部分,DC_FSM能够称为控制部分,在数据部分进行查找操作,将查找结果(DCache命中或失靶)送到控制部分,由控制部分根据查找结果进行下一步的操作。假设是写操作,那么下一步的操作还需參考当前配置的写策略。本节将介绍DCache中的数据部分、控制部分。
13.2.1 DCache模块与其余模块的连接关系
在介绍DCache数据部分、控制部分之前,先给出DCache模块与其余模块之间的连接关系,有助于后面的分析。在第3章介绍QMEM时。已经给出了DCache与QMEM、SB之间的连接关系,从中可知DCache与两者之间都是通过Wishbone总线接口连接的,參考本书光盘中的or1200_top.vsd能够得到DCache模块完整的对外连接关系。如图13.2所看到的。
有下面几点说明:
(1)从图中可知DCache除了具有Wishbone总线接口外还具有特殊寄存器訪问接口:spr_cs、spr_write、spr_dat_i,与ICache一样,这说明DCache中有特殊寄存器,可是该特殊寄存器不能够读(没有spr_dat_o接口),仅仅能写(有spr_dat_i接口)。与ICache不同之处在于DCache还有spr_addr接口。说明DCache中的特殊寄存器是有地址要求的。也就是有索引,而在ICache中仅仅有一个特殊寄存器,所以其索引能够随意,參考表12.1。
(2)相比CPU与ICache之间的接口。CPU与DCache之间还添加了dc_no_writethrough、mtspr_dc_done两个接口,其作用会在后面的分析中介绍。
(3)QMEM的输出中有接口dcqmem_ci_o,该信号实际直接来自DMMU,回顾一下DTLB中每一个表项都有一个标志位CI。表示相应的页能否够被缓存,通过QMEM的dcqmem_ci_o输出到DCache中,假设要訪问一个内存块的内容,可是该内存块所在页的dcqmem_ci_o值为1,那么该内存块是不可能在DCache中。无需查找DCache,直接从内存中訪问,反之,假设dcqmem_ci_o的值为0,还要首先在DCache中进行查找。
(4)相比QMEM与ICache之间的Wishbone总线接口,QMEM与DCache之间的Wishbone总线接口多了两个输入dcqmem_dat_i、dcqmem_we_i,这是与存储操作有关的接口信号。
(5)DCache并没有与WB_BIU模块直接连接,而是在DCache与WB_BIU之间放置了一个SB(Store Buffer)模块,后者与DCache之间的接口也符合Wishbone总线规范。该模块的作用将在下一章分析。
(6)DCache与SB之间的接口名称都是dcsb_xxx_x的形式,DCache与QMEM之间的接口名称都是dcqmem_xxx_x的形式。因此通过名称就能够知道这个接口的位置。
13.2.2 DCache中数据部分
DCache的数据部分包含DC_TAG、DC_RAM,其主体各自是单口RAM、可按字节写的单口RAM。
两者共同组成了图12.2中的文件夹表。查找方法如图13.3所看到的。
此处採用的是OR1200默认的DCache设置,即总容量是8KB,内存块大小为16字节。文件夹表有512 line。图13.3的解释请參考对ICache中数据部分的解释,须要注意一点:DC_TAG每一个表项包括标识、V、Dirty,比IC_TAG多了一个Dirty标志位,该位为1表示当前line相应的缓存数据被改动了可是相应内存块没有改动。也就是缓存与内存不一致,反之表示line相应的缓存与内存一致。參考通写法、回写法的定义可知:仅仅有在使用回写法策略时。才可能存在Dirty为1的情况,在使用通写法策略时。Dirty标志位始终为0。
DC_TAG是通过单口RAM实现的,其主要代码例如以下。注意一点:其数据宽度是21,比ICache中的IC_TAG多了一位用来保存Dirty的值。
or1200_dc_tag.v module 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.sb、l.sh。都是仅仅改动一个字中的一部分。所以须要这样设计。DC_RAM的主要代码例如以下:
or1200_dc_ram.v module or1200_dc_ram( //DC_RAM clk, rst, addr, en, we, datain, dataout ); parameter dw = `OR1200_OPERAND_WIDTH; //数据宽度是32bit parameter 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是使用4个8bit宽度的数组实现的。主要代码例如以下:
or1200_spram_32_bw.v module 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_RAM、DC_TAG的查找结果,以及当前的写策略决定下一步的操作。DCache中的控制部分主要在DC_FSM模块中实现,其主体是一个状态机,有IDLE、CLOADSTORE、LOOP2、LOOP3、LOOP4、FLUSH5、INV6、WAITSPRCS7等8个状态,定义例如以下:
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_FSM、DC_TAG、DC_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_FSM、DC_TAG、DC_RAM模块的接口,以及各个接口连接到DCache的相应变量,每一个模块的左边是输入接口。右边是输出接口。每一个模块内部是接口名。外部引脚上的名称代表DCache中的相应变量。本章在后面分析DCache时须要常常參考该图,各个接口的含义也留在使用到该接口的时候再作说明。
版权声明:本文博客原创文章。博客,未经同意,不得转载。