最近在用国产FPGA搭RISC-V软核SoC,跑RTOS做实时控制,发现中断嵌套处理不好,高优先级中断来了低优先级还没保存完现场,导致丢中断。看了VexRiscv的文档,支持自定义中断控制器,但不知道优先级抢占和嵌套深度怎么配置才合理。有没有做过类似方案的大佬,中断控制器和总线矩阵怎么配合才能保证实时性?
2026年了,FPGA工程师做RISC-V软核SoC,中断嵌套和优先级抢占怎么设计才不丢实时性?
6 个回答

先对齐一下场景吧:你是软核跑RTOS,那么中断延迟的主要矛盾其实不在CPU主频,而在「现场保存」和「总线抢占」这两段。VexRiscv本身没有硬件自动保存上下文,所以现场保存全靠软件,这就会导致你描述的问题——高优先级中断来了,低优先级还没保存完。我的建议是:PLIC按优先级分组,比如把实时性要求最高的几个中断源分到组0,其余分到组1和组2,组间可嵌套,组内不嵌套。嵌套深度别超过3层,超过3层后栈和保存时间都会失控。总线矩阵这边,关键中断对应的外设要挂在优先级高的从端口上,仲裁用固定优先级而不是轮询,这样中断服务程序访问外设时不会被别的master拖住。另外一个小技巧:把中断服务程序的现场保存放到IRQ入口统一做,别让每个中断源各自保存,这样能省不少周期。但最根本的,如果实时性要求特别高,建议把频繁触发的中断改成轮询+硬件标志位,软核上中断永远比轮询慢。你现在的RTOS是直接用现成的还是自己移植的?这个也会影响中断入口的写法。

往深处说一点:你丢中断的根因可能不在嵌套配置本身,而在「中断控制器如何把事件变成请求」这一层。PLIC的pending寄存器是电平触发的,如果高优先级中断在低优先级中断还在跑ISR时就到达,而且你的ISR没有主动清pending,那么当低优先级ISR退出、CPU重新使能全局中断时,高优先级会立刻被响应——这其实不会丢。真正会丢的是:低优先级ISR里访问外设寄存器时,总线矩阵因为仲裁延迟导致高优先级中断的请求信号被PLIC采样到但CPU还没开中断,等它开中断时请求已经过去了(比如边沿触发的中断源)。所以关键不是嵌套深度,而是「中断源触发方式」和「PLIC的请求锁存」。建议把所有关键中断源设为电平触发,并在PLIC里配好priority threshold——低于阈值的请求直接不产生中断,这样高优先级永远能打断低优先级ISR,而不是等它跑完。嵌套深度配3层足够,因为RTOS的临界区一般只关两层中断,第三层留给最紧急的时钟节拍。总线矩阵的仲裁优先级,建议把PLIC的访问端口和关键外设(比如定时器、串口)放在最高优先级,否则ISR里读外设状态会被DMA或其它master拖慢,这个延迟往往比嵌套本身更致命。另外补充一点:如果你的FPGA有硬核ARM,软核RISC-V就别做实时控制,直接用硬核跑RTOS更省心——软核适合做IO扩展和协议解析,实时控制交给硬核,这是工程上最常见的取舍。你现在用的国产FPGA是哪个系列的?有些器件的总线矩阵仲裁策略是固定的,不能改,那就要在PLIC侧做文章了。

先别急着上嵌套,先把「丢中断」的现场还原一下。我怀疑你现在的软核大概率没有硬件自动保存上下文,VexRiscv的默认配置里,mret之前那几步全靠软件来推,所以高优先级打断低优先级时,低优先级ISR里那几十条保存指令就是天然的盲区。想不丢,两个方向:要么在PLIC侧把关键中断源设成电平触发,靠pending寄存器的锁存特性兜底,这样就算CPU晚开中断,请求也不会凭空消失;要么就干脆把中断服务改成「统一入口+按优先级查表分发」,现场保存只做一次,别让每个中断源各存各的。总线矩阵那边,常见做法是把实时性要求高的外设挂在固定优先级的从端口上,别用轮询仲裁,不然高优先级中断的ISR访问外设时,被别的master插一脚,延迟就又上去了。嵌套深度我一般控制在两层,最多三层,超过三层栈和保存时间都不可控。你现在的RTOS是跑在机器态还是监管态?这个会影响PLIC threshold怎么配,可以细说下。

个人感觉你这个问题真正的坑不在PLIC配多少级优先级,而在「中断响应延迟」和「中断处理延迟」被混在一起算了。响应延迟是中断请求到CPU跳进ISR第一条指令的时间,这段主要看PLIC的仲裁和CPU的异常入口;处理延迟是ISR内部保存现场、访问外设、清pending这些动作消耗的时间。你说的高优先级来了低优先级还没保存完现场,其实是处理延迟把响应延迟拖长了,因为低优先级ISR在保存现场时全局中断是关着的,高优先级只能干等。解决思路有两个层次:第一层是硬件层面,VexRiscv支持自定义中断控制器,你可以自己写一个带硬件自动保存上下文的PLIC,把通用寄存器压栈的动作交给硬件完成,这样保存现场从几十个周期降到几个周期;但如果你不想动RTL,那就走第二层,软件层面把中断入口改成汇编写的统一分发器,所有中断源共用一个入口,入口里先判断当前优先级,再决定是直接嵌套还是挂起等待。总线矩阵这边,千万别忽略中断服务程序访问外设时的总线延迟,如果你的关键外设挂在低优先级从端口上,仲裁器又是轮询,那ISR里每次读写都可能被别的master插队,实时性就崩了。建议把关键外设的从端口优先级调到最高,仲裁固定优先级,同时把PLIC的priority threshold设成只放行高于某个阈值的中断,低优先级请求直接不产生中断信号,这样高优先级永远能打断低优,低优也不会反过来拖累高优。最后提醒一句,嵌套深度别贪,三层以内,因为RTOS的任务切换本身也要消耗栈空间,嵌套太深栈溢出比丢中断更难查。你用的是VexRiscv的哪个配置?带不带MMU?这个会影响异常入口的保存策略。

我先把场景分两类说,因为答案完全不同。第一类是中断频率不高、但每个中断都要做大量外设访问的,比如电机控制里每个PWM周期读一次编码器再算一次PID;第二类是中断频率很高、ISR本身很短但要求不丢事件的,比如高速数据采集。你描述的问题其实更像第一类:低优先级ISR还没保存完现场,高优先级就来了,这说明你现在的保存现场动作太慢,而且全局中断关得太久。针对这类场景,我的建议是别在PLIC里做文章,先把中断入口改成汇编统一分发器。VexRiscv的默认行为是每个中断源各自跳转,你改成所有中断共用一个入口,入口里只做最小保存——比如只保存ra、sp、mstatus这几个必要寄存器,然后立即开中断。这样高优先级中断到来时,低优先级只关了十几个周期的全局中断,远小于原来几十上百个周期的保存时间。嵌套深度也别死磕三层,关键是每层保存的寄存器数量要少,如果每层只保存三五个寄存器,五层嵌套也没问题;如果每层都保存二十个寄存器,两层就会爆栈。另外,总线矩阵那边注意一点:你的ISR里访问外设时,如果外设挂在轮询仲裁的从端口上,高优先级中断即使抢占了CPU,访问外设时还是可能被别的master插队,所以关键外设一定要挂在固定优先级的从端口上。你现在的RTOS是跑在哪个核上的?如果是单核,总线矩阵的master只有CPU一个,那问题就不在仲裁,而在外设本身的响应速度了。

说个可能被忽略的点:你丢中断可能根本不是嵌套配置的问题,而是PLIC的pending寄存器被ISR里的外设访问时序搞丢了。比如低优先级ISR里读了一个FIFO寄存器,读操作本身会触发外设清掉请求信号,但如果你读完之后没有确认PLIC的claim寄存器已经把对应的pending位清了,那么高优先级中断到来时,PLIC可能还在处理旧的claim,导致新的请求被覆盖。这种情况在VexRiscv的默认PLIC配置里挺常见的,因为它的pending逻辑比较简单。我建议你在ISR末尾加一个屏障操作:先写claim寄存器完成中断响应,再读一次pending寄存器确认没有新请求,然后再mret。这个操作只多花十几个周期,但能避免很多看似莫名其妙的丢中断。另外,嵌套深度我一般建议不超过两层,不是怕栈溢出,而是怕RTOS的上下文切换逻辑在嵌套场景下出错。你如果跑的是FreeRTOS,它的port层默认不支持嵌套中断,需要自己改port.c里的保存现场部分,这个改动很容易引入bug。你现在的RTOS是哪个?如果是自己写的调度器,那嵌套深度更要保守一点。
发表回答
登录后可在本页底部提交回答