又到一篇笔记。第九篇,我不打算继续推 Verilog 语法,而是想聊聊怎么把一个写好的.v文件真正变成一个能跑在 FPGA 上的设计。不少读者私信问过类似的问题:功能仿真明明通过了,但怎么才能点亮板子上的 LED?答案就在这里——基于 Vivado 的 FPGA 工程创建。这篇笔记会从软件安装一直讲到生成比特流并下载到开发板,同时把约束文件、仿真、工程组织这些容易出错的细节一并过一遍。适合已经写过一些 Verilog、但还没完整跑通 FPGA 工程流程的朋友,也适合刚从 Icarus Verilog 转到 Vivado 的人,按顺序操作基本不会迷路。
1. 为什么我把“创建工程”单开一篇:工具链的思维转变
很多人在学习 Verilog 的初期,是在 Icarus Verilog、Modelsim 或者别的轻量仿真工具里做验证。写一个模块,写一个 testbench,编译、跑仿真、看波形,这一套顺手之后,突然切到 Vivado 会很不适应。因为 Vivado 不只是“仿真器”,它是一整套 FPGA 设计套件,涵盖综合、实现、时序分析、比特流生成乃至板级调试。它的工程概念比单纯一个源码文件复杂得多,所以需要一个专门的操作流程,这就是这篇笔记存在的意义。
1.1 从仿真器到完整设计流程的跨越
在 Icarus 里,你的世界里只有 RTL 代码和 testbench;在 Vivado 里,设计文件、约束文件、IP 核、仿真文件和组织脚本构成了一个工程。综合(Synthesis)会把 Verilog 代码映射成 LUT、触发器、DSP、BRAM 这些实际资源;实现(Implementation)负责将这些资源布局布线到 FPGA 内部;最后一步 Generate Bitstream 生成一个比特流文件,把它通过 JTAG 下载到板子上,硬件才会真正按你的逻辑运行。
这个思维转变很关键。功能仿真通过不等于硬件能跑。仿真器是基于理想时序的抽象模型,而真实 FPGA 有布线延迟、时钟偏斜、建立保持时间约束、IO 标准要求。所以工程创建不仅仅是一个“新建文件夹”的动作,更是一个引入完整设计流的过程。
1.2 Vivado 工程到底管理了什么
一个 Vivado 工程不是一个单个文件,而是一个目录树。默认情况下会生成.xpr工程文件、src源文件目录、sim仿真目录、constrs约束目录,以及runs综合实现目录。工程文件记录了你添加了哪些源文件,哪个是顶层,目标器件是什么,使用了哪些 IP 核,约束文件的优先级等等。
你以后修改代码、换板子、重跑综合,都是基于这个.xpr文件来组织的。所以工程创建的规范性,直接影响后续开发的效率。我见过不少朋友把几十个.v文件直接拖进一个文件夹,用include乱飞,最后在综合报告里根本看不懂哪个模块被例化了几次。工程创建时如果能把目录和文件角色规划清楚,后面会非常省心。
1.3 GUI 与 Tcl 脚本各解决什么问题
创建工程有两种方式:图形界面(GUI)和 Tcl 脚本。初学者推荐从 GUI 入手,因为能直观看到每个选项的作用。等你重复创建过三次以上工程,就会意识到脚本化的必要性——写一个create_project.tcl,以后每次重建工程只要source一下,五分钟恢复全部环境。这篇笔记以 GUI 流程为主线,但在最后一节会补一个脚本化创建的思路。两者并不互斥,我日常是先用 GUI 弄清楚选项,再沉淀成脚本。
2. Vivado 版本选择和安装:这些细节容易翻车
我写这篇笔记时的最新版本已经到了 Vivado 2025.1,不过 2023.1 之后的版本菜单结构基本一致,大家不必追求最新。重点在于版本满足你需要用的 FPGA 型号,以及安装时别踩那几个经典坑。
2.1 免费许可证够用吗
AMD/Xilinx 官方提供免费的 WebPACK 许可证,适用于大多数中小容量的艺术器件(Artix、Kintex 部分型号)和一些单芯片 Zynq 器件。对于学习用的 Artix-7 XC7A35T、XC7A100T、Zynq-7020 这类芯片完全够用。工程创建时如果提示没有 License,一般是因为你选了 Enterprise 版本禁止的器件,或者 License 没有配置好。解决方法是先确认自己在 Project Part 里选的芯片,再去 License Manager 看一下本机 WebPACK 状态。不需要也没必要折腾网上那种来路不明的授权补丁,官方免费版已经覆盖了绝大多数学习场景。
2.2 磁盘、内存和路径的硬性要求
这是我想放到前面说的一点,因为不少人是装到一半才后悔的。Vivado 完整版安装包大约 100GB/解压后占用 70GB 左右空间,即使只安装核心工具也会接近 30GB。内存方面,8GB 只能算入门,综合稍微大一点的图像处理模块,16GB 以上会更舒适。磁盘建议用固态硬盘,综合和实现的中间产物写入非常频繁。
路径问题是这篇系列笔记里最容易被忽略的“第一坑”:工程路径、安装路径千万不要放在包含中文、空格或特殊字符的目录下。Vivado 的 GUI 在某些中文路径下会诡异闪退,Tcl 命令报错,甚至生成比特流失败。我见过有人把工程放在D:\学习资料\FPGA实验\新建文件夹1,结果综合到一半直接崩溃。稳定做法是建立一个类似D:\fpga_workspace\project1的纯英文路径,所有工程统一管理。
2.3 安装过程中的小插曲
安装 Vivado 时,到组件选择页面,默认会勾选“Vivado HL WebPACK”、“Vivado HL System Edition”等一堆选项。不要全勾,除非你的磁盘真的很大。对于最常用的 RTL 开发,只需要勾选Vivado、Vivado HL WebPACK相关内容,以及Hardware Manager(调试和下载需要用到)。SDK 可以后面单独装,安装时可以先不勾选。另外,安装过程中可能会弹窗提示WinPcap安装失败,这个组件主要用于以太网调试和远程连接,不影响正常 JTAG 下载,跳过即可。网上总有人问这个错误,其实不需要处理。
还有一个老生常谈的问题:Vivado 怎么改中文。官方界面没有完整中文,网上所谓的汉化包基本都不靠谱,而且改了之后很容易出现信息缺失、报错定位困难。我的建议是直接接受英文环境,Vivado 的报错关键词本来就是英文,这样遇到问题也方便复制粘贴去搜索。
3. 从 Create Project 到目标器件:新建工程的完整路径
这一节是纯操作步骤,我尽量把每一步的界面选项讲清楚,并且告诉你为什么这么选。
3.1 工程名称和位置的规范习惯
双击 Vivado,在欢迎界面点Create Project,或者菜单File > New Project,进入向导。第一步写 Project Name,我建议尽量用能表达工程内容的名字,例如uart_rx_demo,而不是test1、project_final。工程路径选到一个专门放置工程的目录,比如D:/vivado_prj。注意不能勾选Create project subdirectory以外的那些杂项,除非你想把工程文件拆开。这个路径本身会成为工程根目录,后面所有源文件、子目录都围绕它组织。
3.2 Project Type:RTL Project 还是别的
向导会让你选择 Project Type,常见选项有RTL Project、Post-synthesis Project、I/O Planning Project等。初学者选默认的RTL Project,并且建议勾选下面的Do not specify sources at this time(先不添加源文件)。这样有一个干净的工程骨架,后续用 Add Sources 逐步添加文件,避免向导过程中误选文件。如果你是憋着一股劲想一次建完整,可以现场添加,但我的经验是工程骨架干净一点,后面好改。
3.3 Part Selection:目标器件的选择
到了 Part 选择界面,有两种模式:一种是直接选择芯片型号,另一种是按开发板选择 Board。如果你用的是开发板,可以直接在 Board 页里搜板的品牌,但因为 Board 文件可能需要额外安装,很多时候大家找不到,就转到 Part 页手动选。以常见的学习板为例,如果是 Basys3 或者 EGO1,芯片大概率是 Artix-7 XC7A35T-1CPG236C;如果是 ZedBoard,那就是 Zynq-7045 或类似型号。选中对应型号时,界面会显示资源用量:LUT、Flip-Flop、Block RAM、DSP、IO 数量,你可以大致评估自己的设计规模是否放得下。注意不要选错封装,例如 CSG324 和 CPG236 的引脚数量不同,后续约束文件会完全对不上。
3.4 默认 Summary 和空工程的样子
最后一步会列出一个 Summary,点击 Finish 后会看到 Vivado 主界面:左侧是 Flow Navigator,中间是设计源文件和工程总览,下方会出现 Tcl Console。一个刚创建的空工程,Sources面板只有一个主工程项,没有任何设计源。这时候不要急着写代码,先设置好工程属性:右键工程名,选Settings,在General里确认 Top Module 是自动还是手动,在Simulation里设置仿真器的编译选项。这些设置后面也会经常用到。
4. 设计源文件组织:RTL、Testbench 和 IP 怎么放才算规范
新建了一个空工程之后,接下来要把代码文件导入进去。这一步如果随随便便,后面的仿真、综合和团队协作都会变扭。
4.1 添加 Verilog 文件与目录约定
在 Sources 面板中点加号,选择Add or create design sources。你既可以从磁盘导入现成的.v文件,也可以在工程里直接创建新文件。这里有个建议:在工程目录下提前建好rtl、sim、constrs三个子目录,分别放置设计代码、仿真代码、约束文件。Vivado 虽然不会强制让你这样做,但参考工程维护经验,这是最直观的分类方式。比如你做一个 SPI ADC 采集模块,rtl/spi_adc.v、sim/tb_spi_adc.v、constrs/pin_adc.xdc,一目了然。后续如果扩展成图像处理、UART 传输,也只需要按模块再建子目录。
4.2 小心顶层设置:Set as Top
一个工程允许有多个.v文件,但必须指定一个顶层模块(Top Module)。Vivado 默认会自动推导顶层,但如果你文件里存在多个模块,或者一个文件里定义了多个 module,自动推导可能出错。正确做法是在Sources面板选中自己设计的顶层模块,右键选择Set as Top。这个动作很基础,但很多人忘了,导致综合时选错入口,报出一堆“模块未定义”的错误。
另外,命名规范最好统一。顶层命名不要再用top这种泛名,直接用功能名,例如spi_adc_top。测试模块则统一加上tb_前缀,这样在 Sources 面板里一眼就能和设计文件分开。
4.3 添加 IP 核的入口和文件管理
如果你的设计需要 PLL/MMCM 产生时钟、BRAM 做数据缓存、FIFO 跨时钟域,直接在 Sources 面板点加号,选择Add or create IP。Vivado 会打开 IP Catalog,搜索例如Clocking Wizard、FIFO Generator、Block Memory Generator。添加后 IP 会以.xci文件形式出现在工程中,综合时会被自动处理成网表。这里要注意:IP 核的实例化名和配置文件最好放在一个独立目录,例如ip/,不要跟 RTL 代码混在一起。否则一旦需要重新生成 IP,散落的文件很难清理。
4.4 仿真文件与综合文件分离的重要性
Vivado 会默认把 testbench 和 RTL 源文件混着显示,但仿真和综合是两套文件集合。在Sources面板顶部有三个页签:Design Sources、Constraints、Simulation Sources。仿真文件应出现在 Simulation Sources 下面,并且可以直接右键点Run Simulation触发行为仿真。如果把 testbench 混在设计源里,综合时会把不可综合的initial、#10之类语法也包进去,综合直接报错。我见过太多这种低级错误,本质上是文件分类没有做干净。规范做法是:RTL 文件只在 Design Sources,带tb_前缀的文件只加到 Simulation Sources,约束文件只放 Constraints 下,这是工程创建的核心纪律。
5. XDC 约束文件和引脚规划:不结束最后的自由意志
Verilog 代码告诉 FPGA 逻辑怎么连,但如果没有 XDC 约束文件,FPGA 芯片并不知道clk信号要连接封装上的哪个引脚。约束文件是 FPGA 工程里“最后一公里”。
5.1 为什么要物理约束和时序约束
XDC (Xilinx Design Constraints) 文件包含物理约束(引脚位置、IO 标准)和时序约束(时钟周期、输入输出延迟)。物理约束让sys_clk这个信号映射到板上的晶振引脚,让led[0]映射到 LED 对应的引脚;时序约束告诉工具时钟频率是多少,工具在布局布线时才能判断路径是否收敛。没有 XDC,就算综合成功,实现也可能因为找不到引脚而失败;没有时钟约束,时序分析就是个空壳。
5.2 用 I/O Planning 快速生成 XDC
手工记引脚编号容易出错,特别是开发板上有几十个引脚的时候。推荐先用 GUI 辅助:在窗口顶部选Layout > I/O Planning,或者在 Flow Navigator 中找到Open Elaborated Design后点击I/O Planning。打开后可以看到所有 IO 引脚,在旁边的引脚分配表里双击信号名,从下拉表格中选择要绑定的封装引脚和 IO 标准。完成引脚分配后,点击保存图标,Vivado 会生成一个默认的 XDC 文件并自动加入工程。这比手写set_property PACKAGE_PIN更直观。
对于最简单的实验板点灯工程,XDC 内容基本是两三行:
set_property PACKAGE_PIN P17 [get_ports {clk}] set_property IOSTANDARD LVCMOS33 [get_ports {clk}] set_property PACKAGE_PIN M14 [get_ports {led[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]引脚编号一定要查你自己的板卡原理图,千万不能照抄网上别人的板子。同一颗芯片的不同封装,引脚号完全不一样。IO 标准也要匹配板卡 bank 电压,别把 1.8V 的 bank 插 LVCMOS33,那样轻则采不到信号,重则可能损伤芯片。
5.3 手写 XDC 的常见坑
我见过不少手写 XDC 的教程,但最容易出错的地方是端口名不匹配。get_ports里的名字必须跟顶层模块的端口名一致,少一个字母都会导致错误。其次是通配符的误用,比如get_ports led[*]这种写法有时能对,有时会失效,建议尽量把每根引脚写清楚。还有一个经典问题是把 FPGA 厂商内部信号当成普通端口约束了,比如不同 bank 的时钟输入引脚的专用资源标记CLOCK_DEDICATED_ROUTE,如果你手写 XDC 去约束不存在的端口,布局时就会报错。
5.4 时钟约束:create_clock 与“跑起来的时序”
时钟约束是 XDC 里比较关键的部分,用create_clock定义时钟源。比如开发板用的是 100MHz 晶振,引脚接到了 FPGA 的 E3,那么至少要有:
create_clock -period 10.000 -name sys_clk [get_ports clk]period 是纳秒单位,100MHz 对应 10ns。写完这条约束之后,综合工具就会以 100MHz 为目标做时序收敛。如果你设计里用了 PLL 产生内部时钟,工具会通过内部时钟资源自动推导新时钟约束,不需要你再手动 create,但最好检查一下报告。
很多朋友在生成比特流失败后,看日志看到Timing constraint not met,一脸茫然。其实原因就是写了 create_clock,但实际逻辑延迟太大,布线后的路径达不到 100MHz。这时要么降低时钟频率约束,要么优化 RTL 代码。这是工程创建时就要建立起来的意识:XDC 不仅是一个“引脚映射表”,它还是你与 Vivado 沟通性能目标的合同。
6. 综合、实现、生成比特流:第一次跑通完整流程
这一步是整个 FPGA 工程创建流程的高潮,但我先给你打个预防针:第一次跑通很少是一遍过的。碰到报错不要慌,按报告逐层查。
6.1 三步流程分别做了什么
Flow Navigator 中依次有三个按钮:Run Synthesis、Run Implementation、Generate Bitstream。直接点最后一个按钮,工具会自动帮你跑前两步。但我建议初学者分步跑,因为你需要在每步之后翻阅报告。
综合负责把 Verilog 转换成器件原语网表。如果你用了不支持的语法,到这里就会报错。综合完成后,可以打开Synthesis Report,看看资源利用率、预估功耗、扇出最高的信号。重点看 Utilization 里有没有变成 999%、LUT 数量超不超。如果是这样,不是你工程创建错了,就是设计规模评估失误,或者综合策略太激进。
实现负责布局布线,把网表放到 FPGA 物理结构上,并满足时序约束。这一步通常最耗时。实现完成后,打开Implementation Report,里面不仅有资源利用率,还有详细的时序路径表格。如果出现Slack为负数,说明时序违例。这时你先不要急着调整 XDC,而是去查看是哪条路径超了,是组合逻辑链太长,还是跨时钟域没处理好。
6.2 生成比特流失败的最常见原因与排查顺序
“生成比特流失败”,这几乎是每个 FPGA 新手都遇到过的问题。我遇到过的原因,按出现频率排序是这样的:
- 实现后时序尚未收敛,但工具默认把比特流生成和时序检查挂钩,失败就会停在
write_bitstream步骤。解决方法是先回到 Implementation,重新看时序报告,必要时在Settings > Implementation > Bitstream里关闭-no_timing_driven?我一般不建议改这个选项,硬生成一个不收敛的比特流,硬件跑起来就是半随机。 - 引脚冲突或未分配引脚。设计里有 IO 信号没有在 XDC 中约束,实现会报
unplaced pins错误。排查方法是在I/O Planning界面里看所有端口是否都有绿色引脚标记。 - 器件资源不足。BRAM、DSP、LUT 超了,实现阶段会在映射时报错,这种只能精简设计或者换更大器件。
- 工程路径或文件路径存在中文空格,导致某个中间文件生成失败。这属于上一节说的工程规范问题,重新换个纯英文目录基本就解决。
查看报错时,我习惯先看 Messages 窗口的红色ERROR,再点开 Details 跳到具体源码行。很多看起来骇人的大段日志,其实关键信息只有一行。别去瞎搜“vivado生成比特流失败”,先学会自己定位报告的summary部分,比急着复制错误码更有用。
6.3 连接开发板并下载到 FPGA
比特流生成成功后,会生成后缀为.bit的文件。接下来点击 Flow Navigator 下面的Open Hardware Manager,点击Open target,选择你的 JTAG 连接方式。自动化一般会检测到开发板上的 FPGA 芯片,然后Program Device选择你刚生成的比特流文件。下载成功后,板子上的 LED 或数码管就能看到效果了。如果下载不成功,先检查 JTAG 驱动是否安装,开发板电源是否打开,或者换根 USB 线试一下。这些步骤听起来琐碎,但调试过程中我最常遇到的下载失败基本都和安全连接有关。
7. 仿真不止 Run Simulation:行为、综合后与实现后的三层递进
在工程创建完成之后,仿真其实是验证环节。但 Vivado 的仿真功能和其他工具有不少差别,值得单独说清楚。
7.1 添加 Testbench 并启动 xsim
在 Sources 面板的 Simulation Sources 里右键,点Add sources,把带有tb_前缀的 Verilog 文件加进来。然后选中仿真顶层,右键点Run Simulation > Run Behavioral Simulation,Vivado 会启动 xsim 仿真器并打开波形窗口。注意,第一次打开仿真可能速度会很慢,因为工具会做一遍预处理。启动后如果看不到波形,检查测试里是否给时钟加了forever #5 clk = ~clk;,给复位信号加了足够宽的脉冲,模块的实例名是否和 tb 中的线程一致。
7.2 设置仿真时间与波形观察
默认仿真时间是 1000ns,如果你的 testbench 逻辑比较长,可能只跑了一小部分。在波形窗口的Simulation菜单里点Run for...,输入2000 ns之类的时长,或者用Restart重新开始,然后再跑。观察波形时,可以点击信号,右键Radix切换为十六进制或二进制显示,这样比一堆电平高低直观很多。如果你仿真一个 UART 接收 RX 模块,txd波形里的串行字节流需要配合radix才能看清。
7.3 行为仿真和时序仿真的区别在哪
Vivado 提供三种仿真层级:Behavioral(行为仿真)、Post-Synthesis(综合后仿真)、Post-Implementation(实现后仿真)。行为仿真只验证逻辑功能,延时不真实;综合后和实现后仿真会加入门级延迟和布线延迟,更接近真实硬件。它们的作用场景不同:刚写完逻辑,快速跑行为仿真找功能 bug;综合实现后,再跑一次后仿真,看看有没有因为时序导致的问题。对于学习型工程,行为仿真已经覆盖大部分问题,后仿真耗时较长,等你有心排查顽疾时再用。
7.4 顺手说说 Icarus 与 Vivado 之间的工程差异
不少人的入门工具是 Icarus Verilog + GTKWave,它启动快,适合单文件小模块。但 Vivado 的 xsim 在 IP 核支持和后仿真方面更全,它能自动处理 VHDL/Verilog 混合仿真,也能直接例化工程里的 IP。所以从 Icarus 转过来的人,不需要每次都新建独立测试工程,直接在同一个 Vivado 工程里挂 testbench 即可。唯一的适应成本是 Vivado 的仿真工程本身也是工程的一部分,文件位置和编译顺序都跟着工程走,不能像 Icarus 那样一句命令行就全部搞定。
8. 工程组织中的好习惯:清理重跑、版本管理和脚本化
前面七节把“新建工程—加代码—写约束—仿真—下载”这条主线讲完了。最后一节想聊一些容易被忽略、但真正决定长期效率的工程习惯。
8.1 哪些目录可以删,哪些不能删
Vivado 工程目录里常有一堆.cache、.hw、.runs、.gen之类的自动生成目录。这些文件占地方且经常在版本管理里弄脏提交记录,很多人会问“工程怎么清理”。我通常只删.cache和.runs下生成的临时缓存,但不会删.xpr文件本身,也不会随便删src、constrs、sim这三个自有目录。
如果你要彻底清空中间结果重跑,最保险的方法是关闭 Vivado,手动删除工程目录中除了.xpr、src、constrs、sim、ip之外的文件夹,再重新打开工程,让工具重新综合实现。也可以使用菜单里的Project > Clean,但这仅清理运行产物。这种操作在修改顶层端口名、换芯片型号时需要用到。
8.2 用 Git 管源码,不管理生成物
FPGA 工程不像纯软件工程,runs目录动辄几百 MB,没有必要整体纳入 Git 管理。我的经验是:保留.xpr、src/、constrs/、sim/、ip/和*.tcl脚本文件,忽略*.runs、*.cache、*.hw、*.gen、*.jou、*.log这些自动生成文件。这样你和同事或未来的自己共享时,克隆下来后打开源码工程重新综合,时间和磁盘都会很省。.gitignore的另一个好处是避免大量冲突合并,每个工程刷新的中间文件各不相关,没有版本控制价值。
8.3 Tcl 一键创建工程:把整个流程脚本化
当你想再创建一个结构相同的工程时,完全可以不点 GUI。Vivado 在 Tcl Console 里支持create_project、add_files、add_files -fileset constrs等命令。还可以通过write_project_tcl把当前工程导出成一个 Tcl 脚本,以后再重建时,source ./project.tcl就能恢复工程结构。
举个例子,一个极简脚本:
create_project project_led ./project_led -part xc7a35tcpg236-1 add_files -norecurse ./rtl/led_top.v add_files -fileset constrs_1 -norecurse ./constrs/pin.xdc set_property top led_top [current_fileset] update_compile_order -fileset sources_1这样创建工程就不再依赖鼠标,也能在批量化、多版本复现时保持一致性。我觉得这是每个 FPGA 开发者迟早会主动拥抱的工作方式,但前提是把 GUI 流程跑熟。
8.4 沉淀一套自己的模板工程
最后分享一个经验:当工程创建流程稳定下来后,可以做一个自己的“空模板工程”,里面包含基本的时钟约束、LED/按键/开关的 XDC 引脚约束、一个能跑通的 top 模块和最小 testbench。以后任何新板子的功能验证,直接拷贝这个模板工程,再按需改引脚和顶层内容就行。很多 FPGA 工程师都会维护这么一套模板,而不是每次从 Create Project 开始一步步点,因为新建工程向导再熟练,也要几分钟,还容易在选择器件或路径时出错。
工程创建这件事,乍看只是一个“向导”,实际上它是你从纯语法学习进入硬件设计工作流的第一个分水岭。工具链的规范程度、约束文件的意识、目录与版本管理的良好习惯,都会从这里开始影响你后面的每一个项目。这篇笔记没有讲什么高级算法,就是把我自己在经历过若干次工程崩溃、报错和重删重建设后总结出的一些硬经验写下来。希望你在第一次尝试“基于 Vivado 的 FPGA 工程创建”时,能比我当年少几次折腾。