|
RT,今天早上顿悟,迅速搞定了N-Way FIFO更新模式的组相联Cache,实测命中率比直接映射ICache高了一大截,尽管是个FIFO更新模式的只有1KB的Cache(每组256B)。这个是Tight Coupled Cache实现,0T延迟,设计重点是面积最小化,因此整个SOC速度自然不高。如果想要CPU速度跑起来,总线和CPU和Cache等单元需要仔细设计了,全局单一时钟绑定是最简单的实现方式,而且通常情况下FPGA并没有CPU倍频的需求。适合自己组装SOC的用户使用,直接挂到IBus后面。不支持DBus,因为没有数据写端口。
考虑到面积要最小化(后面还有一大堆数据流模块,给SOC的只有微小的一丢丢面积),这里使用了32位宽度的Cache和RAM。考虑到硬件限制,使用了4组多位64x1的RAM单元,更新方式使用FIFO模式,当Cache命中时不更新Cache内容,否则才更新FIFO内容。且Cache是级联的,因此数据总是从最低的组往最高的组移动,这样实现了极简单的多路组相联模型。看代码可以看到,所有RAM写使能是同一个信号,iff cache未命中时才使能。因此,该cache不具备任何LRU等置换能力,毕竟面积优先。
考虑到当前使用的CPU架构实现,只有最低64KB的RAM空间是rwx的,因此只对低16位地址做缓存。级联RAM时直接顺着代码向下扩展,然后在上面ihit部分填好即可。经测试,2-Way和4-Way模式工作良好,可以满足设计需求,全部4-Way在CPU运行一段时间之后全部参与命中。不足之处是Cache无法invalidate,且万一无效数据被填充将导致Cache错误,这个目前没有考虑解决(需要增加不少面积 )。为了避免上电死机的情况发生,当复位使能时,强制刷新Cache内容,避免意外的无效数据。
如果需要和CPU分离开,则需要(1)加宽数据位宽,此时相当于增加了IF的发射宽度,可以最大化提升IPC;(2)增加L2 Cache,并加入数据预读单元,此时SOC才具备在DDR中运行的能力。像这种TCC实现不适合DDR等场合,延迟太大了,命中失败一次至少得300ns~1000ns响应时间,扛不住。由于TCC 0T架构先天不足,Cache模块不具备数据预读能力(只是个单端口的),也不具备多核交互能力。
下面的模块体使用Synplify 2015综合生成verilog netlist只使用了245个LUT6,DFF用了0个 。这个资源使用率是我之前都不敢想的。也就在我上一次顿悟了多行数据缓存器的移位移行操作之后才开始有一些新的设计思想。该多行数据缓存器的目标是从单端口输入的帧数据中生成NxN的数据块,并且数据需要具备边沿映射模式、行结束填充和帧结束填充。最终实现时使用了一个非常简单的方法:生成一个N行的buffer,每次只向最后一行buffer写数据,同时从每一行buffer读出当前数据推到上一行。这样在推完N行数据之后一定能同时输出N+1行数据。再套一个模块实现数据镜像和帧结束填充即解决了大buffer的缓存问题。在这一次顿悟之前我使用了非常复杂的buffer index映射方式,这个方式需要写大量复杂的mux,而且只能实现3x3的buffer缓存,我写了几个星期也没有写出来没有bug的5x5的缓存控制器。而有一个算法期望23x23的缓存 。
module TinyICache4Way #(
parameter INSTR_ADDR_BITS = 16, // 64KB memory to be cached
CACHE_ADDR_BITS = 6, // 64*32 cache RAM to be used.
USE_REMAP_REGION = 0, // Set to use remap region (for RWX). Bit [26] (0x04000000).
REMAP_REGION_BIT = 26 // Bit [26] as remap region address bit.
)(
input clk_i,
input rst_i,
input [31:0] iaddr_i, // Only INSTR_ADDR_BITS bits used for address.
input ird_i, // Instruction Read Strobe.
output [31:0] idata_o,
output iready_o,
input [31:0] extdata_i,
input extready_i
);
// To achieve best performance, the cache memory should contains the following items:
// iaddr_i[CACHE_ADDR_BITS+1:2] is to be used for cache selection;
// iaddr_i[INSTR_ADDR_BITS-1:CACHE_ADDR_BITS+2] is to be used for cache hit match;
// 1 bit for cache valid flag.
localparam CACHE_PAGE_BITS = USE_REMAP_REGION + INSTR_ADDR_BITS - CACHE_ADDR_BITS - 2; // For default configuration the cache page bits should be 14 - 6 - 2 = 6 bits (32 bit access).
localparam TOTAL_CACHE_BITS = 32 + 1 + CACHE_PAGE_BITS;
// ICache Hit. When hit, do not update cache content.
wire ihit_o;
// Cache write data. Will include remap region bit at Msb.
wire [CACHE_PAGE_BITS-1:0] w_cache_wepagedata = {iaddr_i[REMAP_REGION_BIT], iaddr_i[INSTR_ADDR_BITS-1:CACHE_ADDR_BITS+2]};
wire [TOTAL_CACHE_BITS-1:0] w_cache_wedata = {extready_i, w_cache_wepagedata, extdata_i[31:0]};
// Force flush cache when not ready to avoid invalid data.
wire w_cache_we = ((~rst_i) && ihit_o) ? 0 : extready_i;
wire [TOTAL_CACHE_BITS-1:0] w_cache_odata_0, w_cache_odata_1, w_cache_odata_2, w_cache_odata_3;
// Assert iready_o when (1) external ready, (2) cache is enabled; cache is valid; cache region match; cache address match; address [1:0] is 0.
wire ihit_0 = w_cache_odata_0[TOTAL_CACHE_BITS-1] && (w_cache_odata_0[TOTAL_CACHE_BITS-2:32] == w_cache_wepagedata);
wire ihit_1 = w_cache_odata_1[TOTAL_CACHE_BITS-1] && (w_cache_odata_1[TOTAL_CACHE_BITS-2:32] == w_cache_wepagedata);
wire ihit_2 = w_cache_odata_2[TOTAL_CACHE_BITS-1] && (w_cache_odata_2[TOTAL_CACHE_BITS-2:32] == w_cache_wepagedata);
wire ihit_3 = w_cache_odata_3[TOTAL_CACHE_BITS-1] && (w_cache_odata_3[TOTAL_CACHE_BITS-2:32] == w_cache_wepagedata);
assign ihit_o = ihit_0 | ihit_1 | ihit_2 | ihit_3;
assign iready_o = extready_i | ihit_o;
reg [31:0] w_idata_o;
always @(*) begin
if(ihit_0) w_idata_o <= w_cache_odata_0[31: 0];
else if(ihit_1) w_idata_o <= w_cache_odata_1[31: 0];
else if(ihit_2) w_idata_o <= w_cache_odata_2[31: 0];
else if(ihit_3) w_idata_o <= w_cache_odata_3[31: 0];
else w_idata_o <= extdata_i;
end
assign idata_o = w_idata_o;
//////////////////////////////////////////////////////////////////////////////////
// RAM
//////////////////////////////////////////////////////////////////////////////////
DistRAM64x8SP #(.GEN_SIZE(TOTAL_CACHE_BITS), .ADDR_BITS(CACHE_ADDR_BITS)) cache_ram_0 (
.clk_i (clk_i),
.addr_i (iaddr_i[31:2]), // Common Address Input
.wdata_i (w_cache_wedata),
.we_i (w_cache_we),
.rdata_o (w_cache_odata_0)
);
DistRAM64x8SP #(.GEN_SIZE(TOTAL_CACHE_BITS), .ADDR_BITS(CACHE_ADDR_BITS)) cache_ram_1 (
.clk_i (clk_i),
.addr_i (iaddr_i[31:2]), // Common Address Input
.wdata_i (w_cache_odata_0),
.we_i (w_cache_we),
.rdata_o (w_cache_odata_1)
);
DistRAM64x8SP #(.GEN_SIZE(TOTAL_CACHE_BITS), .ADDR_BITS(CACHE_ADDR_BITS)) cache_ram_2 (
.clk_i (clk_i),
.addr_i (iaddr_i[31:2]), // Common Address Input
.wdata_i (w_cache_odata_1),
.we_i (w_cache_we),
.rdata_o (w_cache_odata_2)
);
DistRAM64x8SP #(.GEN_SIZE(TOTAL_CACHE_BITS), .ADDR_BITS(CACHE_ADDR_BITS)) cache_ram_3 (
.clk_i (clk_i),
.addr_i (iaddr_i[31:2]), // Common Address Input
.wdata_i (w_cache_odata_2),
.we_i (w_cache_we),
.rdata_o (w_cache_odata_3)
);
//////////////////////////////////////////////////////////////////////////////////
// Cache Miss Evaluation
//////////////////////////////////////////////////////////////////////////////////
reg [63:0] rc_cache_total = 0, rc_cache_miss = 0;
wire [63:0] rc_cache_hit = rc_cache_total - rc_cache_miss;
always @(posedge clk_i or posedge rst_i) begin
if(rst_i) begin
rc_cache_total <= 0;
rc_cache_miss <= 0;
end else begin
rc_cache_miss <= rc_cache_miss + w_cache_we;
rc_cache_total <= rc_cache_total + ird_i;
end
end
endmodule
module DistRAM64x8SP #(
parameter GEN_SIZE = 8,
ADDR_BITS = 6
)(
input clk_i,
input [ADDR_BITS-1:0] addr_i, // Common Address Input
input [GEN_SIZE-1:0] wdata_i,
input we_i,
output [GEN_SIZE-1:0] rdata_o
);
genvar i;
generate
if(ADDR_BITS == 6) begin: Gen_RAM64
for(i = 0; i < GEN_SIZE; i = i + 1) begin: Gen_RAM
RAM64X1S #(.INIT(64'h0000000000000000)) ram64 (
.O (rdata_o[i]), // 1-bit data output
.A0 (addr_i[0]), // Address[0] input bit
.A1 (addr_i[1]), // Address[1] input bit
.A2 (addr_i[2]), // Address[2] input bit
.A3 (addr_i[3]), // Address[3] input bit
.A4 (addr_i[4]), // Address[4] input bit
.A5 (addr_i[5]), // Address[5] input bit
.D (wdata_i[i]), // 1-bit data input
.WCLK (clk_i), // Write clock input
.WE (we_i) // Write enable input
);
end
end else if(ADDR_BITS == 7) begin: Gen_RAM128
for(i = 0; i < GEN_SIZE; i = i + 1) begin: Gen_RAM
RAM128X1S #(.INIT(128'h00000000000000000000000000000000)) ram128 (
.O (rdata_o[i]), // 1-bit data output
.A0 (addr_i[0]), // Address[0] input bit
.A1 (addr_i[1]), // Address[1] input bit
.A2 (addr_i[2]), // Address[2] input bit
.A3 (addr_i[3]), // Address[3] input bit
.A4 (addr_i[4]), // Address[4] input bit
.A5 (addr_i[5]), // Address[5] input bit
.A6 (addr_i[6]), // Address[6] input bit
.D (wdata_i[i]), // 1-bit data input
.WCLK (clk_i), // Write clock input
.WE (we_i) // Write enable input
);
end
end else if(ADDR_BITS == 8) begin: Gen_RAM256
for(i = 0; i < GEN_SIZE; i = i + 1) begin: Gen_RAM
RAM256X1S #(.INIT(256'h0000000000000000000000000000000000000000000000000000000000000000)) ram256 (
.O (rdata_o[i]), // 1-bit data output
.A (addr_i), // Address[0] input bit
.D (wdata_i[i]), // 1-bit data input
.WCLK (clk_i), // Write clock input
.WE (we_i) // Write enable input
);
end
end else begin: Gen_RAM32
for(i = 0; i < GEN_SIZE; i = i + 2) begin: Gen_RAM
RAM32X2S #(.INIT_00(32'h00000000), .INIT_01(32'h00000000)) ram32 (
.O0 (rdata_o[i + 0]), // 1-bit data output
.O1 (rdata_o[i + 1]), // 1-bit data output
.A0 (addr_i[0]), // Address[0] input bit
.A1 (addr_i[1]), // Address[1] input bit
.A2 (addr_i[2]), // Address[2] input bit
.A3 (addr_i[3]), // Address[3] input bit
.A4 (addr_i[4]), // Address[4] input bit
.D0 (wdata_i[i + 0]), // 1-bit data input
.D1 (wdata_i[i + 1]), // 1-bit data input
.WCLK (clk_i), // Write clock input
.WE (we_i) // Write enable input
);
end
end
endgenerate
endmodule
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
|