☰
Zynq入门实战:从ARM+FPGA架构到DDR配置与启动烧写全流程解析
2026/10/27 9:21:09 网站建设 项目流程

如果你最近刚接触Zynq,大概已经被满屏的Vivado、SDK、FSBL、BOOT.bin、DDR控制器这些词搞得有点上头。我在第一次用Zynq的时候,最直观的感受是:这不就是一颗“ARM + FPGA”的胶水芯片吗?等真正动手之后才发现,这套东西最难的不是某个单一功能,而是“ARM和FPGA怎么协同工作”这个全局问题。这篇内容我不想重复那些手册上已有的概念,而是把一条能走通的路——从架构认知、开发流程、DDR配置到烧写部署——完整给你梳理一遍,同时把我在实际项目中踩过的坑、验证过的习惯都写进去。适合刚拿到开发板、想在两周内跑通一个最小系统的朋友,也适合从纯FPGA或纯ARM转过来的工程师。

1. 先把Zynq到底“是什么”搞清楚

很多人第一次听到Zynq,看到官网写着“SoC”,再看到Zynq内部既有Cortex-A9又有一大堆可编程逻辑,下意识就把它当成“带硬核CPU的FPGA”。这个理解方向是对的,但很容易让你在后续开发里走弯路。因为Zynq的PS(Processing System,处理系统)和PL(Programmable Logic,可编程逻辑)不是单纯的“CPU + 外设”关系,它们是一对通过AXI高速总线互联的对等单元。换句话说,CPU和FPGA都能主动发起访问,PL既可以当CPU的“加速器”,也可以当CPU的“主人”。

1.1 从三个“为什么”建立整体认知

第一个问题:为什么用Zynq,而不是用“ARM开发板 + 独立FPGA”?

我总结下来最核心的是两点:带宽和功耗。Zynq内部PS与PL之间的互联走的是高性能AXI接口,典型带宽能做到每秒数百兆字节甚至更高。如果外部用SPI、并口甚至PCIe来连接ARM和FPGA,带宽会被接口协议压得很低,而且PCB的布局布线、信号完整性问题会占掉你大量时间。Zynq把这条通路做到片内,电源域和时钟域的管理也更方便,系统整体功耗比两块独立芯片低不少。

第二个问题:PS和PL之间怎么通信?

这个问题是入门Zynq必须迈过的坎。PS和PL之间主要靠AXI3/AXI4接口,在Vivado里以IP形式呈现,比如AXI GPIO、AXI UART、AXI DMA等。你需要理解一次完整的AXI读事务是怎么回事:主设备发起地址,从设备译码后返回数据,这中间有握手信号、等待周期、突发长度等概念。对初学者来说其实不需要把AXI协议背到滚瓜烂熟,但至少要知道“PL侧的内存映射空间”和“PS侧的内存映射空间”是独立又互通的,具体怎么映射在Vivado里可以通过Address Editor来查看和修改。

第三个问题:为什么这块芯片这么难学?

因为Zynq其实藏了两条学习曲线。一条是FPGA开发曲线,你需要懂Verilog/VHDL、时序约束、综合实现;另一条是嵌入式软件曲线,你需要懂GCC编译、链接脚本、Linux内核移植、U-Boot。两条曲线叠在一起,就成了Zynq的学习壁垒。我的感受是,入门阶段别追求两个都精通,而是先选定一条主线。比如你先跑通“PS端裸机程序 + 通过AXI控制PL端LED”,再逐步往Linux系统和复杂PL逻辑上扩展,这样能把两个领域的知识一点点接上,而不是一口吃成个胖子。

1.2 适合Zynq的开发场景与不适合的场景

搞清楚这个很重要,因为不少项目其实用纯FPGA或纯ARM反而更合适。我的判断维度有三个:数据带宽要求、控制复杂度、量产成本。

数据带宽是Zynq的核心优势。比如图像采集与预处理、雷达中频信号处理、SDR软件无线电基带、电机多轴协同控制,这些场景既需要大量并行计算(PL做),又需要复杂的策略逻辑(PS做),用Zynq就非常合适。还有对实时性和功耗同时有要求的场景,比如无人机飞控、医疗超声前端,Zynq能做到在一颗芯片内完成传感器数据采集、滤波、决策和通信,整体系统简洁可靠。

如果你只是做个简单的传感器采集,用一颗带CAN和以太网的MCU就行,根本不需要Zynq。如果算法完全固定、几乎没有控制策略,纯FPGA反而更好,因为没有CPU启动、操作系统、文件系统这些额外负担。量产成本上,Zynq价格普遍高于同档位FPGA,也高于高性能MCU,所以“能不能省掉这个成本”也是选型时必须算的账。

关于Ultrascale系列,我多说一句。Zynq UltraScale+相比老一代Zynq-7000,核心变化是PS从双核A9升级为四核A53(还可以选配R5实时核),内存控制器支持DDR4,PL部分也换到了UltraScale架构。它适合的典型场景是:需要跑Linux且同时做大量数据面加速的系统,比如5G小站、机器视觉、自动驾驶域控制器、高端测试测量仪器。对入门学习来说,我还是建议先用Zynq-7000系列的开发板,开发工具链更成熟,资料和例子最多,踩坑成本也更低。

2. 开发环境搭建与最小系统设计思路

很多人把Zynq入门“第一道难关”归结为Vivado安装,其实安装本身不难,难的是理解“为什么工具链这么杂”。Vivado管FPGA部分,Vitis(以前叫SDK)管ARM软件部分,U-Boot和Linux内核又多了一套编译系统。你得先接受一个事实:Zynq开发天生是多工具协作,不是点一个按钮就能完成的。

2.1 工具链版本选择与安装注意事项

我在工程里用过Vivado 2019.1、2020.1、2021.1等几个版本,整体建议是:不要去追最新版本,选一个稳定且资料多的版本。对绝大多数初学者,Vivado 2020.1或2021.1是不错的选择。一来网上教程多,二来Vitis从2020.1开始把SDK整合进去,流程相对统一。如果你用旧版SDK(如2019.1那种),也能学,但后续想用新IP可能会有兼容问题。

安装时有几个坑要注意。第一,安装路径不要带中文和空格,否则后面综合、仿真时会有莫名其妙的问题。第二,Vivado默认只装Vivado,安装时勾选Vitis相关的组件,不然建完硬件工程后找不到SDK界面。第三,Windows下务必关闭实时病毒扫描或加入白名单,Vivado编译过程中会生成大量临时文件和动态链接库,杀毒软件会显著拖慢编译速度,甚至导致Ela属性解析失败。

2.2 最小系统应该包含什么

所谓最小系统,就是能让Zynq板子在JTAG模式下“活过来”,并完成一件小事的最小软硬件集合。我建议的最小系统是:PS端DDR初始化 + 一个UART串口(PS自带的UART0/UART1,不需要PL逻辑)+ 一个AXI GPIO(在PL里加一个GPIO核,控制4个LED) + 一个定时器跑个滴答中断。这套东西能覆盖你后面80%开发会遇到的基础操作:DDR配置、PS外设、PL外设、AXI总线、中断。

实际创建流程如下:打开Vivado,新建RTL工程,在工程里添加ZYNQ7 Processing System IP,双击打开配置界面。在配置界面里推荐先做这样几件事:

  • 在PS-PL Configuration里勾选需要的AXI接口,比如S_AXI_HP0或S_AXI_GP0。如果只是控制几个GPIO,S_AXI_GP0就够了。
  • 在Peripheral Configuration里开启UART1,并把MIO引脚绑定到板上串口对应的引脚。
  • DDR Configuration里选择DDR3或DDR2,输入颗粒型号、位宽、容量等参数,这一步是后续能不能跑系统的关键。

配置完成后点OK,回到Block Design里添加AXI GPIO IP,然后用Run Connection Automation让Vivado帮你自动连线。注意这里“自动连线”只能连标准的AXI和时钟复位,你要看一下Address Editor里GPIO被分配到的地址,记下来,软件那边要按这个地址操作。

生成完Bitstream之后,在Vivado里File > Export Hardware,导出包含bitstream的xsa文件(注意勾选“Include bitstream”)。这个xsa就是硬件描述文件,后面的Vitis工程要基于它来写软件。

2.3 设计范式:PS主导还是PL主导

这是Zynq项目里很容易忽略的顶层架构问题。我的建议是,对绝大部分应用场景,采用“PS主导”的设计:CPU是主控,PL是用来被调用的加速资源。所有状态机、协议栈、调度逻辑尽量放在PS里用C实现,PL只做那些“必须并行才能满足性能”的部分。这样做的好处很多:软件调试比RTL调试快得多,逻辑改动不用重新综合布线,系统整体可维护性也高。

只有当你的应用需要极低延迟直接处理数据流(比如ADC采样后立刻滤波、伺服电流环),才需要考虑“PL主导”的设计,让PL里的大状态机去控制数据通路,PS通过寄存器来配置参数。新手入门还是先走PS主导,能跑通之后再去碰PL主导。

3. DDR配置不是“随便填一下”的事

Zynq开发里有两个位置都涉及DDR配置,一个是PS端的DDR控制器,另一个是PL端如果用MIG(Memory Interface Generator)IP生成DDR3/DDR4接口。入门阶段你接触的往往是PS端的DDR,因为U-Boot、Linux、裸机程序全都跑在DDR里,DDR配不对,系统根本起不来。

3.1 DDR4和DDR3的关键参数差异

Ultrascale+系列开始支持DDR4,部分老开发板还在用DDR3。搞懂两者差异,你在Vivado里配置DDR时序参数时才不会一头雾水。我做了一张简化对比表:

特性DDR3DDR4
工作电压1.5V/1.35V(低电压版)1.2V
预取长度8n16n
Bank结构8个bank4个bank group,每组4个bank(16/32个bank)
DMI引脚无,用DM信号有,数据掩码与数据翻转检测合并
命令总线并行到达所有颗粒菊花链(fly-by)拓扑,命令地址需菊花链布线
刷新机制普通刷新支持fine granularity refresh,刷新粒度更细
读写延迟CL可调范围较小CL更高,AL和CL可以分别设置

对Zynq用户来说,这些差异最终体现在配置界面的具体参数上:CL(CAS Latency)、tRCD、tRP、tRAS、tRFC、tREFI等。如果你选的是Vivado内置支持的颗粒型号,这些参数会自动填好,不用你手算。但如果你用的板子是定制的、DDR颗粒比较冷门,就必须去颗粒厂家数据手册里查这些时序参数,然后手动输入。我自己遇到过一次DDR3初始化失败,最后发现就是把tRFC填错了,此后再也不敢不查手册直接填了。

3.2 Vivado里配置DDR参数的实操要点

在Zynq PS配置界面里,DDR配置页常见选项有Memory Part、Data Width、ECC、Speed Grade、Burst Length等。我见过不少新手在这里卡住,主要体现在三件事上。

第一件事是“Memory Part选什么”。如果你用的是官方开发板,比如Zedboard、Pynq-Z2,直接选板上标注的颗粒型号就行。如果买的是第三方核心板,问卖家要DDR配置或原理图,或者直接问他们xsa文件里是怎么配的。千万不要自己去猜颗粒型号然后选一个相近的。

第二件事是“Data Width”。Zynq-7000 PS端的DDR接口数据位宽常见的有16bit、32bit、64bit,要和板载颗粒的位宽和片选数匹配。选错的话,虽然软件层面可能没报错,但实际内存大小和地址分布就不对了,跑Linux会随机出错。Ultrascale+系列支持DDR4的64bit甚至更高位宽,配置时同样要和实际PCB一一对应。

第三件事是“三个特殊参数”:Bank Address Count、Row Address Count、Column Address Count。这几个参数代表你的DDR颗粒的地址组织方式。Vivado里选颗粒型号后它会自动填,但你需要明白,这三个参数决定了PS端把DDR视为多大的连续地址空间。如果这里和颗粒实际规格不一致,U-Boot里执行ddr training会直接报错。

3.3 验证DDR配置是否正确

配置完成后,怎么知道DDR到底好没好?方法是从FSBL阶段看输出。FSBL(First Stage Boot Loader,第一级引导加载程序)会做DDR初始化并打印相关信息。如果你在串口终端看到DDR初始化成功,并且U-Boot能正常启动、执行内存读写测试不报ECC错误,那基本说明DDR配置是对的。

更硬的验证方式是跑一遍“裸机DDR测试”:在Vitis里写个小程序,对DDR的每个地址写入固定值再读回对比,多跑几轮。这个测试虽然简单,却很有效。我在调试一个定制板时就遇到过一个奇怪现象:前32MB内存读写正常,超过32MB就出错。后来发现是因为DDR的bank地址配置和颗粒实际对不上,属于典型的高位地址错误,这类问题用纯软件很难发现,非跑一次全地址遍历不可。

4. 烧写与启动:BOOT.bin到底怎么来的

“zynq烧写”这个词在外面的帖子里经常被单独拎出来,因为这一步卡住过很多人。烧写前你对Zynq启动流程的理解,直接决定了你能不能在QSPI Flash不启动的时候快速定位问题。Zynq的启动其实是一个三级跳:BootROM(芯片固化的小段代码)→ FSBL →(U-Boot或裸机应用)。FSBL就像一个小管家,负责把你硬件工程里要用的DDR时钟、PLL、MIO初始化好,然后把PL的bitstream加载进去,再跳转到你的应用程序。

4.1 BOOT.bin的三段式结构

BOOT.bin不是单一程序,它是由多个文件拼接打包而成的。最常见三段是:FSBL.elf、bitstream.bit(也就是PL侧配置)、你的应用elf(可以是U-Boot,也可以是裸机程序)。这个打包动作由Vitis里的Bootgen工具完成,你在Vitis里Build Project时会自动生成。

需要注意的是,FSBL本身也是一个完整的程序,它不是凭空生成的,而是Vitis根据你导出的xsa文件自动生成的一个BSP工程。它可以认为是“定制化启动代码”,里面包含了你的DDR配置、MIO配置等。所以你每次改硬件配置(比如换了DDR颗粒、改了MIO引脚),都要重新生成xsa并重新编译FSBL,否则后续所有启动都可能翻车。

4.2 三种烧写/启动方式对比

启动方式介质适用场景优点缺点
JTAG电脑直连开发调试阶段启动最快,无需烧写,方便在线调试断电丢失,不能在生产中使用
QSPI Flash板载Flash生产部署,单板启动掉电保存,启动速度快,工业上常用烧写次数有限,擦除/写入较慢
SD卡TF/SD卡快速原型、Linux系统调试容量大,可随时替换启动文件读卡速度一般,量产可靠性不如QSPI

我在调试Linux时最喜欢SD卡启动,因为内核和设备树可以随时改、随时换,不用反复擦写Flash。但在产品化阶段,最终一定是回到QSPI + 少量环境变量存储的老路子上。如果你做的是裸机应用,QSPI直装是最稳妥的。

4.3 烧写QSPI Flash的典型步骤

用Vivado的Hardware Manager烧写QSPI是最常见的做法。前提是你已经把BOOT.bin生成好了。流程大致如下:

  • 连接JTAG,打开Vivado Hardware Manager,右键点击设备,选择Add Configuration Memory Device。
  • 选择你的Flash型号(比如常见的是ISSI的IS25LP128、Winbond的W25Q128等)。如果列表里没有,也可以用Generic SPI配置,但地址和页大小要自己确认。
  • 添加BOOT.bin文件,烧写地址一般从0x00000000开始。
  • 点击烧写,等待进度条走完,然后设置板子的启动模式拨码开关到QSPI模式,复位观察串口输出。

这里有个容易被忽略的点:板子上的启动模式拨码开关,决定了BootROM去哪个外设找FSBL。如果你拨码开关拨在JTAG模式,哪怕QSPI里烧好了程序,复位后程序也不会从Flash启动。调试时如果“明明烧了却不跑”,先检查拨码开关和跳线帽,这比查代码快得多。

4.4 烧写过程中常见的坑

第一个坑是Flash型号选错。某个型号的Flash可能容量、扇区大小、时序都不同,选错的话,BootROM从Flash里读FSBL时会校验失败,然后卡死在启动流程里,串口什么输出都没有。建议先看开发板原理图或者卖家的说明文档,确认Flash具体型号。

第二个坑是BOOT.bin里没有包含bitstream。如果你用的是PL侧点亮LED的例子,但忘记勾选导出bitstream,或者忘记在FSBL工程里配置“include bitstream”,那么PS端能启动,但PL侧始终是空白,LED自然不亮。这类问题在Vitis的BSP设置里可以看到相关选项,生成前务必检查。

第三个坑是QSPI地址和启动地址不匹配。Zynq的QSPI Flash线性映射地址通常从0xE000_0000附近开始,但烧写地址指的是Flash内部的偏移地址。多数情况下从0x0000_0000开始烧写没问题,但如果你希望通过多镜像启动,就要看UG585里对Boot Mode Header的描述,别自己随意改地址。

5. 常见问题排查与调试习惯

写这一节很有必要,因为我在入门期间几乎每天都和这些问题搏斗。整理成速查表,供你实际开发时对照排查。

5.1 问题速查表

现象可能原因排查方向
串口完全无输出启动模式拨码错、MIO引脚辅助功能没设对、DDR初始化失败检查拨码到QSPI/JTAG/SD对应位置,核对xsa中UART MIO绑定,串口终端波特率(通常115200)
有输出但卡在“DDR Initialization”DDR颗粒型号/位宽/时序参数配置错误去Vivado PS配置里核对Memory Part,查颗粒手册确认tRP/tRCD/tRFC等
U-Boot能启动但Linux起不来设备树中DDR大小与硬件不符、内核cmdline里mem=设过了检查设备树里memory节点,去掉cmdline里的mem=参数
QSPI烧写成功但复位不启动Flash型号不匹配、BOOT.bin缺失PL位流、拨码开关在JTAG模式看BootROM打印信息,确认Flash在QSPI模式,重新生成BOOT.bin
PL侧的LED全灭bitstream没加载、AXI GPIO地址错误在Vitis里确认地址是否和Address Editor一致,检查复位逻辑是否拉低
AXI总线读写卡死从设备地址不存在、读数据未准备好、时钟不稳在Block Design里看Address Editor地址范围是否越界,检查PL侧复位和时钟约束

5.2 调试三板斧:打印、跑马灯、抓波形

不管问题多复杂,我调试Zynq基本遵循“由近及远”三板斧。先打印,PS端程序里多留串口打印,启动流程走到哪一步一目了然,很多“启动不了”的问题都是这一步就能定位。再跑马灯,PL侧如果点亮LED正常,说明bitstream和PS-PL互联基本通了。最后抓波形,实在不行就上ILA(集成逻辑分析仪)在Vivado里抓AXI信号,看看握手到底是卡在ARVALID还是RVALID上。

这三板斧里,最难的是抓波形,因为它需要你提前把ILA核插入到设计中再综合一次,属于“事后没法补”的操作。所以我在写RTL时有个习惯:把所有对外AXI接口的握手信号单独引到一个调试模块里,平时空着,需要抓波形时用mark_debug指出来,这样就不用每次重新例化ILA了。这个习惯帮我省了大量综合时间。

5.3 一个值得养成的版本管理习惯

入门阶段代码改动频繁,一不小心就把“能用的工程”改坏了。我的做法是给工程建一个很小的流程:每个能启动的版本,马上用Vivado的Archive Project功能归档一份,文件名带日期和状态描述。比如“zedboard_ddr_ok_20250110”,改崩了就退回上一版本重来。别看这个习惯简单,它能让你在面对“昨天还能跑今天突然不行”的玄学问题时,十分钟内恢复环境,而不是去一行行查代码。

另外,Vivado生成的临时文件非常大,一个Block Design工程动辄几十GB。如果磁盘吃紧,注意定期清理仿真和综合产生的中间目录(比如.runs和.sim),这些可以随时重新生成,不用心疼。

6. 给入门者的一点学习路径建议

最后聊点实在的,关于怎么安排自己的学习节奏。我自己经历过的弯路就是太想一口气把所有概念搞懂,结果卡在U-Boot和内核配置上大半个月,最后回到“先把裸机最小系统跑通”才顺了。

我认为比较合理的Zynq入门三步走是:第一步用开发板跑通官方例程,比如Zedboard上跑通Hello World串口Demo,这个过程主要是建立“Vivado到Vitis”的整体流程感;第二步自己建一个定制工程,PS串口加上PL侧AXI GPIO点灯,完整走一遍xsa导出、FSBL生成、BOOT.bin打包,这个阶段要逼自己理解“为什么会需要FSBL”;第三步试着移植一个U-Boot和Linux,把设备树、根文件系统、内核启动参数串起来。到这一步,你对Zynq的掌握已经超过大多数只会在IDE里跑例程的人了。

过程中遇到不懂的概念,我推荐看两个资料:一个是UG585(Zynq-7000 TRM),虽然厚得像砖头,但它是所有启动流程、DDR控制器、AXI互联的权威来源;另一个是UG1165,里面有很多Zynq启动和调试的官方建议。你不需要从头读到尾,只需要在具体问题出现时去查对应章节。这种“按需查询”的效率,比从头啃手册高得多。

如果你手头是Ultrascale+的板子,对应的文档是UG1085(Zynq UltraScale+ TRM),同样是权威资料。概念上很多东西和Zynq-7000互通,掌握了老一代,换到新一代不会太困难。

Zynq这台“ARM + FPGA混动车”确实不那么容易上手,但一旦你熟悉了它的启动流程、DDR配置和AXI总线这三样核心东西,后面再接触RFSOC、Versal这类异构平台,你会发现很多思路都是相通的。学习过程中遇到“板子没反应”“串口无输出”的时候,先别怀疑硬件坏了,多半是启动模式、DDR参数或者BOOT.bin没弄对。按上面说的方式一步步排查,多数问题都能在半小时内水落石出。

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

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

立即咨询