ARTICLE DETAIL

资讯详情

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

Vivado综合后门级网表导出与第三方仿真指南

Vivado综合后门级网表导出与第三方仿真指南 Xilinx 的 Vivado 里Run Synthesis 这个按钮大概是所有 FPGA 工程师点得最多的一个。大多数人点完看一眼时序报告、看一眼资源占用然后直接 Run Implementation 去等 bit 文件。但很少有人意识到综合这一步产出的门级网表gate-level netlist本身就是一个可以单独拿出来用的完整交付物——它能喂给 VCS、Questa、Xcelium 做仿真能交给做静态时序分析的同事能拿去做等价性检查也能作为一个加密模块交给客户。Vivado 使用教程里讲仿真的文章很多讲综合后生成门级网表的却往往只有一两行命令真正落地时会发现文件少了一堆、仿真器起不来、跑出来全是 X。这篇就按我自己从零跑通的那套流程把门级网表的导出、配套文件、仿真器接线和踩过的坑一次说清楚。我第一次做这件事是因为一个很现实的场景设计要交付给另一家公司对方只有 VCS 和 Verdi没有 Vivado license但需要在我们这块板子上跑仿真验证接口协议。给 RTL 不合适因为里面有些模块不方便开放给 bit 文件对方又没法仿真。最后选的方案就是综合后导出工艺映射过的门级网表配上一份 SDC 和必要的黑盒 stub对方在自己的仿真环境里跑通了。整个过程前后折腾了两天问题几乎全都出在配套文件没带全和仿真库路径不对这两件事上。1. 综合之后Vivado 手里到底攒出了什么东西1.1 从 RTL 到门级网表中间那一步真正做了什么理解网表先要理解综合这一步的三段结构。Vivado 拿到 RTL 后第一件事是 elaborate把 HDL 展开成一张通用逻辑网此时还是与工艺无关的加法器、选择器、触发器第二件事是做逻辑优化把常量传播、公共子表达式、移位合并、状态机重编码这类和具体器件无关的化简做掉第三件事才是工艺映射technology mapping把前面那堆通用逻辑硬塞进目标器件真实存在的资源里——六输入查找表 LUT6、各类触发器 FDRE/FDCE/FDSE、进位链 CARRY4、DSP48E1、块存储器 RAMB18/RAMB36端口上再插 IBUF/OBUF/IBUFDS、时钟上插 BUFG/BUFH。映射完成之后Vivado 内存里就有了一张原语级的网表。它里面的每一个实例都是 Xilinx 器件手册里真实存在的 cellLUT 的 INIT 参数就是它对应的真值表触发器的初值也写死在参数里。这份东西已经没有任何 always 块、没有任何行为描述纯粹是实例和连线的集合。打个厨房的比方RTL 是菜谱写的是把肉炒到变色门级网表是按这个厨房现有厨具改写后的操作清单写的是用 3 号锅、中火、翻 12 下而 bit 文件是真正把锅烧热、食材下锅之后的那盘菜。网表知道用什么锅但不知道锅摆在灶台哪个位置——布局布线才决定位置位置决定走线长度走线长度决定延时。这就是为什么综合后的网表和最终时序之间永远隔着一层。1.2 一张综合网表里藏着哪些信息打开一份导出的网表文件你会看到三类东西。第一类是原语实例形如LUT6 #(.INIT(64h...)) u_alu/lut_0 (...)每一行都是器件里一个实实在在的单元。第二类是连线wire声明加上端口连接网表里的连线名往往是综合工具自己生成的_i_1、n_327、alu_op_reg[2]__0这种可读性谈不上但能追。第三类是参数LUT 的 INIT、触发器的 IS_C_INVERTED/INIT、DSP 的运算模式、存储器的初值全都固化在参数里。除了网表本身一次完整的导出还会带出一串配套文件缺一个都可能在别人机器上跑不起来。下面这张表是我实际交付时用的清单可以直接对照检查。文件典型后缀作用缺失后果门级网表.v/.vhd设计本体什么也做不了时序约束.sdc时钟、IO 延时、例外路径时序仿真没有约束、STA 无基准延时信息.sdf单元和网络的延时反标时序仿真退化成功能仿真存储器初值.mem/.mifBRAM 上电内容存储器读出全 X 或全 0黑盒 stub*_stub.v没有仿真模型的 IP 占位编译报 module not found仿真库unisims_ver等原语的仿真模型编译报找不到 LUT61.3 default、funcsim、timesim、synth_stub四种模式别选错write_verilog的-mode有四个取值很多人第一次导网表就是在这里选错导致后面一路报错。它们不是随便起的名字对应的是四种完全不同的用途。default模式导出的是设计本体原语名字干净、层次和网名尽量保留适合交给第三方做综合、做静态时序分析、做等价性检查或者做二次集成。funcsim模式面向功能仿真会在网表里补上仿真器需要的初始化构造配合unisims_ver库使用。timesim模式面向时序仿真会插入 SDF 反标的钩子配合simprims_ver库使用。synth_stub模式不导出实现只导出一个空壳模块声明端口但不含逻辑专门用来给加密 IP 或者暂时不开放的黑盒占位。模式配套仿真库典型用途能不能再综合default不需要交付、STA、等价性检查可以funcsimunisims_ver功能仿真、跑通激励不建议timesimsimprims_ver SDF时序仿真、检查建立保持不建议synth_stub无黑盒占位、IP 接口交付作为空壳可以我在交付那条线上用的是default模式加-rename_top因为对方要把我们的模块当成一个子块挂到他自己的顶层里去模块名得跟他接口文档对齐。而跟仿真组对接时用的是funcsim两边需求不一样导两份文件是最省事的做法别指望一份文件通吃。2. 从 open_run 到一份能直接交付的文件包2.1 先把综合结果打开GUI 和 Tcl 各走一边不管走图形界面还是脚本第一步都是把综合后的设计加载到内存里。工程模式下综合跑完之后Vivado 内存里其实什么都没留必须显式打开open_run synth_1这条命令执行完当前 design 就变成了综合后的网表get_cells、get_nets、report_utilization全都作用在这张网表上。图形界面里对应的操作是点开 Synthesis 那一栏里的 Open Synthesized Design进去之后 File 菜单下能看到导出相关的入口。这里要说句实话GUI 的导出菜单在不同版本里位置和名字都动过2020.2 和 2022.x 就不完全一样所以我后来干脆全部用 Tcl脚本存下来换版本照跑。打开之后建议先做一次自检确认网表状态正常再导出# 确认综合状态 get_property STATUS [get_runs synth_1] # 看一下资源方便和导出的网表回读结果做对比 report_utilization -file ./synth_util.rptget_property STATUS返回synth_design Complete!才说明综合是干净收尾的。如果这里返回的是synth_design ERROR或者带 warning 但被忽略的状态导出的网表可能是不完整的后面仿真时会出现莫名其妙缺模块的报错排查起来非常费劲。2.2 write_verilog 的参数一个一个说明白核心命令就一条write_verilog但它有一堆可选参数每个都对应一个真实的痛点。我常用的形态是这样write_verilog -force -mode default \ -rename_top top_gl \ -default_nettype none \ ./netlist_out/top_synth.v-force表示覆盖已有文件脚本反复跑的时候不加它就会报错停下。-mode前面已经说过。-rename_top会把导出文件里顶层模块的名字改掉交付时对方接口文档里写的模块名是top_gl用这个参数就不用事后手工改网表——手工改网表是大忌改错一个名字整张网表就废了。-default_nettype none会在文件开头插入default_nettype none任何漏声明就使用的线网会直接报错而不是默默生成一根隐式 wire这个开关能帮你抓出网表里的悬空连接。还有几个参数值得知道。-no_net_names会省掉网名文件体积能小一些但代价是全部变成匿名线出了问题根本没法定位我自己从来不用。-no_vlog_keywords和-no_sv_magic是处理标识符转义的当你的信号名和关键字撞车比如信号叫out工具会写成\out这种转义形式个别老仿真器解析不了这两个开关是用来缓解兼容性问题的遇到编译报奇怪的语法错误时值得试一下。-sdf_anno和-sdf_file则是给timesim模式用的会在网表里直接插入$sdf_annotate任务让仿真器自动反标省掉命令行参数。一个容易忽略的点是-port_mapping。当网表要被别人当子模块集成时端口顺序、端口类型这些信息越明确越好这个参数会把端口映射信息写进文件里对于手工检查接口一致性很有用。2.3 约束和 SDF什么时候能写写出来的东西能信几分write_sdc和write_sdf这两条命令前提都是当前 design 已经过了综合。在 RTL 阶段跑write_sdc会直接报错那时候要用的是write_xdc。这是新手最容易卡住的地方明明命令是对的就是跑不过原因就是没open_run synth_1。# 约束转换XDC 语法转成 SDC 语法 write_sdc -force ./netlist_out/top_synth.sdc # 延时信息 write_sdf -force ./netlist_out/top_timesim.sdfwrite_sdc出来的是标准的 SDC 文本create_clock、set_input_delay、set_false_path这些都在里面交给第三方 STA 工具可以直接读。要留意的是XDC 里那些器件相关的物理约束在这份文件里是留不下的SDC 本身也没有对应语法属于正常现象。真正需要摆正心态的是write_sdf。综合后的 SDF 里只有单元延时cell delay没有网络延时net delay因为线还没布。这里的单元延时来自 Vivado 内置的器件延时模型在特定的输入转换率和输出负载假设下算出来的估计值。它能反映这个路径大概有多长但绝对不能拿来签核。表格里这个对比值得记一下阶段SDF 内容可信度适用场合综合后单元延时估计值无走线延时参考级提前发现明显的时序结构问题实现后单元延时 布线延时接近签核级最终时序验证我自己的做法是综合后的 SDF 只用来做一件事就是跑一遍时序仿真看有没有明显的建立时间违例。如果综合后就有大把违例说明 RTL 结构本身有问题这时候回头看代码比继续往下跑更有价值。反过来综合后全过也不能说明什么布线后的走线延时才是大头。2.4 存储器初值和那些容易被忘掉的附带文件块存储器的初值是个特别典型的坑。RTL 里写的initial块在综合时会被翻译成 BRAM 参数里的 INIT_00 到 INIT_3F 这一堆值这些值在网表里是带上了但很多仿真器在跑网表的时候并不会自动加载。这时候需要额外导出一份存储器信息文件write_mem_info -force ./netlist_out/top_mem.mem这份.mem文件配合仿真库里的存储器模型一起用仿真开始时把内容预加载进去存储器读出来才是你在 RTL 里写的那些值而不是一片 X 或者一片 0。IP 的部分同样容易漏。Vivado 生成的 IP 在综合网表里通常变成一个黑盒引用因为 IP 的实现细节尤其是加密 IP不会出现在网表里。交付时必须把 IP 目录下生成的仿真源文件一起打包通常在工程目录/工程名.srcs/sources_1/ip/ip名/下面能看到.veoVerilog 例化模板、.vhoVHDL 模板以及仿真用的.v文件。如果某个 IP 出于保密原因不能给仿真模型就需要用synth_stub模式导一个空壳出来顶上write_verilog -force -mode synth_stub ./netlist_out/secure_ip_stub.v2.5 一份可以照抄的导出脚本把上面这些拼起来就是我目前每个项目都在用的导出脚本。放在综合完成之后用source跑一下文件全齐。# ---- 综合后网表导出脚本 ---- open_run synth_1 set out_dir ./netlist_out file mkdir $out_dir # 备份一份资源报告回读时对比用 report_utilization -file $out_dir/synth_util.rpt report_timing_summary -file $out_dir/synth_timing.rpt # 1) 交付用网表干净、可读、可再综合 write_verilog -force -mode default \ -rename_top top_gl \ -default_nettype none \ $out_dir/top_synth.v # 2) 功能仿真网表 write_verilog -force -mode funcsim \ $out_dir/top_funcsim.v # 3) 时序仿真网表自动插入 SDF 反标 write_verilog -force -mode timesim \ -sdf_anno true \ -sdf_file ./top_timesim.sdf \ $out_dir/top_timesim.v # 4) SDF 与 SDC write_sdf -force $out_dir/top_timesim.sdf write_sdc -force $out_dir/top_synth.sdc # 5) 存储器初值 write_mem_info -force $out_dir/top_mem.mem puts netlist export done: $out_dir有个细节要提醒-sdf_file里写的路径是会被原样写进网表里的仿真器执行反标的时候是相对于它自己的工作目录去解析这个路径。所以最好写相对路径并且约定好仿真工作目录和 SDF 文件的相对位置否则会出现文件明明在仿真器说找不到的情况。3. 让第三方仿真器把这份网表跑起来3.1 仿真库是绕不过去的第一关网表里全是 Xilinx 原语仿真器当然不认识 LUT6所以必须先把 Xilinx 提供的仿真库编译出来。Vivado 自带一个专门干这个的命令compile_simlib -directory ./sim_lib \ -simulator modelsim \ -family all \ -language all-simulator可以填 modelsim、questa、vcs、xcelium、riviera 等-family all表示把目标器件的所有系列都编进去。这一步会花不少时间几分钟到十几分钟都有但只需要做一次之后所有项目都可以共用这个库目录。需要编出来三个库用途完全不同别混用库名内容用在哪种仿真unisims_ver原语的功能模型无延时功能仿真simprims_ver带延时信息的仿真原语模型时序仿真配合 SDFsecureip特殊硬核的加密模型用到 GT、PCIE、部分存储控制器时功能仿真配unisims_ver时序仿真配simprims_ver两者不要同时挂。挂了simprims_ver却不反标 SDF延时信息全是默认值跑出来的结果既不是功能仿真也不是真时序仿真属于最没有意义的一种状态。3.2 ModelSim 上的编译与仿真glbl 那个老问题以 ModelSim 为例功能仿真的完整命令链是这样vlib work vlog -sv ./netlist_out/top_funcsim.v vlog -sv ./tb/tb_top.v vsim -t 1ps -L unisims_ver -L secureip work.tb_top-L的顺序有讲究unisims_ver要排在前面secureip排在后面因为调用关系是从 glbl 到原语再到硬核模型。-t 1ps把仿真精度定在 1ps网表里很多原语模型的延时是 ps 级精度低于 1ps 会出现延时被舍入成 0 的情况。glbl是个从 ISE 时代延续下来的历史包袱。老流程里需要一个全局模块来驱动 GSR全局置位复位信号网表里的触发器初值要靠这个信号才能生效不给它仿真开始时触发器就是 X。Vivado 时代从compile_simlib编出来的库里通常已经包含了glbl一般不需要手动加。但如果仿真器报错说找不到glbl可以从 Vivado 安装目录的data/verilog/src/glbl.v把它单独拿出来编译进 work 库然后vsim时把顶层和glbl一起写上去vsim -t 1ps -L unisims_ver -L secureip work.tb_top work.glbl这个坑的特点是不报错只是所有寄存器一直是 X激励跑下去没有任何反应第一次遇到会怀疑是不是网表导坏了。实际就是 GSR 没被拉起来。3.3 SDF 反标路径写错是最常见的原因时序仿真的命令比功能仿真多一个反标参数vsim -t 1ps -L simprims_ver -L secureip \ -sdfmax /tb_top/u_dut./netlist_out/top_timesim.sdf \ work.tb_top work.glbl-sdfmax表示取最大延时对应最坏情况-sdfmin取最小延时用来查保持时间-sdftyp是典型值。这三个参数在同一个仿真里可以同时挂三个 SDF 文件做三组分析。路径/tb_top/u_dut是反标的目标实例路径必须和仿真里的层次完全一致大小写敏感一个字符都不能差。反标失败时 ModelSim 通常只会在 transcript 里打几行 warning很容易被忽略然后你看着一份跑了时序仿真的结果其实延时根本没进去。跑完之后一定要在 transcript 里搜一下sdf关键字确认反标成功的实例数和你预期对得上。如果用的是timesim模式导出的网表并带了-sdf_anno true网表文件里会自带$sdf_annotate(top_timesim.sdf, ...)这样的任务调用命令行就不用再写-sdfmax了仿真器执行到那一行会自动反标。两种方式选一种同时用会重复反标报错。3.4 VCS 和 Xcelium 上的差异点换到 VCS 上主要差别在编译选项。功能仿真大致是这样vcs -full64 -sverilog -debug_accessall \ -timescale1ps/1ps \ -L unisims_ver -L secureip \ -f filelist.f -o simv-L指定的库路径需要提前在synopsys_sim.setup或者命令行里通过-y和libext指出来。compile_simlib -simulator vcs生成的目录里会带一份 README里面有官方给的编译和仿真命令模板照着改比从零摸要快得多。Xcelium 的情况类似差别主要在-makelib和-endlib的用法上。不管换哪个仿真器真正需要你自己确认的永远是三件事库路径挂对了没有、SDF 反标成功了没有、存储器初值加载了没有。这三件事搞定了剩下的都是语法差异。4. 综合网表翻车现场我踩过的几个坑4.1 层次被拍平、信号名找不到问题出在综合设置默认情况下 Vivado 的flatten_hierarchy是rebuilt意思是综合时先把层次打平做优化再按原结构重建出来。听起来很美好实际结果是层次虽然恢复了但里面很多中间信号已经合并掉了你想在仿真里观察某个内部节点很可能根本找不到因为对应的线在被拍平的那一瞬间就被优化掉了。要保留完整层次需要在综合设置里把这个参数改成none。GUI 里在 Synthesis Settings 的 Flatten Hierarchy 下拉框Tcl 里在综合前设置set_property -name {STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY} \ -value none -objects [get_runs synth_1] reset_run synth_1 launch_runs synth_1 -jobs 8 wait_on_run synth_1注意必须reset_run之后再重新综合光改属性不会生效。把层次保留下来之后资源占用通常会比rebuilt模式高一点因为跨层次的优化做不了了这是可读性和面积之间的取舍。我一般会在项目前期用none方便调试到了收尾阶段再切回rebuilt榨一榨面积。另一个让人抓狂的现象是加了-no_net_names之后所有线全变成匿名。这个参数省下来的文件体积在今天的磁盘容量面前几乎没意义所以我的建议是永远别加。4.2 跑起来全是 X按这个顺序排查网表仿真出 X 是家常便饭但原因就那么几类按顺序查基本能定位。先看是不是 GSR 没拉起来。如果激励施加到输入端但输出一直不动且所有寄存器都是 X八成就是这个原因解决办法前面说过了补上 glbl。再看存储器。如果只有涉及 BRAM 的那部分数据是 X其他逻辑正常那就是.mem初值没加载检查存储器模型有没有读到初值文件路径对不对。再看复位。综合后的触发器初值来自 RTL 里的复位逻辑或者initial语句如果 RTL 写的是上电后靠复位拉低清零而仿真里复位根本没拉过那触发器自然一直是 X。这种情况在 RTL 仿真里可能因为 initial 语句被模拟器支持而没暴露一到门级仿真就现原形了。最后才是怀疑网表本身。真到这一步可以把导出的default模式网表读回 Vivado看看模块结构是否完整比自己一行行啃网表快得多。4.3 综合后 SDF 不等于最终时序别拿它下结论这一点我在前面的章节提过但值得单独再强调一次因为它造成的误判特别严重。综合后 SDF 里没有走线延时而现代器件里走线延时在总延时里的占比可以很高。曾经有个设计综合后 SDF 跑出来时序一片绿布线后出现了路径违例两边结论完全相反。如果拿综合后的时序仿真结果去跟别人汇报时序没问题到实现阶段是要出事的。我给自己定的规矩是综合后的时序仿真只用来抓结构性错误比如跨时钟域没有同步器、组合逻辑环路、复位释放时间不对这类问题这些和走线无关综合后就能看出来。至于建立保持时间永远以布线后的时序报告为准。4.4 仿真速度断崖式下降几种实测有效的缓解办法门级网表的仿真速度比 RTL 慢一到两个数量级是正常的。几万个 LUT 实例摆在那儿每个实例的求值都是一个独立的事件。能做的事情有几件。第一只在必要的时候用timesim。功能层面的问题用funcsim网表跑速度快得多延时信息对功能验证没有价值。第二控制波形 dump 的范围。全设计 dump 波形会让文件涨到几个 G只 dump 你关心的模块和信号比如用 ModelSim 的add wave -recursive /tb_top/u_dut/u_模块/*限定范围。第三如果非要用vopt加速编译注意它会和 SDF 反标、部分原语模型冲突用之前先做一次小规模验证。第四把长激励切成多段跑每段从检查点恢复比一口气跑几百万个周期要现实。5. 几个进阶玩法把网表用得更彻底5.1 把网表读回 Vivado 做自检交付之前我会做一次回读自检方法是在非工程模式下把导出的default模式网表读回来重新统计资源和综合时的报告做对比read_verilog ./netlist_out/top_synth.v link_design -part xc7z020clg400-1 report_utilization -file ./netlist_out/readback_util.rpt两份报告里的 LUT、FF、DSP、BRAM 数量应该基本一致。如果有明显偏差说明导出的网表在传输或者生成过程中掉了东西这时候还没交付出去来得及补救。这个方法我在一次跨版本交付时救过自己一回对方用的是不同大版本的 Vivado网表里有个原语在他们的库里名字对不上回读时报错提前暴露了问题。回读时优先用default模式的网表funcsim和timesim模式的网表里带了仿真专用的构造回读可能会报一些无关紧要的错影响判断。5.2 加密交付EDIF 加 security_mode如果网表需要交付给外部但不想暴露内部逻辑Verilog 文本基本等于开源这时候可以用 EDIF 格式配合加密选项write_edif -force -security_mode all ./netlist_out/top_encrypted.edf-security_mode有几个取值all表示把所有内容加密none表示不加密中间还有只加密部分层次的选项。加密后的 EDIF 能正常被 Vivado 读回来做实现但打不开看内容。需要对方配合的时候再单独提供一份synth_stub模式的网表让对方知道端口长什么样就够了。这条路的前提是对方也用 Vivado跨厂商的场景下加密 EDIF 的兼容性要看对方工具的版本支持情况交付前最好先拿一个小模块试一遍。5.3 交给等价性检查工具时要注意的三件事把网表拿去做形式验证或者等价性检查是另一个常见用途尤其在 RTL 改动之后要确认功能没变。这时候有三件事需要提前准备。第一综合参数要固定。等价性工具是拿两份网表做结构比对如果两次综合用的flatten_hierarchy、优化等级不一样比对结果会因为有大量冗余差异而变得不可读工具会报一大堆非等价点其实逻辑是等价的。第二命名要尽量保留方便工具做名称匹配能省掉大量手工映射。第三黑盒要一致。如果一份网表里某个 IP 是黑盒另一份是展开的工具没法比对两边要么都黑盒要么都用同一份实现。这几件事在导出脚本里都能提前配置好等工具报一堆结果再回头改成本要高得多。最后分享一个我自己用的小技巧把导出脚本和仿真脚本一起放进版本控制和 RTL 放在同一个仓库的不同目录下。这样每次交付或者回归只要跑对应的脚本就行不用回忆上次那几个参数是怎么设的。门级网表这条链路涉及的参数和文件太多了靠记忆迟早会出问题脚本化是唯一的解法。
返回列表