Zynq 实战 03|Vivado 基础设计流程:从空工程到点亮 LED 全流程手册
Zynq 实战 03|Vivado 基础设计流程:从空工程到点亮 LED 全流程手册
这是《Zynq FPGA 嵌入式系统设计实战》系列的第 3 篇。 板子:Pynq-Z2(XC7Z020-1CLG400C)。工具链:Vivado / Vitis 2023.2。 上一篇:Zynq 实战 02|开发环境搭建:Vivado / Vitis / PetaLinux 2023.2 安装避坑指南
0. 这一篇要解决什么问题
在前两篇搞清楚架构、装好工具链之后,现在要做第一件真正的事:跑通一个完整的 Vivado 设计流程。
目标非常具体:在 Pynq-Z2 的 PL 端,用 IP Integrator 搭一个 ZYNQ7 PS + AXI GPIO 的最小系统,让 PS 能通过 AXI 控制 4 个板载 LED(LD0~LD3)。
本文会讲什么:工程创建 → 板子文件配置 → IPI Block Design → XDC 约束 → 综合/实现 → 比特流生成 → Hardware Manager 烧录 → Tcl 重建脚本。
本文不会讲:Vitis 软件侧(写 C 代码控制 GPIO)放在下一篇,本篇只把硬件设计做到能下载进板子。
1. 工程类型先想清楚:别一上来就点 RTL Project
Vivado 新建工程向导第三步会问你 Project Type,新手一般直接选默认的 RTL Project,不会想太多。但实际上这里有个日后会反复影响你工作流程的决定。
主要三种类型对比:
| 类型 | 是否生成工程文件夹 | 综合方式 | 适合场景 |
|---|---|---|---|
| RTL Project | ✅ 生成 .xpr + 完整文件夹 | GUI 驱动 | 日常开发、有 IPI Block Design |
| Post-synthesis Project | ✅ | 导入已综合网表 | 拿到第三方 netlist 继续实现 |
| Project-less (Tcl) | ❌ 只有脚本 | Tcl 脚本驱动全流程 | CI/CD、可重现 build |
推荐的工程策略:用 RTL Project 做 GUI 开发,但同时维护一个 Tcl 重建脚本(本文最后一节讲怎么生成)。这样:
- 开发时有完整 GUI(方便 IP Integrator、时序分析、调试)
.xpr文件和 IP 缓存不用进 git(几百 MB 的编译中间产物没必要 commit)- 团队成员只需要
source rebuild_project.tcl就能从头重建整个工程
🚧 避坑:新手最常犯的错误是把整个 Vivado 工程文件夹
git add .,结果 repo 里多出几百 MB 的.runs/缓存。正确做法:.gitignore里排除.Xil/、*.jou、*.log、.runs/、.cache/,只 track 源文件、约束、IP 配置和 Tcl 重建脚本。
2. 创建 Pynq-Z2 工程(含 board file 正确安装姿势)
2.1 先装 board file,否则板子选不到
Vivado 默认不自带 Pynq-Z2 的板级描述文件。要在 Create Project 向导里的”Board”标签页选到它,你需要先安装。
方法一:Xhub 在线安装(推荐,2023.2 已内置)
Vivado → Tools → Vivado Store → Boards
搜索 "pynq-z2" → 点 Install
安装完成后,板子文件路径:
~/.Xilinx/Vivado/2023.2/xhub/board_store/XilinxBoardStore/boards/Xilinx/pynq-z2/
方法二:手动克隆安装
# 板子文件官方仓库
git clone https://github.com/Xilinx/XilinxBoardStore.git
# 把 pynq-z2 复制到 Vivado 搜索路径
cp -r XilinxBoardStore/boards/Xilinx/pynq-z2 \
~/.Xilinx/Vivado/2023.2/xhub/board_store/XilinxBoardStore/boards/Xilinx/
# 重启 Vivado 后,Create Project → Board 标签即可搜到
🚧 避坑:Vivado 2023.2 的 Xhub 在某些网络环境下(尤其日本国内代理)会超时。如果 Vivado Store 加载很慢,直接用方法二手动克隆。仓库地址:github.com/Xilinx/XilinxBoardStore
2.2 新建工程步骤
1. Vivado → Create Project → Next
2. Project name: led_gpio_demo
Project location: ~/Projects/zynq/
□ Create project subdirectory(勾上)
3. Project Type: RTL Project
□ Do not specify sources at this time(勾上,Sources 后面再加)
4. Default Part → Board 标签页
搜索 "pynq-z2"
选 "PYNQ-Z2" (TUL Embedded,版本 1.0)
5. Finish
选了 board 之后,Vivado 会自动知道:
- 目标器件是
xc7z020clg400-1 - PS 有 DDR3 配置、MIO 映射、时钟设定
- 后面 Block Automation 的默认配置会贴合 Pynq-Z2 的实际硬件
3. IP Integrator:搭 ZYNQ7 PS + AXI GPIO 最小系统
这是本篇最核心的一节。我们要在 IPI 里建一个最小系统:PS 通过 AXI GP0 总线控制一个 AXI GPIO IP,GPIO 的输出接到 4 个 LED 引脚。
3.1 创建 Block Design
Flow Navigator → IP INTEGRATOR → Create Block Design
Design name: system
→ OK
空白 Diagram 出现后,按 Ctrl+I(或点工具栏的 + 按钮)搜索并添加 IP:
Step 1:添加 ZYNQ7 PS
搜索 zynq,双击 ZYNQ7 Processing System,画布出现 PS7 块。
Step 2:运行 Block Automation
IP 添加后顶部会出现绿色提示条:
Run Block Automation
点击 → 选 All Automation → OK。
这一步做了什么:Vivado 读取你之前选的 board preset(pynq-z2.tcl),自动配置 PS7 的:
- DDR3 接口(512MB,400MHz)
- 外部 DDR 引脚(MIO 绑定)
FCLK_CLK0 = 100MHz- UART0 / ENET0 / USB0 / SD0 的 MIO 映射
这就是选 board 而不是直接选 part 的好处——不需要手动配 DDR timing。
Step 3:添加 AXI GPIO
再按 Ctrl+I,搜索 axi gpio,双击添加。双击 GPIO 块打开配置:
GPIO Width: 4
All Outputs: ✅(勾选,LED 是输出)
Channel 2: 不勾(我们只用 Channel 1)
→ OK
Step 4:运行 Connection Automation
顶部再次出现绿色提示:
Run Connection Automation
点击 → 全选 → OK。
Vivado 会自动:
- 在 PS7 和 AXI GPIO 之间插入 AXI Interconnect
- 连接
FCLK_CLK0→ AXI 时钟 - 连接
FCLK_RESET0_N→ 复位 - 分配 AXI 地址(GPIO 默认
0x4120_0000,4KB 空间)
Step 5:把 GPIO 的 IO 端口引出来
找到 AXI GPIO 块上的 GPIO 端口(绿色),右键 → Make External。
这会在 BD 边界创建一个叫 gpio_rtl_0 的外部端口,后面 XDC 里要引用这个名字。
Step 6:验证设计
Diagram 工具栏 → Validate Design(√ 图标)
如果出现 Critical Warning 说 gpio_rtl_0 没有约束,先忽略,XDC 里会补上。其他 error 才需要修。
Step 7:生成 HDL Wrapper
Sources 面板 → Design Sources → system.bd
右键 → Create HDL Wrapper
→ Let Vivado manage wrapper and auto-update → OK
这会生成 system_wrapper.v,作为顶层模块被 Vivado 综合。
🚧 避坑:不要手动编辑
system_wrapper.v——Vivado 每次修改 BD 后会重新生成它,你的改动会被覆盖。如果需要在 wrapper 层加逻辑,创建一个新的顶层文件,把system_wrapper例化进去。
4. XDC 约束:Pynq-Z2 真实引脚定义
XDC(Xilinx Design Constraints)是 Vivado 的约束格式,语法基于 Tcl,但文件名以 .xdc 结尾。
4.1 添加约束文件
Flow Navigator → PROJECT MANAGER → Add Sources
→ Add or create constraints → Create File
File name: pynq_z2_top.xdc
→ OK → Finish
4.2 Pynq-Z2 完整 LED 引脚 XDC
以下是基于 Pynq-Z2 官方原理图(Rev C)的真实引脚定义。Board 上 LD0~LD3 连接的 PL 引脚:
| LED | PACKAGE_PIN | IOSTANDARD | Bank |
|---|---|---|---|
| LD0 | R14 | LVCMOS33 | Bank 35 |
| LD1 | P14 | LVCMOS33 | Bank 35 |
| LD2 | N16 | LVCMOS33 | Bank 35 |
| LD3 | M14 | LVCMOS33 | Bank 35 |
## pynq_z2_top.xdc
## Pynq-Z2 (XC7Z020-1CLG400C) — PL User LEDs
## 参考:Pynq-Z2 Rev C Schematic, Sheet 6 (LED Connections)
## LD0 - PL_LED0
set_property PACKAGE_PIN R14 [get_ports {gpio_rtl_0_tri_o[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_rtl_0_tri_o[0]}]
## LD1 - PL_LED1
set_property PACKAGE_PIN P14 [get_ports {gpio_rtl_0_tri_o[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_rtl_0_tri_o[1]}]
## LD2 - PL_LED2
set_property PACKAGE_PIN N16 [get_ports {gpio_rtl_0_tri_o[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_rtl_0_tri_o[2]}]
## LD3 - PL_LED3
set_property PACKAGE_PIN M14 [get_ports {gpio_rtl_0_tri_o[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_rtl_0_tri_o[3]}]
端口名说明:gpio_rtl_0_tri_o 是 AXI GPIO IP 在 IPI 里生成的 tristate 端口名称。如果你 Make External 后 Vivado 给它起的名字不同(比如 gpio_rtl_0),打开 system_wrapper.v 查一下实际的顶层端口名称,改成对应的。
4.3 关于 create_clock 的说明
在纯 IPI 设计里,FCLK_CLK0 不需要在 XDC 里写 create_clock。原因:PS7 IP 的约束(.xdc 文件随 IP 一起打包)已经包含了对所有 FCLK 端口的时序约束声明,Vivado 在综合/实现时会自动读取它们。
如果你在做纯 PL 的 RTL 设计(不用 PS,用外部晶振),则需要:
## 仅在纯 PL 设计中使用外部时钟引脚时才写这行
## Pynq-Z2 没有直接接到 PL 的独立晶振(所有 PL 时钟来自 PS FCLK)
## 下面的例子是假设你有一个外部 125MHz 时钟输入
create_clock -name sys_clk -period 8.000 [get_ports sys_clk_p]
🚧 避坑:原资料里那个 XDC 例子直接写
set_property PACKAGE_PIN Y11和create_clock -name clk_fpga——Y11 在 7Z020 CLG400 封装里根本不是 clock-capable 引脚,综合时会报 Critical Warning,时序分析结果不可信。用 IPI + PS7 的设计,FCLK 时钟约束交给 Vivado 的 IP 约束机制,别自己乱写。
5. Synthesis 与 Implementation:流程差异与 Log 解读
5.1 综合(Synthesis)做什么
综合是把你写的 RTL(寄存器传输级描述)翻译成逻辑门级网表的过程。这一步和具体器件的物理位置无关——输出的 .dcp(Design Checkpoint)文件里是抽象的 LUT、FF、CARRY4 等原语,还没有分配到 XC7Z020 里任何具体的物理坐标。
综合后必看的两个报告:
-
Utilization Report(
report_utilization):看 LUT 使用了多少、FF 多少、BRAM 多少。如果这里显示资源超标(100%+),后面实现一定失败。 -
Timing Report(综合后):此时时序分析是估算(基于理想走线),不精确。但如果综合后 WNS(Worst Negative Slack)已经是 -5ns,实现后也很难过。
5.2 实现(Implementation)做什么
实现把综合网表映射到器件物理资源上,分四个子阶段:
| 子阶段 | 命令 | 做什么 |
|---|---|---|
| Opt Design | opt_design | 逻辑级优化(去除冗余逻辑、合并等) |
| Place Design | place_design | 把每个 LUT/FF 分配到具体的 SLICE 坐标 |
| Phys Opt Design | phys_opt_design | 基于真实物理位置做时序驱动优化 |
| Route Design | route_design | 用芯片内部互联资源走线,完成所有连接 |
实现后必看的报告:
# Timing summary — 最重要的
report_timing_summary -file timing_summary.rpt
# 看 WNS (Worst Negative Slack):
# WNS ≥ 0:时序通过,可以生成比特流
# WNS < 0:时序违规,需要优化(降频 / 改设计 / 加 pipelie)
# Utilization — 资源使用
report_utilization -file utilization.rpt
# Power estimate — 功耗预估
report_power -file power.rpt
Log 里你会反复看到的关键词:
TIMING-18:提示有时序违规的 INFOCRITICAL WARNING [Drc 23-20]:DRC 设计规则检查警告,很多是可忽略的,但 ERROR 必须修Slack (MET):时序已满足Slack (VIOLATED):时序未满足,后面跟着具体的路径
🚧 避坑:不要轻易忽略
TIMING-18。很多新手的习惯是”综合通过了就生成比特流”——但综合后的时序报告是估算,实现后的才是真实的。有过 WNS 在综合后 +1.2ns,实现后变成 -0.8ns 的情况,下载进板子后逻辑时不时出错,极难调试。养成习惯:实现完看一次report_timing_summary,WNS ≥ 0 才生成比特流。
6. 生成比特流与 Hardware Manager 烧录
6.1 生成比特流
Flow Navigator → PROGRAM AND DEBUG → Generate Bitstream
或者在 Tcl Console:
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1
比特流文件路径:
led_gpio_demo/led_gpio_demo.runs/impl_1/system_wrapper.bit
6.2 连接板子 + 烧录
Pynq-Z2 用 micro USB 连到 PC 的 PROG/UART 口(板子上标 “PROG”,不是 “UART”),跳线帽拨到 JTAG 位置(否则会从 SD 卡或 QSPI 启动,Hardware Manager 找不到 TAP)。
Open Hardware Manager → Open Target → Auto Connect
# 找到 xc7z020_1
右键 → Program Device
Bitstream file: .../impl_1/system_wrapper.bit
→ Program
或者 Tcl:
open_hw_manager
connect_hw_server
open_hw_target
set_property PROGRAM.FILE {./impl_1/system_wrapper.bit} [get_hw_devices xc7z020_1]
program_hw_devices [get_hw_devices xc7z020_1]
close_hw_target
烧录成功后,Pynq-Z2 上 LD0~LD3 的状态由 AXI GPIO 寄存器控制。此时 LED 可能全亮、全灭或随机状态——因为我们还没有从 PS 侧写软件,GPIO 的输出寄存器初始值是 0x0 或未定义。这是正常的,下一篇 Vitis 软件篇里会写 C 代码来控制它们。
🚧 避坑:烧录比特流后,你烧的是 PL 配置,不是板子的 Flash。断电后比特流会丢失,下次上电需要重新烧。要做到上电自动加载,需要把比特流和 FSBL 打包写入 SD 卡或 QSPI Flash——那是 boot 流程的内容,后面篇章讲。
7. Tcl 重建脚本:write_project_tcl 的妙用
这是整个流程里最容易被跳过、但长期来看最重要的一步。
Vivado 工程文件夹里有大量编译中间产物(.runs/、.cache/、.srcs/bd/.gen/),如果这些都进了 git,仓库会迅速膨胀到几百 MB,而且在不同机器上有路径问题。
正确做法:让 git 只 track 源文件和重建脚本,用脚本重建工程。
7.1 生成重建脚本
在工程跑通之后,在 Vivado Tcl Console 里执行:
# 切换到工程根目录
cd ~/Projects/zynq/led_gpio_demo
# 生成重建脚本,排除编译产物
write_project_tcl -force \
-no_copy_sources \
./scripts/rebuild_project.tcl
参数说明:
-force:覆盖已有文件-no_copy_sources:不把源文件复制到脚本,只记录引用路径(源文件已在 git 里)
7.2 用脚本重建工程
在另一台机器(或删掉 .xpr 文件后)恢复工程:
# Vivado batch 模式运行 Tcl 脚本
vivado -mode batch -source ~/Projects/zynq/led_gpio_demo/scripts/rebuild_project.tcl
# 或者在 Vivado GUI Tcl Console 里:
# source ~/Projects/zynq/led_gpio_demo/scripts/rebuild_project.tcl
脚本会重新创建 .xpr、重新关联所有源文件、重建 Block Design,恢复到和你当时一模一样的状态。
7.3 推荐的 .gitignore
# Vivado generated files
*.jou
*.log
*.str
*.xpr
.Xil/
.cache/
.runs/
.sim/
.ip_user_files/
ip_user_files/
# 保留这些 ↓
# scripts/rebuild_project.tcl ← 重建脚本
# src/ ← 自己写的 RTL
# constrs/ ← XDC 约束
🚧 避坑:
write_project_tcl默认生成的脚本里会有绝对路径(/home/kevin/Projects/...)。如果你要在团队里共享,在生成后检查脚本里的路径,把绝对路径改成相对路径,或者加一行set origin_dir [file dirname [info script]]作为基准路径。
8. 本篇你应该带走的几个判断
- 创建工程时选了 board 而不是 part,能享受自动 preset 配置,值得
- Board file 手动克隆路径:
~/.Xilinx/Vivado/2023.2/xhub/board_store/XilinxBoardStore/boards/Xilinx/pynq-z2/ - IPI 设计中 PS FCLK 时钟不需要在 XDC 里写
create_clock,PS7 IP 自带约束 - Pynq-Z2 板载 LD0~LD3 真实引脚:R14 / P14 / N16 / M14,LVCMOS33,Bank 35
- 综合后时序是估算,实现后才是真实的——养成实现后看
report_timing_summary的习惯 -
write_project_tcl是工程可重现、可版本控制的关键——每次修完 BD 就更新一次
9. 下一篇预告
下一篇 《Zynq 实战 04|Vitis 嵌入式软件入门:从 Hello World 到 AXI GPIO 控制 LED》,我们会:
- 在 Vitis 2023.2 里从本篇的硬件平台(
.xsa文件)创建 Platform + Application - 理解 BSP(Board Support Package)里的地址映射是怎么来的
- 写 C 代码通过
XGpio_DiscreteWrite()控制 LED 流水灯 - 通过 UART 打印调试信息,用 SDK Terminal 看输出
- JTAG 调试:断点、单步、寄存器查看
参考资料
| 文档号 | 名称 | 用途 |
|---|---|---|
| UG585 | Zynq-7000 SoC Technical Reference Manual | PS7 寄存器定义、AXI GP/HP 端口细节 |
| UG994 | Vivado Design Suite User Guide: Designing IP Subsystems Using IP Integrator | IP Integrator 完整操作参考 |
| UG895 | Vivado Design Suite User Guide: System-Level Design Entry | Block Design、HDL Wrapper 创建 |
| UG896 | Vivado Design Suite User Guide: Designing with IP | IP 核配置、约束管理 |
| UG901 | Vivado Design Suite User Guide: Synthesis | 综合策略、synth_design 参数 |
| UG904 | Vivado Design Suite User Guide: Implementation | 实现子流程、place_design / route_design |
| UG835 | Vivado Design Suite Tcl Command Reference Guide | write_project_tcl 等 Tcl 命令完整参考 |
| PG144 | AXI GPIO Product Guide | AXI GPIO IP 寄存器映射、配置选项 |
| XilinxBoardStore | github.com/Xilinx/XilinxBoardStore | Pynq-Z2 board file 官方仓库 |
这是《Zynq FPGA 嵌入式系统设计实战》系列第 3 篇。 本系列覆盖从架构、开发环境、AXI 设计、PetaLinux 到完整项目实战的全流程。 欢迎留言交流,踩过的坑越多,后面的坑越容易绕开。