FPGA到HPS中断通路实战:基于Agilex 5 GHRD的工程修改指南

FPGA到HPS中断通路实战:基于Agilex 5 GHRD的工程修改指南 上一篇我把FPGA到HPS的中断链路掰开揉碎讲了一遍结尾说下一篇动手改工程。今天填坑目标很明确在一套Agilex 5 SoC FPGA的GHRDGolden Hardware Reference Design工程里把FPGA侧一个按键的脉冲中断真正送进HPS的中断控制器让Linux内核能通过/proc/interrupts看到它、响应它。这篇文章是系列第二篇重点放在Quartus工程侧软件驱动部分后面单独开一篇。这篇适合谁手上已经有Agilex 5开发板、想用HPS跑Linux、又需要在FPGA侧加自定义中断源的工程师。如果你还没有把Quartus Prime Pro装好或者打开工程时遇到“not compatible with the device”这类报错先花点时间解决环境问题再继续。我默认你已经能跑通官方Hello World级别的GHRD工程也知道Platform Designer以前叫Qsys的基本操作。1. 为什么非改GHRD不可默认工程的“中断盲区”1.1 GHRD默认给了我们什么GHRD是Intel官方提供的硬件参考设计它不是给你做产品用的而是为了让你拿到板子之后能以最短路径把HPS的Linux跑起来。默认工程里已经帮你配好了HPS侧的时钟、DDR控制器、SD/MMC、UART、USB等基本外设FPGA侧连接HPS的AXI桥轻量桥和全功能桥一些演示用的GPIO、JTAG、I2C IP核FPGA和HPS之间的复位、时钟同步逻辑换句话说你拿到板子编译一次烧进去Linux启动串口能进shell这套流程官方README就能搞定。GHRD就像一个精装修的样板间能住人但你想在墙上加一个开关就得自己动电钻了。1.2 默认工程里中断通路是断的问题就在这里GHRD虽然把AXI总线桥拉通了但FPGA侧到HPS的中断信号默认并没有给你连好。具体到Agilex 5这代器件HPS的GIC通用中断控制器里预留了多路FPGA中断输入信号名一般叫fpga_interrupt这些信号是FPGA逻辑和HPS处理器之间最直接的事件通知通道。但是你在GHRD里搜一下很可能发现这些fpga_interrupt信号根本没从HPS组件里引出来或者引出来了但没有接到任何FPGA侧IP核上。就算接了中断号、触发方式也没人替你确认。很多人在这一步会犯一个错误以为在Linux里写个request_irq驱动指定一个中断号FPGA那边只要拉电平中断就自动上来了。真相是硬件通路没打通驱动写得再漂亮也白搭。内核日志里最常见的情况就是genirq: No irq handler for vector或者干脆静默不响应你在/proc/interrupts里根本看不到这个中断向量的计数变化。1.3 动工之前先备份改坏了能反悔GHRD工程比较大包含的Platform Designer子系统动辄几十个IP核。我强烈建议你在动手之前把原始工程整体复制一份。别只复制.qsys或者.ip文件整个目录打包因为Platform Designer在重新生成时可能会连带更新多个文件。我自己习惯的做法是把官方GHRD工程完整解压到一个工作目录编译一遍确认基线可用记录下编译通过时的Quartus版本和IP版本再做任何修改前用git初始化仓库或者直接复制一个备份目录之所以强调版本是因为Agilex 5只能用Quartus Prime Pro而Pro版本迭代非常快不同小版本之间IP核的生成文件可能不兼容。你从网上下载的GHRD工程如果用最新版Quartus打开第一次就会弹出一大堆IP升级确认框这一步别偷懒全部选升级否则后续编译会报各种莫名其妙的错误。2. 动手前必须想明白的三件事中断线、触发方式、GIC映射2.1 FPGA到HPS的中断物理路径在没有动工程之前先把这块的概念搞明白。FPGA侧任何IP核要通知HPS“我这里有事情发生了”信号路径是这样的FPGA侧IP核的中断输出引脚比如PIO核的irq信号→ FPGA顶层的一个wire信号 → HPS组件上的fpga_interrupt输入引脚 → HPS内部的GIC → ARM核心的IRQ异常。这里的fpga_interrupt通常是一组总线比如fpga_interrupt[0]到fpga_interrupt[7]每一路都能独立触发一个中断。不同路之间互不干扰所以你可以把不同外设的中断分配到不同的线上。你可以把这条路理解成小区门禁FPGA外设是访客HPS是住户fpga_interrupt就是单元门上的门铃按钮。访客按了一个按钮住户家里的门铃响。但前提是楼盘交付时这个“门铃按钮”和“室内门铃”之间的线得真的接上GHRD相当于精装房没给你接这个线——你得住进去自己拉线。2.2 边沿触发还是电平触发这一步真不是随便选的硬件上配置中断触发方式Linux设备树里也要写对应的触发类型两边必须一致。很多人在这一步踩坑。先看触发方式的区别触发方式信号条件适用场景风险电平触发高/低信号保持高/低电平状态类事件如温度过高报警如果驱动没有及时清中断会反复触发导致中断风暴上升沿触发信号从低跳变到高边沿事件如按键按下产生高脉冲如果脉冲太窄GIC可能采样不到下降沿触发信号从高跳变到低边沿事件如按键拉低同上对实际工程来说我的建议是如果中断源FPGA侧是一个自定义IP核尽量让IP输出一个单脉冲然后选择上升沿触发。脉冲宽度最好超过一个HPS时钟周期工程上留3倍余量比较稳。如果直接接物理按键按键按下接地还是接VCC决定了选择上升沿还是下降沿。比如按键一端接GND、另一端接FPGA引脚按键按下变成低电平那你应该用下降沿触发。电平触发也可以用但要求驱动里能快速清掉中断源否则CPU会被中断反复打断你甚至能在top命令里看到ksoftirqd占满CPU。还说一个新手最容易忽略的PIO核的边沿检测寄存器是FPGA侧的逻辑它检测到边沿后产生的irq信号如果保持一段时间这个“保持”的长度也受PIO核内部寄存器配置影响。用PIO核时你生成的中断信号是一个脉冲这个脉冲送给HPS的GIC后GIC会锁存不需要太担心脉冲宽度不够的问题。真正需要担心的是极性配反了那样你再怎么按按键中断都不来。2.3 设备树中断号和硬件实际连线的对应关系你在Platform Designer里把中断信号接到了fpga_interrupt[0]这个[0]对应Linux设备树里具体哪一个中断号这里有一条绕不开的换算逻辑。ARM GIC在设备树里的中断描述一般是三个cellinterrupts 0 SPI_INDEX IRQ_TYPE其中第一个cell填0表示SPIShared Peripheral Interrupt共享外设中断第二个cell是SPI索引号第三个cell是触发类型4表示高电平触发1表示上升沿触发关键的坑在这里有些手册给出的中断表里写的是“GIC中断号”比如写着184而设备树里要填的是SPI索引号。两者之间的关系是设备树SPI索引 GIC中断号 - 32也就是说如果手册里写某个中断对应GIC中断号184设备树里应该填152。如果你直接写184内核会认为你访问的是另一个中断驱动自然不会被触发。具体到Agilex 5每一路fpga_interrupt对应哪一号SPI不同型号可能不一样必须查对应型号的HPS技术参考手册TRM里的中断分配表。这个表一般在“Interrupts”章节是一张很大的表里面有每个中断源的名字和对应的SPI编号。用fpga_interrupt关键词在PDF里搜一下就能定位。我见过太多人卡在这一步硬件连线明明对了Platform Designer里也看到信号了最后就死在设备树中断号填错上。所以这一步咱们在改硬件的时候就要同步记录下来你接的是fpga_interrupt[几]软件那边要查表换算成SPI号这个对应关系写进你的设计文档里别只存在脑子里。3. 实际操作在Platform Designer里把中断通路拉通3.1 打开GHRD工程找到Platform Designer子系统先打开Quartus Prime Pro加载GHRD工程。工程打开之后在Project Navigator里找到以.ip结尾的Platform Designer文件双击打开。GHRD一般用的是soc_system.ip这个命名双击后大概要等几秒到几十秒取决于工程复杂度和电脑性能。打开Platform Designer之后你会看到非常多的IP核实例。别慌这些东西大部分你不用动。你只需要关注两样东西HPS组件实例和你要添加的PIO核。在System Contents列表里找到HPS相关的实例比如hps_0。点中它在右侧的接口列表里找到fpga_interrupts相关的信号。如果这一组信号是空的说明中断线没有引出。3.2 添加一个PIO核用来模拟FPGA侧中断源我们要在FPGA侧造一个中断源。最方便的办法就是添加一个Parallel I/OPIOIP核把它配置成输入模式再接一个按键这样按键按下去PIO检测到边沿就能吐出一个中断。在Platform Designer左边的IP Catalog里搜索“PIO”或者“Parallel I/O”找到“Parallel I/O (PIO) Intel FPGA IP”双击添加。添加之后双击这个PIO核进入配置界面关键参数按下面这张表来设置参数项推荐值说明Width1我们只需要1位输入足够DirectionInput按键输入方向Edge capture register勾选让PIO能检测边沿并锁存Enable IRQ勾选把PIO的中断输出引出来IRQ type根据按键电路选Rising/Falling按键按下是高电平就选Rising是低电平就选Falling如果你接的按键默认通过上拉电阻接高电平按下时接地那就选Falling Edge。这一步就是我前面说的“极性”问题选错的话后续查起来很耗时间。3.3 修改PIO生成的中断信号名并连接到HPS添加完PIO核之后PIO核上会多出一个输出信号通常叫pio_0.irq。现在需要把这个信号接到HPS的fpga_interrupt上。在Platform Designer的Connections视图里找到pio_0的irq引脚按住鼠标拖到hps_0的fpga_interrupt输入端选择其中一路比如fpga_interrupt[0]。连线完成之后检查一下HPS组件里FPGA-to-HPS桥是否打开。因为你在Linux里读PIO寄存器、控制按键中断的清除都要通过AXI桥访问FPGA侧寄存器。如果只连了中断线没开桥Linux驱动连PIO寄存器都访问不到中断来了也没法查询或清除。连线完成后在System Contents里还要给PIO分配一个基地址。一般用一个包含HPS地址映射的窗口点击菜单Assign Base Address系统会自动分配。记住这个地址后面写Linux驱动时要在设备树或用ioremap访问这个地址。3.4 升级IP、重新生成Platform Designer输出如果你的Quartus版本和GHRD工程原先使用版本不一致打开时可能已经提示升级了。在修改完连接关系之后还要再检查一次有没有IP核版本过旧。点菜单Project → Upgrade IP Components把列出的IP全部升级。然后是最关键的一步重新生成Platform Designer的输出文件。在Platform Designer窗口右下角点Generate会弹出生成配置窗口一般默认选Synthesis综合就够用不需要勾选Simulation。点击Generate后Quartus会自动根据你改过的连接关系生成所有HDL文件和IP核例化代码。这一步的等待时间取决于工程里IP的数量一般几分钟。中间如果报错九成是因为IP版本不匹配或者某个接口没连好按报错信息回去检查连接即可。生成完成之后关闭Platform DesignerQuartus主界面会弹出一个对话框问你是否要把新生成的工程文件更新到当前Quartus工程里。一定要选是否则刚才生成的底层文件不会被Quartus重新编译。4. 顶层的那些“看不见”的细节引脚分配、综合与上板前的几道关卡4.1 别让PIO的输入信号悬空Platform Designer生成之后新添加的PIO核会多出几个顶层端口比如pio_0_external_connection_export。这个端口就是FPGA侧按键接进来的地方。很多第一次做FPGASoC的工程师在这里会卡住Platform Designer关掉之后Quartus顶层模块文件并没有自动把pio_0_external_connection_export连到FPGA物理引脚上。你需要手动在顶层Verilog/VHDL文件里找到这个端口名把它引到顶层端口然后在Pin Planner里分配到具体的FPGA引脚。如果你不做这一步综合工具会给你报一堆警告说某些输入端口悬空。如果悬空的是按键输入那中断自然永远触发不了。更麻烦的是如果恰好分配到了某个内部弱上拉或下拉的引脚你可能莫名其妙看到中断一直触发或者一直不触发。所以正确顺序是打开工程顶层HDL文件把pio_0_external_connection_export声明并例化到顶层端口打开Assignments → Pin Planner在All Pins列表里找到这个端口在Location一列填上开发板按键对应的FPGA引脚编号看板卡原理图标注为KEY、BTN之类的引脚保存引脚分配重新编译4.2 编译时最容易翻车的三个报错修改完Platform Designer后第一次全编译大概率不会一次通过。我把自己踩过的坑列出来你遇到了可以先对号入座。第一类IP核版本报错。打开工程时忘了升级IP老版本IP生成的文件和新版Quartus不兼容综合时给你一长串错误。解决办法是回Quartus主界面重新执行Project → Upgrade IP Components然后重新生成Platform Designer。第二类端口连接报错。顶层文件里手动加的端口名和Platform Designer生成的端口名不一致。Quartus对大小写很敏感比如你写Pio_0_External_Connection_Export但实际生成的是pio_0_external_connection_export那综合一定报错。遇到这种问题去Platform Designer生成的HDL文件里搜一下确切端口名复制粘贴到顶层别手敲。第三类时序不收敛。加了新逻辑后Fitter报告里出现负slack的路径。如果是低速测试用且在HPS和FPGA之间的跨时钟域路径上可以先尝试约束宽松一点或者把PIO核的时钟接到一个稳定的时钟源上。GHRD工程里FPGA侧有现成的时钟别自己新搞一个PLL省很多事。4.3 全编译还是增量编译我的建议GHRD工程本身就比较大全编译一次可能得半小时起步。有人为了省时间用增量编译。但修改了Platform Designer连接关系之后底层IP核重新生成增量编译很容易出现“旧文件引用新文件”的混乱反而更慢。我建议第一次修改后老老实实全编译。Processing → Start Compilation然后去倒杯咖啡。编译通过之后后续再做小改动再考虑增量编译。编译完成后在Compilation Report → Fitter → Pin Out File里检查一下你分配的按键引脚状态确认Final Pin Assignment列里是你预期的引脚而不是被Fitter自动分配出去的其他引脚。4.4 上板前先抓一下波形别直接盲调Linux很多人改完硬件就着急启动Linux结果中断不响应来回折腾。我的习惯是先花十分钟做一次静态验证。Quartus自带Signal Tap逻辑分析仪这就是不少人在论坛里问的“Quartus怎么抓数”的答案。把fpga_interrupt信号和PIO核的输入信号添加进Signal Tap的观察列表重新编译一次然后上电下载.sof文件打开Signal Tap一边按按键一边观察信号。这一步能过滤掉大量硬件低级问题如果按键输入信号都不翻转说明引脚分配错了或者按键电路有问题如果PIO输入翻转了但irq没拉高/没产生脉冲说明PIO配置极性反了如果PIO的irq有脉冲但hps的fpga_interrupt信号没反应说明Platform Designer里连接断掉了在Signal Tap里看到中断信号正常翻转之后再进Linux调试会省下很多冤枉时间。5. 编译产物与软件侧的接口约定别让昨天的决定坑死明天的驱动5.1 生成并部署新的.rbf文件全编译通过之后Quartus会生成一个.sof文件这个是JTAG调试用的。但你的目标是在HPS上启动LinuxLinux端的BootloaderU-Boot在启动时会加载FPGA配置文件这个文件格式是.rbf不是.sof。所以还需要把.sof转换成.rbf。Quartus里操作路径File → Convert Programming Files在窗口里选择Programming file type为Raw Binary File (.rbf)然后在Input files to convert区域添加.sof文件最后点Generate。生成之后把这个新的.rbf文件拷贝到SD卡启动分区的对应位置覆盖原有的FPGA配置文件。不同的开发板BSP里文件名可能不同最常见的是叫soc_system.rbf也有的叫agilex5.rbf你按原文件名覆盖就对了。这一步千万注意如果你改了FPGA硬件却没更新SD卡里的.rbfLinux启动后还是会加载老的FPGA配置你在Quartus里做的一切修改都等于白做。这种问题特别容易在开发阶段反复出现表现为“我明明改好了怎么上板还是老样子”。5.2 设备树里要写什么以及中断号换算的坑既然硬件上你把中断信号接到了fpga_interrupt[0]软件侧设备树里就要为这个中断源声明节点。拿Linux设备树的语法来说在你要用的节点里加上interrupts属性例如interrupts 0 152 4;前文说过第二个数字是SPI索引号由查表或换算得到第三个数字4表示高电平触发。如果你的PIO核配置成了上升沿触发这里就写1表示上升沿触发不要和4混用。关于这个数值我再强调一次必须去你手上这颗具体型号的HPS TRM中断表里查不同系列差异非常大。查表时看清楚表头写的是“GIC SPI”还是“GIC Interrupt ID”如果是后者记住减去32再填进设备树。还有一种更“懒”的验证方法在Linux内核源码的arch/arm64/boot/dts/intel目录下找到对应芯片的dtsi文件搜fpga_interrupt相关字样有些BSP会预留注释好的中断定义。如果能找到现成的引用直接照抄也是一种办法但前提是你确认BSP版本和你手上的硬件匹配。5.3 我建议的软硬件联调顺序把硬件改完、rbf更新好后接下来的联调顺序很重要。我的建议是第一步先用一个最简单的字符设备驱动驱动里只做一件事情request_irq注册中断中断处理函数里把计数打印出来或者记录到全局变量然后打开/proc/interrupts看中断计数有没有增加。不要一上来就写复杂的业务逻辑那样出问题都不好定位。第二步确认中断能触发之后再测试读PIO寄存器确认在中断处理函数里能正确读取按键状态并清除中断标志。这一步能验证AXI桥通路是否正常也能排除是个“关不掉的中断”。第三步再一步步扩展成正式驱动。关于“中断为什么不能阻塞”这个问题联调时顺便验证一下。Linux中断上下文里绝对不能调用msleep、ioremap这类可能睡眠的函数更不能做耗时太长的printk。如果你在中断处理里打印太多信息丢掉下一次中断的概率会大增。FPGA侧如果连续快速产生脉冲测试时就会发现/proc/interrupts里的计数不是每次按键都累加大概率就是因为中断处理耗时太长导致丢失。这是软件篇要展开讲的内容但硬件联调第一轮你就得心里有数。我个人在实际操作中的一条心得是处理这类FPGA到HPS的中断问题时间成本最大的环节几乎都是“硬件侧极性/连接”和“设备树中断号”二者之一。Signal Tap的使用再怎么强调都不过分它能帮你把硬件侧问题在十分钟内定性而设备树那个SPI索引号的换算务必在改硬件的同时就在文档里记录好别等着写驱动时再去翻手册那时候脑子里的上下文早就凉了。下一篇我打算把Linux侧的设备树写法、中断驱动骨架、以及中断处理函数里访问FPGA寄存器时的注意事项完整跑一遍到时候这一篇埋下的设备树中断号和触发方式正好能用上。如果你在这篇任何一个环节卡住了优先检查我列的那三个点Platform Designer连接、引脚分配、触发极性这三个过了后面就是时间问题。