ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Synplify与Vivado协同流程:带IP核FPGA工程的实践指南

Synplify与Vivado协同流程:带IP核FPGA工程的实践指南 做FPGA做过一段时间的人大概率都经历过这个场面Vivado装好IP核一个个生成RTL写完之后直接点综合一条龙下来好像没什么问题。但当工程里塞进五六个IP核时序收敛开始吃力或者你想在前端就把逻辑结构固定下来很多人又会把Synplify翻出来。Synplify和Vivado这两个名字放在一起经常被误以为是二选一的关系实际上它们是一条流水线上的上下游Synplify负责把RTL“揉”成门级网表Vivado负责在具体器件上做布局布线和出比特流。这篇文章我按自己的实战流程拆一下带IP核的FPGA工程里Synplify怎么和Vivado协作以及那些别人不常写进教程里的坑都在哪儿。1. 为什么我还要把Synplify搬回Vivado流程里1.1 它俩本来不是对手是上下游很多人刚接触FPGA时以为Synplify是Xilinx的老对手和Vivado二选一。其实这个理解从一开始就偏了。Synplify做的是综合Synthesis把Verilog/VHDL转成由LUT、FF、BRAM、DSP这些底层资源组成的门级网表Vivado做的是实现Implementation在具体芯片上做布局Place、布线Route、生成比特流。所以它们不是竞争关系而是前后级关系。那问题来了Vivado本身也带综合功能为什么还要单独用Synplify我用过的场景里最典型的几个理由是前端团队和后端团队分离RTL工程师希望综合阶段就能看到面积和时序的初步反馈而不需要每次跑到Vivado全流程。Synplify在某些大逻辑、多状态机的设计里综合策略和Vivado不一样尤其对状态机编码、寄存器复制、资源共享的处理更透明设计者可以通过属性和选项干预的粒度更细。团队里已经积累了一套Synplify约束和脚本体系换到Vivado综合意味着所有工程管理方式都要推翻。想对比两个综合器的结果看看相同RTL在时序或面积上到底差多少再决定最终用什么。说句实在话不是所有工程都值得折腾。零散的实验板项目、纯学习用的工程直接用Vivado一条龙流程最省心。但到了工程里有大量IP核、数据通路很宽、时钟域很多或者你需要在实现之前就固定出一份稳定的结构网表时Synplify这套额外流程带来的可控性就体现出来了。1.2 什么情况下值得用Synplify走一遍我自己判断一个工程要不要引入Synplify会拿四个维度快速过一遍。判断维度不适合用Synplify适合用Synplify工程规模几千行RTL单时钟域上万行RTL多时钟域多个IP核时序压力只求功能正确时序宽松接近芯片速度等级上限反复收敛不了IP核占比只有一两个简单FIFO有高速收发器、DSP、复杂总线接口类IP核团队协作一个人从头写到尾前端RTL、IP集成、后端实现由不同人负责如果四个维度里有两个以上指向右边那就值得在工程早期把Synplify流程搭起来而不是等到Vivado综合时序收敛困难时才想起来换工具。因为Synplify综合出的网表和Vivado综合出的网表结构不同前期已经做过的约束、时序例外都要重新调临阵换工具成本非常高。1.3 先看清楚代价再决定如果你是从Vivado原生流程转过来第一个要接受的现实是工程会比以前“多一层”。原来Vivado工程里直接放XCI综合时自动处理IP核现在Synplify不会直接解析XCI里的加密RTL你必须明确告诉它这个IP核是黑盒、是预综合网表还是透明RTL。这一步对习惯了GUI点点点的开发方式的人来说会很不适应但它是整个协同流程的核心。第二个代价是约束文件要维护两套。Synplify有自己的SDC方言Vivado用的是XDC。虽然两者都源自SDC标准但命令差异足以让你踩坑。不能指望把Synplify的约束文件原封不动丢给Vivado反之亦然。我见过不少团队导入Synplify后前一两周效率不升反降原因就是把大量精力花在“让两个工具互相理解”上而不是用在业务逻辑上。所以这个决定一定要建立在“工程确实需要”的基础上不要跟风。2. 带IP核的Synplify工程怎么把“黑盒”玩明白2.1 IP核在Synplify眼里的三种形态Synplify处理Vivado IP核本质上只有三种办法。第一种是黑盒。Synplify把整个IP核当成一个不透明的模块只保留端口定义综合时不做任何内部逻辑展开。IP核内部的时序、资源消耗Synplify一概看不见。优点是处理简单加密IP只能这么干缺点是综合报告里的面积和时序都很“假”必须等Vivado实现之后才知道真实结果。第二种是预综合网表。在Vivado里先把IP核用OOCOut-of-Context方式单独综合好导出一份EDIF或DCP网表然后作为已有网表喂给Synplify。Synplify在综合顶层时会把这份网表当成已实现单元挂接不需要再看IP核内部。这种办法比黑盒可靠因为IP核内部逻辑已经以门级网表形式存在端口和时序信息都能看到。第三种是透明RTL视图。如果IP核提供可综合的RTL源码或者那是你自己封装的IP直接把它添加进Synplify工程让Synplify像处理普通模块一样展开综合。听起来最完美但问题在于很多Xilinx加密IP核内部含有大量原语例化Synplify直接综合时可能产生意想不到的优化且加密文件Synplify根本读不了。所以我的经验排序是能拿到透明RTL的选透明RTL拿不到RTL的优先用OHCOut-of-Context预综合网表只有实在没有办法时才用纯黑盒。2.2 把Vivado生成的IP核挂进Synplify工程以Xilinx的AXI-Stream FIFO IP核为例走一遍典型流程。假设你在Vivado里已经生成好了IP核文件系统里会有ip_name.xciIP核配置工程文件ip_name_wrapper.vIP核的顶层例化包装注释掉或移除内部引用后wrapper可以当成普通Verilog模块在Vivado里右键IP核选择Generate Output Products综合选项选择Out of context per IP。这样Vivado会把该IP核单独综合成一份网表生成目录里的.dcp文件。此时再打开综合设计用write_edif导出一份EDIF网表比如axi_fifo.edf。接下来去Synplify工程把wrapper文件和EDIF文件都加进去# Synplify Pro Tcl示意具体命令以当前版本为准 project -new top_syn add_file -folder rtl -verilog rtl/top.v add_file -folder ip -verilog ip/axi_fifo_wrapper.v add_file -folder ip -edif ip/axi_fifo.edf set_option -top top impl -add impl_top impl -active impl_top project -save这里有个很容易犯的错wrapper文件里如果引用了加密的IP核模板Synplify会跑去解析那些读不懂的库文件报一堆语法错误。我习惯把wrapper里对加密IP实体的引用删掉只保留端口列表或者直接用Vivado的write_verilog -mode ports生成一份纯端口描述文件只给Synplify看端口不给它看内部实现。2.3 综合输出项里哪些才是Vivado要的东西Synplify综合完成后会生成一整批文件但Vivado真正关心的只有几个.edf或.edn门级网表这是最重要的输入。.sdcSynplify侧的时序约束Vivado需要转成XDC后才能完整使用。.srr综合报告主要给前端工程师查面积和时序用不参与实现流程。.stp一些版本里生成的综合约束文件情况和SDC类似不能直接当XDC用。在Vivado里创建一个新工程直接添加.edf文件然后添加top.xdc约束。如果要保留IP核单独的OOC网表可以把它对应的.dcp文件也加进工程但这时候要注意不要把Synplify导出的顶EDIF和Vivado的OOC DCP混用两套接口定义否则Vivado在启动实现时经常报端口不匹配或网表重复。最简单的方式是二选一要么顶网表和IP网表全部用EDIF要么顶层用Synplify EDIFIP核用同一个Vivado版本生成的DCP不要混搭。3. 协同流程里最容易翻车的四个环节3.1 IP核版本与工具链版本不一致我调试过无数工程最隐蔽的一个坑就是IP核版本和两侧工具版本不一致。Vivado升级后同一个IP核的XCI版本号会变端口定义也可能悄悄改。比如做高速收发器时用的Aurora 8B/10B IP核不同版本里gt_reset、reset、power_down这些引脚的时序和极性都可能不一样。你在Synplify里按老版本wrapper建立的黑盒端口到Vivado新版本里一加载网表端口对不上实现阶段就开始报错。这个问题的诡异之处在于功能仿真可以全绿综合能过只有跑到布局布线或上板后才发现真没工作。我现在的应对策略很死板工程开始前固定一组工具链版本组合把Synplify、Vivado、IP核版本矩阵记在工程根目录的README里。以后不管谁接手第一件事就是核对版本。版本不一致导致的IP核问题最可靠的解决办法不是硬调而是回到Vivado里重新生成一遍IP核再重新综合导出EDIF不要试图手工改端口。3.2 黑盒端口没对上Vivado实现阶段爆DRC“Vivado implement design变红”这个现象论坛里一问一堆我遇到最多的情况是Synplify黑盒IP的端口和Vivado侧网表端口没对上。Synplify综合时黑盒模块内部是空的它只按wrapper里的端口连接到顶层网表。如果wrapper端口位宽写错了或者某个输入漏接Synplify不会报错因为黑盒不参与逻辑推导。等到Vivado布局布线时DRCDesign Rule Check一查才发现这些端口悬空、无驱动或多驱动日志里就可能出现类似[DRC RTSTAT-2]这样的违规条目。我在实际工程里处理这类问题的步骤是先看report_drc输出找到违规网络的名字通常长得很像axi_fifo_inst/rd_rst_busy之类。在Vivado里打开综合网表定位到这个网络。回Synplify检查wrapper里对应的端口连接是不是有悬空或者方向反了。重点检查复位信号和状态输出它们最容易被忽略也最容易在版本升级后改变极性。排查这种问题时不要一上来就怀疑网表本身优先怀疑“端口契约”没有对齐。我后来写了一个小脚本解析wrapper端口列表和EDIF端口列表把名称、方向、位宽逐项比对每次导入网表前跑一遍省了大量无头苍蝇式检查。3.3 综合属性跨工具“水土不服”Synplify和Vivado各自有一套综合属性名字看着像含义却不一定一样。Synplify常用的syn_preserve、syn_keep、syn_ramstyle、syn_replicateVivado综合器不认。反过来Vivado里常见的(* keep true *)、(* use_dsp48 yes *)Synplify也未必生效。如果RTL里已经写满了Vivado风格属性再丢给Synplify综合很多属性会被当成普通注释网络可能被优化掉。我的做法是**对全局约束尽量用工具无关的方式实现比如真正需要保留的寄存器可以在RTL里用“加载一个值后永不更改”的结构去保证而不是只靠属性。Synplify独有属性统一写在一个单独的sdc或头文件里用include方式加载这样切换工具时容易隔离。跨工具流程里不要依赖属性来保留层次。Synplify一旦合并了跨层次逻辑后面Vivado做floorplan时会很痛苦。有次处理FFT IP核协作者在Vivado里设置了小数时钟输入IP核就是不肯工作最后排查下来发现IP配置里频率填成了非整数而MMCM/PLL生成的时钟频率又没完全对齐整条链路直到实现阶段才暴露。这种事和综合属性不直接相关但同样告诉我们IP核的时钟引脚不是普通逻辑信号它的配置和约束必须和IP内部要求严格一致Synplify只是“搬运工”不会替你校验这些。3.4 复位和时钟问题别等实现后再后悔“FPGA复位信号亚稳态”是热门话题在双工具协同里尤其突出。Synplify综合时如果复位逻辑写得不够标准或者复位信号跨时钟域综合器可能把它当成普通信号处理导致你期望的“全局复位”在实现后变成局部复位上板之后表现时好时坏。带IP核的工程里这个问题会放大。IP核本身有自己的复位逻辑比如高速收发器核往往要求外部复位必须保持一定拍数的有效时间而FIFO核有独立的写复位和读复位引脚。Synplify不会帮你保证这些引脚的复位时序它只知道端口连没连。所以我建议在顶层做一个复位同步模块统一生成所有IP核需要的复位信号而不是让每个模块各自处理自己的异步复位。这样Synplify综合时能清晰看到复位树结构Vivado实现时也方便做复位规划的约束。时钟也一样IP核的输入时钟尽量连到BUFG或全局时钟资源上否则实现时经常出现时钟资源相关的DRC错误。4. 约束如何一份拆两份不让SDC和XDC打架4.1 两者同源但方言不同SDC是Synopsys提出的标准Synplify和Vivado都声称支持它但真实情况是“同源不同方言”。Synplify的SDC里有很多自己的扩展命令比如define_clockVivado的XDC里没有Vivado的XDC虽然也是Tcl脚本但大量使用create_clock、set_clock_groups这种工具特定的命令。如果你把Synplify的SDC文件直接交给Vivado的read_xdc大概率会看到一堆“Unknown command”的报错。所以不要让两个工具直接共享同一个约束文件。我的方案是**主时钟约束、引脚约束、时序例外全部以Vivado的XDC为准。Synplify侧只保留最少量的综合约束比如虚拟时钟、路径分组、某些跨时钟域例外。两者的时钟命名尽量保持一致方便在Vivado里复用Synplify的分析结果。4.2 时钟约束与异步时钟组的正确翻译给出一组常用的对照关系Synplify SDCVivado XDCdefine_clock -name sys_clk -freq 100 -clockport sys_clkcreate_clock -name sys_clk -period 10.000 [get_ports sys_clk]set_false_path -from [clock eth_clk] -to [clock sys_clk]set_clock_groups -asynchronous -group [get_clocks eth_clk] -group [get_clocks sys_clk]set_input_delay 2.0 -clock sys_clk -port data_inset_input_delay -clock sys_clk 2.000 [get_ports data_in]翻译完不是结束关键要看两条约束的语义是否真的等价。比如set_clock_groups -asynchronous和set_false_path在效果上很接近但如果两侧都已经设过就可能造成约束叠加或冲突。我习惯在XDC里只选其中一种表达方式不两套同时写。带IP核工程里还有一个细节IP核自己的XDC里往往已经定义了它的内部时钟和异步关系不要随意在顶层XDC里重复覆盖这些路径。你只需要在顶层约束IP核的输入时钟来源内部的时钟关系交给IP核的XDC去管理。4.3 时序例外尽量不在综合工具里设综合器里的时序分析和实现后的时序分析差异很大原因很简单综合阶段不知道具体布局布线的延迟它只是预估。Synplify里设了一大堆false path、multi-cycle path看着时序报告挺好的但Vivado实现时发现这些例外引用的内部节点名根本不存在或者被综合器的优化改名了约束就打了折扣。我在协同流程里的习惯是Synplify侧只做“结构级”约束比如告诉工具哪几个时钟是异步的哪个引脚是虚拟时钟剩下的复杂例外全部放到Vivado的XDC里。尤其是跨时钟域路径如果用IP核或XPM原语已经做了同步处理直接在XDC里用set_clock_groups把它标为异步就完了不要在Synplify里画蛇添足。5. 从Synplify网表到Vivado实现完整跑一个可控流程5.1 用Tcl串起Synplify和Vivado手动在GUI里点流程工程一多就容易出错。我是把Synplify综合和Vivado实现都做成Tcl脚本每次只改参数然后一键跑。Synplify侧脚本大致长这样# 设置工程与器件 project -new fpga_top set_option -part xcvu9p-flga2104-2L-e set_option -top top # 添加RTL和IP网表 add_file -folder rtl -verilog rtl/top.v add_file -folder ip -verilog ip/axi_fifo_wrapper.v add_file -folder ip -edif ip/axi_fifo.edf # 设置综合选项 set_option -language v2001 set_option -vlog_std v2001 # 添加综合约束 add_file -folder cons -sdc constraints/synplify.sdc # 启动综合 impl -add impl_top impl -active impl_top project -saveVivado侧脚本大致长这样create_project prj_vivado ./prj_vivado -part xcvu9p-flga2104-2L-e # 导入Synplify网表和IP核网表 add_files synth_out/top.edf add_files ip/axi_fifo.edf # 添加约束 add_files -fileset constrs_1 constraints/top.xdc set_property top top [current_fileset] # 跑实现 launch_runs impl_1 -to_step write_bitstream wait_on_run impl_1脚本化最大的好处是可复现。调整一次IP核版本改个路径参数就能重跑整个流程不用每次人工去找综合产物在哪个文件夹。刚开始搭脚本确实麻烦但搭好之后省下的时间非常可观。5.2 实现完成后先看这四个报告拿到Vivado实现结果不要急着生成比特流先看四个关键报告。第一是Timing Summary。先看WNS最差负时序裕量和TNS总负时序裕量再按路径分类重点看有没有“穿过IP核内部”的失败路径。如果IP核内部路径失败通常要回IP配置去找问题而不是在顶层约束里硬调。第二是Utilization Report。核对IP核占用的LUT、BRAM、DSP是否在预期范围内。有时候Synplify综合时把黑盒IP的资源消耗算成零Vivado实现后资源突然爆掉不要惊讶这是黑盒流程的正常现象。第三是DRC Report。不只关注报错的条目警告级别的DRC也要扫一眼。特别是时钟资源、IO标准、复位相关的DRC很多上板后的诡异问题都能在这里找到苗头。第四是QoR Suggestion。Vivado会给出一些可选的优化建议比如调整物理约束、修改扇出、替换寄存器等。这个报告适合在时序已经收敛但还想进一步提升时看。5.3 一个DRC RTSTAT-2的真实排查链路拿我之前调过的一个工程举例。带了一个Aurora 8B/10B IP核和两个FIFO IP核Synplify综合很快Vivado导入网表后跑到place_design日志里出现类似[DRC RTSTAT-2]的违规implement design直接变红。我当时的排查顺序是这样的打开Vivado的DRC报告定位违规网络发现指向某个FIFO IP核的rd_rst_busy输出。回到Synplify的wrapper代码发现这个FIFO的rd_rst_busy端口根本没有连接悬空在那里。继续看wr_rst_busy虽然连接了但连接到了一个已经被综合器优化掉的中间信号导致整个FIFO的复位状态完全不可控。把两个复位忙信号都连接到顶层寄存器并加上syn_preserve属性避免被优化重新综合。再跑Vivado实现DRC通过比特流生成成功。这个案例里没有人故意写错逻辑纯粹是IP核端口在wrapper里漏连了。Synplify和Vivado对未连接端口的容忍度不一样Synplify觉得无所谓Vivado的DRC却很严格。所以我的习惯是每次从Synplify导EDIF之前先做一次端口完整性检查不要指望Vivado来兜底。6. 我在这条协同流程里保留的几个固定习惯6.1 版本矩阵和目录规划用Synplify和Vivado协同做带IP核的工程最怕的就是“线上文档没更新线下工具一堆乱”。我在所有工程里都固定维护一个版本矩阵。组件版本说明Synplify ProS-2021.03太老的synplify v8.4这种版本连现代Xilinx器件都不支持建议直接放弃Vivado2020.2与Synplify版本配套测试过IP核各自XCI版本每次升级后必须重新生成并校验目录结构也尽量统一fpga_prj/ rtl/ ip/ # Vivado IP核及导出网表 constraints/ # 顶层XDC和Synplify SDC scripts/ # Synplify和Vivado的Tcl脚本 output/ # 综合产物和比特流这样不管过了多久回头再看工程至少能快速定位“这是什么版本、哪些文件是哪个流程生成的”少走很多弯路。6.2 IP核重封装与统一接口我会给每个IP核再做一层wrapper不直接用Vivado生成的那层。统一接口包含一个参考时钟、一个异步复位输入、一组数据总线和valid信号所有IP核的复位同步、时钟启用逻辑都收口在这层wrapper里不让上层业务逻辑直接摸IP核引脚。这层封装的收益在双工具协同里尤其明显。Synplify综合时看到的是统一接口约束可以写得非常规整Vivado实现时如果某个IP核要调整配置只需要重生成IP核和这层wrapper顶层几乎不用动。我还在脚本里写了一个端口比对检查每次重新生成IP核后跑一遍发现端口数量或位宽变化会立刻报警省了大量手工核对时间。6.3 前期做结构预留比后期优化更划算用Synplify的人容易陷入一个误区把所有希望寄托在综合工具的优化选项上比如retiming、自动流水线。但实际上带IP核的工程里真正的收益来自结构本身。我更喜欢在RTL早期就把逻辑拆成控制平面和数据平面把IP核集中在数据路径里控制逻辑独立成模块。这样Synplify可以对控制逻辑做状态机优化对数据路径单独保留层次Vivado实现时也更容易做位置约束和物理优化。我自己实际维护这套协同流程三年多最大的感受是两个工具配合得好不好七成取决于前期规划三成才取决于后期调试。IP核版本、约束拆分、端口完整性这三件事搞顺了剩下的都是体力活。如果你正在被带IP核的FPGA工程折磨建议先别急着调工具参数把流程本身重新捋一遍往往问题就消失了一半。
返回列表