Zynq老工程迁移指南:从SDK到Vitis的完整移植路线与踩坑实录
发布时间:2026/10/4 9:36:50 锦皓数字建站

前阵子把公司里一批老的赛灵思 Zynq 工程从传统 SDK 工作流往 Vitis 上迁过程比想象中坎坷。网上能搜到的资料要么是官方文档的精简转述要么只讲“新建工程”这种最顺的情况真到了移植老工程这一步坑一个接一个。这篇就把我从 Vivado 2018.x SDK 2018.x 工程迁到 Vitis 2020.2 的完整过程、踩过的坑和最终的稳妥做法写出来给正在做同样迁移的工程师一份能直接照着做的路线图。先说结论Vitis 不是 SDK 换了个皮肤而是工程模型变了。SDK 时代的 workspace 下面挂一堆互相依赖的工程到了 Vitis 被拆成了 platform 工程和应用工程两层硬件描述文件也从 .hdf 变成了 .xsa。不理解这两点后面所有操作都会觉得别扭。理解了其实移植本身不复杂真正的成本在重新配置 BSP、核对链接脚本和回归验证。这篇文章适合手里攥着老工程、不想推倒重写又必须切到新工具链的工程师也适合刚接手老项目、面对 Vitis 一脸茫然的新人。1. 移植前先弄清两件事工程结构和版本关系1.1 老 SDK 工程的典型结构老 SDK 的 workspace 里通常长这样my_workspace/ ├── my_hw_platform/ # 硬件平台描述由导出 HDF 生成 │ ├── my_hw_platform.hdf │ ├── ps7_init.c │ └── ... ├── my_app/ # 应用工程 │ ├── src/ # 用户源码 │ ├── bsp/ # 板级支持包 │ ├── Debug/ 或 Release/ # 编译产物、链接脚本 │ ├── _ide/ # Eclipse 元数据 │ ├── .cproject │ ├── .project │ ├── Makefile │ └── lscript.ld # 链接脚本这里每个目录的职责要搞清楚因为后面移植时它们会被打散重排my_hw_platform是 SDK 根据 Vivado 导出的 HDF 生成的里面是硬件描述、bitstream、ps7_init 初始化代码。它在 Vitis 中对应 platform 工程的一部分但不会再以“目录拖进去”的方式工作。bsp板级支持包包含 standalone 或 FreeRTOS、外设驱动、xparameters.h这些在 Vitis 里要基于新 XSA 动态生成不能直接拷贝老文件。应用工程src/是真正要保留的东西代码、头文件都在这里。lscript.ld是链接脚本决定代码段、数据段、堆栈放哪块内存这是移植时最容易被漏掉、出问题时最隐蔽的一点。所以整个移植工作的本质映射关系就一句话老 SDK 的“硬件平台工程 应用工程”对应 Vitis 的“platform 工程 app 工程”。不要试图让 Vitis 直接打开老 SDK 的 workspace也别指望有“一键转换”按钮。工具侧没有这个功能我们必须手动完成映射。1.2 从 HDF 到 XSA、从 SDK 到 Vitis 的版本匹配赛灵思在 2019.2 版本正式把 SDK 更名为 Vitis同时硬件描述文件也换掉了阶段硬件描述文件配套工具2019.1 及更早.hdfVivado SDK2019.2 及之后.xsaVivado Vitis这两个文件虽然都是 Vivado 导出出来的硬件描述但内部结构不同。Vitis 的 platform 工程创建向导通常要求提供 .xsa。如果你手上只有老工程导出的 .hdf不同版本 Vitis 对它的兼容情况不一样这里建议不要赌。最稳妥的路线是用新版 Vivado版本号要和目标 Vitis 匹配重新打开硬件工程升级 IP、重新综合实现、导出 .xsa再用这份 .xsa 去建 Vitis platform。版本匹配上我吃过亏。Vivado 2019.2 的 XSA 拿到 Vitis 2020.2 里创建 platform一开始看着没问题但后面调试时偶尔出现硬件初始化行为不一致。排查到最后还是老实用 Vitis 2020.2 配 Vivado 2020.2问题消失。所以建议Vivado 和 Vitis 严格用同一版本不折腾。如果你用的是 2022.1、2023.1 这些相对新的版本同理Vivado 和 Vitis 保持同版本。版本问题解决了下面就可以正式开干。2. 移植的第一步用 Vitis 重建 Platform 工程2.1 从 XSA 创建平台工程平台工程是 Vitis 里所有应用工程的“地基”。它包含硬件描述、处理器域、BSP以及最终的 bitstream。创建步骤很简单但有几个细节值得注意。打开 Vitis选择一个全新的 workspace 目录。千万不要直接选择老 SDK 的 workspace两个 Eclipse 元数据体系不兼容容易出各种奇怪问题。菜单栏选择 File - New - Platform Project。输入平台工程名字比如zynq_base_platform。Platform project 来源选择从 XSA或 HDF创建浏览选中你导出的 .xsa 文件。点击 Finish 完成创建。创建完成后工程视图里会多出一个 platform 工程里面有一个.xpfm文件。双击它会打开 platform 编辑器你可以在这里看到硬件资源的概览包括处理器类型、外设列表、中断、地址映射等。这个编辑器是后续所有平台级配置的入口。这里要强调一个新手常犯的错误platform 工程创建完成后它们只是“壳”BSP 还得单独生成。很多人以为 XSA 导进来就万事大吉直接去新建应用工程结果编译时报一堆找不到头文件的错误。原因就是 BSP 没生成xparameters.h这些核心文件根本不存在。2.2 核对硬件单元和地址映射platform 创建成功不等于移植完成。老硬件工程如果中间升级过 Vivado 版本IP 配置和地址分配可能会被改动即便没动过也建议核对一遍。打开 platform 编辑器重点核对这四类信息处理器型号Zynq-7000 应该是ps7_cortexa9_0Zynq UltraScale 应该是psu_cortexa53_0这类。外设基地址UART、GPIO、SPI、I2C、DDR 的地址是否和老 SDK 工程里的xparameters.h一致。中断号外设使用的中断 ID 是否和旧工程一致。时钟频率standalone BSP 会用到 CPU 频率、UART 频率等参数。具体怎么核对老 SDK 工程里my_app/bsp/ps7_cortexa9_0/include/xparameters.h就是一把尺子。把 Vitis 新生成的 BSP 里的xparameters.h和它对拍有差异就说明硬件工程版本升级时哪里被悄悄改掉了。比如我之前遇到过一个项目老硬件里 UART0 基地址是 0xE0000000Vivado 升级后不知为什么变成了另一个值串口打印乱码排查了很久才找到原因。所以这个“对拍”步骤千万别省。另外如果老工程在使用过程中手动改过xparameters.h不推荐但确实有人这么干那么移植时这些修改会全部丢失。你必须在新的 BSP 里重新把这些参数找回来或者干脆改代码用正式的 API 读取配置。2.3 在 Platform 上重新生成 BSP这是 platform 阶段最核心的一步。在 platform 工程视图里找到处理器域节点比如ps7_cortexa9_0右键选择 Generate BSP。如果你想用 FreeRTOS也可以在选择 OS 时指定。默认情况下选 standalone跟老 SDK 工程保持一致的裸机环境。BSP 生成后双击生成的.bsp文件会进入 BSP Settings 页面。这里有几个地方要重点检查OS 版本standalone 有自己的版本号比如 v7.1、v7.2不同版本之间 API 有细微差异。外设驱动确认需要用到的所有外设驱动都在列表里。比如工程里用了 GPIOBSP 应该包含xgpiop驱动用了 SD 卡应该有xilffs库和xps7_sddrv驱动。附加库lwip网络、xilffs文件系统、openamp异构多处理这些库需要在 BSP 的 Libraries 页面里手动勾选然后点击 Regenerate BSP Sources。这个操作和老 SDK 里“修改 BSP 设置后重新生成”是同一个道理但界面入口完全不同。注意绝对不要老 SDK 工程 bsp 目录下的.c/.h文件直接复制到新工程的 BSP 里。两个工具链的 BSP 源码版本不同接口细节可能有差异硬拷贝会引入各种难以定位的重复定义和版本不匹配问题。正确的做法永远是新 BSP 全部重新生成然后针对差异点做代码层适配。3. 应用工程移植的完整操作流程3.1 源码导入的三种方式我推荐哪种应用工程的源码是整场移植的核心资产。导入方式有三条路我逐一说一下实际体验。第一种使用 Vitis 新建应用工程向导里的“Import sources from a previous SDK project”功能。在 File - New - Application Project 向导中选择平台之后有一个步骤可以指定从旧 SDK 工程导入路径。Vitis 会尝试把旧工程src/下的源码带过来。这个方式省事适合老 SDK 工程结构标准、没有太多自定义构建步骤的情况。但注意它带过来的是源码不是构建配置BSP、编译参数、链接脚本这些还是要重新弄。第二种创建 Empty Application然后手动把旧src/目录下的文件拖入新工程的src/目录。这看起来麻烦但最可控。尤其当老工程里有不少自定义文件目录结构时这种方式能把源码组织得干干净净。我的建议是优先用这种方式因为移植本身就是一次重建干净起步反而省心。第三种用 xsct 命令行脚本实现批量导入和构建。适合几十个工程的批量迁移但学习成本高不适合作为第一步。后面第 5 章再展开。无论选哪种方式有一点要特别注意老 SDK 工程自动生成的Makefile、_ide/目录、.cproject、.project文件不要带入新工程。Vitis 会自动为应用工程生成新的构建文件带入旧的会引发混乱轻则重复编译重则工程打开报错。3.2 重新配置编译选项和头文件路径源码就位后编译配置是第二个大坑。很多老工程并不是“裸奔”的往往带自定义编译宏、优化等级、头文件搜索路径、标准库选择这些设置Vitis 不会自动继承这些。右键应用工程 - Properties - C/C Build - Settings重点检查这几个地方编译器Zynq-7000 对应arm-none-eabi-gccC 语言标准比如-stdc99要按老工程设置还原。优化等级老工程如果是-O2发布版调试时你可能会想改成-O0。这里建议先按老工程原有设置来等确认行为一致后再动优化。预处理器宏比如DEBUG、XILINX_BOARD_NAME、__ARM_NEON这类宏必须逐项核对。头文件路径凡是老工程里用绝对路径指向 workspace 外部目录的 include 路径这里一定要改成相对路径或使用 Vitis 的构建变量比如${workspace_loc}。之后换电脑、换目录才能不炸。这里我踩过一个经典坑老工程代码里用到了XPAR_AXI_GPIO_0_DEVICE_ID但新 BSP 生成后新xparameters.h里的宏名变成了XPAR_AXI_GPIO_0_BASEADDR这种新风格。原因是硬件工程里 IP 命名变化或者 BSP 版本换代。碰到未定义标识符的报错别急着去代码里补#define先到新xparameters.h里查真正的名称从根源上解决。3.3 链接脚本与内存布局处理链接脚本是移植中最容易被忽视、出了 bug 又最难查的一环。Vitis 新建应用工程后会自动在Debug/目录下生成一个默认的lscript.ld这个默认文件通常是把所有段都放在 DDR 里和老工程的定制布局往往不一致。正确的操作步骤是从老 SDK 应用工程里找到lscript.ld打开把 Memory Regions 和 Sections 截图或者抄下来。回到 Vitis双击Debug/lscript.ld在图形化的 linker script 编辑器中逐项核对.text代码段.rodata只读数据.data读写数据.bss未初始化数据.heap和.stack堆栈如果老工程把这些段放在了 DDR、OCM片上存储器等多个区域必须在新lscript.ld里重新分配。比如一个老工程是这么布局的_MEMORY_REGSION_DDR : ORIGIN 0x00100000, LENGTH 0x1FF00000 _MEMORY_REGSION_OCM : ORIGIN 0xFFFF0000, LENGTH 0xFFFF .text - DDR .stack - OCM这种布局往往是为了性能或关键数据隔离移植时必须原样恢复。只把源码搬过去、直接编译运行表面看也能跑但一旦代码量变大、访问到未初始化区域就会出现随机死机或数据被踩是特别折磨人的一类问题。还有一个相关操作有些工程把lscript.ld放在了工程根目录而不是Debug/下新工程直接复制时容易漏掉。建议在 Vitis 工程视图里打开“Show Hidden Files”确认链接脚本文件确实被工程引用了。3.4 编译、烧写、调试环节的差异工程配置完毕编译反而成了最简单的一步。点击 BuildVitis 会调用 Makefile 完成编译。这里和老 SDK 没有太大区别唯一需要注意的是首次编译可能会比较慢因为 BSP 库也要一并构建。但烧写和调试的入口变了这是很多人卡住的地方。老 SDK 里右键工程 - Debug As - Launch on Hardware 是固定的习惯动作。Vitis 里同样是 Debug As - Launch on Hardware但流程更复杂Vitis 需要把 bitstream 从 platform 工程里加载到 FPGA然后连接调试器。第一次运行时推荐先打开 Run - Debug Configurations在 Xilinx C/C application (System Debugger) 下面确认以下设置Debug Configuration 名称和要调试的应用工程是否对应。Target Setup选择正确的 JTAG 连接、目标处理器。Zynq-7000 选择ps7_cortexa9_0即可。Program FPGA确认这一项勾上了bitstream 会从 platform 中选取。如果这里没勾下载后程序可能根本无法启动。Reset Type通常选System Reset保证整个系统从初始状态启动。调试连接有个细节Vitis 默认会启动一个 hw_server 后台进程来管理 JTAG。如果你连着别的调试工具比如老的 SDK 或者独立版本的 hw_server端口会冲突。记得先把其他工具关闭或者配置 Vitis 使用已有的 hw_server 地址。这个坑在团队开发、多人共用一台测试工作站时尤其常见。4. 常见问题与排查技巧实录4.1 编译类问题速查表症状原因处理办法报找不到xparameters.hBSP 未生成或应用工程未关联 platform回到 platform 工程为处理器域生成 BSP确认应用工程的 platform 选择正确报undefined reference to XGpio_...BSP 没有勾选对应外设驱动打开 BSP Settings在驱动列表勾选对应驱动重新生成报multiple definition of main导入时把 SDK 自带例程或_ide中的代码也带进来了清理src/只保留自己的源码删除自动生成的模板文件头文件里宏名对不上Vivado 升级导致 IP 命名变化或 BSP 版本换代打开新xparameters.h查找真实宏名改代码或加统一的宏映射链接时找不到libgloss等库工具链配置被改动或工程编译选项异常检查 C/C Build - Settings 中的工具链设置确认是 arm-none-eabi 体系第一类问题的根因绝大多数是你没有在 platform 上生成 BSP或者应用工程创建时没有正确选择 platform。这个顺序一定不要乱先有 platform再生成 BSP最后才能建应用工程。4.2 运行与调试类问题实录编译过了下载也成功不代表移植结束了。运行期的坑更隐蔽我这里列几个真实遇到过的案例一程序能下载但跑起来就飞。这种情况我排查到最后发现是链接脚本里.text段的地址不对。老工程把代码放在 DDR 的 0x00100000新工程默认的lscript.ld给我放到了 0x01000000编译不报错下载也不报错一运行就取指异常。当时的排查手段就是在调试器里看 PC 指针发现它跳到错误地址。解决方案就是第 3.3 节讲的严格按老链接脚本恢复内存布局。案例二串口无输出或乱码。一个是 UART 基地址不匹配另一个是 BSP 里的波特率设置和板上实际时钟不一致。尤其是在用了外部时钟芯片的板子上standalone BSP 默认的 UART 时钟频率可能不对。对策是回到硬件工程里查xparameters.h中的XPAR_..._CLOCK_FREQ_HZ或者直接看一下platform.c里初始化时传入的时钟参数。案例三GDB 连接不稳定。换到 Vitis 后hw_server 的启动方式、端口占用、JTAG 驱动兼容都可能成为不稳定因素。我遇到过 Vitis 启动的 hw_server 和电脑上另一个老版本 Vitis 自带的 hw_server 抢端口的情况。解决办法是统一工具版本只用一套 hw_server。如果 JTAG 线质量一般还可以在 Run Configuration 里把 Target Connection 的 TCK 频率调低比如从默认的 15MHz 降到 5MHz连接稳定性能立刻改善。案例四下载成功但代码似乎没执行。检查 Program FPGA 选项是否勾选以及 Reset Type 是否选对。没有 bitstream 加载处理器根本没有可执行环境。4.3 关于第三方库和许可证老工程如果依赖了第三方库比如某个特定版本的 OpenCV、某个支付芯片厂家给的 SDK移植时一定要重点核对库许可证和版本兼容性。Vitis 自带的库如 OpenCV和 SDK 时代的库版本可能不同接口也变了。我的经验是第三方库尽量以源码形式纳入工程版本管理不要依赖系统环境的“动态库”或工具链自带的某个奇怪版本。这样以后换任何版本的 Vitissrc/里的东西都能顶起来。另外有些商用库是按版本绑定的比如老库只认老编译器或老处理器内核对齐方式这时如果新 Vitis 的编译器版本升级了比如从 GCC 8 升到 GCC 10库就会编不过或运行异常。提前查好许可证和版本兼容说明能省很多麻烦。5. 一次移植长期可维护的工程管理建议移植不是终点而是新工作流的起点。折腾完这一轮我强烈建议你把工程管理方式也顺带升级一下不然下次换电脑、换版本又要痛苦一遍。先说版本控制。Vitis 工程里有大量自动生成的文件不建议全部提交进 Git。实践下来一个干净仓库只应该包含这些源码src/链接脚本lscript.ld自定义构建配置.cproject、.project里你手动改过的部分懒人做法是整体提交但每次合并时会被自动生成文件的冲突折磨到怀疑人生platform 工程里的platform.xpfm和 XSA 文件硬件工程导出的 XSA 源文件而Debug/、build/、_ide/、.metadata这些应该写进.gitignore一律不提交。再说脚本化构建。Vitis 提供了命令行工具比如xsct可以用来创建工程、导入源码、编译甚至下载。如果你的团队要维护几十个产品变体强烈建议写一套脚本把“建 platform - 建 app - 配 BSP - 编译”全流程自动化。这样每次有硬件改动只要替换 XSA重新跑一次脚本全流程几分钟完成比人工点点点可靠得多。注意版本匹配命令行工具绑定在 Vitis 安装目录下用哪个版本的工具就用哪个版本的流程。还有一个建议把“移植前后配置差异”记录下来。我用一个 Markdown 文件专门记录每次移植的 BSP 设置、链接脚本变化、编译宏增量这样三个月后同事问起来“当时那个 UART 时钟为什么那么配”你能有一个能翻的记录而不是靠回忆。别小看这一步它能帮你把个人的迁移经验沉淀成团队的资产。最后关于验证顺序工程移植完成后先跑最基础的外设 demoUART 回环、GPIO 闪烁再跑存储类SD 卡读写、再跑通信类网络、USB最后才跑业务主逻辑。别一上来直接跑完整业务否则一旦出问题你根本分不清是移植导致的还是业务逻辑本身的问题。我记得第一次移植完整个业务跑起来表面很正常但连续跑几小时后偶发死机最后查出来是 DDR 内存校验失败。这种问题定位特别费时全靠早期把内存、基础外设测透才能避免。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。