FPGA到HPS中断通路实战:基于Agilex 5 GHRD的工程修改指南
2026/9/7 1:36:52 网站建设 项目流程

上一篇我把FPGA到HPS的中断链路掰开揉碎讲了一遍,结尾说下一篇动手改工程。今天填坑,目标很明确:在一套Agilex 5 SoC FPGA的GHRD(Golden 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在重新生成时可能会连带更新多个文件。

我自己习惯的做法是:

  1. 把官方GHRD工程完整解压到一个工作目录
  2. 编译一遍确认基线可用,记录下编译通过时的Quartus版本和IP版本
  3. 再做任何修改前,用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在设备树里的中断描述一般是三个cell:

interrupts = <0 SPI_INDEX IRQ_TYPE>

其中:

  • 第一个cell填0,表示SPI(Shared 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/O(PIO)IP核,把它配置成输入模式,再接一个按键,这样按键按下去,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 Designer,Quartus主界面会弹出一个对话框,问你是否要把新生成的工程文件更新到当前Quartus工程里。一定要选是,否则刚才生成的底层文件不会被Quartus重新编译。

4. 顶层的那些“看不见”的细节:引脚分配、综合与上板前的几道关卡

4.1 别让PIO的输入信号悬空

Platform Designer生成之后,新添加的PIO核会多出几个顶层端口,比如pio_0_external_connection_export。这个端口就是FPGA侧按键接进来的地方。

很多第一次做FPGA+SoC的工程师在这里会卡住:Platform Designer关掉之后,Quartus顶层模块文件并没有自动把pio_0_external_connection_export连到FPGA物理引脚上。你需要手动在顶层Verilog/VHDL文件里找到这个端口名,把它引到顶层端口,然后在Pin Planner里分配到具体的FPGA引脚。

如果你不做这一步,综合工具会给你报一堆警告,说某些输入端口悬空。如果悬空的是按键输入,那中断自然永远触发不了。更麻烦的是,如果恰好分配到了某个内部弱上拉或下拉的引脚,你可能莫名其妙看到中断一直触发,或者一直不触发。

所以正确顺序是:

  1. 打开工程顶层HDL文件,把pio_0_external_connection_export声明并例化到顶层端口
  2. 打开Assignments → Pin Planner,在All Pins列表里找到这个端口
  3. 在Location一列填上开发板按键对应的FPGA引脚编号(看板卡原理图,标注为KEY、BTN之类的引脚)
  4. 保存引脚分配,重新编译

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上启动Linux,Linux端的Bootloader(U-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卡里的.rbf,Linux启动后还是会加载老的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连接、引脚分配、触发极性,这三个过了,后面就是时间问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询