1. 先把概念说清:门级网表在 Vivado 流程里到底是个什么东西
刚上手 Vivado 的朋友经常会被"网表"这个词绕进去,觉得它是个高级玩法,跟自己没关系。其实真不是。你在 Vivado 里点一下 Run Synthesis,工具在后台做的事情之一,就是把你的 RTL 代码翻译成一张由 LUT、触发器、DSP、BRAM、IO 缓冲这些底层原语互相连线组成的"电路图",这张图落到磁盘上,就是门级网表。Vivado 综合、门级网表这三个词是绑在一起的,理解不了它,你就永远只能停在"写代码—点按钮—烧板子"的层面,出了问题只能靠猜。
我从很早的 ISE 时代就开始折腾这东西,中间换到 Vivado 又踩了几年坑。说句实话,导出网表这件事本身极度简单,一条write_verilog就完事了,真正难的是知道什么时候该导、导出来的东西长什么样、导完之后怎么验证它是对的。很多人卡在最后一步——网表导出来,仿真一跑满屏 X,或者编译报一堆找不到的模块,最后不了了之。
这篇东西我准备按实战顺序讲:先搞清楚综合后网表和实现后网表的区别,再讲综合策略里哪几个参数会直接改变网表的形态,然后是 GUI 和 Tcl 两条导出路径的具体操作,接着是网表的验证链路(glbl.v、仿真库、SDF 反标),最后把我这些年整理的报错速查表和个人踩坑心得一起放出来。
适合谁看呢?写过一点 Verilog、能跑通基本综合流程,现在需要给第三方交付网表、需要做后仿真、需要做形式验证或者需要加速大型系统仿真的同学。纯新手也能看,前面概念部分我尽量用大白话。全程用 Vivado 2020.2 之后的版本演示,命令在 2022.2 / 2023.2 上我都实测过,基本通用。
1.1 从 RTL 到门级:综合这一步到底干了什么
综合(Synthesis)本质上是三个动作串起来的:翻译、优化、映射。翻译阶段把你写的 always 块、assign 语句转成一个跟工艺无关的逻辑网络;优化阶段做布尔化简、公共子表达式提取、状态机重编码、资源共享;映射阶段才真正跟器件挂钩,把逻辑网络塞进 6 输入 LUT,把时序逻辑映射成 FDRE/FDCE/FDPE 这类触发器,把算术逻辑识别成 CARRY4 和 DSP48E2。
所以综合后的网表有两个非常关键的特征。第一个特征是只有 LUT 级结构,没有物理延迟信息。你能看到信号经过了几个 LUT 级,但每根线走多长、经过几个开关盒,综合阶段完全不知道,这些要等实现(Implementation)做完布线才有。第二个特征是名字全变了。你在 RTL 里写的data_cnt_reg[3],综合后可能变成data_cnt_reg[3]_i_4或者干脆并在某个LUT6的输入里,只有在特定条件下才保留原名。
这两个特征决定了综合后网表的用途边界:它可以做功能验证、可以做逻辑等价性检查、可以交付给别人当黑盒,但不能用来评估时序。我见过太多人拿着综合后网表跑时序仿真,SDF 文件死活生成不出来,还以为是工具坏了,其实是流程搞错了。
1.2 综合后网表 vs 实现后网表:一字之差,天壤之别
这个区别我必须单独拎出来讲,因为它是最容易出问题的地方。Vivado 的流程里,网表可以在三个时间点生成,每个时间点的产物用途完全不同。
综合完成(synth_design done)之后生成的网表,包含的是 LUT、FF、DSP、BRAM 这些逻辑原语,管脚上会带 IBUF/OBUF,时钟上会带 BUFG,但没有布局布线信息。它对应的仿真类型是功能后仿真(functional post-synthesis simulation),也叫零延迟仿真。这个网表可以加密,可以给第三方,可以做逻辑等价性验证。
实现完成(route_design done)之后,物理信息全都有了。这时候导出的网表如果配合write_verilog -mode timesim,会带上 specify 块的时序检查结构,再配一个从实现结果里write_sdf导出的 SDF 文件,才能做时序后仿真(timing simulation)。这才是报告真实建立保持时间的仿真。
还有一个中间态:opt_design或place_design之后导出的网表,属于"部分实现",一般只在特定的 ECO 场景里用,日常很少碰。
| 时间点 | 网表内容 | 能否拿 SDF | 典型用途 |
|---|---|---|---|
| 综合后 | LUT/FF/DSP/BRAM + IO 缓冲 | 不能 | 功能后仿、形式验证、网表交付 |
| 实现后(opt/place/route) | 逻辑原语 + 布局布线信息 | 能 | 时序后仿、功耗分析、门级调试 |
提示:
write_sdf只能在实现后的 design 上执行。如果你在综合后的 design 上敲这条命令,工具会直接报错,别怀疑是自己命令写错了。
1.3 哪些场景真的需要导出网表
不是所有项目都需要这一步,我把它归纳成五类场景,你可以对号入座。
第一类是对外交付。你把设计做成 IP 交给客户,又不想暴露 RTL 源码,这时候交付加密后的 Verilog 网表或者 EDIF 网表就是标准做法。客户拿到之后能例化、能综合、能仿真,但看不到你的实现思路。
第二类是大型系统仿真加速。SoC 项目里,CPU 核跑 RTL 仿真慢得让人抓狂,把它综合成网表再做混合仿真,速度能提升一大截,因为网表是扁平化的,仿真器不用反复解析层次和过程块。
第三类是逻辑等价性检查(LEC)。综合过程可能因为优化改变了逻辑结构,形式验证工具需要拿"综合前的 RTL"和"综合后的网表"做等价性比对,确认功能没被改坏。这也是网表的一个重要用途,尤其在有安全要求的场景里。
第四类是模块级独立验证。子模块单独综合成网表,可以在不带完整顶层的情况下单独仿真,缩短迭代周期。
第五类是教学和研究。想看清楚 Vivado 把自己的代码变成了什么样的电路,-flatten_hierarchy none加网表导出是唯一的路子,比看综合报告直观一百倍。
2. 动手前必须搞定的工程准备
我特别反感那种"打开软件就点按钮"的教程,因为网表导出这事对工程状态的依赖很强。综合设置没配对,导出来的网表要么仿真跑不通,要么结构乱得看不懂,回头重来一遍更费时间。这一章讲导出前必须确认的几件事。
2.1 目录结构与版本约定
我的习惯是给每个工程配一套固定的目录模板,网表相关的东西全部集中管理,具体是这样:
proj/ ├─ rtl/ # 设计源码 ├─ sim/ # testbench 与仿真脚本 │ └─ tb_top.v ├─ constr/ # xdc 约束 │ ├─ timing.xdc │ └─ pin.xdc ├─ scripts/ │ ├─ build_netlist.tcl │ └─ run_sim.tcl ├─ netlist/ # 所有导出的网表产物 │ ├─ funcsim/ │ ├─ timesim/ │ └─ edif/ └─ sim_lib/ # 编译好的仿真库这么搞的理由很实际:网表文件动辄几十兆,混在工程根目录里,几次迭代下来你自己都分不清哪个是哪个版本。按类型分目录、按日期或 commit id 打尾缀,比如top_funcsim_20240612.v,出了问题回溯起来一目了然。
2.2 综合前必须核对的三件事
导出网表之前,下面这三项我每次都要过一遍,缺一项都可能让网表变成废品。
第一件,顶层模块名确认。综合报告里第一行会打印 Top module name,一定要跟你后续write_verilog -cell或者仿真 top 名对得上。新手最容易犯的错是把 testbench 模块设成了顶层,综合出来的网表里带着 initial 块和延时语句,看着就不对劲。
第二件,约束文件是否齐全。综合阶段的 xdc 主要管两件事:时钟定义和 IO 引脚。综合后网表虽然不带物理延迟,但时钟定义会影响 BUFG 的推断和 MMCM 的配置,IO 约束会影响 IBUF/OBUF 的插入。约束不全,网表结构和实际硬件可能对不上。
第三件,IP 核是否已经生成。Vivado 的 IP 在综合时会走 OOC(Out-of-Context)流程,单独综合成.dcp文件。如果 IP 还没生成就去综合顶层,工具会把 IP 当黑盒处理,导出的网表里就是一个空壳模块,仿真时必然找不到内部逻辑。
2.3 keep_hierarchy 与 flatten_hierarchy 的取舍
这一对参数直接决定网表的可读性,必须提前想清楚。Vivado 默认的-flatten_hierarchy rebuilt会把整个设计打散重新组织,只保留必要的层次边界用于跨层次优化。结果就是网表里几乎看不到你的模块结构,全是xxx_LUT6_2这种机器生成的名字。
如果你是为了做形式验证或者交付,扁平化反而是好事,因为它优化得更充分。但如果你是为了看懂网表、或者需要按模块定位问题,就必须在 RTL 里对关键模块加属性:
(* keep_hierarchy = "yes" *) module data_path #( parameter WIDTH = 16 )( input wire clk, input wire [WIDTH-1:0] din, output reg [WIDTH-1:0] dout );keep_hierarchy属性会强制工具保留这个模块的边界。实测下来,加了属性之后网表里会多出一个层级的壳,虽然名字后面还是会带_0后缀,但至少能按模块找到对应的逻辑块,定位问题方便太多。代价是跨边界的优化会被阻断,资源可能多用 1% 到 3%,时序紧张的工程要权衡一下。
3. 综合策略里的关键参数,直接决定网表长什么样
Vivado 综合设置界面里选项一大堆,大部分保持默认就行,但有那么几个会实打实地改变网表形态。这一章我把它们挑出来,讲清楚每个参数背后的逻辑,以及我自己的取值习惯。
3.1 flatten_hierarchy 三种取值的实测对比
-flatten_hierarchy有三个取值:full、none、rebuilt。我用一个 8 层层次、约 12 万 LUT 的通信基带工程做过对比测试,结果如下。
| 取值 | 网表层次 | LUT 用量 | 综合时间 | 适用场景 |
|---|---|---|---|---|
| full | 完全扁平,只剩顶层 | 最少 | 最长 | 交付、形式验证、追求面积 |
| none | 完整保留 RTL 层次 | 最多 | 最短 | 调试、教学、模块定位 |
| rebuilt(默认) | 保留边界但内部重组 | 接近 full | 中等 | 大多数常规工程 |
full的优化最激进,跨层次常量传播和资源共享能做到底,面积和时序通常最好,但代价是编译慢、网表可读性为零。none保留了完整层次,网表读起来最舒服,但优化受限,资源可能多用 5% 以上,而且在边界处容易出现冗余逻辑。
我的建议是:交付和形式验证用full或默认的rebuilt;需要调试可读性的时候,用rebuilt配合 RTL 里的keep_hierarchy属性,比整个设计用none划算得多。
3.2 keep_equivalent_registers 什么时候该打开
这个选项的作用是阻止工具合并"功能等价"的寄存器。举个具体例子,你写了两组寄存器,输入完全一样,输出也完全一样,只是分别驱动不同的下游逻辑。默认情况下 Vivado 会把它们合并成一组,省寄存器省功耗。听起来很美好对吧?
问题出在仿真和调试上。合并之后,你在波形里看不到原来那两组信号了,后仿真时对着网表找半天也找不到。更麻烦的是,如果这两个寄存器在 RTL 里带不同属性(比如一个带ASYNC_REG、一个不带),合并可能导致跨时钟域处理失效。
所以我的经验是:含跨时钟域逻辑的设计,把-keep_equivalent_registers打开。资源会多用一点,但能避免很多诡异的时序问题。纯数据通路、时序宽裕的工程,保持关闭就行。
3.3 no_iobuf:模块级网表仿真的救命开关
这个是排在flatten_hierarchy之后的第二大坑。综合的时候,Vivado 会给你顶层的每一个 input/output 端口自动插上 IBUF、OBUF 或者 IOBUF。你如果是做整芯片仿真,这没问题。但如果你是拿某个子模块单独做网表仿真,麻烦就来了:子模块端口上多出来的 IBUF/OBUF 需要连接到实际的引脚或者顶层缓冲才能正常工作,悬空的话输出就是 X。
解决方式就是在综合时加上-no_iobuf:
synth_design -top data_path -part xc7z020clg400-2 -no_iobuf -flatten_hierarchy rebuilt加了之后,顶层端口上不再插入 IO 缓冲,网表就是一个纯粹的内部逻辑模块,直接例化在 testbench 里就能跑。这个选项在 GUI 里的位置比较隐蔽,在 Synthesis Settings 的 More Options 输入框里手写-no_iobuf即可。
注意:整芯片交付的网表不要加这个选项,因为下游需要真实的 IO 缓冲结构。只有模块级仿真才用。
3.4 directive 与 retiming 的取舍
-directive是 Vivado 给的"策略包",一个参数顶一堆细碎设置。常用取值里,Default是平衡型,AreaOptimized_high拼命省面积,PerformanceOptimized冲时序,RuntimeOptimized缩短综合时间。
我的习惯是:第一版综合用Default,看报告;如果时序差得不多(比如 WNS 差 0.2ns 以内),换PerformanceOptimized再跑一次;如果面积超标严重,才考虑AreaOptimized_high。注意AreaOptimized_high会打开资源复用,代价是组合逻辑路径变长,时序压力变大,一定要重新看时序报告。
-retiming是另一回事,它允许工具在组合逻辑之间移动寄存器位置,平衡路径延迟。这个功能对数据通路有效,但对含有异步复位、或者需要严格保持寄存器一一对应的设计(比如跨时钟域同步器)要慎用,因为搬动位置之后你原来的约束可能就失效了。我对它的使用原则是:纯 DSP 数据通路可以开,含控制逻辑的模块不开。
4. 动手生成网表:GUI 与 Tcl 两条路
准备工作做完,正式导出。我建议你第一次用 GUI 走一遍,搞清楚每一步在干什么,之后立刻转到 Tcl,因为网表导出这件事迟早要进流水线的。
4.1 GUI 操作路径
流程是这样的:左侧 Flow Navigator 打开Synthesis→Open Synthesized Design,等 design 加载完(右上角会出现 "Synthesized Design" 标识)。然后菜单File→Export→Export Netlist,弹出对话框后选择格式(Verilog 或 EDIF)、输出路径、以及是否加密。
这个对话框里有个 Options 标签页,能选仿真模式(对应-mode参数)。我第一次用的时候没注意,导出的是默认模式,拿去跑仿真发现很多原语没有仿真模型,编译报了一堆错。所以这个下拉框一定要按用途选,具体见下一小节。
导出完成后,工具会在 Tcl Console 里回显对应的命令。把这些命令记下来,这是转 Tcl 脚本最省事的办法——GUI 本质上就是在帮你拼 Tcl。
4.2 Tcl 脚本:可复现的导出方式
下面这个脚本是我实际在用的,放在scripts/build_netlist.tcl,用vivado -mode batch -source调用即可:
# ============================================================ # 综合后网表导出脚本 # 用法: vivado -mode batch -source build_netlist.tcl # ============================================================ set proj_name "top_proj" set top_module "top" set part "xc7z020clg400-2" set out_dir "./netlist" file mkdir $out_dir/funcsim file mkdir $out_dir/edif # 1. 打开综合后的 design open_run synth_1 -name synth_1 # 2. 导出功能仿真网表 write_verilog -force \ -mode funcsim \ $out_dir/funcsim/${top_module}_funcsim.v # 3. 导出默认网表(用于形式验证 / 第三方综合) write_verilog -force \ -mode default \ $out_dir/${top_module}_default.v # 4. 导出 stub 网表(只有端口声明,做黑盒最方便) write_verilog -force \ -mode synth_stub \ $out_dir/${top_module}_stub.v # 5. 导出 EDIF 网表 write_edif -force \ $out_dir/edif/${top_module}.edif # 6. 导出资源报告,方便核对 report_utilization -file $out_dir/${top_module}_util.rpt puts "=== Netlist export done ==="几个细节说明一下。open_run synth_1是把已经综合好的结果加载进内存,不需要重新跑综合。-force是覆盖已有文件,批处理模式必加,不然第二次运行会直接报错中断。资源报告一定要一起导出,它是你跟客户或者同事核对网表完整性的第一手凭证。
4.3 write_verilog 四种 mode 的区别
这是全文最需要记住的一张表。
| mode | 生成内容 | 用途 | 需要 unisim 库 |
|---|---|---|---|
| default | 纯逻辑网表,无仿真结构 | 交付、形式验证、再综合 | 否 |
| funcsim | 含仿真原语与初始化结构 | 功能后仿真 | 是 |
| timesim | 含 specify 时序检查结构 | 时序后仿真(需 SDF) | 是 |
| synth_stub | 仅模块端口声明,空壳 | 黑盒占位、顶层例化 | 否 |
我踩过的最典型的坑是:用default模式导出网表,拿去跑 XSIM 仿真,编译时报Module 'FDRE' is not defined。原因就是 default 模式假设下游有完整的工艺库,仿真器没有。换成funcsim立刻就过了。
反过来,如果你要给第三方公司做再综合,千万别给funcsim,因为里面混了仿真专用的结构,综合器会报错。给default或者 EDIF。
4.4 EDIF 导出与加密交付
EDIF 是业界通用的网表交换格式,Vivado 的write_edif生成的 EDIF 能被大多数后端工具和仿真器吃下去。如果你交付的对象用的不是 Xilinx 工具链,EDIF 通常是首选。
加密方面,Vivado 支持 IEEE 1735 标准的加密 Verilog。命令形式如下:
write_verilog -force -mode default \ -encrypt -key ./keys/mykey.txt \ ./netlist/encrypted/top_enc.v密钥文件里放的是 RSA 公钥,格式是工具规定的,不能自己瞎编。加密后的网表里,模块内部被替换成pragma protect包裹的密文,下游工具只要有对应私钥就能正常综合和仿真,没有私钥就只能看到端口。
write_edif也有-security_mode all选项做加密,但这属于 Xilinx 的专有加密,下游必须也是 Vivado 才能解开,通用性反而不如 IEEE 1735。交付前一定要问清楚对方用什么工具链,我因为这个来回折腾过一次,白白多花了两天。
4.5 产物命名与归档
最后提一句归档。网表文件是二进制/文本混合的大文件,用 Git 直接管会撑爆仓库。我的做法是:网表不进版本库,但生成网表的脚本和综合日志进版本库,同时在网表目录下放一个MANIFEST.txt,记录生成时间、Vivado 版本号、综合策略、顶层模块名和 md5 校验值。这样任何时候都能复现出同一份网表,审计也方便。
5. 网表验证:从编译报错到波形正确
网表导出来了,接下来的问题是:它到底对不对?我的验证习惯是分三步走——先过编译,再看功能,最后(可选)做时序。
5.1 glbl.v:为什么你的波形第一拍全是 X
这是后仿真里出现频率最高的问题,没有之一。你把网表编译通过了,仿真跑起来了,波形一打开,所有寄存器输出全是 X,等一万个时钟周期也不变。
原因在于 Xilinx 的原语模型里有一个全局信号叫 GSR(Global Set/Reset),上电时必须由glbl模块驱动它完成一次初始化。glbl模块不在你的网表里,它是一个独立的仿真专用模块,位置在:
$XILINX_VIVADO/data/verilog/src/glbl.v对应的 VHDL 版本是glbl.vhd。你的 testbench 顶层需要例化它:
module tb_top; reg clk = 0; reg rst_n = 0; always #5 clk = ~clk; initial begin #200 rst_n = 1; #10000 $finish; end // 网表顶层 top u_top ( .clk (clk), .rst_n (rst_n) ); // 全局初始化模块,不能省 glbl glbl(); endmodule编译的时候glbl.v必须一起加进去,仿真顶层(elaboration top)要写成tb_top glbl,两个模块都要指定。这一点几乎所有新手都会漏,因为 XSIM 不会报错,只是默默给你一堆 X。
5.2 用 XSIM 跑功能后仿真
完整的命令行流程如下:
# 1. 编译网表、testbench、glbl xvlog -i $XILINX_VIVADO/data/verilog/src \ ./netlist/funcsim/top_funcsim.v \ ./sim/tb_top.v \ $XILINX_VIVADO/data/verilog/src/glbl.v # 2. 精化,指定仿真库和两个顶层 xelab -debug typical \ -L unisims_ver -L secureip -L unimacro_ver \ tb_top glbl -s tb_sim # 3. 运行 xsim tb_sim -R关键在xelab那一行。-L unisims_ver加载基础原语库,-L secureip加载加密原语(MMCM、PLL、GT 这些都在里面),-L unimacro_ver加载宏原语。工程里只要用了 MMCM 或者收发器,secureip就是必须的,漏了会报找不到模块。
如果你用的是第三方仿真器,需要先用compile_simlib把库编出来,这个命令会把 Xilinx 全部仿真库编译成目标仿真器格式,比较耗时,但一次编译长期使用。命令大致是:
compile_simlib -simulator vcs_mx -family all -language all \ -dir ./sim_lib -no_ip_compile编译一次大概十几分钟,之后每次仿真直接复用。
5.3 时序后仿真与 SDF 反标
严格来说,综合后网表做时序仿真没有意义,因为没有延迟信息。真正的时序仿真要在实现完成之后做,步骤是这样:
# 打开布线完成的 design open_run impl_1 # 导出时序仿真网表 write_verilog -force -mode timesim \ -sdf_anno true \ -sdf_file ./netlist/timesim/top.sdf \ ./netlist/timesim/top_timesim.v # 导出 SDF write_sdf -force ./netlist/timesim/top.sdf-sdf_anno true会把$sdf_annotate系统任务直接写进网表文件里,仿真时自动反标延迟,省得你手动加。SDF 文件里有 typ(典型)、min(最小)、max(最大)三组延迟值,仿真时通过-sdf_typ之类的编译选项选择。做建立时间检查用 max,做保持时间检查用 min,两个都要跑一遍,这是标准做法。
时序后仿真跑得极慢,一个中等规模设计跑 100us 可能要几个小时。我的建议是:只针对关键路径附近的场景跑短时仿真,别拿它做全功能回归,那是效率灾难。
5.4 加速网表仿真的几个实操技巧
跑过时序仿真的人都懂那种煎熬。我总结了几个提速手段,实测有效。
第一,把非关键模块换回行为模型。整个系统不需要全部用门级网表,只把需要精确验证的模块换成网表,其余保持 RTL,混合仿真速度能快好几倍。
第二,减少 dump 的层次和信号量。$dumpvars不加参数会把所有信号全 dump 下来,IO 压力巨大。指定只 dump 顶层和关键模块即可。
第三,合理设置仿真的 timing check 开关。调试阶段可以把$timeformat精度调粗,或者临时关闭部分时序检查,等逻辑跑通了再打开验证。
第四,用-debug off编译。XSIM 默认带调试符号,关掉之后运行速度有明显提升,代价是不能再加波形信号,所以只在回归验证阶段用。
6. 常见问题与排查速查表
这一章是我这些年攒下来的问题库,按类型整理,遇到问题直接查表。
6.1 编译类问题
| 报错信息 | 根本原因 | 解决方式 |
|---|---|---|
| Module 'FDRE' is not defined | 网表 mode 选成了 default | 改用-mode funcsim重新导出 |
| Cannot find 'MMCME4_ADV' | 缺少 secureip 库 | xelab 加-L secureip |
| Module 'glbl' not found | 没编译 glbl.v | 把$XILINX_VIVADO/data/verilog/src/glbl.v加入文件列表 |
| Duplicate module definition | 网表和 RTL 同时参与了编译 | 仿真工程里只保留网表版本,剔除对应 RTL |
| 'IOBUF' has no simulation model | 用了-no_iobuf之外的场合 | 确认仿真库编译完整,或改用 funcsim |
第一条和第二条是最常见的。我建议你在编译脚本里把三个-L参数写死,别等报错再补。
6.2 仿真行为异常类问题
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 所有输出持续为 X | GSR 未释放 / glbl 没接 | 查 glbl 例化、查复位时序 |
| 部分寄存器一直为 0 | 寄存器被优化合并 | 打开-keep_equivalent_registers |
| 时钟不出波形 | MMCM 未锁定 / BUFG 未驱动 | 查 LOCKED 信号、查时钟约束 |
| 输出比 RTL 仿真晚几拍 | 网表插入了额外的流水寄存器 | 对比综合报告,检查 retiming 设置 |
| BRAM 读出的数据错位 | 读使能/输出寄存器配置差异 | 检查 IP 配置是否与 RTL 一致 |
第三条我特别要强调。MMCM 在仿真里锁定时间跟实际芯片不一样,仿真中的锁定时间由模型参数决定,通常是几百个时钟周期。如果你的 testbench 只跑了 100 个周期就检查结果,肯定看到的是未锁定状态。把仿真时间拉长,或者等LOCKED信号拉高后再开始激励。
6.3 交付与兼容类问题
| 问题 | 处理方式 |
|---|---|
| 第三方打开网表报语法错误 | 确认对方工具支持的 Verilog 标准版本,必要时加-verilog_std |
| 加密网表对方解不开 | 核对密钥文件格式,确认对方拿到的是正确私钥 |
| EDIF 导入后资源翻倍 | 对方工具把 LUT 拆成了门级,属于正常现象 |
| 网表综合后时序变差 | 层次被展平导致优化空间变化,属预期行为 |
| 网表里找不到某个模块 | 被综合优化掉了,检查是否真的被使用 |
最后一条有个排查小技巧:在综合日志里搜Removed或者unused,Vivado 会明确告诉你哪些逻辑被优化掉了。如果这个模块本来应该被使用,那就是它没有被正确例化或者被常量优化剪掉了。
7. 几个踩坑之后才明白的道理
网表导出这件事,我前后断断续续做了七八年,有几个认知是踩了坑才建立起来的。
第一个是先想清楚用途,再选 mode。我早年拿到需求"导个网表",直接一条write_verilog完事,结果对方要的是 EDIF,返工。后来我养成了一个习惯:导出之前在 README 里写清楚四件事——用途、mode、是否需要加密、接收方工具链。这四件事确认了,导出就是纯机械操作。
第二个是保留综合日志。日志里记录了每一个参数的实际取值,包括你用 GUI 设置的和你写进脚本的。有次客户反馈网表行为跟 RTL 不一致,我翻出综合日志,发现是-keep_equivalent_registers默认关闭导致两个跨时钟域寄存器被合并了。如果没日志,这个问题能查一整天。
第三个是网表不是"更底层所以更可靠"。恰恰相反,网表是工具优化后的产物,工具可能做了你意想不到的变换。所以任何一次网表交付,我都建议配一次形式验证或者至少一次带覆盖率的功能后仿真,确认优化没有改变功能语义。
第四个,也是我最有感触的一条:小工程别急着上后仿真。很多同学一听说"后仿真更严谨",就把每个工程都跑一遍时序仿真,结果几十个小时耗进去,发现的还是 RTL 仿真阶段就能发现的问题。我的做法是:RTL 仿真 + 静态时序分析能覆盖 95% 的验证需求,后仿真只在涉及时钟域交互、异步接口、上电复位这些对延迟敏感的场景才启用,而且要针对性地跑短时场景,不要全量回归。
最后再分享一个我常用的检查小动作。网表导出来之后,别急着跑仿真,先用文本编辑器打开文件头几行,看看有没有module声明、endmodule结尾,funcsim模式下应该能看到一堆FDRE、LUT6、CARRY4的例化。同时用grep -c "^ LUT6"之类的命令粗略统计一下原语数量,跟综合报告里的 LUT 数量对一下。如果差得离谱,说明导出过程出了问题。这个动作花不到一分钟,但能帮你省下很多弯路。