ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vivado中FPGA网表文件生成实战:OOC综合与EDIF导出要点

Vivado中FPGA网表文件生成实战:OOC综合与EDIF导出要点 做FPGA工程交付或者多人协作开发的时候网表文件是个绕不开的东西。在Vivado里把某个模块从RTL代码综合成网表文件既能保护核心代码不被改动又能在不暴露源码的情况下让整个工程正常走完布局布线。这篇文章把我平时在Vivado里生成网表文件的实际操作和踩过的坑整理出来涵盖OOC综合、write_edif命令、约束配套、仿真验证这些关键环节适合做IP交付、团队协作或者单纯想缩短编译时间的工程师参考。1. 网表文件到底是什么为什么需要它1.1 网表文件的基本概念网表文件在FPGA开发里本质上是综合器对RTL代码进行逻辑综合之后产生的一份电路连接描述。它把Verilog或者VHDL描述的电路行为转换成了由查找表单元、触发器、DSP、BRAM、进位链这些底层资源组成的连接关系列表。Vivado里面常见的网表文件格式包括EDFEDIF网表通用交换格式、EDNXilinx专用的EDIF二进制格式、以及综合后生成的DCP文件DCP里除了网表还带有约束、综合策略等上下文信息。很多朋友一开始容易把网表和比特流搞混。比特流是布局布线之后生成的配置数据已经绑定到具体器件和具体管脚基本没有修改空间。网表则停留在“电路结构”这一层拿到网表的人仍然可以把它作为整个工程的子模块自己去分配管脚、写约束、做布局布线最终生成目标平台的比特流。所以需要给别人“半成品”时网表是比比特流更合适的交付形态。1.2 什么场景下我会选择生成网表文件我在实际项目里用到网表文件主要有这么几类场景。第一类是IP交付。公司内部有自研的算法模块比如某个通信协议栈或者视频处理核心不想把RTL源码直接发给合作方但又希望对方能在他们的工程里正常综合、布局布线。这种情况下把模块综合成网表交给对方对方只看到端口定义和时序约束接口内部实现完全不可见既能保住知识产权又保证了可集成的能力。第二类是团队并行协作。同一个工程里A同事负责算法模块B同事负责接口和系统集成。算法模块迭代慢、综合时间长B如果每次都要重新综合A的模块一天能浪费大半个小时。A把稳定版本综合成网表交付给BB在做自己的修改时直接把网表作为黑盒参与布局布线编译速度会有明显提升。第三类是第三方工具链配合。有些团队习惯用Synopsys、Cadence等综合工具做逻辑综合然后把综合后的网表导入Vivado做布局布线。这种流程在ASIC原型验证领域尤其常见。Vivado本身也支持直接读入EDIF网表配合约束文件完成后续流程。还有一类场景是复用一些固定功能模块比如DDR控制器、某些固定的接口模块。这些模块一旦调通基本不会频繁改动每次重新综合纯属浪费时间。把它们固定成网表后续版本里只处理增量部分能省出不少编译时间。1.3 生成网表前必须想清楚的几个问题动手生成网表之前先把需求想明白否则很可能会白干一场。第一个问题是交付粒度。你要交付的是一个完整的独立IP还是工程里的一个子模块如果是完整IP需要在顶层属性里设置好端口并且确保综合后的网表不依赖任何未解析的子模块如果是子模块要确认父模块里对它的例化方式、端口命名、参数传递是否兼容。第二个问题是时钟和复位怎么处理。网表只保留模块的对外端口时钟约束、生成时钟、异步复位等都要靠XDC文件来传递。也就是说交付网表时通常会附带一份端口约束或者时序约束文件明确告诉使用方这个模块需要多高的时钟频率、哪些引脚是时钟输入、哪些是复位输入。第三个问题是仿真验证怎么做。RTL代码可以直接做行为仿真但网表文件本身没法做行为仿真一般需要综合后的功能仿真模型或者门级仿真模型。如果你交付的是网表却不提供对应的仿真模型对方集成后只能做上板验证调试难度会明显增加。2. 生成网表文件的几种核心方式2.1 OOC综合模式最省事的自动产出方式Vivado默认的综合方式是Global综合也就是整个工程一起综合。这种方式适合最终实现但如果只是想为某个子模块生成网表效率很低。更常用的做法是给目标模块设置OOCOut-of-Context脱离上下文综合。OOC的含义是让这个模块脱离顶层工程上下文独立完成综合。Vivado会为它单独创建综合运行输出一个DCP文件。DCP文件里已经包含了该模块综合后的网表、综合约束、时序上下文等信息。后续顶层工程综合时直接把这个DCP当作黑盒调用不会重复对该模块内的逻辑做综合。设置OOC的方式有两种。一是在Sources面板里选中模块右键选择“Set as Top”然后在Flow Navigator里单独跑综合更标准的方式是在Sources面板里右键模块选择“Set Synthesis Options”在Mode里选“Out of context per IP”。设置好之后在Design Runs面板里会看到这个模块单独占用一个综合任务跑完会生成对应的.dcp文件。2.2 write_edif命令手动生成标准网表的最佳路径OOC自动产出的是DCP文件方便是方便但DCP的格式相对封闭别人拿过去基本只能在Vivado里用。如果你要做跨工具交付或者合作方不一定用Vivado做综合更稳妥的方式是用write_edif命令手动导出标准EDIF网表。write_edif命令在Tcl Console里执行核心用法非常简单# 打开综合后的设计 open_run synth_1 # 导出EDIF网表 write_edif -file D:/output/my_module.edf # 同时导出约束文件 write_xdc -file D:/output/my_module.xdc需要注意的是执行write_edif之前必须先完成综合并打开综合设计否则命令会报错。如果当前工程已经跑过布局布线打开的是布局布线设计同样可以先打开综合后的设计再导出。write_edif导出的网表是文本格式的EDIF理论上可以用任意EDA工具打开和查看兼容性比DCP好不少。除了EDIFVivado还支持用write_verilog和write_vhdl把综合后的电路以结构化的Verilog或VHDL网表格式输出。这种格式更适合做仿真验证也方便和原有的RTL工程做混合仿真。2.3 IP核和DCP文件里的网表如何提取很多朋友用的是Vivado内置的IP核比如FIFO、BRAM、MIPI接口、FFT等。IP核在Vivado里默认就会生成一个综合后的DCP文件通常位于工程目录下project.gen/sources_1/ip/ip_name/ip_name.dcp。如果你只是想拿到IP核的网表交付给第三方可以直接把这个DCP文件拷贝出去同时带上IP对应的XDC约束文件。但这里有个坑DCP里包含的网表不是普适的标准网表它是在特定器件和特定Vivado版本下综合生成的。换一个器件型号或者换一个大版本Vivado很可能直接报错必须用对应版本重新生成。如果是第三方综合工具生成的EDIF网表导入Vivado用的命令是read_edif。导入后Vivado会把EDIF网表映射到当前器件资源上然后正常走布局布线。这种流程下要注意网表里用的单元名称是否被当前器件系列支持有些过旧工艺的网表单元在7系列甚至UltraScale上已经找不到对应资源需要做单元映射操作起来比较繁琐。3. 实操过程在Vivado里一步步生成网表文件3.1 配置OOC综合并生成DCP网表我这里用一个具体的例子说明整个操作过程。假设工程结构如下顶层模块top.v负责系统集成不需要交付目标模块my_crypto.v需要交付的算法模块顶层约束top.xdc管脚约束、整体时序约束第一步打开工程后在Sources面板里选中my_crypto.v右键选择“Set Synthesis Options”在Mode一栏选择“Out of context per IP”。这一步也可以直接右键目标模块选择“Set as Top”然后用综合设置里的OOC模式效果一样。第二步在Flow Navigator里双击“Generate Design Runs”或者直接在Design Runs窗口右键选择“Create Runs”。确认列表里已经出现一个以模块名字命名的综合任务OOC标记为“Out-of-context”。第三步运行综合。如果工程比较小直接点击“Generate Bitstream”也可以但那样会连着实现一起跑。更好用的方式是只运行综合在Design Runs窗口里右键对应的综合任务选择“Launch Runs”让Vivado只做综合不跑实现速度会快很多。第四步综合完成后在工程目录的project.runs/module_synth_1/目录下就能找到module.dcp文件。这个文件就是包含网表的综合结果。这里有一个非常容易踩的坑如果在设置OOC时没有把模块内的所有子模块都包含进来或者某些子模块没有被识别综合会报“black box”错误。所谓黑盒就是综合器知道这个模块要被例化但不知道内部逻辑只能保留一个空壳。最终生成的DCP里对应路径就是空的集成后整个功能必然异常。所以生成网表后第一步要做的是检查综合报告里有没有黑盒警告。3.2 用Tcl命令导出EDIF网表和配套约束DCP文件适合在Vivado内部使用但如果你需要交付标准网表还是要导出EDIF。我的习惯流程是这样打开工程先在Flow Navigator里跑综合。综合完成后菜单栏依次选择“Open Synthesized Design”或者直接在Tcl Console里输入open_run synth_1 -name netlist_export然后查看一下当前综合设计的顶层信息确认端口、层次结构都正确get_current_design get_ports如果确认无误直接导出网表。这里我一般会同时导出三样东西EDIF网表、端口约束XDC、时序约束XDC。# 导出网表 write_edif -force -file D:/delivery/my_crypto.edf # 导出只包含IO约束的XDC write_xdc -force -file D:/delivery/my_crypto_pins.xdc -mode pins # 导出包含完整时序约束的XDC write_xdc -force -file D:/delivery/my_crypto_timing.xdcwrite_xdc的-mode参数很关键。pins模式只导出管脚相关的约束比如IOLocation、IOStandard不带mode参数时导出的是综合后设计里的全部约束包括生成的时钟约束。交付给对方的约束文件一般建议把两种分开让对方根据他们实际的系统环境重新分配管脚。另外有一个细节需要注意write_edif默认会把综合后的设计扁平化输出也就是不保留层次结构。如果你希望对方在网表里还能看到子模块层次可以在综合设置里打开-flatten_hierarchy为none或者rebuild然后在write_edif时通过-keep_hierarchy参数保留层次。这对调试和增量修改有一定帮助但网表文件体积也会明显变大。3.3 第三方综合工具的网表怎么导入Vivado用第三方工具综合出EDIF网表再导入到Vivado走布局布线的流程我简单说一下。这里以Synopsys DC产出的EDIF为例。在Vivado里新建一个空工程器件型号选好之后在Tcl Console里执行# 读入EDIF网表 read_edif D:/delivery/design.edf # 读入约束文件 read_xdc D:/delivery/design.xdc # 读入可能需要的FPGA底层原语库文件 # 这一步不是必须的视网表单元使用情况而定读完后设置顶层然后直接运行综合。Vivado检测到已经有网表读入会跳过逻辑综合步骤直接把EDIF网表映射到当前器件的资源上。接下来就是正常的布局布线流程。用第三方网表有三个高频问题。第一个问题是时序约束格式差异DC里写的是SDC格式Vivado支持SYNTHESIS XDC子集基本兼容但某些命令比如set_clock_latency、set_clock_uncertainty在布局布线阶段会被忽略需要整体检查一遍。第二个问题是原语映射DC综合时如果针对Xilinx器件调用了原语库比如BUFG、DSP48等导入时问题不大如果使用的是通用标准单元库Vivado可能在映射时浪费大量资源甚至报不支持错误。第三个问题是IO约束第三方网表里的端口名和你的实际管脚定义要严格匹配一旦名字对不上布局布线时Vivado会直接报错。3.4 生成网表后怎么验证完整性网表生成完之后验证这一步不能省。我的验证习惯分三层来做。第一层是编译检查。把生成的EDIF重新读入一个空工程跑一遍综合和布局布线看是否正常通过。这一步能过滤掉大部分格式、端口和约束问题。第二层是逻辑等效性检查用Vivado自带的逻辑等效性检查工具在综合设置里可以勾选或者用第三方形式验证工具对比RTL综合结果和网表综合结果在逻辑功能上是否一致。第三层是仿真验证用网表仿真模型跑一个小的回归测试确认关键功能点的行为没有变化。仿真这块需要多说一句。Vivado生成网表后如果用write_verilog导出结构化网表可以直接用Vivado Simulator或者ModelSim配合对应的仿真库做门级仿真。需要加载的仿真库包括UNISIMS_VER和secureip等通常位于Vivado安装目录的data/verilog/unisims下。门级仿真比行为仿真慢很多所以我的建议是抽选关键测试用例没必要跑全量回归。4. 常见问题与排查技巧实录4.1 “black box”黑盒问题这是我见过最多的问题。设置OOC或者手动导出网表后对方集成时报错说某个模块是黑盒内部没有实现。排查思路分几步看。先确认生成网表时该模块内部所有的子模块是否都存在并且可以被综合。经常有人写代码的时候例化了一个子模块但忘了把子模块的源文件加入工程综合器只能留个黑盒。解决方法是打开综合日志搜“WARNING”看有没有“cannot find definition of module”之类的提示。另外要确认生成网表的顶层模块没被标记为(* black_box yes *)这类综合属性。有时候为了做跨工具流程代码里故意对一些模块做了黑盒标记如果这些标记影响到目标模块导出的网表就是空壳。检查方法是在综合后的设计里执行get_cells -hier -filter {IS_BLACKBOX 1}如果输出为空说明没有黑盒如果有输出就要逐个排查。4.2 时序约束没有跟着网表走网表生成后时序约束是独立的。很多人只交付一个EDF文件对方拿过去跟顶层约束一配跑实现发现时序一团糟。原因通常是网表的时钟端口没有在顶层约束里被正确约束内部生成的时钟没有被识别异步跨时钟域约束缺失。我的解决办法是交付时附上端口约束和时序约束的说明文件。端口约束里要写明哪些是时钟输入、频率多少、接口电平标准是什么时序约束里要写明模块内部的时钟分组、false path和max delay设置。这样对方在集成时能快速对齐约束不会瞎猜。另外也建议在网表里显式地给时钟端口加上create_clock约束这样即使对方没写约束综合工具也会默认生成一个时钟约束至少不会出现未约束时钟的警告。4.3 生成网表后仿真模型不可用RTL阶段做行为仿真直接跑testbench就行。但网表交付后对方拿到的如果是EDF文件没法直接做行为仿真。我一般会在交付包里同时放一个Verilog格式的网表文件用write_verilog导出和对应的仿真脚本这样对方可以在Vivado Simulator或者ModelSim里跑门级仿真。门级仿真的核心前提是仿真库里要有对应器件的原语模型。Vivado安装目录下的unisims库已经包含这些模型不用额外下载。但要注意版本匹配用Vivado 2020.2生成的网表最好在接近版本的环境里仿真不同小版本之间偶发兼容问题虽然不多但遇到过一次接口定义不一致导致的仿真崩溃。4.4 网表交付后管脚不匹配管脚不匹配是集成阶段最容易报的问题。顶层工程里例化网表模块时端口名和网表里定义的端口名只要差一个字母综合都会报错或者产生悬空端口。这个问题在前期做好一件事就能避免生成网表后单独运行report_ports命令把网表的所有端口名、类型、位宽导出一份清单随网表一起交付。对方集成时直接用这份清单做接口映射基本不会出差错。另外如果你交付的是始终IP核的DCP还要注意DCP内部的网表端口和IP对外包装端口的区别。很多Vivado IP核的DCP只包含核心逻辑不包含AXI接口等外围逻辑外围逻辑仍然在顶层设计里。这时候直接交付DCP对方集成时根本看不到完整的IP端口。正确做法是把整个IP的output product全部交付包括包装层和综合后的DCP或者直接导出EDIF网表把IP打包成完整的模块再交付。5. 网表生成中的关键细节与进阶技巧5.1 编译速度优化哪些模块适合固定成网表如果只是想通过网表文件加速整个工程编译这里有一个实用的判断思路。打开Vivado的编译报告看看每个模块在综合阶段消耗的时间占比。那些综合时间占比高、代码改动基本为零的模块就是固定成网表的最佳候选。比如视频处理管线里的scaler、某种固定的纠错编码模块、接口协议栈等。把这些模块设成OOC模式后后续工程里它们的综合任务不会重复执行每次编译能省掉少则几十秒、多则十几分钟的时间。对于多版本迭代的项目积累下来的收益非常可观。5.2 保持层次结构方便后续调试Vivado综合默认会做层次展开把所有子模块的逻辑打平成一层。这样生成网表后内部逻辑全部混在一起上游逻辑和下游逻辑没有清晰的边界。如果后续对方在集成时发现时序问题定位起来特别痛苦。解决方法是综合前在Vivado的Synthesis Settings里把-flatten_hierarchy设置为rebuilt然后在write_edif时使用-keep_hierarchy参数。这样生成的网表会保留模块层次综合报告里可以按模块查看时序余量ORT也更容易定位问题。代价是网表文件会变大综合时间略有增加。5.3 版本兼容性Vivado版本和器件之间的匹配关系网表文件有一个隐性坑——它带着生成时的器件和工具版本信息。用Vivado 2020.2综合出的网表拿到Vivado 2024.1里打开大多数情况下能正常工作但偶尔会因为器件资源模型变化、原语名称调整等原因出现警告甚至错误。为了减少这种兼容性问题我的习惯是在交付文档里注明“此网表由Vivado 2020.2生成基于Kintex-7系列器件综合”。如果对方使用的Vivado版本和你的差异超过两个大版本建议让对方用自己手里版本的工具重新综合如果做不到至少要保证器件型号一致并且让对方仔细检查综合日志里的原语映射警告。5.4 配合增量实现追求极致的布局布线效率Vivado还支持一种更灵活的工作方式网表文件只参与综合后的编译不参与布局布线或者反过来。对于顶层工程里已经固定好的模块可以使用增量实现Incremental Implementation中的参考DCP机制。这种做法的思路是先用一个完整版本跑通实现保存参考DCP后续修改工程时让Vivado参考上一版的布局布线结果做增量式优化能显著缩短实现时间。如果你的交付目标和增量实现配合使用可以把模块的OOC DCP和参考DCP都提供出来。这样既保护了源码又方便对方在实现层面做时序收敛优化属于比较大气的交付方式。5.5 网表文件的体积控制网表文件有时候会非常大特别是复杂的SoC模块一个EDIF文件可能达到几十甚至上百MB。在交付时可以考虑把网表和约束文件压缩打包同时在文档中把目录结构、文件清单写清楚避免对方不知道哪个文件是干什么的。如果交付的模块涉及多个独立功能也可以拆分成多个网表文件每个文件对应一个功能子模块这样对方在集成时能更灵活地替换或调整。不过拆分越多端口映射工作量也越大这个平衡点要自己根据项目情况把握。6. 网表文件使用中的常见误区6.1 误把DCP当成可以跨工程直接使用的网表DCP文件虽然包含网表但它更准确的说法是“上下文相关的综合结果”。同一个DCP文件换一个工程的顶层约束换一个器件甚至换一个Vivado小版本都可能无法直接使用。很多人拿到一个IP的DCP想直接塞到另一个工程里当子模块用结果发现端口对不上、器件类型不匹配折腾半天还是报错。正确思路是如果你是模块生成方尽量交付标准的EDIF网表再配一份XDC约束如果你是模块使用方优先向对方索取EDIF网表这样才能保证后续集成时足够的灵活性。6.2 忽视网表仿真模型导致联调失败网表交付的真正诀窍在于“成套”。只有网表文件没有仿真模型对方只能上板测试功能调试全靠猜。要是网表和RTL行为在某些边界条件下不一致定位起来就是灾难。所以交付网表的时候我会同时提供标准EDIF网表用于集成结构化Verilog网表用于门级仿真一份端口定义清单用于管脚映射一份约束文件说明用于时序收敛一个简单testbench示例用于验证网表集成是否正确。这样对方拿到手之后不需要额外沟通就能直接把网表跑起来整个交付过程的摩擦会小很多。6.3 不做DRC检查就匆忙交付Vivado在布局布线之后会做DRC检查但网表生成阶段很多时候并不会暴露所有问题。对方拿过去做实现时如果DRC报的是物理实现相关的问题比如时钟资源缺失、复位信号走线违规、PLL配置错误就特别浪费时间。减少这类问题的办法是在生成网表后自己先跑一遍完整的布局布线故意让DRC问题暴露在自己的环境里。先解决掉一批实现层面的DRC问题再交付网表对方的集成体验会好很多。如果交付周期紧至少要跑一版布局布线确认没有致命DRC报错再发出去。7. 项目实战一次完整的网表交付流程回顾拿我之前做过的一个视频处理模块交付来举例。这个模块负责把输入的YUV420数据做缩放、色彩空间转换和HDR色调映射整个RTL代码大概有一万多行综合时间在五分钟左右。合作方不想拿到源码但需要用在他们自己的图像处理系统里。第一步我把模块代码整理干净确认所有子模块都包含在工程里综合报告无黑盒警告。第二步设置OOC综合单独跑一遍综合拿到DCP文件。第三步打开综合设计用write_edif导出EDIF网表用write_verilog导出Verilog网表用write_xdc分别导出管脚约束和时序约束。第四步我把导出的EDIF网表重新读入一个新建的空白工程选同一个器件型号跑一遍完整布局布线确认DRC没有报严重错误。第五步做了一轮门级仿真用几个关键测试向量确认功能正常。最后把这些文件打包附上端口清单和集成说明文档发给合作方。合作方后来反馈集成过程很顺利从拿到网表到跑通布局布线大概只花了一个小时主要时间花在管脚约束对齐上。没有出现黑盒、端口不匹配或者时序异常的问题。这个流程看起来简单但每一步都有不少细节。尤其是约束文件的整理不同项目的约束风格差异很大一个负责任的交付方会在约束文件里用注释把每个约束的含义写清楚而不是丢一个裸的XDC文件出去就不管了。8. 实用经验网表交付前的检查清单我习惯在交付网表之前过一遍自检清单这里分享出来供大家直接拿去用。综合日志里是否有“black box”、“undefined module”等警告是否有未约束的时钟端口write_edif导出的EDIF文件能否在独立空白工程中正常读入导出的Verilog网表能否正常完成门级仿真XDC约束文件里是否包含必要的create_clock、set_input_delay、set_output_delay和false path端口清单是否与网表实际端口一致网表文件生成时使用的Vivado版本和器件型号是否记录清楚是否附带完整的集成说明文档和仿真示例。这八条是我踩过不少坑之后总结出来的每次交付前对照检查能省掉大量的来回沟通成本。网表文件生成这件事从根本上说就是一次“模块封装”。封装得好不好直接决定合作方集成的顺利程度和使用体验。从工具操作、约束配套、仿真验证、文件打包到文档说明每一步花的时间都不会白费。平时在项目里多留一份心把成熟的模块沉淀成标准网表交付包对整个开发流程的效率提升是很明显的。
返回列表