如何在Verilog中实现无时钟的直接映射写回LRU Cache?
无时钟无FSM实现直接映射写回Cache的思路
核心思路:用握手信号替代时钟驱动状态更新
Cache的内部状态(标签、有效位、脏位、数据块)不需要时钟触发器,而是利用主存返回的mem_done信号作为状态更新的触发条件——只有当主存完成读写操作时,才更新Cache的内部状态,以此实现时序逻辑的效果。
具体实现步骤
1. 命中/Miss判断:纯组合逻辑实时计算
直接根据当前CPU地址的索引和标签,与Cache对应索引位置的标签、有效位对比,完全不需要时钟:
// 先定义Cache的参数(根据作业要求调整) parameter BLOCK_SIZE = 4; // 块大小4字节 parameter CACHE_SIZE = 1024; // Cache总大小1KB parameter OFFSET_WIDTH = $clog2(BLOCK_SIZE); parameter INDEX_WIDTH = $clog2(CACHE_SIZE / BLOCK_SIZE); parameter TAG_WIDTH = 32 - INDEX_WIDTH - OFFSET_WIDTH; wire [TAG_WIDTH-1:0] curr_tag; wire [INDEX_WIDTH-1:0] curr_index; assign curr_index = addr[INDEX_WIDTH + OFFSET_WIDTH - 1 : OFFSET_WIDTH]; assign curr_tag = addr[31 : INDEX_WIDTH + OFFSET_WIDTH]; // 实时判断命中 always @(*) begin hit_miss = (cache_tag[curr_index] == curr_tag) && cache_valid[curr_index]; end
2. 内部状态存储:组合逻辑锁存器
用Verilog的组合逻辑块(always @(*))结合条件判断实现状态保持,只有在特定条件下(命中写、mem_done触发)才更新状态,未触发时保持原有值(相当于生成组合锁存器):
// 定义Cache内部存储 reg [TAG_WIDTH-1:0] cache_tag [0 : (CACHE_SIZE/BLOCK_SIZE)-1]; reg cache_valid [0 : (CACHE_SIZE/BLOCK_SIZE)-1]; reg cache_dirty [0 : (CACHE_SIZE/BLOCK_SIZE)-1]; reg [31:0] cache_data [0 : (CACHE_SIZE/BLOCK_SIZE)-1]; always @(*) begin // 命中写操作:直接更新Cache数据和脏位 if (hit_miss && rw == 1'b1) begin cache_data[curr_index] = wdata; cache_dirty[curr_index] = 1'b1; end // 主存操作完成时更新Cache状态 if (mem_done) begin if (mem_rw == 1'b0) begin // 主存读完成:加载新块到Cache cache_tag[curr_index] = curr_tag; cache_valid[curr_index] = 1'b1; cache_dirty[curr_index] = 1'b0; cache_data[curr_index] = mem_rdata; end else begin // 主存写回完成:清除脏位 cache_dirty[curr_index] = 1'b0; end end end
3. 主存控制逻辑:纯组合逻辑生成请求
根据当前的命中状态、脏位状态,直接生成主存的读写控制信号,完全不需要FSM:
always @(*) begin // 初始化所有输出,避免生成不必要的锁存器 mem_rw = 1'b0; mem_addr = 32'b0; mem_wdata = 32'b0; rdata = 32'b0; if (hit_miss) begin if (rw == 1'b0) begin // 读命中:返回Cache数据给CPU rdata = cache_data[curr_index]; end else begin // 写命中:CPU写数据已更新到Cache,可返回写数据(可选) rdata = wdata; end end else begin // Miss处理 if (cache_dirty[curr_index]) begin // 当前块脏,先发起写回请求 mem_rw = 1'b1; // 生成写回的主存地址(标签+索引+块起始偏移) mem_addr = {cache_tag[curr_index], curr_index, {OFFSET_WIDTH{1'b0}}}; mem_wdata = cache_data[curr_index]; end else begin // 当前块干净,直接发起读主存请求 mem_rw = 1'b0; // 生成块对齐的主存地址 mem_addr = {addr[31:OFFSET_WIDTH], {OFFSET_WIDTH{1'b0}}}; end end end
4. 关于LRU算法的说明
直接映射Cache每个索引仅对应一个块,发生Miss时只能替换该索引下的唯一块,因此LRU算法在这里没有实际意义(没有多个块可供选择),无需额外实现LRU相关逻辑,按题目要求直接替换当前块即可。
关键注意事项
- 所有输出信号必须设置默认值,避免Verilog编译器生成不必要的锁存器,导致逻辑错误。
- 利用
mem_done的脉冲特性(主存完成操作时短暂拉高)来触发状态更新,替代时钟的边沿触发效果。 - 组合逻辑锁存器的使用需要确保状态更新的条件互斥,避免出现冲突赋值。
内容的提问来源于stack exchange,提问作者Jackoo
相关产品推荐
相关产品推荐

