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个)。
`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~8DMA、UART等
从PLIC1级联1~1699~24GPIO0~GPIO15
从PLIC2级联1~161025~40I2C0~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)逻辑:
    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)提供以下关键寄存器:

寄存器偏移名称说明
0x000000priority_base + interrupt_id * 4设置每个中断源的优先级(0~7,0表示禁用)
0x002000pending_base + (interrupt_id / 32) * 4中断挂起寄存器
0x002080enable_base + hart_id * 0x100 + (interrupt_id / 32) * 4中断使能寄存器
0x003FFFFclaim_completeCPU Claim/Complete寄存器(读写同一地址)
1. Priority(优先级)寄存器
  • 每个中断号对应一个优先级寄存器
  • 优先级范围:0~7(可配置),0表示该中断被禁用
  • 优先级数值越大,优先级越高
2. Enable(使能)寄存器
  • 每个Hart(CPU核)有独立的使能寄存器
  • 需显式使能每个中断源,否则不会触发CPU中断
3. Claim/Complete 机制
// 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_claimCPU → PLICCPU读取中断ID(认领中断)
cpu_claim_idPLIC → CPUPLIC返回的中断号
cpu_completeCPU → PLICCPU处理完成(清除中断)
cpu_interrupt_reqPLIC → CPU有中断待处理(中断通知)
4.2 交互时序
CPUPLICCPUPLIC1. 外设触发中断2. CPU读取中断ID3. CPU执行ISR4. CPU完成中断interrupt_req (拉高)cpu_claim = 1claim_id = 5 (返回中断号)cpu_claim = 0[执行中断处理程序...]cpu_complete = 1interrupt_req (拉低)cpu_complete = 0
4.3 Verilog 代码示例

PLIC侧(中断控制器):

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_idclaim_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_reqcpu_claim(读取中断ID)cpu_claim_id=16 (从PLIC1: GPIO7)读取细分中断号返回本地ID=8 (GPIO7)读取细分中断号返回本地ID=16 (I2C15)执行对应外设ISRcpu_completecpu_complete清除slave_int_req

七、多Hart(多核)支持

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

  • 使能寄存器(enable)
  • 优先级阈值(threshold)
  • Claim/Complete 寄存器
// 多核支持的关键:每个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

八、实际配置示例

// 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寄存器,直接用原始中断信号进行仲裁,会导致低优先级中断丢失:

// ❌ 错误做法:直接用原始信号仲裁
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,不直接参与仲裁
// ✅ 正确做法
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 claimT2: CPU响应bit3中断请求pending = 0b00101000读取pendingbit5=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. 实际使用需注意多核支持、寄存器配置、优先级阈值等关键配置项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值