
简介面向ZYNQMP平台的嵌入式Linux开发人员这份PDF文档详细讲解如何在不重启板卡的前提下直接在Linux系统中完成PL可编程逻辑程序在线更新适用于项目研发与调试阶段可大幅缩短下载与验证周期。包体为1个PDF文件大小约736KB内容配有操作截图与配置说明便于对照实现。资源核心涵盖存储介质规划QSPI NorFlash存放BOOT.BINEMMC分区存放内核、设备树及pl_system.bit.bin、Uboot设备树添加fpga_full节点、Linux内核使能FPGA配置框架与连续内存分配器、bootgen生成bin格式镜像以及挂载EMMC分区执行更新脚本等环节还包含vivado不同版本生成bin文件的注意事项。已有1852人学习/下载适合需要为ZYNQMP平台部署PL动态更新能力的嵌入式工程师参考能帮助读者理清从镜像准备到内核配置再到在线写入的完整链路。 搞ZYNQMPLinux这套组合的兄弟应该都有同感项目前期调PL逻辑无非就是JTAG一插SDK里点一下就下载了舒服得很。可真到了设备部署进机房、装到野外站点之后再想拎着仿真器跑现场那成本就完全不一样了。更别提有些产品形态本身就没有JTAG引出外壳一封唯一的通道就是网口或者串口。所以“在Linux系统里把新的PL程序更新进去”这件事不是图方便而是产品化阶段绕不过去的一道坎。这篇文章我就结合自己在这条路上踩过的坑把ZYNQMP平台上在Linux下更新PL程序的完整思路和实操过程梳理一遍。从底层原理讲到操作命令再到故障排查尽量让刚接触这套架构的人也能按图索骥。本文涉及的内容主要面向ZYNQ UltraScale MPSoC平台也就是ZYNQMP系列跑的是嵌入式Linux。1. 项目背景为什么非要在Linux下更新PL1.1 从一次现场升级说起去年我给一款边缘视频处理设备做远程升级方案主控就是ZYNQMP。设备部署在十几个站点每个站点的网络条件参差不齐有的能连内网有的只能通过4G模块拨号。机电设备侧的PL程序承载了视频采集接口和部分预处理算法因为现场反馈说某些分辨率下图像有撕裂感需要调整PL内的时序和DDR读写逻辑——说白了就是得把PL程序重新烧一遍。这种场景下你不可能挨个站点开箱插JTAG。唯一的办法就是通过现有的Linux系统入口把新的bitstream灌进去。当时我花了整整两天把FPGA Manager这套机制跑通过程中也翻了不少文档今天把这些沉淀下来希望能帮你少走点弯路。1.2 哪些场景最需要动态更新PL结合我接触过的项目以下三类需求是最常见的产品交付后修复PL侧的逻辑缺陷或时序问题通过远程方式加载新的bitstream不用现场开箱。同一硬件平台需要适配多种功能比如软件无线电切换不同带宽的波形IP、图像处理平台在H.264和H.265编码核之间切换这类场景需要在系统运行期间动态重配PL。设备启动时自动从存储介质加载最新PL固件相比把bitstream固化在BOOT.BIN里的方式独立加载更灵活升级时也只需要替换文件不用重新烧录启动镜像。1.3 方案选型三种路子怎么选在ZYNQMP的Linux环境下加载PL程序主要有三种方式方式加载时机优点缺点适用场景JTAG下载研发阶段操作直观、可调试需物理接触无法远程开发调试BOOT.BIN打包上电启动启动即生效升级需替换整个启动镜像重启才能生效批量出厂Linux运行态加载系统中任意时刻灵活、可远程、无需重启需配置好FPGA Manager对内核有要求现场升级、动态切换我这篇文章要重点展开的就是第三种方式也就是通过Linux内核的FPGA Manager框架在系统运行时更新PL程序。它最大的价值在于你可以在保持PS端稳定运行的情况下单独更新PL逻辑。当然这里有个前提——你的PL程序和PS端交互时设计上要支持“热插拔”式的加载这个我后面会在踩坑部分细说。2. 核心原理解析PL程序在Linux里是怎么被加载的2.1 认识ZYNQMP的配置通路要搞懂Linux下更新PL首先得明白ZYNQMP里谁管配置这件事。ZYNQMP的PL配置通路核心是PCAPProcessor Configuration Access Port它归属于CSUConfiguration Security Unit管控。简单理解PCAP就是PS侧访问PL配置逻辑的一扇门CPU往PCAP相关的寄存器里写数据PCAP就会按照FPGA的配置协议把bitstream一样一样地挪到PL的配置电路里。PCAP支持两种基本操作一种是全配置就是完整加载整个PL镜像另一种是部分配置也就是DFXDynamic Function eXchange动态功能复用可以在PL运行期间只替换某一个区域。做远程升级时我大多数情况用的是全配置除非你的PL里已经划分好了动态区域才会用到部分配置。2.2 FPGA ManagerLinux下的标准加载框架早期在Linux下加载PLXilinx提供了/dev/xdevcfg这个字符设备应用层直接cat xxx.bin /dev/xdevcfg就能灌进去。但这种方式比较原始很多校验逻辑没做一旦bitstream格式不对或者PCAP处于异常状态系统直接就挂着不动了。现在的正统做法是走内核的FPGA Manager框架。这个框架把FPGA的加载抽象成三类驱动FPGA Manager驱动负责底层配置时序ZYNQMP平台的实现代码位于drivers/fpga/zynqmp-fpga.c。FPGA Region驱动负责描述“在哪片区域加载”对应设备树里的fpga-region节点。FPGA Bridge驱动负责控制配置期间PL与PS之间的数据通路隔离避免加载过程中PS端访问PL地址空间导致总线错误。应用层操作则统一收口到sysfs接口也就是/sys/class/fpga_manager/fpga0/。加载时只需要把bitstream文件写入firmware属性即可内核会调用对应的区域、桥接、管理器驱动完成整个加载流程。这套框架的好处是接口统一、状态可查询、出错有日志直接省掉了很多贴寄存器操作的麻烦。2.3 为什么加载的是.bin而不是.bit很多新手第一次接触时会有个疑惑Vivado工程里明明生成的是.bit文件为什么加载时要转成.bin原因在于.bit是Xilinx私有的描述格式文件头部包含目标器件型号、编译时间、bitstream长度等一大堆元信息。而PCAP要的是原始配置数据流也就是去掉这些头部之后的一连串配置命令和数据。所以在Linux下加载之前需要先用bootgen工具把.bit转换成.bin格式。转换方式也不复杂写一个BIF文件内容如下all: { [destination_devicepl] ./pl_test.bit }然后在Vitis的安装目录下找到bootgen工具或者在Xilinx工具链路径下执行bootgen -image pl_test.bif -o pl_test.bin执行完成后生成的pl_test.bin就是可以直接给FPGA Manager使用的文件格式。3. 实操过程从编译到加载验证的完整链路3.1 检查内核配置与设备树第一步不是拷贝文件而是先确认你的内核镜像里把FPGA Manager相关支持编进去了。ZYNQMP平台至少需要以下配置项CONFIG_FPGAy CONFIG_FPGA_MGRy CONFIG_FPGA_MGR_ZYNQMP_FPGAy CONFIG_FPGA_REGIONy CONFIG_FPGA_BRIDGEy如果你的内核没有打开这些选项你会发现/sys/class/fpga_manager/目录根本不存在。检查方式很简单zcat /proc/config.gz | grep FPGA如果输出里对应项是y或者m说明支持已经编进去了。如果用的是出厂预编译内核需要确认开发板厂商是否已经把这些选项打开。目前主流厂商提供的BSP里一般默认都是开启的但有些精简版内核可能会把FPGA Manager裁掉这点务必先确认。设备树方面需要有一个fpga-region节点来描述PL加载区域。以常见的ZYNQMP设备树为例会在根节点下声明fpga_full: fpga-full { compatible fpga-region; fpga-mgr zynqmp_pcap; #address-cells 1; #size-cells 1; ranges; };zynqmp_pcap节点则指向底层的PCAP管理器。如果你的设备树里已经包含了这部分内容运行时就可以通过sysfs接口直接操作不需要额外改设备树。如果加载时内核提示找不到FPGA区域就要回头检查这一节配置。3.2 生成bin文件并拷贝到目标板接下来把工程里生成的pl_test.bit按前面说的方式转换成.bin然后通过网口、U盘或者NFS等任意方式传到开发板上建议放到/lib/firmware/目录下。这是因为FPGA Manager的firmware属性默认会从固件搜索路径里找文件省得每次写绝对路径。拷贝完成后可以先查看一下fpga manager的状态cat /sys/class/fpga_manager/fpga0/state正常情况下输出应该是unknown或者read。如果输出operating说明之前已经加载过PL程序了这次更新属于重新加载这没问题继续往下走。3.3 执行加载用sysfs接口灌入bitstream执行加载就一行命令cat /lib/firmware/pl_test.bin /sys/class/fpga_manager/fpga0/firmware就这么简单。内核的FPGA Manager驱动会完成剩余的工作选中对应的manager、调用PCAP配置PL、等待配置完成、更新状态。如果想在写入前清一下配置标记位也可以先设置flagsecho 0 /sys/class/fpga_manager/fpga0/flags这个flags的作用是传递配置参数比如你要做部分重配置DFX就要把flags设为1。常规全配置场景下设为0即可。加载过程中建议同步用dmesg观察内核日志dmesg | tail -50正常情况下能看到类似下面的输出fpga_manager fpga0: writing pl_test.bin to Xilinx ZynqMP FPGA Manager fpga_manager fpga0: FPGA configuration successful这个就说明bitstream已经成功写入了。3.4 验证PL程序是否真正生效加载完成后通过以下几种方式确认PL程序工作正常查看状态cat /sys/class/fpga_manager/fpga0/state输出应该是operating。如果你的PL设计里包含版本寄存器或控制寄存器可以通过devmem工具直接读一下地址验证逻辑是否正确。比如PL基地址映射在0xA0000000读版本寄存器devmem 0xA0000000 32如果读出的值和你编译时写死在PL里的版本号一致说明加载成功且逻辑运行正常。观察实际功能表现比如LED闪烁、串口输出、外设响应等。这一步虽然是老土了点但往往最直观很多时候内核说配置成功但实际PL里的MMCM没有锁定、外设时序没匹配功能层面还是会出问题。4. 常见问题与排查技巧实录4.1 加载报错写firmware时提示Invalid argument这个错误我遇到不止一次。多数情况下是因为.bin文件转换时BIF写错了导致生成的文件其实不是标准的PL配置流。比如有的兄弟直接拿.bit文件改个名就往里灌那肯定不行。排查方式file pl_test.bin如果是标准PL bin文件开头会是一连串0xFF填充然后紧跟同步字。检查生成的BIF里是否明确写了[destination_devicepl]如果漏了这个标识bootgen会产生包含[bootloader]或[pmufw_image]等section的镜像这就不是纯PL配置流加载自然失败。另外确认一下内核里有没有相关错误日志dmesg | grep -i fpga如果有类似fpga_manager fpga0: error writing image的提示优先检查文件格式和BIF配置。4.2 加载成功但PL端时钟频率不对这个话题在不少讨论组里被反复提起也是我实际踩过的一个坑。现象是bitstream加载成功状态也变成operating但PL里的逻辑跑起来时钟频率明显不对比如预期的100MHz实际只有25MHz或者干脆没有时钟翻转。问题多半出在PS-PL时钟也就是fclk0到fclk3的配置上。ZYNQMP的PS侧可以为PL提供四路可配置时钟这四路时钟在设备树里对应psu_pl_clk0之类的时钟节点。如果PL设计里用的是外部时钟源而不是PS提供的fclk那就看硬件原理图和参考设计确认时钟是否正常供给。排查步骤先确认设备树里fclk频率配置是否正确cat /sys/kernel/debug/clk/clk_summary | grep pl_clk确认PL内部MMCM/PLL的锁定状态。如果锁不住往往是参考时钟没有输入需要查fclk引脚有没有实际翻转。检查硬件连接有的平台fclk输出经过电平转换或扇出芯片芯片没使能也会导致PL端无时钟。我当时的根因是设备树里psu_pl_clk0的频率被默认设成了100MHz但PL工程里MMCM的参考时钟引脚接的是fclk1而fclk1没有正确配置。改完设备树重新加载bitstream时钟频率就恢复正常了。4.3 加载过程中系统卡死或复位这个问题的根源往往不在FPGA加载本身而在于PL逻辑和PS端有交互。如果你的PL设计里挂着AXI外设、DMA引擎或者中断控制器那么在PL被重新配置的瞬间PS端还在正常访问这些地址空间。配置过程中PL逻辑停止工作总线没有任何响应PS端的访问就会卡在总线事务上严重时整机看门狗超时复位。解决思路是在加载PL之前先把所有与PL相关的驱动卸载或者停止访问。具体操作包括如果有对应的内核模块先rmmod没有模块机制的先把中断停掉echo 0 /proc/irq/xxx。如果是DMA处理先停止DMA通道确保没有挂起的事务。如果是自定义的字符设备驱动需要在驱动里增加一个“暂停”接口在更新PL之前调用。在我的项目里我总结出一个固定的升级流程停止业务进程 → 卸载PL相关驱动模块 → 加载新bitstream → 重新加载驱动 → 恢复业务。这样操作下来系统稳定性明显提升几乎没有再出现过卡死的情况。4.4 远程升级时的防掉电策略如果你的设备部署在野外升级过程中网络中断或者意外断电那么PL就会处于一个未配置或者配置异常的状态。再上电时如果系统设计成从文件系统加载PL那还能补救如果bitstream只在启动镜像里那就危险了。针对这个问题我的做法是新bitstream先存放在文件系统一个独立的备份分区。加载前先对旧bitstream做一次备份存为pl_active.bin。加载完新程序后立刻通过PL里的版本寄存器验证。验证失败则自动回滚到备份版本。系统启动时默认从pl_latest.bin加载如果校验失败自动回退到pl_safe.bin。这套机制实现起来并不复杂但对设备可靠性提升很明显。尤其是没有本地人工介入条件的场景这个成本是值得付出的。5. 踩坑记录与个人经验总结5.1 别忽略PL与PS握手信号的复位时序动态加载PL和开发板刚上电配PL有个很大的不同上电时PS和PL是一起初始化的时序天然就对齐而运行态加载时PS已经在跑Linux了PL里的逻辑需要经过复位后才能正常工作。如果PL设计里有AXI接口和PS通信务必要在PL内部设计一段“初始化完成后释放复位”的状态机避免PS侧驱动加载后看到的是一片废墟。我当时在写PL里的DMA控制器时一开始没有做这个释放逻辑结果bitstream加载成功后PS侧驱动读到的寄存器全是0xFF干扰定位了半天。后来在PL里加了一段基于时钟计数的软复位释放逻辑问题才彻底解决。5.2 保持bitstream与驱动的版本一致性这一点属于经验之谈。PL程序和PS驱动往往是配套开发的PL里寄存器地址变了、中断号换了驱动没跟上加载成功也是白搭。建议在PL内部固定一个只读版本寄存器每次升级时把版本号一并更新PS驱动在初始化时先读版本号不匹配就报错提示。这样能省去很多在电话里远程排查“明明加载成功了为什么功能不对”的时间。5.3 安全性加密与认证的考量如果你的产品有防抄板需求PL bitstream可以考虑启用加密功能。Vivado支持使用AES密钥加密bitstreamCSU在配置时会自动解密。启用加密后转出来的.bin就不是明文了远程传输时安全性会好一些。但要注意启用加密的bitstream在Vivado生成时需要以.bit格式保留一份明文工程版本否则后续想改配置就麻烦。另外加密bitstream的bootgen参数也需要相应调整BIF里要加上密钥文件路径这部分建议参考Xilinx的安全手册来配置。5.4 最后一个小技巧把加载过程做成脚本化我在做现场升级方案时最后把整个加载和校验流程封装成了一个shell脚本配合一个升级包目录结构业务人员只需要上传压缩包、执行一条命令即可完成升级。脚本核心逻辑大致如下#!/bin/sh FW_DIR/lib/firmware ACTIVE_BINpl_active.bin NEW_BINpl_new.bin VER_REG0xA0000000 EXPECT_VER$1 # 1. 停止业务进程此处省略具体服务名 systemctl stop my_pl_service # 2. 卸载PL相关驱动 rmmod my_pl_driver # 3. 备份当前版本 cp $FW_DIR/$ACTIVE_BIN $FW_DIR/pl_backup.bin # 4. 加载新bitstream cp $FW_DIR/$NEW_BIN $FW_DIR/$ACTIVE_BIN cat $FW_DIR/$NEW_BIN /sys/class/fpga_manager/fpga0/firmware # 5. 验证版本 NEW_VER$(devmem $VER_REG 32) if [ $NEW_VER ! $EXPECT_VER ]; then echo version mismatch, rollback... cat $FW_DIR/pl_backup.bin /sys/class/fpga_manager/fpga0/firmware exit 1 fi # 6. 恢复业务 modprobe my_pl_driver systemctl start my_pl_service脚本里我故意留了一个注意点devmem读取版本寄存器前最好加一个短延时等待PL内部复位释放完毕不然读数可能还是全F。这个延时时间取决于你PL里的初始化逻辑一般在毫秒级就够了。说实话ZYNQMP这套架构的Linux更新PL机制一旦把原理搞明白了操作起来就是一层窗户纸。但“知道怎么操作”和“能稳定地操作”之间还是隔着不少细节的。把上面这些坑提前避掉你的设备就能拥有一个比较靠谱的PL在线升级能力。本文还有配套的精品资源点击获取