项目里用Vivado搭了RISC-V软核,跑Zephyr RTOS,结果外部中断一直进不去,PLIC寄存器读出来全是0,怀疑是地址映射没对,但核心里看CSR又正常。有没有做过类似移植的兄弟?中断控制器初始化顺序、设备树配置、还有多级中断嵌套这块,有没有现成的排错思路或者调试脚本可以参考?
2026年了,FPGA工程师做RISC-V软核,用Vivado跑Zephyr系统,中断控制器PLIC配置总出错,怎么排查?
6 个回答

先别急着怀疑设备树,PLIC读出来全是0,九成是总线根本没访问到。Vivado里核对一下地址译码器的地址范围,再确认AXI互联里有没有把PLIC的slave口接到CPU的master口上,光看CSR正常没用,那走的是另一条路。

兄弟,我去年也踩过这个坑,说下我的排查顺序吧。第一步,先用ILA抓CPU到PLIC的AXI写通道,重点看AWADDR和WSTRB,确认你写的地址确实落在了PLIC的基地址区间内,别被地址偏移搞晕,PLIC的priority、pending、enable、claim这些寄存器偏移是固定的,但基地址得和设备树里的reg属性完全一致,差一个字节都白搭。第二步,检查Zephyr的驱动初始化顺序,一般要先配好PLIC的enable寄存器,再开全局中断,最后才去配置具体中断源,顺序反了会导致pending位一直被清掉。第三步,看看你的软核是否支持PLIC的claim机制,有些RISC-V实现里claim读操作会返回当前最高优先级中断号,如果硬件不支持这个特性,Zephyr驱动就会卡在读claim上。最后,确认一下设备树里interrupt-cells是不是设成了1,而且interrupt-parent要指向PLIC节点,这两个写错最常见。另外,你用的Vivado版本和Zephyr的RISC-V平台支持版本要匹配,老版本Zephyr对某些PLIC寄存器偏移的定义有bug,建议直接拉最新的LTS分支试试。

这个问题的坑比较深,我给你拆成三个层面讲。第一层是硬件连接,你读PLIC全是0,最直接的原因就是AXI地址译码没把CPU的访问路由到PLIC的slave接口上,Vivado里生成block design时,有时候会自动断开未连接的接口,你检查一下Address Editor里PLIC的地址段有没有被分配,没分配的话默认就是0x0,读出来自然全是0。第二层是设备树,Zephyr里RISC-V的PLIC节点一般长这样:compatible = "sifive,plic-1.0.0",reg属性要精确到字节偏移,而且interrupt-controller和interrupt-cells这两个属性缺一不可,很多移植失败都是因为interrupt-cells设成了2,但驱动只处理1的情况。第三层是驱动行为,Zephyr的PLIC驱动在初始化时会读取base address并计算寄存器偏移,如果你的软核实现了PLIC但没实现标准的中断优先级仲裁逻辑,驱动可能默认只支持有限的中断源数量,比如只支持31个,你如果用了32号中断就会出问题。给你个小脚本思路:写个简单的C程序,直接往PLIC的enable寄存器写0xFFFFFFFF,再读回来,对比是否一致,能快速判断总线访问是否正常。另外,多级中断嵌套这块,Zephyr默认不支持嵌套,你需要改Kconfig里的CONFIG_IRQ_OFFLOAD和中断优先级配置,如果硬件不支持抢占式中断控制器,嵌套就别想了。最后提一句,如果你用的是Vivado自带的RISC-V IP,比如MicroBlaze V,它的PLIC实现可能有特殊性,建议去查一下该IP的官方文档里PLIC寄存器的地址偏移定义,别拿标准的SiFive PLIC文档硬套。你的软核是自研的还是用现成IP的?这个信息对后续排查很关键。

先别急着查Zephyr的驱动,PLIC读全是0,九成是AXI地址译码没把访问送过去。你在Vivado里打开Address Editor,确认PLIC的地址段有没有被分配,没分配的话默认就是0x0,读出来当然全是0。然后看设备树里的reg属性,基地址和大小必须和Address Editor里完全一致,差一个字节都不行。核心里CSR正常不代表总线访问正常,CSR走的是另一条通路。先把这两处对上,再谈中断使能那些事。你用的是哪个软核?如果是VexRiscv,它的PLIC基地址默认是0x0C000000,设备树里写对了吗?

个人感觉这问题一半出在地址映射,一半出在初始化顺序上,但排查顺序有讲究。先别管中断嵌套,那属于后话。第一步用ILA抓CPU到PLIC的AXI写通道,重点看AWADDR和WSTRB,确认你写的地址确实落在PLIC的基地址区间内,而且是32位写。经常有人把基地址写对了,但偏移搞错,比如priority寄存器偏移从0x0开始,pending从0x1000开始,enable从0x2000开始,这些在RISC-V PLIC规范里是固定的,但不同软核实现可能略有差异。第二步查设备树,Zephyr的PLIC节点一般长这样:compatible = "sifive,plic-1.0.0",reg属性要精确到字节偏移,interrupt-controller和interrupt-cells缺一不可,interrupt-cells设成1就行,设成2驱动会解析错。第三步看驱动初始化顺序,Zephyr里要先配PLIC的enable寄存器,再开全局中断,最后才配置具体中断源,顺序反了pending位会被清掉。另外提醒一句,有些软核的PLIC不实现claim机制,读claim会返回0,Zephyr驱动会卡在那里,你得确认硬件支持。你的软核是自研的还是开源的?如果方便,把设备树里PLIC节点贴出来看看。

这个问题我去年在自家板子上折腾了两周,最后发现根子不在Zephyr,而在软核的PLIC实现和驱动版本不匹配。你的场景我估计是自研软核或者魔改过开源核,因为Vivado自带的MicroBlaze根本不走PLIC,RISC-V软核里VexRiscv和PULPissimo的PLIC实现细节有差异,Zephyr驱动默认按SiFive的PLIC规范写,如果你的硬件没完全遵守,读全是0就很正常。我的排查路径是这样:第一,用ILA抓CPU发出的AXI读请求,看地址是否命中PLIC的基地址段,同时看读返回的RVALID和RDATA,如果地址对了但返回0,说明PLIC内部逻辑有问题,比如寄存器没被正确映射到总线上。第二,检查PLIC的全局使能位,RISC-V的PLIC有个全局中断使能寄存器(通常叫GLOBAL_ENABLE或类似),很多实现里这个寄存器默认是关闭的,你得在Zephyr启动早期把它打开,驱动初始化不一定做这事。第三,看中断源映射,外部中断号在设备树里通过interrupts属性指定,比如中断源1对应PLIC的source 1,但有些软核把中断号做了偏移,比如从16开始,Zephyr驱动不认就全读0。第四,确认Zephyr版本,老版本(比如3.2之前)的PLIC驱动对非标准实现兼容性差,新版本改进了不少,你试试升级到最新LTS版本,或者反过来,如果你用的是很新的Zephyr但软核是老IP,驱动可能用了新特性而硬件不支持。多级中断嵌套这块,Zephyr默认不支持PLIC的优先级抢占,除非你手动开CONFIG_RISCV_PLIC_PREEMPT,但那要求硬件支持claim和complete的嵌套语义,很多软核实现是简化版,不支持就别开。最后建议你写个最小测试程序,不跑Zephyr,直接裸机往PLIC的priority寄存器写0xFF,再读回来,如果读出来还是0,那就是硬件或地址映射问题,跟Zephyr无关;如果读出来正常,那再回头查设备树和驱动。裸机验证能帮你把问题域砍掉一半,这招比反复改设备树高效得多。你现在的软核是32位还是64位?AXI数据宽度是多少?这个会影响PLIC寄存器访问方式,有些实现要求64位读写。
发表回答
登录后可在本页底部提交回答