FPGA新手在Xilinx Vivado里第一次调IP核的时候,十有八九会被同一个问题卡住:Customize IP对话框点完OK,回到主界面,Sources窗口里那个IP核图标旁边挂着一个黄色的感叹号,右键弹出来的菜单里有Generate Output Products,也有Create HDL Wrapper,到底先点哪个?我在带新人的时候,这个问题的出现频率高得惊人,网上搜到的答案大多是“先Generate再Create”,但很少有人把为什么讲清楚。这篇文章就专门把这两个操作拆开揉碎,把顺序背后的依赖关系解释透。你看完就能理解Vivado这套流程的逻辑,而不是死记硬背“先点这个再点那个”,遇到版本变化、IP改动、报错排查的时候,也不至于抓瞎。
1. 这两个操作分别解决什么问题
1.1 Generate Output Products:把IP核变成能用的“半成品”
要理解Generate Output Products,得先想清楚一个问题:IP核在Vivado里到底是什么形态?
你在Customize IP界面里配置了半天参数,点完OK,其实只是在工程里写下了一条“我要用某个IP,参数是这样这样”的记录。这个时候,Vivado只知道你需要这个IP,但还没有为你准备后续综合(Synthesis)、仿真(Simulation)所需要的实际文件。Generate Output Products干的就是这件事——把这些配套文件全部生成出来。
具体来说,它主要产出了这几类东西:
- 综合相关文件:IP核的网表文件(.dcp)和时序约束(.xdc)。综合器需要这些文件才能把IP放进整个设计里。
- 仿真相关文件:IP核的行为级仿真模型,通常是Verilog或VHDL文件,Modelsim、Vivado Simulator都靠它们进行功能仿真。
- 例化模板:也就是后缀为.veo或.vho的模板文件。Verilog工程生成.veo,VHDL工程生成.vho。这个文件里已经把IP核的端口声明和例化模板写好了,后面Create HDL Wrapper要用到的核心内容就是它。
- 部分IP还会附带示例设计和XDC约束示例,方便你了解该IP在板级上的接线方式。不过这些通常不会自动加入工程,需要手动确认。
打个不严谨但好懂的比方:IP核本身是一个“内容物”,Generate Output Products把它打包成了贴着标签、写着说明、可以装进快递盒的半成品。你接下来不管是自己手写顶层模块去例化它,还是让Vivado帮你生成Wrapper,都得以这些产物作为基础。
有意思的是,很多新手容易把“IP核定制完成”误解为“IP核已经准备好可以综合了”。实际上,如果你定制完IP直接去Run Synthesis,Vivado大概率会报错或者弹提示,告诉你某些IP没有生成输出产物。这个状态你可以在Sources窗口里看到,IP核的名称旁边会有一个黄色感叹号,Status列写着“Needs generation”,就是这个意思。
1.2 Create HDL Wrapper:给IP核套一个“标准外壳”
Generate Output Products解决的是“文件齐不齐”的问题,Create HDL Wrapper解决的则是“IP核怎么挂进设计里”的问题。
在Vivado里,一个IP核不能单独成为整个设计的顶层。你要么自己写一个顶层模块,在代码中手动例化这个IP核,把一个个端口信号连好;要么让Vivado帮你自动生成一个包装文件,把例化代码写好,这个文件就叫HDL Wrapper。
打开Create HDL Wrapper对话框之后,你会看到两个选项:
- Let Vivado manage wrapper and auto-update:让Vivado自动管理这个Wrapper文件。你之后修改了IP核参数、重新生成输出产物,Vivado会自动同步更新Wrapper里的端口和例化代码,不需要你手动去改。
- Copy generated wrapper to allow edit:复制一份Wrapper文件到工程里,允许你手动编辑。
绝大多数情况下,默认的“Let Vivado manage wrapper and auto-update”就是最优解。但很多新手就是因为不理解这个选项的含义,选了第二个,又在Wrapper里手动加了信号,结果后面IP一更新,Wrapper里的例化代码和端口对不上,排查起来一头雾水。这个坑后面专门讲。
Create HDL Wrapper本质上是从IP核的例化模板出发,生成一个顶层HDL模块,里面把IP核实例化好了,对外暴露的端口和你配置出来的IP端口完全一致。生成完以后,你还需要在Sources窗口把这个Wrapper文件右键设置为顶层(Set as Top),整个设计的顶层才算确定下来。
1.3 这两个操作和“顶层模块”是什么关系
很多新手做完Create HDL Wrapper后一脸茫然:明明我已经有Wrapper了,为什么还要Set as Top?按理说,Vivado不是应该自动把新建的Wrapper设为顶层吗?
实际上Vivado并不会自动这么做。它只是生成了一个Wrapper文件,但你的工程可能还有其他源文件,也可能你已经有了一个旧的顶层模块。Vivado无法判断你打算用哪个作为真正的顶层,所以需要你手动指定。这一步漏掉的后果就是:综合的时候Vivado找不到顶层模块,或者综合出来的顶层根本不是包装了IP的那个模块。
另外要说清楚,Create HDL Wrapper不是唯一的选择。很多有经验的工程师不喜欢用Wrapper,习惯自己手写一个顶层模块来例化IP核。这个做法在纯Verilog工程里完全可行,而且灵活度更高。但对于新手来说,我建议老老实实用Vivado生成的Wrapper,理由有两个:一是自动生成的代码标准、清晰、不容易写错端口;二是电脑帮你生成的例化代码,信号列表和端口宽度一定是对得上的,而你手写的时候一旦少打一个信号或者位宽搞错,排查起来要命。
2. 为什么顺序一定是先 Generate 再 Create
2.1 从依赖关系看执行顺序
回到核心问题:为什么必须先Generate Output Products,再Create HDL Wrapper?
我之前说了,Create HDL Wrapper的本质是根据IP核的例化模板生成一层包装。而这个例化模板——也就是.veo/.vho文件——恰恰是Generate Output Products的产物之一。换句话说,Create HDL Wrapper是“消费”数据的,Generate Output Products是“生产”数据的。先有生产,后有消费,这是一个很朴素的依赖关系。
如果你还没Generate,就直接点击Create HDL Wrapper,会发生什么?
- 部分Vivado版本(比如2020.1之后)会把Create HDL Wrapper按钮置灰,直接不让你点。
- 有些版本允许点击,但会弹出一个警告框,提示你当前IP还未生成输出产物,需要先运行Generate Output Products。
- 即便你强行生成了Wrapper,由于没有例化模板作为依据,生成的Wrapper可能内容不完整,端口丢失或者格式错误。后续你在工程里看这个Wrapper文件,会发现里面只有模块声明,IP核例化部分缺失或者残缺。
所以不要跟Vivado“讨价还价”。在FPGA开发流程里,Generate Output Products是前置步骤,Create HDL Wrapper是后续步骤,这个顺序不是Vivado拍脑袋定的,而是文件依赖关系决定的。
2.2 如果先 Create 再 Generate 会发生什么
我特意在“能用的版本”和“不能用的版本”两种情况下都试过,把经验写在这里,方便大家遇到类似问题时心里有底。
在较早的Vivado版本(比如2018.x)里,先Create HDL Wrapper是可以点下去的,Vivado会在弹出的提示里提醒你先执行Generate Output Products。但不少新手误以为这个提示只是一个“建议”,点了Yes继续生成Wrapper,结果后面综合报错才回头看。这时候的Wrapper文件大概率是一个空壳——顶层模块名字有了,端口是空的,IP例化代码也不存在。你后续再回去Generate Output Products,这个Wrapper也不会被自动修复,因为Vivado认为你已经有一个Wrapper了。你需要右键选择Re-create HDL Wrapper让它重新生成,或者删掉旧的Wrapper再来一次。
在较新的版本(2020.1及以后)里,Create HDL Wrapper基本被锁定在Generate Output Products之后,你还没Generate的时候,这个选项是灰的,没法点。这种情况下Vivado相当于帮你强制规范了顺序,但如果你不明白背后的道理,依然会困惑:为什么这个按钮不是亮的?是不是我哪里配置错了?
还有一个常见误区是:先Generate Output Products后,再创建Wrapper,结果在Wrapper里看到了很多奇怪的信号(比如IP核内部的控制信号、状态信号),新手容易怀疑自己是不是操作错了。其实这是正常的,IP核的端口在定制界面里显示的是经过封装的用户端口,但在Wrapper的例化代码里,某些IP会暴露额外的控制端口,这是IP本身的设计决定的,不影响使用。
2.3 Wrapper 与顶层模块之间的版本协同
还有一个容易踩坑的点在于:Wrapper生成之后,并不是一劳永逸的。
IP核的参数一旦修改(比如把FIFO的数据位宽从8bit改成16bit,把时钟频率从100MHz改成150MHz),IP核的状态会重新变成“Out of date”。这时候你必须重新执行Generate Output Products。
如果你是让Vivado管理Wrapper(选了Let Vivado manage),那么重新Generate之后,Wrapper的端口会自动同步更新。如果你是复制了一份Wrapper手动编辑(选了Copy generated wrapper to allow edit),那么Vivado不会自动改你的Wrapper,新生成的例化模板和你的Wrapper文件就出现了版本不一致。端口没变还好,顶多内部IP版本号不同,还能凑合综合;端口一变,你手改的Wrapper就和实际IP接口对不上了,综合报错是必然的。
所以,如果你没有特殊理由,一律选择Let Vivado manage wrapper and auto-update。选择手动编辑这个选项的真正用途,是你需要在Wrapper内部添加额外的逻辑,而不只是单纯例化IP。关于这一点,后面实操部分再展开说。
3. 完整实操流程走一遍
3.1 从添加 IP 到生成比特流的标准步骤
下面以Xilinx Vivado为例,走一遍完整的流程。无论是做数码管动态显示、图像采集、MIPI接口还是简单的流水灯工程,只要用到了IP核,这个流程都是一模一样的。
**第一步:新建工程并选择器件。**这个就不赘述了,器件选型按你的开发板型号来就行。
**第二步:添加IP核并配置参数。**在Flow Navigator里点IP Catalog,搜索你要的IP。比如做数字时钟显示就用Clocking Wizard,做数据缓存就用FIFO Generator,做ADC采集就用XADC。双击IP,进入Customize IP界面,配置好参数,点OK。
**第三步:Generate Output Products。**在Sources窗口的IP Sources标签下,右键刚添加的IP核,在菜单中选择Generate Output Products。弹出的对话框中,Synthesis Options选择Out of context per IP(默认选项),Run Settings勾选Synthesis和Simulation两项,点Generate。等待右下角的进度条跑完,IP核状态变为“Generated”。
提示:不建议在Synthesis Options里选Global。Global模式会让IP和整个设计一起综合,每次改动顶层逻辑,IP也要跟着重新综合,白白浪费时间。Out of context模式是让IP单独综合成网表,顶层综合时直接调包,大幅减少综合时间。
**第四步:Create HDL Wrapper。**右键IP核,选择Create HDL Wrapper,弹出对话框中保持默认选项Let Vivado manage wrapper and auto-update,点OK。
**第五步:Set as Top。**在Sources窗口里,选中生成的Wrapper文件,右键,选择Set as Top。这一步千万别漏。设置成功后,Wrapper文件会变成粗体,表示它是顶层模块。
**第六步:添加用户文件。**如果你已经写好了逻辑代码(比如数码管扫描模块、图像处理模块),以Add Sources的方式加入工程。这些文件会成为Wrapper下的下一级模块,被Wrapper例化或者和Wrapper并列。
**第七步:添加约束文件。**新建或添加XDC约束文件,把引脚约束和时钟约束写好。
**第八步:Run Synthesis、Run Implementation、Generate Bitstream。**按顺序跑完这三个环节,生成比特流,下载到FPGA里验证。
整个流程看起来步骤多,但真正和本文主题强相关的就是第三步、第四步和第五步。很多人做到第三步就被“Generate Output Products”卡住,其实只要理解了它是给IP准备文件,你的思路就清晰了。
3.2 修改 IP 之后如何正确同步
FPGA开发中,IP参数修改是家常便饭。比如做图像处理时,图像分辨率从640x480改成1920x1080,FIFO的深度就要跟着改;做通信接口时,时钟频率调整也会牵连到Clocking Wizard的配置。
修改IP参数的方法是:双击Sources窗口里的IP核图标,重新打开Customize IP界面,改好参数后点OK。这时候你会注意到,IP核的状态又变成了“Out of date”,而且Flow Navigator里可能弹出一个提示条,提醒你有IP需要重新生成输出产物。
正确的处理步骤是:
- 右键IP核,重新执行Generate Output Products。
- 如果你使用的是Let Vivado manage wrapper(默认推荐),Wrapper会在输出产物重新生成后自动更新。你可以在Wrapper文件里看到端口已经变成了参数修改后的新版本。
- 如果你的IP核是被你自己的顶层模块例化的,你还需要手动去更新顶层模块中的例化代码,把新增/删除的端口信号补上或去掉。
这里有一个非常典型的坑:修改IP参数后,明明已经重新Generate了,但综合还是报错,说某些信号找不到。排查时发现,是因为自己写了顶层模块例化IP,而例化代码里的端口还是旧版本。对于这种情况,建议直接看看IP核目录下生成的.veo文件,里面有最新的例化模板,对照着修改自己的顶层代码即可。
3.3 批量处理多个 IP 的实用技巧
实际工程中,一个设计往往不止一个IP。比如一个图形显示项目,可能同时用到Clocking Wizard、FIFO Generator、Block Memory Generator、XADC等四五个IP。
批量操作的时候,技巧是:按住Ctrl或Shift多选IP核,然后右键统一执行Generate Output Products。Vivado支持对多个IP同时生成输出产物,会逐个处理,你不用一个个点。生成的时候留意右下角的进度,有时会弹出OOC综合窗口,这是IP在单独做综合,属正常现象,不要关掉它。等它跑完,你会发现后面的顶层综合时间短很多,这就是OOC的魅力。
如果你想把某个IP的例化模板找出来手动参考,可以在工程目录下的<project>.gen/sources_1/ip/<ip_name>/<ip_name>.veo路径下找到,用文本编辑器打开就能看到规范的例化代码。
4. 顺序搞错怎么办:常见问题与排查技巧
4.1 新手最容易踩的 5 个坑
我把带新人时遇到的共性问题整理成一张速查表,方便大家遇到问题直接对照。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| Create HDL Wrapper按钮是灰的 | 还没先生成输出产物 | 先执行Generate Output Products再创建Wrapper |
| 创建了Wrapper但综合报找不到顶层 | 没有把Wrapper设为Top | 右键Wrapper → Set as Top |
| 修改IP参数后综合报错,信号丢失 | 老版本Wrapper没自动更新 | 右键IP重新Generate,再右键Wrapper选择Re-create HDL Wrapper |
| 仿真报错找不到IP仿真模型 | Generate时只勾选了Synthesis,没勾Simulation | 重新Generate,勾选Simulation选项 |
| Wrapper里自己加了逻辑,IP一更新全乱了 | 创建Wrapper时选了Copy generated wrapper to allow edit | 删掉手动改过的Wrapper,重新创建并选用自动管理模式 |
第五个坑值得多说两句。很多新手看到“Copy generated wrapper to allow edit”这个选项,以为选了它才有权限加自己的逻辑,其实不然。在Let Vivado manage模式下,Wrapper文件虽然被标记为自动管理,但你仍然可以打开它查看代码。如果你想在顶层加一些自己的逻辑,完全可以在Wrapper下面再添加一个自己写的模块,不需要直接在Wrapper文件里改。倘若你真的需要在Wrapper内部写逻辑,就要接受“IP更新后需要手动同步Wrapper”的代价,这一点要想清楚。
4.2 一个真实排查案例
有一次我在做图像帧缓存工程,用Vivado 2019.1,工程里挂了一个FIFO IP和一个Clocking Wizard。原本综合、布线都正常,后来因为分辨率调整,我把FIFO的数据位宽从24位改成了32位,深度也翻了一倍。改完参数后,我直接点了Run Synthesis,结果综合到一半报错,提示找不到fifo_generator_inst/din[31:24]这样的信号。
排查思路是这样的:先看错误信息里提到的信号确实出现在FIFO例化模板里,新版本的din端口已经是32位了。再看自己写的顶层文件,例化FIFO的那一段代码还是旧的24位版本。问题就出在:我没让Vivado管理Wrapper,而是自己手写顶层例化IP,改IP参数后忘了同步顶层代码。
我当时没有用Vivado的自动Wrapper,而是手写了一个顶层文件。处理办法是打开IP核的.veo模板,照着新的端口定义把顶层文件里的例化代码更新了一遍,然后重新Generate Output Products,再Run Synthesis就通过了。
这个案例说明一个道理:无论你是用自动Wrapper还是手写顶层例化,都离不开一个“同步更新”的环节。区别只在于自动Wrapper把这个环节交给Vivado,手写顶层则靠你自己记住。新手阶段,用自动Wrapper能少踩很多雷。
4.3 几条实操心得
最后分享几条我自己的实操心得,算是多年用下来最值得记住的经验。
第一,养成固定顺序的肌肉记忆。不管什么版本、什么IP,永远是先Generate Output Products,再Create HDL Wrapper,最后Set as Top。这条链路熟悉以后,你会觉得Vivado的很多操作其实很有逻辑,只是最初上手时少一个全局视角。
第二,修改IP后注意看状态和提示。Vivado在IP需要重新生成输出产物时,会在IP核旁边显示状态标记(Out of date之类),同时界面顶部会弹出更新提示。不要直接忽略这些提示,它们其实是在告诉你流程的下一步是什么。
第三,仿真输出在Generate的时候直接勾上。很多跟着教程做仿真的人会发现找不到仿真模型,折腾半天,其实只是因为Generate Output Products时没勾选Simulation选项。一步到位,后面少很多事。
第四,不同Vivado版本的按钮位置和名称有差异,但逻辑骨架是一样的。2020.1之后Create HDL Wrapper的入口可能更隐蔽,但依赖关系没有变。你看懂了依赖关系,换什么版本都不会慌。
最后再补充一点:如果你用的是Altera(Intel)的Quartus环境,对应的概念是“Generate HDL”和“Create Symbol File for Current File”,命名和Vivado不一样,但思考方式一致——先把IP的文件生产出来,再让IP参与顶层设计。工具可以换,思路是通用的。
根据我个人经验,FPGA开发中很多让人挠头的问题,本质都是没搞清工具的操作逻辑。Generate Output Products和Create HDL Wrapper的先后顺序,是你接触IP核之后遇到的第一个典型流程问题。把这两个操作的关系吃透,后面再遇到更复杂的流程控制(比如增量编译、调试配置、版本管理),你就不容易一头雾水。希望这篇能把你的困惑解决掉,让你少走一点弯路。