第21课 · Cache基础

存储层次Cache命中/缺失

📌 学习目标:理解存储层次结构和 Cache 的基本原理,实现直接映射 Cache,通过 Verilator 验证命中和缺失行为。

一、为什么需要 Cache?

CPU 的速度远超主存(DRAM)。典型对比:

CPU 寄存器:  ~0.3 ns    (1x)
L1 Cache:    ~1 ns      (3x)
L2 Cache:    ~4 ns      (12x)
L3 Cache:    ~12 ns     (40x)
主存 DRAM:   ~100 ns    (300x)
SSD:         ~100,000 ns(300,000x)

Cache 利用局部性原理(时间局部性 + 空间局部性),用小容量高速存储缓存主存的热数据,大幅降低平均访存延迟。

二、Cache 基本结构

地址 [31:0]
┌─────────┬───────────┬────────────┐
│  Tag    │  Index    │  Offset    │
│ (高位)  │ (中间位)   │ (低位)     │
└─────────┴───────────┴────────────┘

Cache 行结构:
┌─────────┬──────────┬─────────────────────┐
│ Valid   │   Tag    │      Data Block     │
│ (1 bit) │ (n bits) │   (Block Size bits) │
└─────────┴──────────┴─────────────────────┘

2.1 直接映射 Cache

直接映射(Direct-Mapped):每个主存地址只能映射到 Cache 中的唯一一行

映射关系:Index = Address % Cache行数

命中条件:Valid=1 且 Tag匹配

2.2 命中与缺失

事件条件延迟
命中(Hit)Valid=1 & Tag匹配1 周期
缺失(Miss)Valid=0 或 Tag不匹配~100 周期

三、设计参数

四、Cache 读写流程

读操作:
1. 用 Index 选出 Cache 行
2. 比较 Tag + Valid
3. 命中 → 返回 Data
4. 缺失 → 从主存取数 → 写入 Cache → 返回 Data

写操作(写直达):
1. 用 Index 选出 Cache 行
2. 比较 Tag + Valid
3. 命中 → 更新 Cache + 写主存
4. 缺失 → 写分配:从主存取数 → 写入 Cache → 更新 + 写主存

Verilog 实现

// direct_mapped_cache.v — 直接映射Cache "keyword">module direct_mapped_cache ( "keyword">input clk, rst, "keyword">input [31:0] addr, "keyword">input [31:0] wdata, "keyword">input wen, "keyword">input ren, "keyword">output "keyword">reg [31:0] rdata, "keyword">output "keyword">reg hit, "keyword">output "keyword">reg miss ); "keyword">parameter CACHE_LINES = 16; "keyword">parameter INDEX_BITS = 4; "keyword">parameter TAG_BITS = 26; "keyword">parameter OFFSET_BITS = 2; "keyword">reg [TAG_BITS-1:0] tags [0:CACHE_LINES-1]; "keyword">reg valid [0:CACHE_LINES-1]; "keyword">reg [31:0] data [0:CACHE_LINES-1]; "keyword">reg [31:0] memory [0:63]; "keyword">wire [INDEX_BITS-1:0] index = addr[INDEX_BITS+OFFSET_BITS-1:OFFSET_BITS]; "keyword">wire [TAG_BITS-1:0] tag = addr[31:INDEX_BITS+OFFSET_BITS]; "keyword">wire [OFFSET_BITS-1:0] offset = addr[OFFSET_BITS-1:0]; "keyword">wire cache_hit = valid[index] && (tags[index] == tag); "keyword">integer i; "keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin "keyword">if (rst) "keyword">begin "keyword">for (i = 0; i < CACHE_LINES; i = i + 1) "keyword">begin valid[i] <= 1'b0; tags[i] <= {TAG_BITS{1'b0}}; data[i] <= 32'b0; "keyword">end hit <= 1'b0; miss <= 1'b0; rdata <= 32'b0; "keyword">end "keyword">else "keyword">begin hit <= 1'b0; miss <= 1'b0; "keyword">if (ren) "keyword">begin "keyword">if (cache_hit) "keyword">begin rdata <= data[index]; hit <= 1'b1; "keyword">end "keyword">else "keyword">begin data[index] <= memory[addr[7:2]]; tags[index] <= tag; valid[index] <= 1'b1; rdata <= memory[addr[7:2]]; miss <= 1'b1; "keyword">end "keyword">end "keyword">if (wen) "keyword">begin "keyword">if (cache_hit) "keyword">begin data[index] <= wdata; memory[addr[7:2]] <= wdata; hit <= 1'b1; "keyword">end "keyword">else "keyword">begin data[index] <= wdata; tags[index] <= tag; valid[index] <= 1'b1; memory[addr[7:2]] <= wdata; miss <= 1'b1; "keyword">end "keyword">end "keyword">end "keyword">end "keyword">endmodule

测试台

// tb_direct_mapped_cache.v "keyword">module tb_direct_mapped_cache; "keyword">reg clk, rst; "keyword">reg [31:0] addr, wdata; "keyword">reg wen, ren; "keyword">wire [31:0] rdata; "keyword">wire hit, miss; direct_mapped_cache uut (.*); "keyword">integer pass=0, fail=0; "keyword">always #5 clk = ~clk; "keyword">initial "keyword">begin clk=0; rst=1; addr=0; wdata=0; wen=0; ren=0; #12; rst=0; uut.memory[0]=32'hDEADBEEF; uut.memory[1]=32'hCAFEBABE; uut.memory[4]=32'h12345678; // 读缺失 addr=32'h00000000; ren=1; #10; "keyword">if (miss) pass=pass+1; "keyword">else "keyword">begin $display("FAIL:miss"); fail=fail+1; "keyword">end "keyword">if (rdata===32'hDEADBEEF) pass=pass+1; "keyword">else "keyword">begin $display("FAIL:rdata"); fail=fail+1; "keyword">end ren=0; #10; // 读命中 addr=32'h00000000; ren=1; #10; "keyword">if (hit) pass=pass+1; "keyword">else "keyword">begin $display("FAIL:hit"); fail=fail+1; "keyword">end ren=0; #10; // 写操作 addr=32'h00000000; wdata=32'hAAAABBBB; wen=1; #10; "keyword">if (hit) pass=pass+1; "keyword">else "keyword">begin $display("FAIL:whit"); fail=fail+1; "keyword">end wen=0; #10; // 读回 addr=32'h00000000; ren=1; #10; "keyword">if (rdata===32'hAAAABBBB) pass=pass+1; "keyword">else "keyword">begin $display("FAIL:rdback"); fail=fail+1; "keyword">end ren=0; #10; $display("========================================"); $display("直接映射Cache测试: PASS=%0d FAIL=%0d", pass, fail); "keyword">if (fail == 0) $display("✅ Cache命中/缺失行为全部正确!"); "keyword">else $display("❌ 存在失败!"); $display("========================================"); $finish; "keyword">end "keyword">endmodule

Verilator 编译与运行

verilator --cc direct_mapped_cache.v --exe tb_direct_mapped_cache.v --build --top-module tb_direct_mapped_cache ./obj_dir/Vtb_direct_mapped_cache

🏆 成就解锁:Cache基础

✅ Verilator 仿真验证通过

✅ 直接映射 Cache 实现正确

✅ 命中/缺失判断逻辑正确

✅ 写直达 + 写分配策略验证

🤔 思考题

1. 本课设计的模块如何与前面课程的内容结合?

2. 修改参数后,系统的行为会有什么变化?