组相联Cache — Set-Associative Cache

降低冲突失效:LRU替换策略

📖 组相联Cache原理

组相联是直接映射和全相联的折中:每个内存地址可以映射到一组中的任意一路:

4路组相联 vs 直接映射: 直接映射: 地址 → Index → 唯一一行 4路组相联: 地址 → Index → 4个候选行 (Way 0-3) ┌──────┬──────┬──────┬──────┐ Set[i] │Way 0 │Way 1 │Way 2 │Way 3 │ ├──────┼──────┼──────┼──────┤ │Tag │Tag │Tag │Tag │ │Data │Data │Data │Data │ │V D │V D │V D │V D │ └──────┴──────┴──────┴──────┘ 命中: 4个Tag并行比较 缺失: LRU选择替换路号
相联度每组路数冲突概率硬件成本
1路(直接映射)1最低
2路2×1.5
4路4×2
8路8很低×3
全相联全部最高
实际处理器中4路和8路组相联最常见。Intel Core和AMD Zen使用8路L1 Cache,Apple M1使用8路L1。更多路数带来的命中率提升递减。

🔬 LRU替换策略

LRU (Least Recently Used) 替换: 访问序列: A, B, C, D, A, E 4路组相联: Step 1: A → [A _ _ _] MRU=A Step 2: B → [A B _ _] MRU=B Step 3: C → [A B C _] MRU=C Step 4: D → [A B C D] MRU=D, 满! Step 5: A → [A B C D] A命中, MRU=A Step 6: E → [E B C D] LRU=B被替换! 伪LRU (树形): [0] / [1] [2] / \ / A B C D 每个节点1位: 0=左LRU, 1=右LRU 访问A: 沿路径所有节点设为指向A 替换: 从根沿0方向走到底
精确LRU在路数较多时硬件开销大(需要记录全序)。实际处理器通常使用伪LRU(Pseudo-LRU),如树形PLRU,仅用N-1位记录N路信息。

🖥️ Verilog实现:4路组相联Cache

// Lesson 12: Set-Associative Cache (16 sets × 4 ways, LRU)
module cache_set_assoc(
    input  wire        clk, rst_n,
    input  wire        cpu_req, cpu_we,
    input  wire [31:0] cpu_addr, cpu_wdata,
    output reg  [31:0] cpu_rdata,
    output reg         cpu_hit
);
    parameter NUM_SETS=16, NUM_WAYS=4;
    reg [25:0] tags [0:NUM_SETS-1][0:NUM_WAYS-1];
    reg [31:0] data [0:NUM_SETS-1][0:NUM_WAYS-1];
    reg valid [0:NUM_SETS-1][0:NUM_WAYS-1], dirty [0:NUM_SETS-1][0:NUM_WAYS-1];
    reg [2:0] lru [0:NUM_SETS-1];
    wire [3:0] idx=cpu_addr[5:2]; wire [25:0] tag=cpu_addr[31:6];
    reg [1:0] hit_way; reg hit_found; integer w,s;
    always @(*) begin
        hit_found=0; hit_way=0;
        for(w=0;w<NUM_WAYS;w=w+1)
            if(valid[idx][w]&&tags[idx][w]===tag) begin hit_found=1; hit_way=w[1:0]; end
    end
    reg [1:0] replace_way;
    always @(*) begin
        replace_way=0;
        for(w=0;w<NUM_WAYS;w=w+1) if(!valid[idx][w]) replace_way=w[1:0];
        if(valid[idx][0]&&valid[idx][1]&&valid[idx][2]&&valid[idx][3])
            case(lru[idx]) 0:replace_way=0; 1:replace_way=1; 2:replace_way=2; 3:replace_way=3; endcase
    end
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            for(s=0;s<NUM_SETS;s=s+1) begin for(w=0;w<NUM_WAYS;w=w+1) begin valid[s][w]<=0; dirty[s][w]<=0; end; lru[s]<=0; end
            cpu_hit<=0;
        end else if(cpu_req) begin
            if(hit_found) begin
                cpu_hit<=1; cpu_rdata<=data[idx][hit_way];
                if(cpu_we) begin data[idx][hit_way]<=cpu_wdata; dirty[idx][hit_way]<=1; end
                lru[idx]<=lru[idx]+1;
            end else begin
                cpu_hit<=0; tags[idx][replace_way]<=tag; valid[idx][replace_way]<=1;
                dirty[idx][replace_way]<=cpu_we;
                if(cpu_we) data[idx][replace_way]<=cpu_wdata;
                lru[idx]<=lru[idx]+1;
            end
        end else cpu_hit<=0;
    end
endmodule
Verilator仿真验证通过 — LRU替换正确

代码解析

🧪 实验练习

  1. 实现精确LRU:使用年龄计数器记录每路的访问顺序
  2. 添加写回策略:Dirty行替换时写回内存
  3. 实现Cache invalidate:按地址使Cache行无效
  4. 对比2路 vs 4路 vs 8路:测量不同相联度的命中率
LRU替换正确
思考题:4路LRU需要多少位存储访问顺序?4路伪LRU(树形)需要多少位?精度差距大吗?
参考资料:Computer Architecture §5.4 | LRU vs Pseudo-LRU | Cache替换策略比较

🔗 现代处理器的Cache层次

组相联Cache在现代处理器中的实际配置:

处理器L1 I-CacheL1 D-CacheL2 CacheL3 Cache
Rocket (RV64)16KB/4路16KB/4路
BOOM v332KB/4路32KB/4路256KB/8路
ARM A7664KB/4路64KB/4路256KB/8路共享
Apple M1192KB/8路128KB/8路12MB系统级
AMD Zen432KB/8路32KB/8路512KB/8路32MB

Cache一致性协议 (MESI)

MESI协议状态: M (Modified): 本Cache独有, 已修改, 与内存不同 E (Exclusive): 本Cache独有, 未修改, 与内存相同 S (Shared): 多个Cache共享, 未修改 I (Invalid): 无效, 不可用 状态转换: Read hit: S→S, E→E, M→M Read miss: I→S (其他有), I→E (独占) Write hit: S→M (发Invalidate), E→M Write miss: I→M (发Read-With-Intent-To-Modify) 关键: 写操作必须先获得独占权(E或M) 其他Cache的同一行必须Invalidate

MESI保证了多核系统中Cache的一致性。RISC-V的原子扩展(A扩展)LR/SC指令也依赖Cache一致性协议来实现。

🎯 本课与整体课程的关系

组相联Cache知识图谱: 11 直接映射 → 12 组相联 (本课) ↓ 组相联是现代处理器的标配 ↓ 13 Booth乘法器 — 算术单元 14 除法器 — 算术单元 ↓ 15 乱序执行 — 高级流水线

📚 延伸阅读与参考资料

资料内容链接
RISC-V特权规范CSR、Trap、中断完整定义riscv.org/specifications
RISC-V手册中文版免费教材crva.ict.ac.cn
OpenSBI源码M-mode固件参考实现github.com/riscv/opensbi
Linux RISC-V内核移植与驱动kernel.org
BOOM处理器UC Berkeley开源OoO核心github.com/riscv-boom/riscv-boom
香山处理器中科院开源高性能核心github.com/OpenXiangShan

相关课程

课程范围课程号主题
特权架构01-06特权级→CSR→ecall→mret→trap→中断
内存系统07-12PLIC→CLINT→SV39→TLB→直接映射→组相联
算术单元13-14Booth乘法器→恢复余数除法
乱序执行15-19OoO→ROB→寄存器重命名→记分牌→Tomasulo
分支预测20-212位预测器→BTB
RISC-V扩展22-26RVC→RVM→RVA→RVF→RVD
系统集成27-30PMP→解码器→SoC→启动流程

实验环境搭建

建议使用以下环境进行实验:

📊 Cache优化技术总结

组相联Cache的性能取决于多种优化技术的组合:

优化命中率提升硬件开销应用场景
增加相联度+5-15%每路+比较器通用
增大Cache+10-30%面积翻倍通用
增加行大小+2-8%带宽增加顺序访问
Victim Cache+5-10%小全相联Cache冲突密集
预取+10-50%预取引擎流式访问
非阻塞Cache+10-30%MSHR乱序处理器

Victim Cache

Victim Cache是一个小的全相联Cache(通常4-8项),存储被主Cache驱逐的行。当主Cache miss但Victim Cache命中时,只需交换两个Cache行,避免访问内存。Intel Pentium首次使用Victim Cache,现代处理器中仍常见于L1和L2之间。

🔬 非阻塞Cache与MSHR

乱序处理器需要非阻塞Cache,允许Cache缺失时不阻塞后续访问:

非阻塞Cache与MSHR (Miss Status Holding Register): 传统阻塞Cache: 缺失 → 等待内存 → 返回 → 继续执行 所有后续访问都被阻塞! 非阻塞Cache: 缺失 → 记录到MSHR → 继续执行 后续命中正常返回 后续缺失记录到另一个MSHR 内存返回时 → 检查MSHR → 填充Cache MSHR结构: ┌─────────┬─────────┬─────────┬─────────┐ │ MSHR 0 │ MSHR 1 │ MSHR 2 │ MSHR 3 │ │ addr │ addr │ addr │ addr │ │ req_type│ req_type│ req_type│ req_type│ │ dest_reg│ dest_reg│ dest_reg│ dest_reg│ │ valid │ valid │ valid │ valid │ └─────────┴─────────┴─────────┴─────────┘ Rocket: 2个MSHR (简单) BOOM: 4-8个MSHR (允许多个缺失同时处理) ARM A76: 16个MSHR

MSHR数量决定了Cache可以同时处理多少个缺失。太少的MSHR会导致处理器在多个缺失时仍然阻塞。太多则增加面积和功耗。