这些年带过不少刚接触ZYNQ的工程师,也经常在论坛上看到有人卡在“Vivado建好了硬件却不知道怎么把程序跑起来”这一步。说句实在话,ZYNQ的难点不在某一个工具多难用,而在于它的开发模式跟传统单片机和纯FPGA完全不一样——你得先在Vivado里把PS和PL的组合关系定下来,再把这个硬件描述文件交到SDK里写软件,中间还夹着DDR配置、IP核连线、管脚约束这些容易被忽略的细节。这篇内容就是给真正准备动手的人准备的,按一条完整链路把从Vivado工程创建到SDK裸机开发的流程捋一遍,常见问题我也结合自己踩过的坑一并整理了,希望能帮你在起步阶段少走点弯路。
1. 项目概述:ZYNQ开发到底在做什么
1.1 ZYNQ平台的核心架构:PS与PL的分工
很多新手第一次看到ZYNQ的架构图就头大,其实拆开看并不复杂。ZYNQ内部有两大块:PS(Processing System,处理系统)是ARM Cortex-A9双核处理器加上DDR控制器、UART、SPI、I2C等硬核外设;PL(Programmable Logic,可编程逻辑)就是FPGA那部分逻辑资源。两者通过高性能的AXI总线互联,PS能启动Linux跑复杂应用,PL能实现高速并行运算,关键是可以互相协同。
我用一个生活化的类比:PS好比是你家楼下的物业经理,负责处理日常事务、跑系统、管网络;PL则是楼里的电梯和电力系统,讲究的是极高响应速度和并行工作能力。物业经理下发指令给电梯,电梯执行完再反馈结果,两者配合起来才能让整个楼正常运转。ZYNQ的妙处就在这——你不仅可以用ARM跑业务逻辑,还能用FPGA做实时数据采集、DMA搬运、高速信号处理,两边的优势都占了。
1.2 完整开发链路:Vivado硬件设计 + SDK软件开发的“双轨”模式
ZYNQ的开发流程跟普通MCU最大的区别,就是“先硬件后软件,硬件定义软件”。整个链路可以概括为四步:
- 在Vivado里创建工程,搭建PS和PL的硬件系统,配置DDR型号、UART引脚、时钟频率等;
- 把硬件配置导出成XSA文件(Xilinx Support Archive),这一步相当于把硬件的“说明书”打包给软件工程师;
- 打开SDK,基于XSA文件创建BSP(Board Support Package)和Application工程;
- 在SDK里编写C代码、交叉编译、下载调试。
这套“双轨”模式的本质是:Vivado决定了硬件长什么样,SDK则决定了系统跑什么逻辑。硬件配置一旦变化,XSA一换,SDK里的BSP也得跟着重新生成。很多新手经常犯的错就是改了硬件设计忘了重新导出XSA,导致SDK还在用旧的硬件描述,最后调试半天发现外设地址对不上。
1.3 适合人群与学习路线
这篇文章针对的是正在转型做嵌入式或FPGA的开发者,尤其是手里有一块ZYNQ-7020开发板、想从零开始跑通整个流程的人。学习路线我建议按“点灯→串口→外设→固化”的顺序来,不要一上来就折腾Linux、DMA那些高难度内容,先把裸机流程彻底跑通,理解PS和PL是怎么配合的,后面再进阶PetaLinux或者自定义IP就顺理成章了。
2. 开发环境准备:Vivado与SDK的安装配置
2.1 版本选型的实践心得
Vivado的版本号多得让人眼花,而且官网下载动辄几十个GB,选错版本浪费时间不说,还容易踩坑。我的建议是:如果用的是ZYNQ-7020这种经典器件,优先选Vivado 2018.3到2020.2之间的版本,这些版本经过大量用户验证,教程最多、问题答案最好搜。2020.2之后的版本对SDK的界面和流程做了不少调整,新版本的Vitis把SDK整合进去了,如果是新手反而容易被界面变化困扰。
我实际用得最多的是Vivado 2019.1,原因有几点:稳定、Vivado和SDK一体化安装、对ZYNQ-7000系列的器件支持非常成熟。2022.2我也用过,界面改了、License机制变了,但对新手来说并没有实质性的功能提升,反而不少老教程的操作步骤对不上号。表格对比一下:
| 版本 | 特点 | 适合人群 |
|---|---|---|
| 2018.3 | 经典稳定,教程最多 | 新手入门首选 |
| 2019.1 | 对ZYNQ-7000支持好,SDK集成度高 | 裸机开发推荐 |
| 2020.2 | 增加了部分新器件支持 | 需要兼容新板卡的场景 |
| 2022.2 | Vitis取代SDK,界面变化大 | 有经验的高级用户 |
2.2 安装过程中的四大经典坑点
安装Vivado几乎是每个新手遇到的第一个坎,我在这里集中说几个高频问题。
第一,安装路径必须全英文。这条听着像废话,但每年都有人栽在这。路径里一旦出现中文或者特殊符号,后面综合、仿真、SDK启动都可能报一些莫名其妙的错,排查起来非常痛苦。
第二,磁盘空间预留要充足。Vivado完整安装需要100GB以上的空闲空间,我用的是2019.1,装完大概80多GB。如果你只选了“Vivado + SDK”的定制安装,也要预留至少60GB。装到一半磁盘满了,整个环境可能文件不完整,修都难修。
第三,安装过程中偶尔会弹出WinPcap安装失败的提示。这个是Xilinx安装器用来支持硬件服务器功能用的网络抓包库,跟本地JTAG调试实际上没有直接关系。出现这个提示不用慌,直接忽略继续装即可,后续用到远程调试或者硬件服务器再单独安装也行。我有一台电脑当初也是报了WinPcap失败,后面该用SDK调试还是一切正常。
第四,驱动与License的问题。Windows下用Xilinx Cable连接板卡时,第一次插上USB线,设备管理器里大概率显示一个带感叹号的未知设备。这时候需要手动安装Vivado安装目录下的drivers里的驱动,路径一般为Xilinx/Vivado/2019.1/data/xicom/cable_drivers/nt64。装好驱动后,设备管理器里会出现“Xilinx USB Cable”之类的设备名。
2.3 License配置与板卡驱动
License这块我多说一句。很多新手会去纠结破解、激活那些事情,我个人的建议是去Xilinx官网申请一个免费的WebPack License,对ZYNQ-7020这种中小型器件完全够用。申请流程很简单:注册账号→进入License管理页面→选择ZYNQ-7000→生成License文件→在Vivado的License Manager里加载。整个过程大概十几分钟,比在网上找那些来路不明的License安全得多。
License配置完后记得验证一下,打开Vivado的Help→About→License,或者直接在启动界面看状态。如果License状态正常,就进入下一步创建工程;如果提示License不支持当前器件,八成是申请时没勾选对器件型号。
3. Vivado工程创建与硬件设计流程
3.1 新建工程:从零搭建硬件平台(以ZYNQ-7020为例)
在Vivado里新建工程,第一步就是给工程起名字、选路径,同样要求全英文路径。然后来到器件选择界面,这里要注意:ZYNQ-7020有两个常见封装,一个是xc7z020clg484-1,一个是xc7z020clg400-2,具体的型号以你板卡上的丝印为准。选错封装会直接导致管脚约束无法匹配。
创建完工程之后,核心动作是“Create Block Design”。在IP Integrator里新建一个Block Design,然后添加ZYNQ7 Processing System IP。这时候Vivado会弹出一个极大的配置框,别被吓到,其实需要改的只有几项:
- DDR配置:选板卡对应的DDR型号和位宽,ZYNQ-7020开发板最常见的是DDR3、16bit或32bit;
- UART:ZYNQ的MIO 14和15默认就是UART1的收发引脚,很多板子也是这么连的,勾选UART1即可;
- 时钟:PS端的CPU主频默认是667MHz(PLL配置),DDR频率按板卡实际参数选;
- GPIO:如果需要MIO引脚直接控制LED或者按键,在GPIO那个页面勾选对应的MIO位。
配置完PS,别急着导出,还要做一步非常关键的:右键点击Block Design里的ZYNQ核,选择Create HDL Wrapper。这一步会生成一个顶层Verilog文件,把整Block Design包装成一个可综合的模块。如果不做这一步,后续综合实现直接报“no top module”。
3.2 配置PS端:DDR、UART、GPIO等核心外设
我详细说一下DDR配置,因为这是新手最容易翻车的地方。在ZYNQ7 Processing System的配置界面里,DDR配置页有很多参数,比如型号选择、位宽、速度等级。很多开发板的DDR型号是MT41K256M16HA-125,对应DDR3、16bit、速度等级为-125,频率一般跑533MHz或者667MHz。如果你的板子资料不全,可以在板卡的原理图或者官方wiki上找DDR颗粒型号。
这里有一个容易忽略的点:DDR的Bank Address和Row/Column地址位宽必须和颗粒一致。Vivado里的DDR配置页提供了常见型号的下拉选择,选对型号后地址参数会自动填好。但如果你的板子是定制的,用的颗粒型号不在列表里,就得手动改Row Address、Column Address、Bank Address这些参数,任何一个填错,程序启动时DDR自检都会失败,表现为SDK里DDR初始化卡死或读取数据全是0xDEADBEEF。
UART的配置相对简单。ZYNQ-7020的MIO 14和MIO 15固定连接到UART1,板子上通常通过USB转串口芯片连接到电脑。在Vivado里勾选UART1后,不需要额外指定引脚,因为MIO是固定的PS引脚,不需要PL约束。
3.3 创建HDL Wrapper并导出硬件描述文件(XSA)
配置完成后,在Flow Navigator里点击Generate Output Products,然后点击Run Synthesis进行综合。综合通过后,再点击Run Implementation进行实现。这里我特别提醒:ZYNQ工程的实现步骤经常“变红”,这在热词里出现频率非常高,新手看到红色就慌,其实大部分变红的原因是时序约束没有正确传递,或者Block Design里的地址映射冲突。
2019.1之后的版本在实现后会提示是否生成比特流,如果只是先验证逻辑,可以不生成比特流直接导出XSA。但如果要固化PL的逻辑,就必须生成比特流。完整流程是:Generate Output Products → Run Synthesis → Run Implementation → Generate Bitstream → File → Export Hardware。在Export Hardware对话框里,一定要勾选“Include bitstream”,这样导出的XSA文件里才会包含比特流数据。
导出完成后能找到一个后缀为.xsa的文件,这个文件就是SDK的“硬件说明书”,包含PS配置信息、PL比特流、地址映射表、外设参数等。从这一刻起,Vivado的使命暂时结束,接下来所有工作在SDK里完成。
4. SDK开发实战:从Hello World到外设驱动
4.1 创建BSP与Application工程
启动Xilinx SDK时,需要指定一个Workspace路径。Workspace就是存放SDK工程的目录,建议跟Vivado工程目录分开,比如在工程文件夹下建一个sdk_workspace子目录,避免两个工具的文件混在一起。
第一次打开SDK会弹出“Launch”界面,问是否要设置Workspace,然后会进入一个欢迎页提示导入硬件平台。此时点击File → New → Project,选择“Application Project”,在弹窗里填工程名,然后点击下一页,“Board Support Package”选择“Create New BSP”,OS Platform选“standalone”,硬件平台选刚导出的XSA。这时候SDK会帮你自动生成BSP,里面包含了UART、GPIO、中断控制器等各种驱动库的源码和头文件。
这里有个细节值得关注:BSP生成的启动文件里会有一段初始化代码,负责配置DDR控制器、初始化堆栈、使能Cache等。这段代码就是等效于FSBL功能的前期初始化,如果你后续做固化,这个部分的重要性会直接体现出来。所以理解BSP的生成逻辑,比单纯会点“Next”要重要得多。
4.2 第一个程序:UART打印Hello World
在新建Application Project时,SDK会弹出模板选择窗口,里面有一堆模板:Hello World、Empty Application、Peripheral Tests、Dhrystone等等。对新手来说,选“Hello World”或“Empty Application”就够了。选Hello World模板后,SDK会生成一个简单的main.c,代码里调用了xil_printf往UART输出字符串。
关键一步来了:用USB线连接板卡和电脑,确保串口调试工具(比如SecureCRT或者串口助手)里能看到对应的COM口。打开板卡电源后,在SDK里右键工程名→Run As→Launch on Hardware (System Debugger),SDK会自动通过JTAG下载比特流和ELF程序,然后串口工具里就能看到打印输出。
如果你的串口工具没有任何输出,优先检查几下件事:
- DEVICE是否选对了(Vivado里勾了UART1)
- SDK里的BSP配置中stdin/stdout是否指向了ps7_uart_1
- 串口工具波特率是否与BSP里的缺省波特率一致(通常9600或115200)
板卡调试中我遇到最多次的就是第三点,波特率不对,串口里全是乱码,但程序其实已经跑起来了。
4.3 常见外设开发:GPIO、DMA、USB等要点
Hello World跑通之后,很多人想操作GPIO。ZYNQ的GPIO分为MIO和EMIO两种:MIO是PS的专用引脚,Lauch之后直接在代码里访问XGpioPs驱动;EMIO则是通过PL扩展的GPIO,需要先分配PL引脚再做约束。MIO的操作非常简单:
- 调用
XGpioPs_LookupConfig获取设备配置; - 调用
XGpioPs_CfgInitialize初始化; - 调用
XGpioPs_SetDirectionPin设置方向; - 调用
XGpioPs_WritePin输出高低电平。
如果你用手里的ZYNQ开发板点过灯,大概率就是走这个流程。这里提醒一下:MIO的点灯操作是不需要重新做Vivado工程的,只要之前配置了MIO和GPIO就行;但如果要走EMIO,那就必须回到Vivado里添加GPIO IP、配置引脚、重新综合和导出。
DMA是绕不开的进阶话题。ZYNQ同时支持SG(Scatter-Gather)DMA和简单的寄存器模式DMA,在PL端如果用了AXI DMA IP,SDK侧通常要配合中断和Buffer描述符来做数据搬运。新手常常在DMA这块卡住,有个经典陷阱:SDK的DMA传输需要一个“对齐”的内存缓冲区,直接malloc出来的内存地址往往不对齐,建议用Xil_DCacheFlush和Xil_DCacheInvalidate配合处理Cache一致性,否则会出现“DMA搬运的数据是旧数据”这种诡异问题。
USB在ZYNQ里属于PS的外设,但前提是Vivado里勾选了USB和相应的MIO分配。在裸机环境下跑USB HOST/Device的代码比Linux下复杂,主要是USB驱动栈的状态机太绕。如果你只是想要USB通信,我个人的建议是:要么用PetaLinux去跑,要么用PL端加USB Controller IP,要么直接外扩USB转串口芯片,别跟SDK裸机的USB驱动死磕。
5. 固化与烧写:让程序脱离JTAG运行
5.1 生成FSBL与Boot Image
前面在SDK里调试的程序,掉电就没了,因为程序是烧在RAM里运行的。要让程序上电自动运行,需要做两件事:生成启动镜像(BOOT.BIN)和写入Flash。
第一步,生成FSBL(First Stage Boot Loader)。在SDK里,通过File → New → Project → “FSBL”模板创建工程,SDK会自动生成一个fsbl.elf。FSBL的作用是:系统上电后,BootROM把FSBL加载到OCM(片上内存),FSBL再完成DDR初始化、时钟配置、把比特流下载到PL、最后把应用代码从Flash拷贝到DDR并跳转执行。
第二步,生成BOOT.BIN。在SDK里选择Xilinx → Create Boot Image,在弹窗中按顺序添加三样东西:FSBL的elf文件、硬件比特流(.bit)、你的应用程序elf文件。顺序不能错,错了系统可能能启动一部分,但应用跑不起来。
很多教程会让人去用命令行工具bootgen生成Boot Image,其实SDK图形界面已经封装好了,我建议新手先用Create Boot Image图形界面,等理解了镜像结构再回头研究bootgen脚本也不迟。
5.2 QSPI Flash固化完整流程
固化的标准套路有两种:一种是利用SDK的“Program Flash”工具,直接在JTAG模式下把Boot Image写进Flash;另一种是先通过代码把镜像加载到DDR里,再调XilSpiPs库往QSPI Flash里写。对于新手,我用得最多的还是SDK自带的Program Flash Memory工具。
具体操作步骤如下:
- 确保BOOT.BIN已经生成,路径一般在
工程目录/sdk_workspace/工程名/Debug/下; - 在SDK里选择Xilinx → Program Flash;
- Flash Type选“qspi-x4-single”或者“qspi-ospi”这种,具体看板卡的Flash类型;
- Image File选择BOOT.BIN路径;
- Offset Address用默认的0x0;
- 点击Program,等待进度条走完。
写完之后,把板卡启动模式拨到QSPI方式(一般是四位拨码开关,具体看板卡说明书),重新上电或复位,程序就会自动运行。如果程序没起来,先看看启动模式拨码是否拨对了,再检查BOOT.BIN里有没有包含bitstream。
5.3 JTAG固化Flash时必须接DDR吗
这个问题被问过太多次了,直接说结论:不是必须,但大多数情况下建议接DDR。
为什么这么说?因为SDK的Program Flash工具在烧写时,并不是直接把BOOT.BIN逐字节写进Flash那么简单。它实际上是把FSBL先加载到内存里运行,再由运行起来的FSBL配合工具把镜像写入QSPI。FSBL运行时需要一个缓冲区来暂存镜像数据。如果你在设计里配置了DDR,FSBL默认会把DDR初始化好,然后用DDR去搬运数据;如果工程里没有DDR,那FSBL会用OCM,但OCM只有256KB,放不下大一点的镜像。
所以答案取决于你的BOOT.BIN有多大。如果你的应用很小(比如裸机Hello World),BOOT.BIN通常不到100KB,不接DDR也没问题。但如果包含完整的Linux镜像或者较大的FPGA比特流加应用程序,镜像动辄几十MB,不初始化DDR就没法往Flash里写。实际开发中,几乎没有不含DDR的ZYNQ工程,所以“JTAG固化Flash时必须使用DDR”在绝大多数场景下成立。
6. 常见问题排查与避坑实录
6.1 Vivado工程与综合实现问题
先聊“Implement Design变红”的问题。我在前面提到过,这属于ZYNQ新手的高频bug。你点了Run Implementation后,日志最后一行报[TIMING-1]或者[PLACE-1]之类的错误,进度条变成红色,其实就是实现流程中断。常见诱因有:
- 顶层模块没有用HDL Wrapper,导致工具找不到顶层;
- 工程里混入了其他工程残留的XDC约束文件,引脚位置跟实际板卡不符;
- 时钟频率配置过高,时序不满足要求。
排查思路是先看Messages窗口,找到红色的Critical Warning或Error,双击能跳转到具体位置。如果是时序问题,最简单的急救方案是把PS端CPU或DDR的时钟频率往下调一档再综合试试,定位到问题后再细查约束。
Vivado里还有个叫BUFGMUX的错误,很多时候也是时钟网络的问题。比如你在Block Design里把同一个时钟接到了多个需要不同时钟域的IP上,生成了不兼容的时钟结构。解决思路是确保每个时钟域都有明确的来源,在需要跨时钟域的地方用AXI Clock Converter或异步FIFO做隔离。
6.2 SDK编译与调试问题
SDK编译报错里常见的有两类:一类是链接时报undefined reference,另一类是头文件找不到。第一类通常是因为你没有在BSP设置里启用对应的库,比如用了XGpioPs但BSP里没勾选“xilgpio”;第二类是工程路径问题,建议把整个工程放在纯英文且没有空格的路径下,再Project → Clean一下。
调试时的断点失灵也是个经典问题。SDK调试器默认情况下会在main入口停一下,但如果你在中断服务函数里设了断点,可能会遇到断点不生效的情况。这是因为代码运行时启用了O2优化,某些变量和断点被编译器优化掉了。把编译优化等级改成O0再试,通常在工程属性里的Optimization选项可以调。
还有一个我印象很深的坑:SDK调试时程序进入了一个死循环,CPU复位后依然卡住。后来发现是OCM被占满、堆栈溢出导致的。裸机工程没有虚拟内存保护,堆栈写穿后程序行为完全不可预测,表现就是各种“无理由”死机。解决方法是把链接脚本里堆栈大小调大,或者简化代码逻辑,别在中断里做耗时的循环。
6.3 硬件连接与配置问题
硬件层面的问题往往比软件更难排查。最常见的是“SDK连接板子失败”,点Run时弹窗提示找不到cable或者device。这时候按以下顺序排查:
- 设备管理器里看驱动是否正常,出现感叹号就重装驱动;
- 确认JTAG档位和跳线是否正确,有些板卡在JTAG模式下需要特定的跳线;
- 用Vivado的Hardware Manager连接一次,能连上说明硬件没问题,再回SDK操作;
- 尝试更换USB线或USB口,有的USB口是纯充电口,不能传数据。
关于“zynq裸机USB通信方案”的搜索热度一直很高。很多人想把ZYNQ的USB用作数据传输,但裸机环境下的USB驱动确实不友好。如果你只是想在PC和ZYNQ之间传数据,我实测下来最简单可靠的是:
- 在PS上用UART,速度不高但非常稳定;
- 在PL端做USB UART桥接,或者直接用CP210x这类USB转串口芯片;
- 如果必须高速,优先考虑在Linux环境里用标准的USB gadget/host框架,别在裸机里硬啃。
同理,“QT ZYNQ serialport库编译”这种问题也常见于嵌入式Linux场景,一般需要先交叉编译Qt,再把串口模块的插件编译进Qt安装目录。裸机SDK里没有Qt的概念,这个方向属于PetaLinux的范畴,需要的开发者可以等后续专门写一篇讲PetaLinux的文章。
6.4 关于Xilinx FIR核与多相滤波的补充
热词里有“vivado fir核多相滤波”,虽然这篇主要聊SDK,但既然搜到这个大概率是PL开发遇到瓶颈的人,我简单补一刀。Xilinx FIR Compiler IP支持多相结构的抽取和插值滤波,配置时要注意:
- 多相因子(Polyphase Factor)在“Hardware Oversampling Specification”或滤波系数重排相关页面设置;
- FIR核的时钟频率要满足
输出采样率×抽取比×滤波器阶数/2的吞吐需求; - 多相滤波的系数重排规则容易搞错,建议先用MATLAB的
polyphase函数验证结果,再对照IP的仿真输出。
FIR核的仿真和调试在Vivado里用AXI接口比较方便,SDK侧不用参与太多,但如果PL端有CPU介入做系数更新,就涉及AXI Lite寄存器操作,这部分回到我们前面讲的GPIO/DMA代码套路,逻辑是相通的。
7. 写在最后的实际体会
ZYNQ这套东西,说白了就是个跨界产品,要求你既懂FPGA的硬件思维,又懂ARM的软件思维。很多人卡住不是因为他笨,而是因为第一次接触这种“硬件也能改、软件也能跑”的异构系统,思路一时间转不过弯来。我的建议始终是:别贪多,先照着版本文档把Vivado导出XSA、SDK烧个Hello World、固化到QSPI这三步滚瓜烂熟,ZYNQ的门就算真正迈进去了。
我自己带项目时,最常用的调试三板斧就是看串口日志、读寄存器值、查地址映射表。遇到问题别急着怀疑工具,先看看自己的硬件配置是否跟代码一致,再确认XSA有没有更新,很多玄学问题最后都能归结到这些基础环节。
开发板上如果还有SD卡接口,建议你下一步把启动模式切换到SD卡方式试试——SD卡固化比QSPI更灵活,迭代Linux内核和根文件系统时特别方便。这个坑我当年也是一步一步踩过来的,等你在SDK裸机流程上积累够感觉了,自然会有兴趣去探索更完整的启动和部署方案。