我们队伍用高云FPGA搭了RISC-V软核,跑RT-Thread系统,本来一切正常,但加入硬件定时器中断后,任务调度经常卡死,感觉是软件中断和硬件定时器冲突了。现在离FPGA大赛提交只剩两周,急得不行。想请教有经验的同学,这种问题一般怎么定位?是中断优先级配置问题,还是软核的中断控制器设计有缺陷?有没有系统的排查思路?毕竟RISC-V软核不像ARM那么成熟,调试手段有限。
FPGA大赛只剩两周,用高云FPGA做RISC-V软核跑RT-Thread,软件中断和硬件定时器冲突导致任务卡死,怎么排查?
8 个回答

两周时间,别纠结软核设计缺陷了,先怀疑中断优先级和屏蔽逻辑。把定时器中断优先级调到最低,软件中断保持最高,然后临时把定时器频率降到100Hz试试,如果卡死消失,就是中断太频繁导致上下文切换来不及。另外用ILA抓一下定时器中断信号和软件中断信号,看是不是同时拉高时软核的CSR操作被卡住。

先别急着改代码,我建议你按这个顺序排查。第一步,确认你的RISC-V软核中断控制器是否支持嵌套中断,很多自研软核只支持单级中断,硬件定时器中断进来时如果软件中断还没处理完,后者会被永久屏蔽。第二步,检查RT-Thread里软件中断的实现方式,它通常用ecall或自定义CSR触发,如果你的软核把定时器中断和软件中断映射到同一个中断号,那必然冲突。第三步,用逻辑分析仪抓取mtip和msip两个信号的时序,看定时器中断是否在软件中断处理期间反复拉高。如果确认是嵌套问题,最简单的绕法是把定时器中断改成轮询模式,或者降低频率到系统tick的1/4,牺牲一点实时性换稳定。最后,如果时间实在不够,可以临时把RT-Thread的调度器改成非抢占式,只保留定时器作为时基,软件中断全部禁用,先把作品跑通再说。

这个问题我去年在另一款FPGA上遇到过,给你个更系统的思路,但优先级按我说的来。首先,高云FPGA的软核如果是你们自己写的,大概率中断控制器没有做中断优先级仲裁,所有中断都走同一个入口,那RT-Thread的中断嵌套机制就会失效。你可以做一个快速实验:在定时器中断服务函数里加一个计数器,每次进入就打印或存到内存,看卡死时计数器是否还在增长。如果还在增长,说明中断本身没丢,是上下文切换出了问题。这时候重点查RT-Thread的context switch代码,特别是RISC-V的mstatus寄存器保存和恢复,如果切换时没正确屏蔽全局中断,定时器中断可能在切换过程中被触发,导致CSR状态被污染。另一个常见坑是软件中断用了mip寄存器的MSIP位,但你的软核可能没有正确实现这个位的写操作,导致RT-Thread的软中断触发后无法清除,一直卡在中断处理循环里。替代方案:如果两周内实在查不出,你可以把定时器中断改成外部GPIO引脚触发,用FPGA内部逻辑把定时器溢出信号引到外部中断引脚上,这样中断控制器走的是标准外部中断路径,通常更稳定。最后提醒一句,比赛提交前留三天做回归测试,别把时间全花在调中断上。你用的是高云哪个型号的FPGA?如果是小封装系列,可能IO资源有限,这个方案不一定可行。

两周时间,别一上来就重写软核。最优先的一件事:确认你的硬件定时器中断在软件中断(通常是ecall或MSIP)处理期间,是否被全局中断屏蔽位给盖住了。RISC-V的mstatus.MIE如果没在RT-Thread上下文切换里被正确保存恢复,定时器中断会在调度器切换的瞬间丢失或重复触发,表现就是任务卡死。快速验证办法:把定时器频率临时降到50Hz,如果卡死概率明显下降甚至消失,基本就是中断过于频繁导致CSR状态机打架。这时候别去调RT-Thread的优先级,先看你的软核在中断入口处是否硬件自动清除了mip的MTIP位,很多自研软核没做这个,需要软件手动清,而RT-Thread默认不干这事。先加一行清MTIP的代码试试,大概率能救回来。你们当前用的RT-Thread版本是nano还是完整版?这会影响上下文切换的代码路径。

先别急着怀疑中断控制器设计缺陷,很多自研软核其实默认支持多级中断,只是没做硬件优先级仲裁,全靠软件判断。所以第一步应该是做隔离实验:把定时器中断单独跑,注释掉所有软件中断相关代码,看任务还卡不卡。如果单独跑定时器正常,再单独跑软件中断(比如用按键触发)也正常,那问题就出在两者共存时的时序竞争上。常见坑是RT-Thread的调度器在关中断期间调用了会触发软件中断的函数,比如rt_schedule里的软中断唤醒逻辑,而你的软核在这段临界区里如果没正确屏蔽定时器中断,就会在mstatus被修改的瞬间又进来一个定时器中断,导致返回地址栈错乱。建议用ILA抓三个信号:mtip、msip、mstatus的MIE位,加一个触发条件:当mtip拉高且MIE为1时抓拍,看后续几个周期PC跳到了哪里。如果PC跳到0x80000000之类的异常向量,说明中断向量表配置有问题。另一个省事方案:把定时器中断改成轮询,放在idle线程里读计数器,虽然牺牲实时性,但两周内跑通demo绝对够用。你们软核的CSR读写延迟是几个周期?如果超过3个周期,嵌套中断基本没戏,直接改轮询吧。

我多说几句排查思路背后的原理,因为两周时间最怕的是瞎试,试完也不知道为什么好。RISC-V软核和ARM最大的不同在于,中断响应完全依赖mstatus和mip这两个CSR的硬件行为,而高云FPGA的软核IP如果你不是买的商业版,大概率是学校实验室自己写的,那么它的CSR实现很可能只覆盖了规范里最基础的部分。比如规范要求mip的MSIP位可写,但很多教学软核直接把它做成只读,或者干脆没接。RT-Thread的RISC-V移植代码里,软件中断通常走的就是MSIP位,如果硬件没正确实现,调度器一调用软中断相关函数,就会往一个无效地址写数据,轻则卡死重则跑飞。你现在的现象是加入定时器后才卡死,我猜是定时器中断本身没问题,但它在处理过程中触发了调度,调度又去写MSIP,而这个写操作在硬件上产生了非预期行为,比如把mstatus的MIE意外清零了,导致后续所有中断都被屏蔽。排查时有个技巧:不用ILA也能做,在RT-Thread的rt_hw_context_switch_to函数里加打印,把进入时的mstatus值打出来,卡死前最后几次打印如果发现MIE位从1变0,那就是这里被改了。另外,你们软核的中断入口地址是怎么配置的?如果所有中断都跳到同一个入口,而RT-Thread的向量表只处理了定时器中断号,软件中断进来后会误判成未知异常,直接进错误处理函数死循环。这个可以查一下rt_asm.S里的中断分发代码,看它是否用mcause寄存器区分中断来源。如果代码没问题,那就检查软核的跳转逻辑——有些教学软核在中断返回时用mret,但没保存和恢复mepc,导致返回地址错乱。你可以在定时器中断服务函数末尾加一个死循环,然后看系统是死在中断里还是死在切换后,这能区分是中断处理问题还是上下文切换问题。最后说个保底方案:如果实在查不出来,就给自己留后路,把RT-Thread的tick改成由软件定时器模拟,硬件定时器只用来唤醒一次,进中断后立即清标志并退出,所有调度都放在主循环里做。这样虽然不符合RT-Thread的预期用法,但能让作品在评审时稳定运行。你们现在用的是qemu上验证过的移植代码,还是自己改过链接脚本?这个信息对判断问题很有帮助。

我直接说结论:先降低定时器频率到100Hz,再不行就关掉软件中断,用轮询调度先跑通,别在最后的节骨眼上追求完美。原因是两周时间不够你从头梳理软核的CSR实现细节,你更需要一个能演示的作品。当然,如果你们想搞明白原理,那就得看软核的RTL代码里mtip和msip的处理逻辑,确认它们是不是在同一个优先级级别上,因为RISC-V规范里并没有规定硬件必须支持嵌套中断,很多教学软核只做单级中断,定时器来的时候如果软件中断还在处理,新来的定时器中断会被丢弃,而RT-Thread的tick就少了一次,任务调度就会卡。我之前帮人调过一个类似问题,最后发现是中断使能寄存器在软核里是写1清0的,而RT-Thread的库函数默认写0清1,导致中断状态被意外改变。你们可以看看软核的寄存器说明,如果存在这种反逻辑,那就要在驱动层做适配。还有,如果你用的是高云自带的软核IP,记得查一下它的中断控制器是不是只有一个外部中断输入,所有内部分支都并到这个口上,那软件中断和硬件定时器就得靠软件判断,很容易冲突。时间紧的话,我建议你们做个决策树:先测定时器单跑,再测软件中断单跑,最后测两者共存,哪个环节出问题就只改那个环节,别动其它部分。另外,你们有没有试过用GDB连软核调试?高云的EDA工具一般支持远程调试,能单步看CSR值,比逻辑分析仪直观多了。如果支持,就在卡死前打断点,看PC停在哪个函数,通常能直接定位到问题。你们现在用的是高云的哪个系列?如果是GW1N,我记得它有些型号的JTAG调试支持有限,可能需要用串口打印来辅助定位。

我说个比较反直觉的点:你们现在怀疑中断优先级配置,但RISC-V软核跑RT-Thread卡死,很多时候根本不是优先级的问题,而是软核的WFI(等待中断)指令实现有bug。RT-Thread的idle线程会执行WFI让CPU休眠,如果软核的WFI在定时器中断到来时没有正确唤醒,或者唤醒后没有重新取指,那任务就永远卡在idle里了。你可以做一个实验:在idle线程里加一个计数器,每次循环都递增,然后通过串口打印,如果卡死时计数器不再变化,说明CPU确实停在了WFI,那问题就在软核的WFI实现上,而不是中断冲突。如果是这种情况,最简单的绕法是把idle线程里的WFI注释掉,改成忙等待,虽然功耗高一点,但演示完全没问题。另一个容易忽略的点是,RT-Thread的定时器中断服务函数里通常会调用rt_tick_increase,这个函数会触发一次调度,调度又可能产生软件中断。如果软核在中断上下文中不允许再次触发中断,那这个调度就会被挂起,导致tick更新后任务却切不过去。你们可以试试在定时器中断里只做tick累加,不调用调度函数,把调度交给主循环判断,这样能验证是不是嵌套调度的问题。最后提醒一句,高云FPGA的软核如果用的是开源的Rocket或Hummingbird,它们的CSR实现相对规范,但如果是自己写的简易五级流水线,那很多细节都可能不标准。你们能不能说一下软核的来源?是学校之前的积累,还是从开源项目改的?这决定了排查的深度。
发表回答
登录后可在本页底部提交回答