第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 容量:16 行 × 32bit = 64 字节
- 地址宽度:32 bit
- Index 位宽:4 bit(2⁴ = 16 行)
- Offset 位宽:2 bit(字编址,4 字节/行)
- Tag 位宽:26 bit(32 - 4 - 2)
四、Cache 读写流程
读操作:
1. 用 Index 选出 Cache 行
2. 比较 Tag + Valid
3. 命中 → 返回 Data
4. 缺失 → 从主存取数 → 写入 Cache → 返回 Data
写操作(写直达):
1. 用 Index 选出 Cache 行
2. 比较 Tag + Valid
3. 命中 → 更新 Cache + 写主存
4. 缺失 → 写分配:从主存取数 → 写入 Cache → 更新 + 写主存
Verilog 实现
"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
测试台
"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. 修改参数后,系统的行为会有什么变化?