在RISC-V架构的嵌入式系统设计中,PLIC(Platform-Level Interrupt Controller)作为平台级中断控制器,负责管理和分发各种外设中断。但随着系统复杂度提升,外设数量增加,原生PLIC的中断号数量往往不足以满足需求。这就是中断级联技术要解决的核心问题。
我最近在一个FPGA项目中遇到了这样的挑战:系统需要支持40多个外设中断,但使用的RISC-V核原生PLIC只支持16个中断号。通过设计主从PLIC级联结构,我们成功将中断处理能力扩展到了40个中断源。下面分享这个方案的具体实现和关键细节。
中断级联的核心思想是通过分层管理来扩展中断号容量。在我们的实现中:
这种架构的优势在于:
在我们的实现中,中断号分配如下:
| 中断类型 | 中断号范围 | 管理设备 |
|---|---|---|
| 主PLIC直接中断 | 1-8 | DMA、UART等高速设备 |
| 从PLIC1级联 | 9-24 | GPIO0-GPIO15 |
| 从PLIC2级联 | 25-40 | I2C0-I2C15 |
这种分配方案确保了:
从PLIC的核心功能是将16个中断源仲裁为1个聚合请求。关键设计点包括:
verilog复制module riscv_plic_slave (
input clk,
input rst_n,
// 外设中断源(16个)
input [15:0] slave_int_sources,
// 主PLIC接口
output cascade_int_req, // 聚合中断请求
output [3:0] cascade_int_id, // 最高优先级中断号
// CPU接口
input cpu_claim,
input cpu_complete,
input [3:0] cpu_hart_id
);
// 中断优先级配置
localparam [3:0] PRIO_NONE = 4'd0;
reg [3:0] slave_int_prio;
reg slave_int_req;
// Pending寄存器防止中断丢失
reg [15:0] 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
slave_int_prio <= i + 1;
slave_int_req <= 1'b1;
break;
end
end
end
end
// Pending寄存器更新逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pending <= 16'h0;
end else begin
pending <= (pending | slave_int_sources);
if (cpu_claim && (slave_int_prio != PRIO_NONE)) begin
pending[slave_int_prio - 1] <= 1'b0;
end
end
end
assign cascade_int_req = slave_int_req;
assign cascade_int_id = slave_int_prio;
endmodule
关键设计要点:
主PLIC需要管理直接中断和级联中断,其核心逻辑如下:
verilog复制module riscv_plic_master (
input clk,
input rst_n,
// 直接中断源(8个)
input [7:0] master_int_sources,
// 从PLIC接口
input slave1_cascade_req,
input [3:0] slave1_cascade_id,
input slave2_cascade_req,
input [3:0] slave2_cascade_id,
// CPU接口
output cpu_interrupt_req,
input cpu_claim,
output [5:0] cpu_claim_id,
input cpu_complete,
input [3:0] cpu_hart_id
);
// 中断号定义
localparam [5:0] INT_SLAVE1 = 6'd9;
localparam [5:0] INT_SLAVE2 = 6'd10;
localparam [5:0] INT_NONE = 6'd0;
reg [5:0] current_int_id;
reg master_int_req;
// 仲裁逻辑:直接中断优先于级联中断
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;
// 优先仲裁直接中断
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
// 其次处理级联中断
if (current_int_id == INT_NONE) begin
if (slave1_cascade_req) begin
current_int_id <= INT_SLAVE1 + slave1_cascade_id - 1;
master_int_req <= 1'b1;
end else if (slave2_cascade_req) begin
current_int_id <= INT_SLAVE2 + slave2_cascade_id - 1;
master_int_req <= 1'b1;
end
end
end
end
// CPU接口
assign cpu_interrupt_req = master_int_req;
assign cpu_claim_id = (cpu_claim) ? current_int_id : INT_NONE;
endmodule
设计特点:
中断触发阶段:
CPU响应阶段:
中断处理阶段:
c复制void plic_isr() {
// 1. 读取全局中断ID
uint32_t global_id = plic_claim();
// 2. 判断中断来源
if (global_id <= 8) {
// 处理主PLIC直接中断
handle_master_int(global_id);
}
else if (global_id <= 24) {
// 处理从PLIC1中断
uint32_t local_id = global_id - 9;
handle_gpio_int(local_id);
}
else if (global_id <= 40) {
// 处理从PLIC2中断
uint32_t local_id = global_id - 25;
handle_i2c_int(local_id);
}
// 3. 完成中断处理
plic_complete(global_id);
}
在初期设计中,我们遇到了低优先级中断丢失的问题。经过分析发现:
问题原因:
解决方案:
当系统需要支持多核时,需特别注意:
为确保中断响应及时,需要特别关注以下时序路径:
建议在关键路径插入寄存器打拍,确保满足时序要求。
基于项目经验,分享几个实用建议:
优先级配置原则:
调试技巧:
性能优化:
扩展性考虑:
这个级联设计在实际项目中表现出色,成功支持了包含40多个外设的复杂系统。通过合理的优先级配置和硬件优化,所有中断的响应时间都满足了系统要求。希望这个方案对面临类似挑战的开发者有所启发。