PLIC中断控制器 — Platform-Level Interrupt Controller

多源中断优先级路由与Claim/Complete机制

📖 PLIC架构

PLIC(Platform-Level Interrupt Controller)是RISC-V的标准中断控制器,管理来自外部设备的中断:

PLIC结构: ┌──────────┐ ┌──────────┐ ┌──────────┐ │ UART │ │ Ethernet │ │ GPIO │ 中断源 (最多1023个) │ IRQ=1 │ │ IRQ=2 │ │ IRQ=3 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ┌────▼────────────▼────────────▼─────┐ │ PLIC │ │ ┌─────────────────────────────┐ │ │ │ 优先级设置 (0-7) │ │ │ │ 阈值过滤 │ │ │ │ 使能掩码 │ │ │ │ Claim/Complete │ │ │ └─────────────────────────────┘ │ └──────┬──────────────┬──────────────┘ │ │ ┌────▼────┐ ┌────▼────┐ │ Hart 0 │ │ Hart 1 │ 多核目标 │ MEI │ │ MEI │ └─────────┘ └─────────┘
概念说明默认值
优先级每个中断源可设置0-7级0=禁用
阈值每个Hart可设优先级阈值0=全部通过
使能每个Hart可独立使能/禁用中断全部禁用
Claim获取当前最高优先级中断ID-
Complete通知PLIC中断处理完毕-

🔬 Claim/Complete机制

Claim/Complete流程: 1. 设备拉高IRQ线 → PLIC记录pending 2. PLIC向Hart发MEI → mip.MEIP = 1 3. Hart响应中断 → 跳转到trap handler 4. Handler读claim寄存器 → 获取中断ID, 同时清除pending 5. Handler处理中断 → 读取设备寄存器, 处理数据 6. Handler写complete → 通知PLIC可接收新中断 关键: Claim是原子的! • 读取claim自动清除该中断的pending • 同一中断不会被两个Hart同时claim • Complete后该中断才能再次触发
必须在中断处理完成后写complete寄存器!否则PLIC不会再次发送该中断。这是最常见的PLIC bug——忘记complete导致中断永远不再触发。

🖥️ Verilog实现:简化PLIC

// Lesson 07: PLIC — Platform-Level Interrupt Controller
module plic(
    input  wire        clk, rst_n,
    input  wire [7:0]  irq_src,
    input  wire [2:0]  irq_priority_0, irq_priority_1, irq_priority_2, irq_priority_3,
    input  wire [2:0]  irq_priority_4, irq_priority_5, irq_priority_6, irq_priority_7,
    input  wire [7:0]  irq_enable,
    input  wire [2:0]  threshold,
    input  wire        claim_req,
    output reg  [2:0]  claim_id,
    output reg         claim_valid,
    input  wire        complete_req,
    input  wire [2:0]  complete_id
);
    reg [2:0] priorities [0:7];
    always @(*) begin
        priorities[0]=irq_priority_0; priorities[1]=irq_priority_1;
        priorities[2]=irq_priority_2; priorities[3]=irq_priority_3;
        priorities[4]=irq_priority_4; priorities[5]=irq_priority_5;
        priorities[6]=irq_priority_6; priorities[7]=irq_priority_7;
    end

    reg [2:0] best_id, best_priority; reg best_pending;
    integer i;
    always @(*) begin
        best_id=0; best_priority=0; best_pending=0;
        for (i=0; i<8; i=i+1)
            if (irq_src[i] && irq_enable[i] && priorities[i]>threshold)
                if (!best_pending || priorities[i]>best_priority ||
                    (priorities[i]==best_priority && i<best_id))
                begin best_id=i[2:0]; best_priority=priorities[i]; best_pending=1; end
    end

    reg [7:0] in_flight;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin claim_valid<=0; claim_id<=0; in_flight<=0; end
        else begin
            if (claim_req && best_pending && !in_flight[best_id])
                begin claim_id<=best_id; claim_valid<=1; in_flight[best_id]<=1; end
            else claim_valid<=0;
            if (complete_req) in_flight[complete_id]<=0;
        end
    end
endmodule
Verilator仿真验证通过 — 中断优先级+路由正确

代码解析

🧪 实验练习

  1. 扩展到32个中断源:使用存储器映射的优先级寄存器
  2. 实现多Hart目标:不同中断路由到不同核心
  3. 添加中断亲和性:指定中断只能由特定Hart处理
  4. 实现中断优先级抢占:高优先级中断抢占低优先级处理
中断优先级+路由正确
思考题:如果两个中断优先级相同且都pending,PLIC选择哪个?如果中断源1的ID小于中断源2,谁先被claim?
参考资料:RISC-V PLIC Spec §4 | SiFive PLIC手册 | QEMU PLIC实现

🔗 PLIC在QEMU virt平台上的配置

QEMU virt平台是RISC-V开发的标准环境,其PLIC配置如下:

QEMU virt PLIC配置: 中断源: ID 1: UART0 ID 2: virtio disk ID 3: virtio net ID 4: virtio gpu ID 5: virtio input ID 6: virtio serial ID 7: virtio balloon ID 8: PCIe INTA ID 9: PCIe INTB ID 10: PCIe INTC ID 11: PCIe INTD 内存映射: 0x0C000000 - 0x0FFFFFFF PLIC区域 0x0C000000 优先级寄存器 (每源4字节) 0x0C002000 使能寄存器 (每Hart 4字节) 0x0C200000 threshold (每Hart 4字节) 0x0C200004 claim/complete (每Hart 4字节)

PLIC驱动实现(OpenSBI)

// PLIC中断处理伪代码: void plic_handler() { // 1. Claim: 获取最高优先级中断ID uint32_t id = readl(PLIC_CLAIM); if (id == 0) return; // 无待处理中断 // 2. 根据ID分派处理 switch(id) { case 1: uart_handler(); break; case 2: virtio_handler(); break; ... } // 3. Complete: 通知PLIC处理完毕 writel(PLIC_COMPLETE, id); // 必须写! 否则该中断不会再触发 }

🎯 本课与整体课程的关系

PLIC知识图谱: 06 中断 → 07 PLIC (本课) ↓ PLIC是外部中断的核心路由器 ↓ 08 CLINT — 另一个中断源(定时器/软件) ↓ 09 SV39 — 虚拟内存(与中断系统并行) 10 TLB — 地址翻译缓存

📚 延伸阅读与参考资料

资料内容链接
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→启动流程

实验环境搭建

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

📊 PLIC与高级中断控制器对比

特性PLICAPLICIMSIC
最大中断源10234095~200(Hart×ID)
优先级级别0-70-255按ID排序
触发方式电平/边沿电平/边沿MSI写入
多Hart路由支持支持每Hart独立
虚拟化部分完整
MSI支持支持原生

PLIC是RISC-V的"基础版"中断控制器,适用于大多数嵌入式场景。APLIC和IMSIC是"高级版",适用于服务器和虚拟化场景。

PLIC的局限性