plic中断级联设计和使用

一、RISC-V中断级联的核心目标(扩展中断号)

RISC-V标准PLIC(平台级中断控制器)的中断号数量有限(比如基础版支持128个中断号),当外设中断源超过该数量时,需要通过中断级联(Cascade)扩展:

  1. 主PLIC:管理高优先级中断 + 下级从PLIC的中断请求(从PLIC的请求作为主PLIC的一个「聚合中断号」);
  2. 从PLIC:管理细分的低优先级中断源,多个从PLIC可级联到主PLIC;
  3. 核心逻辑:从PLIC将自身的中断请求聚合为1个中断号上报给主PLIC,主PLIC收到后,CPU通过读取从PLIC的寄存器确定具体的中断源。

二、Verilog实现(中断号扩展型级联)

以下实现1个主PLIC + 2个从PLIC的级联结构,扩展中断号数量:

  • 主PLIC:原生支持16个中断号(1~15),其中:
    • 1~8:直接管理高优先级外设(如DMA、UART);
    • 9:级联「从PLIC1」的聚合中断;
    • 10:级联「从PLIC2」的聚合中断;
  • 从PLIC1:管理16个低优先级中断(GPIO0~GPIO15),聚合为1个中断号(主PLIC的9号);
  • 从PLIC2:管理16个低优先级中断(I2C0~I2C15),聚合为1个中断号(主PLIC的10号);
  • 最终总中断数:8 + 16 + 16 = 40个(通过级联从16个扩展到40个)。
verilog 复制代码
`timescale 1ns/1ps

// -------------------------- 从PLIC模块(扩展中断号) --------------------------
module riscv_plic_slave (
    input         clk,
    input         rst_n,
    // 外设中断源(16个)
    input  [15:0] slave_int_sources,  // 16个中断源(bit0~bit15对应中断号1~16)
    // 主PLIC接口
    output        cascade_int_req,    // 向主PLIC发起的聚合中断请求
    output [3:0]  cascade_int_id,     // 从PLIC内最高优先级中断号
    // CPU配置/响应接口
    input         cpu_claim,          // CPU读取中断(claim)
    input         cpu_complete,       // CPU完成中断(complete)
    input  [3:0]  cpu_hart_id         // CPU核ID
);

// 中断优先级配置(简化:bit越高优先级越高)
localparam [3:0] PRIO_NONE = 4'd0;

reg [3:0] slave_int_prio;    // 从PLIC内最高优先级中断号
reg       slave_int_req;     // 从PLIC中断请求

// 步骤1:仲裁从PLIC内最高优先级中断号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        slave_int_prio <= PRIO_NONE;
        slave_int_req  <= 1'b0;
    end else begin
        // 从bit15到bit0遍历,找到第一个置位的中断源(高bit优先级高)
        slave_int_prio <= PRIO_NONE;
        slave_int_req  <= 1'b0;
        for (int i=15; i>=0; i=i-1) begin
            if (slave_int_sources[i]) begin
                slave_int_prio <= i + 1;  // 中断号1~16对应bit0~bit15
                slave_int_req  <= 1'b1;
                break;
            end
        end
    end
end

// 步骤2:Pending寄存器(防止中断丢失)
reg [15:0] pending;  // 每个中断源对应1位

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        pending <= 16'h0;
    end else begin
        // 新中断触发置位pending,CPU处理后清除
        pending <= (pending | slave_int_sources);
    end
end

// CPU claim后清除对应的pending位
always @(posedge clk) begin
    if (cpu_claim && (slave_int_prio != PRIO_NONE)) begin
        pending[slave_int_prio - 1] <= 1'b0;
    end
end

// 步骤3:仲裁时用pending而不是原始信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        slave_int_prio <= PRIO_NONE;
        slave_int_req  <= 1'b0;
    end else begin
        slave_int_prio <= PRIO_NONE;
        slave_int_req  <= 1'b0;
        for (int i=15; i>=0; i=i-1) begin
            if (pending[i]) begin  // 用pending仲裁
                slave_int_prio <= i + 1;
                slave_int_req  <= 1'b1;
                break;
            end
        end
    end
end

// 步骤4:CPU响应后清除中断请求
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        slave_int_req <= 1'b0;
    end else if (cpu_complete && (slave_int_prio != PRIO_NONE)) begin
        slave_int_req <= 1'b0;
    end
end

// 输出到主PLIC的聚合中断
assign cascade_int_req = slave_int_req;
assign cascade_int_id  = slave_int_prio;

endmodule

// -------------------------- 主PLIC模块(级联从PLIC) --------------------------
module riscv_plic_master (
    input         clk,
    input         rst_n,
    // 直接外设中断源(8个:中断号1~8)
    input  [7:0]  master_int_sources,
    // 从PLIC1级联接口
    input         slave1_cascade_req,
    input  [3:0]  slave1_cascade_id,
    // 从PLIC2级联接口
    input         slave2_cascade_req,
    input  [3:0]  slave2_cascade_id,
    // CPU接口(RISC-V PLIC标准)
    output        cpu_interrupt_req,  // 向CPU发起中断
    input         cpu_claim,          // CPU读取中断ID
    output [5:0]  cpu_claim_id,       // 给CPU的中断ID(扩展到40个)
    input         cpu_complete,       // CPU完成中断
    input  [3:0]  cpu_hart_id         // CPU核ID
);

// 中断号定义(核心:级联扩展)
localparam [5:0] INT_DMA      = 6'd1;   // 主PLIC直接中断1
localparam [5:0] INT_UART     = 6'd2;   // 主PLIC直接中断2
localparam [5:0] INT_SLAVE1   = 6'd9;   // 级联从PLIC1的聚合中断号
localparam [5:0] INT_SLAVE2   = 6'd10;  // 级联从PLIC2的聚合中断号
localparam [5:0] INT_NONE     = 6'd0;

reg [5:0] current_int_id;    // 当前最高优先级中断ID
reg       master_int_req;    // 主PLIC向CPU的中断请求

// 步骤1:仲裁所有中断(直接中断 > 级联中断)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        current_int_id <= INT_NONE;
        master_int_req <= 1'b0;
    end else begin
        current_int_id <= INT_NONE;
        master_int_req <= 1'b0;

        // 第一步:仲裁主PLIC直接中断(1~8,bit7~bit0对应中断号8~1)
        for (int i=7; i>=0; i=i-1) begin
            if (master_int_sources[i]) begin
                current_int_id <= i + 1;
                master_int_req <= 1'b1;
                break;
            end
        end

        // 第二步:若无直接中断,仲裁级联中断(从PLIC1 > 从PLIC2)
        if (current_int_id == INT_NONE) begin
            if (slave1_cascade_req) begin
                // 级联中断ID:主PLIC中断号(9) + 从PLIC内偏移(1~16 → 9~24)
                current_int_id <= INT_SLAVE1 + slave1_cascade_id - 1;
                master_int_req <= 1'b1;
            end else if (slave2_cascade_req) begin
                // 级联中断ID:主PLIC中断号(10) + 从PLIC内偏移(1~16 → 25~40)
                current_int_id <= INT_SLAVE2 + slave2_cascade_id - 1;
                master_int_req <= 1'b1;
            end
        end
    end
end

// 步骤2:CPU Claim/Complete接口(RISC-V标准)
assign cpu_interrupt_req = master_int_req;
assign cpu_claim_id      = (cpu_claim) ? current_int_id : INT_NONE;

// 调试:打印中断号扩展信息
always @(posedge clk) begin
    if (rst_n && master_int_req && cpu_claim) begin
        if (current_int_id <= 8) begin
            $display("[%0t] 主PLIC直接中断:ID=%0d", $time, current_int_id);
        end else if (current_int_id <=24) begin
            $display("[%0t] 级联从PLIC1中断:总ID=%0d (从PLIC1内ID=%0d)", 
                     $time, current_int_id, current_int_id - INT_SLAVE1 + 1);
        end else begin
            $display("[%0t] 级联从PLIC2中断:总ID=%0d (从PLIC2内ID=%0d)", 
                     $time, current_int_id, current_int_id - INT_SLAVE2 + 1);
        end
    end
end

endmodule

// -------------------------- 顶层模块(主+从PLIC级联) --------------------------
module riscv_interrupt_cascade_extend (
    input         clk,
    input         rst_n,
    // 直接外设中断(8个:DMA/UART等)
    input  [7:0]  master_ints,
    // 从PLIC1中断源(16个:GPIO0~GPIO15)
    input  [15:0] slave1_ints,
    // 从PLIC2中断源(16个:I2C0~I2C15)
    input  [15:0] slave2_ints,
    // CPU接口
    output        cpu_int_req,
    input         cpu_claim,
    output [5:0]  cpu_claim_id,
    input         cpu_complete,
    input  [3:0]  cpu_hart_id
);

// 从PLIC1实例化
wire slave1_cascade_req;
wire [3:0] slave1_cascade_id;
riscv_plic_slave u_slave1 (
    .clk(clk),
    .rst_n(rst_n),
    .slave_int_sources(slave1_ints),
    .cascade_int_req(slave1_cascade_req),
    .cascade_int_id(slave1_cascade_id),
    .cpu_claim(cpu_claim && (cpu_claim_id >=9 && cpu_claim_id <=24)),
    .cpu_complete(cpu_complete && (cpu_claim_id >=9 && cpu_claim_id <=24)),
    .cpu_hart_id(cpu_hart_id)
);

// 从PLIC2实例化
wire slave2_cascade_req;
wire [3:0] slave2_cascade_id;
riscv_plic_slave u_slave2 (
    .clk(clk),
    .rst_n(rst_n),
    .slave_int_sources(slave2_ints),
    .cascade_int_req(slave2_cascade_req),
    .cascade_int_id(slave2_cascade_id),
    .cpu_claim(cpu_claim && (cpu_claim_id >=25 && cpu_claim_id <=40)),
    .cpu_complete(cpu_complete && (cpu_claim_id >=25 && cpu_claim_id <=40)),
    .cpu_hart_id(cpu_hart_id)
);

// 主PLIC实例化
riscv_plic_master u_master (
    .clk(clk),
    .rst_n(rst_n),
    .master_int_sources(master_ints),
    .slave1_cascade_req(slave1_cascade_req),
    .slave1_cascade_id(slave1_cascade_id),
    .slave2_cascade_req(slave2_cascade_req),
    .slave2_cascade_id(slave2_cascade_id),
    .cpu_interrupt_req(cpu_int_req),
    .cpu_claim(cpu_claim),
    .cpu_claim_id(cpu_claim_id),
    .cpu_complete(cpu_complete),
    .cpu_hart_id(cpu_hart_id)
);

endmodule

// -------------------------- 测试用例 --------------------------
module tb_riscv_interrupt_cascade_extend;

reg         clk;
reg         rst_n;
wire        cpu_int_req;
reg         cpu_claim;
wire [5:0]  cpu_claim_id;
reg         cpu_complete;
reg  [3:0]  cpu_hart_id;
reg  [7:0]  master_ints;
reg  [15:0] slave1_ints;
reg  [15:0] slave2_ints;

// 实例化顶层模块
riscv_interrupt_cascade_extend u_top (
    .clk(clk),
    .rst_n(rst_n),
    .master_ints(master_ints),
    .slave1_ints(slave1_ints),
    .slave2_ints(slave2_ints),
    .cpu_int_req(cpu_int_req),
    .cpu_claim(cpu_claim),
    .cpu_claim_id(cpu_claim_id),
    .cpu_complete(cpu_complete),
    .cpu_hart_id(cpu_hart_id)
);

// 时钟生成
initial begin
    clk = 1'b0;
    forever #5 clk = ~clk;
end

// 测试流程
initial begin
    // 初始化
    rst_n = 1'b0;
    cpu_claim = 1'b0;
    cpu_complete = 1'b0;
    cpu_hart_id = 4'd0;
    master_ints = 8'b0000_0000;
    slave1_ints = 16'h0000;
    slave2_ints = 16'h0000;
    #100;
    rst_n = 1'b1;
    #100;

    // 步骤1:测试主PLIC直接中断(ID=2:UART)
    $display("\n=== 步骤1:主PLIC直接中断(ID=2) ===");
    master_ints = 8'b0000_0010;  // ID=2置位
    #20;
    cpu_claim = 1'b1;  // CPU读取中断ID
    #10;
    cpu_claim = 1'b0;
    #20;
    cpu_complete = 1'b1;  // CPU完成中断
    #10;
    cpu_complete = 1'b0;
    master_ints = 8'b0000_0000;
    #100;

    // 步骤2:测试级联从PLIC1中断(GPIO7 → 总ID=9+7=16)
    $display("\n=== 步骤2:级联从PLIC1中断(GPIO7 → 总ID=16) ===");
    slave1_ints = 16'h0080;  // GPIO7(从PLIC1内ID=8)
    #20;
    cpu_claim = 1'b1;
    #10;
    cpu_claim = 1'b0;
    #20;
    cpu_complete = 1'b1;
    #10;
    cpu_complete = 1'b0;
    slave1_ints = 16'h0000;
    #100;

    // 步骤3:测试级联从PLIC2中断(I2C15 → 总ID=10+15=25)
    $display("\n=== 步骤3:级联从PLIC2中断(I2C15 → 总ID=25) ===");
    slave2_ints = 16'h8000;  // I2C15(从PLIC2内ID=16)
    #20;
    cpu_claim = 1'b1;
    #10;
    cpu_claim = 1'b0;
    #20;
    cpu_complete = 1'b1;
    #10;
    cpu_complete = 1'b0;
    slave2_ints = 16'h0000;
    #200;

    $display("\n=== 测试结束 ===");
    $finish;
end

endmodule

三、核心逻辑解释(重点:中断号扩展)

1. 中断号映射规则(级联扩展的核心)
中断类型 从PLIC内ID 主PLIC聚合ID 最终总中断ID 对应外设
主PLIC直接中断 1~8 - 1~8 DMA、UART等
从PLIC1级联 1~16 9 9~24 GPIO0~GPIO15
从PLIC2级联 1~16 10 25~40 I2C0~I2C15
  • 核心:从PLIC的每个中断源,通过「主PLIC聚合ID + 从PLIC内偏移」映射为全局唯一的中断号,实现从16个到40个的扩展。
2. 级联关键逻辑
  • 从PLIC:将自身16个中断源仲裁出最高优先级的中断号,然后向主PLIC发起1个「聚合中断请求」(仅1个信号);
  • 主PLIC:把从PLIC的聚合中断请求当作普通中断源(占用1个主中断号),CPU读取主PLIC的中断ID后,通过ID范围判断属于哪个从PLIC,再读取对应从PLIC的寄存器得到具体中断源;
  • CPU交互 :通过RISC-V标准的claim/complete接口,CPU先读取主PLIC的全局中断ID,再根据ID归属,读取从PLIC的细分中断信息。
3. 仿真输出示例(直观看到中断号扩展)
复制代码
=== 步骤1:主PLIC直接中断(ID=2) ===
[220] 主PLIC直接中断:ID=2

=== 步骤2:级联从PLIC1中断(GPIO7 → 总ID=16) ===
[420] 级联从PLIC1中断:总ID=16 (从PLIC1内ID=8)

=== 步骤3:级联从PLIC2中断(I2C15 → 总ID=25) ===
[620] 级联从PLIC2中断:总ID=35 (从PLIC2内ID=16)

=== 测试结束 ===

四、RISC-V标准对齐说明

  1. PLIC级联规范 :RISC-V PLIC支持将多个从PLIC的interrupt_req信号作为主PLIC的中断源,主PLIC仅需为每个从PLIC分配1个中断号,即可扩展成百上千个中断源;

  2. 软件层适配 :CPU中断服务程序(ISR)逻辑:

    c 复制代码
    void plic_isr() {
        // 1. 读取主PLIC的全局中断ID
        uint32_t global_id = plic_claim();
        // 2. 判断中断归属
        if (global_id <=8) {
            // 处理主PLIC直接中断(如DMA/UART)
        } else if (global_id <=24) {
            // 计算从PLIC1内的本地ID
            uint32_t local_id = global_id - 9 + 1;
            // 处理从PLIC1的GPIO中断
        } else if (global_id <=40) {
            // 计算从PLIC2内的本地ID
            uint32_t local_id = global_id -10 +1;
            // 处理从PLIC2的I2C中断
        }
        // 3. 完成中断
        plic_complete(global_id);
    }

五、PLIC寄存器接口(标准RISC-V定义)

RISC-V PLIC通过MMIO(内存映射IO)提供以下关键寄存器:

寄存器偏移 名称 说明
0x000000 priority_base + interrupt_id * 4 设置每个中断源的优先级(0~7,0表示禁用)
0x002000 pending_base + (interrupt_id / 32) * 4 中断挂起寄存器
0x002080 enable_base + hart_id * 0x100 + (interrupt_id / 32) * 4 中断使能寄存器
0x003FFFF claim_complete CPU Claim/Complete寄存器(读写同一地址)
1. Priority(优先级)寄存器
  • 每个中断号对应一个优先级寄存器
  • 优先级范围:0~7(可配置),0表示该中断被禁用
  • 优先级数值越大,优先级越高
2. Enable(使能)寄存器
  • 每个Hart(CPU核)有独立的使能寄存器
  • 需显式使能每个中断源,否则不会触发CPU中断
3. Claim/Complete 机制
c 复制代码
// CPU侧标准操作流程
uint32_t plic_claim(void) {
    return *(volatile uint32_t *)PLIC_CLAIM_COMPLETE_ADDR;
}

void plic_complete(uint32_t interrupt_id) {
    *(volatile uint32_t *)PLIC_CLAIM_COMPLETE_ADDR = interrupt_id;
}
4. CPU_Claim / CPU_Complete 信号详解(Verilog版)

这两个信号是 CPU与PLIC交互的标准接口,用于中断的"认领"和"完成"。

4.1 信号定义
信号 方向 说明
cpu_claim CPU → PLIC CPU读取中断ID(认领中断)
cpu_claim_id PLIC → CPU PLIC返回的中断号
cpu_complete CPU → PLIC CPU处理完成(清除中断)
cpu_interrupt_req PLIC → CPU 有中断待处理(中断通知)
4.2 交互时序

CPU PLIC CPU PLIC 1. 外设触发中断 2. CPU读取中断ID 3. CPU执行ISR 4. CPU完成中断 interrupt_req (拉高) cpu_claim = 1 claim_id = 5 (返回中断号) cpu_claim = 0 [执行中断处理程序...] cpu_complete = 1 interrupt_req (拉低) cpu_complete = 0

4.3 Verilog 代码示例

PLIC侧(中断控制器):

verilog 复制代码
module plic_example (
    input         clk,
    input         rst_n,
    input  [7:0]  int_sources,    // 中断源
    input         cpu_claim,     // CPU来读取中断
    output [7:0] claim_id,       // 返回给CPU的中断号
    input         cpu_complete,  // CPU来完成中断
    output        interrupt_req  // 中断请求信号
);

    reg [7:0] current_id;      // 当前中断ID
    reg       int_req;          // 中断请求寄存器
    
    // 仲裁:找出最高优先级的中断
    always @(*) begin
        current_id = 0;
        int_req = 1'b0;
        for (int i = 7; i >= 0; i--) begin
            if (int_sources[i]) begin
                current_id = i + 1;   // 中断号从1开始
                int_req = 1'b1;
                break;
            end
        end
    end
    
    // 输出中断请求
    assign interrupt_req = int_req;
    
    // CPU claim:返回中断ID(读时有效)
    assign claim_id = (cpu_claim) ? current_id : 0;
    
    // CPU complete:清除中断
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            int_req <= 1'b0;
        end else if (cpu_complete) begin
            int_req <= 1'b0;          // 清除中断请求
        end
    end
    
endmodule
4.4 关键理解点
概念 说明
Claim(认领) 告诉PLIC"我要处理这个中断了",读取中断ID,同时"锁定"这个中断
Complete(完成) 告诉PLIC"我已经处理完了",清除中断请求,允许新的中断触发
必须配对 每个中断必须先claim后complete,缺一不可
只读claim_id claim_id只在cpu_claim=1时有效,其他时间为0
4.5 常见错误
错误 后果
只claim不complete 中断无法再次触发(pending位无法清除)
多次claim 行为未定义,可能丢失中断
claim后未处理就complete 中断被提前清除,可能丢失
4.6 类比理解
操作 生活中类似 作用
interrupt_req 电话铃响 "有中断来了!"
cpu_claim 接电话 "这个中断我来处理"
cpu_complete 挂电话 "处理完了,可以再打来"

六、中断处理完整流程

1. 流程图(使用Mermaid)

主PLIC直接中断
从PLIC级联中断
外设触发中断
PLIC仲裁
CPU响应
读取Claim寄存器
判断中断归属
执行主PLIC中断ISR
读取从PLIC获取细分中断号
执行从PLIC中断ISR
写入Complete清除中断

2. 时序图(使用Mermaid)

CPU 主PLIC 从PLIC 外设(GPIO/UART) CPU 主PLIC 从PLIC 外设(GPIO/UART) 步骤1: 外设触发中断 步骤2: CPU响应中断 步骤3: 判断中断归属 alt [中断ID ∈ [9, 24] (从PLIC1)] [中断ID ∈ [25, 40] (从PLIC2)] 步骤4: 执行ISR 步骤5: 清除中断 中断请求(assert) 聚合中断请求(cascade_int_req) cpu_interrupt_req cpu_claim(读取中断ID) cpu_claim_id=16 (从PLIC1: GPIO7) 读取细分中断号 返回本地ID=8 (GPIO7) 读取细分中断号 返回本地ID=16 (I2C15) 执行对应外设ISR cpu_complete cpu_complete 清除slave_int_req

七、多Hart(多核)支持

RISC-V PLIC原生支持多核,每个Hart有独立的:

  • 使能寄存器(enable)
  • 优先级阈值(threshold)
  • Claim/Complete 寄存器
verilog 复制代码
// 多核支持的关键:每个Hart独立的中断接口
module riscv_plic_multihart (
    input         clk,
    input         rst_n,
    input  [7:0]  int_sources,      // 中断源
    // Hart 0 接口
    output        hart0_interrupt,
    input         hart0_claim,
    output [7:0]  hart0_claim_id,
    input         hart0_complete,
    // Hart 1 接口
    output        hart1_interrupt,
    input         hart1_claim,
    output [7:0]  hart1_claim_id,
    input         hart1_complete
);
    // 每个Hart独立仲裁,可以配置不同优先级阈值
endmodule

八、实际配置示例

c 复制代码
// 1. 配置UART中断(主PLIC直接中断,ID=2)
#define PLIC_PRIORITY_UART    2
#define PLIC_ENABLE_UART      (1 << 2)

// 设置优先级
*(volatile uint32_t *)0x10000008 = PLIC_PRIORITY_UART;  // interrupt_id=2, offset=2*4

// 使能UART中断(Hart 0)
*(volatile uint32_t *)0x10002008 = PLIC_ENABLE_UART;    // enable_base + 0x80 + 0

// 设置阈值(只响应优先级>1的中断)
*(volatile uint32_t *)0x10020000 = 1;

// 2. 配置GPIO中断(从PLIC1级联,假设映射到主PLIC ID=9)
#define PLIC_PRIORITY_GPIO    3
#define PLIC_ENABLE_GPIO       (1 << 9)

// 从PLIC1设置GPIO优先级
*(volatile uint32_t *)0x20000004 = PLIC_PRIORITY_GPIO;  // GPIO1, offset=1*4

九、硬件设计注意事项

  1. 时序关键路径

    • 从外设中断信号到CPU中断请求的路径需严格约束
    • 建议在PLIC入口添加寄存器打拍(pipeline)
  2. 中断优先级配置原则

    • 时延敏感的外设(UART、DMA)配置高优先级
    • 批量数据传输(I2C、SPI)配置低优先级
  3. 级联数量限制

    • 主PLIC的剩余中断号决定最大级联数量
    • 建议保留至少2个直接中断号给紧急外设
  4. 面积与性能权衡

    • 从PLIC数量越多,面积越大,但中断细分能力越强
    • 可根据实际外设数量规划从PLIC规模

十、中断丢失问题详解

1. 问题描述

如果代码中没有pending寄存器,直接用原始中断信号进行仲裁,会导致低优先级中断丢失:

verilog 复制代码
// ❌ 错误做法:直接用原始信号仲裁
for (int i=15; i>=0; i=i-1) begin
    if (slave_int_sources[i]) begin  // 原始信号
        slave_int_prio <= i + 1;
        slave_int_req  <= 1'b1;
        break;
    end
end

问题场景:

  • 时刻T0: bit5=1, bit3=1 同时触发
  • 仲裁选中bit5(高优先级),CPU开始处理
  • 时刻T1: 外设撤销了bit5的中断请求(中断信号变为0)
  • 时刻T2: CPU处理完bit5,尝试处理bit3,但此时bit3可能也已过期
  • 结果:低优先级中断丢失
2. 解决方案:使用Pending寄存器

关键设计:

  • pending 寄存器保存已触发的中断
  • 仲裁时读取 pending 而不是原始信号
  • CPU claim后清除对应的pending位
  • 外设中断信号只负责置位pending,不直接参与仲裁
verilog 复制代码
// ✅ 正确做法
reg [15:0] pending;

// 新中断触发时置位pending
always @(posedge clk) begin
    pending <= pending | slave_int_sources;
end

// CPU claim后清除pending
always @(posedge clk) begin
    if (cpu_claim) begin
        pending[slave_int_prio - 1] <= 1'b0;
    end
end

// 仲裁用pending
for (int i=15; i>=0; i=i-1) begin
    if (pending[i]) begin
        // ...
    end
end
3. 中断处理完整时序

CPU 仲裁器 Pending寄存器 外设 CPU 仲裁器 Pending寄存器 外设 T0: bit5=1, bit3=1 同时触发 T1: CPU claim T2: CPU响应bit3 中断请求 pending = 0b00101000 读取pending bit5=1(高优先级) 响应bit5中断 claim(读取ID=6) pending = 0b00001000 (bit3保留) 再次读取 bit3=1 响应bit3中断 claim(读取ID=4) pending = 0b00000000

4. 重要结论
机制 作用
Pending寄存器 保存已触发但未处理的中断,防止丢失
Claim机制 读取中断ID的同时清除对应pending位
Complete机制 清除中断请求信号(slave_int_req)
两者配合 确保中断处理完整,不丢失任何中断

十一、常见问题与调试

问题 可能原因 解决方案
中断不触发 未使能中断/优先级为0 检查enable寄存器配置
中断频繁触发 未调用complete清除 ISR结束后必须写complete
中断号错误 级联ID映射错误 核对主PLIC+从PLIC偏移计算
多核中断混乱 未正确配置hart选择器 确保每个hart独立配置
低优先级中断丢失 未使用pending寄存器 改用pending机制保存中断

总结

  1. RISC-V中断级联的核心目标是扩展中断号数量:主PLIC为每个从PLIC分配1个聚合中断号,从PLIC管理细分中断源,实现中断源数量的倍数级扩展;
  2. 本实现中,主PLIC原生16个中断号,级联2个从PLIC后扩展到40个,可无限级联(只要主PLIC有剩余中断号);
  3. 核心逻辑是「从PLIC聚合中断请求 → 主PLIC映射全局中断ID → CPU根据ID归属处理细分中断」;
  4. 完全对齐RISC-V PLIC标准,可直接适配RISC-V CPU的中断控制器架构;
  5. 实际使用需注意多核支持、寄存器配置、优先级阈值等关键配置项。
相关推荐
JSMSEMI114 小时前
JSM3488E RS‑485/RS‑422 收发器芯片
fpga开发
学习永无止境@15 小时前
Vivado FPGA程序压缩
fpga开发
daxi15021 小时前
Verilog入门实战——第2讲:核心语法基础(数据类型+赋值语句)
fpga开发·fpga
嵌入式-老费1 天前
Linux camera驱动开发(vivado hls不能导出ip的问题)
图像处理·fpga开发
CoderIsArt1 天前
FPGA量子计算教学平台设计方案与实现步骤
fpga开发·量子计算
学习永无止境@1 天前
Vivado FPGA输入时钟约束
开发语言·fpga开发·fpga
上班最快乐2 天前
基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(1)
fpga开发
国科安芯2 天前
抗辐照加固CAN FD芯片的商业航天与车规级应用解析
科技·嵌入式硬件·安全·fpga开发·安全威胁分析
简宸~2 天前
FPGA(十一)DataMover 自编辑IP
网络协议·tcp/ip·fpga开发·开源