第四节:网表提取实战——用Tcl脚本从Vivado提取网表
好,咱们直接进入正题。网表提取这件事,说白了就是把 Vivado 里综合或实现后的电路结构“倒”出来。你想想看,FPGA 设计做完之后,我们手里拿到的是一堆 .bit 文件,但中间到底生成了什么逻辑?LUT 怎么连的?寄存器怎么接的?这些信息全藏在网表里。
我个人习惯,每次做完一个模块,第一件事就是拉网表看一眼。不是为了炫技,而是为了确认综合器有没有“自作主张”优化掉我的关键路径。嗯,这里要注意,Vivado 的网表提取其实非常灵活,但前提是你得会用 Tcl 脚本。
4.1 综合后网表提取
综合完成后,Vivado 会生成一个逻辑网表。这时候提取,得到的是纯逻辑级别的连接关系——没有布局布线信息,但能看到 LUT、FF、DSP、BRAM 这些基本单元是怎么拼起来的。
我常用的脚本是这样的:
# 打开综合后的设计
open_run synth_1
# 写出综合后网表(Verilog 格式)
write_verilog -force post_synth.v
# 写出综合后网表(EDIF 格式,适合第三方工具)
write_edif -force post_synth.edf
这里有个坑,我曾经踩过——如果不加 -force 参数,Vivado 会问你是否覆盖已有文件,脚本跑批处理的时候直接卡住。所以,养成习惯,所有写文件操作都带上 -force。
write_verilog -cell [get_cells my_module] 只导出指定模块。我在调试 IP 核时经常这么干,省得看整个顶层网表,眼睛都看花了。
4.2 实现后网表提取
实现后的网表,包含了布局布线的物理信息。这时候提取,你会看到每个逻辑单元被放到了哪个 SLICE,走的是哪条布线资源。说白了,这才是“真家伙”。
提取实现后网表的脚本:
# 打开实现后的设计
open_run impl_1
# 写出实现后网表(Verilog 格式,含布线信息)
write_verilog -force -mode impl post_impl.v
# 写出实现后网表(VHDL 格式)
write_vhdl -force -mode impl post_impl.vhd
注意那个 -mode impl 参数。不加的话,默认是 -mode synth,导出的还是综合级网表。我刚开始用 Vivado 时就犯过这个错,折腾了半天发现导出的网表里没有布线信息,还以为工具坏了。
less 或者专门的代码查看工具。
4.3 网表导出选项详解
Vivado 的 write_verilog 命令其实有很多选项,我挑几个最常用的说说:
| 选项 | 说明 | 我的建议 |
|---|---|---|
-mode synth |
导出综合级网表 | 逻辑分析时用 |
-mode impl |
导出实现级网表(含布线) | 时序分析、物理验证时用 |
-cell |
只导出指定模块 | 调试子模块时必用 |
-force |
强制覆盖已有文件 | 脚本中必须加 |
-port_directions |
在网表中保留端口方向 | 建议开启,方便阅读 |
还有一个选项我特别想提——-include_xilinx_libs。这个选项会把 Xilinx 的原语库也包含进来。如果你想把网表拿到其他工具里做仿真,这个选项必须开。否则,你会看到一堆 unknown module 的错误。
4.4 实战:一键提取脚本
好了,我把上面这些知识点串起来,写一个完整的 Tcl 脚本。这个脚本我用了好几年,每次新项目都直接拿来改改就用:
# 一键网表提取脚本
# 用法:vivado -mode batch -source extract_netlist.tcl
# 设置工程路径
set project_path "./project_1.xpr"
set output_dir "./netlist_output"
# 创建输出目录
file mkdir $output_dir
# 打开工程
open_project $project_path
# ===== 提取综合后网表 =====
puts "提取综合后网表..."
open_run synth_1
write_verilog -force -mode synth \
[file join $output_dir "post_synth.v"]
write_edif -force \
[file join $output_dir "post_synth.edf"]
puts "综合后网表提取完成"
# ===== 提取实现后网表 =====
puts "提取实现后网表..."
open_run impl_1
write_verilog -force -mode impl \
[file join $output_dir "post_impl.v"]
write_vhdl -force -mode impl \
[file join $output_dir "post_impl.vhd"]
puts "实现后网表提取完成"
# ===== 提取指定模块网表 =====
puts "提取顶层模块网表..."
write_verilog -force -mode impl \
-cell [get_cells top] \
[file join $output_dir "top_impl.v"]
puts "全部完成!"
这个脚本跑完之后,你会在 netlist_output 目录下看到 5 个文件。我一般先看 post_synth.v,确认逻辑正确;再看 post_impl.v,确认布线没问题。
- 综合后网表:看逻辑结构,确认综合器没乱优化
- 实现后网表:看物理连接,确认布线正确
- 模块级网表:调试子模块时用,效率高
- EDIF 格式:适合第三方工具,比如 Formality 做形式验证
4.5 网表提取后的检查
网表导出来之后,别急着关 Vivado。我建议至少做三件事:
- 文件大小检查——如果综合后网表只有几 KB,大概率是空的,检查一下
open_run是否成功 - 模块数量检查——用
grep -c "module" post_synth.v看看有多少个模块,跟预期是否一致 - 关键信号检查——在网表里搜一下你的关键信号名,确认没有被综合器优化掉
我曾经遇到过一个案例,综合器把一组状态机的状态寄存器全部优化成了组合逻辑,因为我觉得“状态机肯定会被实现成寄存器”,结果网表里一个 FF 都没有。从那以后,我每次提取网表都会先搜一下关键信号,确认它们还在。
这张图把整个流程串起来了。你从 RTL 出发,经过综合得到逻辑网表,经过实现得到物理网表。虚线表示网表提取的时机——你可以在综合后提取,也可以在实现后提取,甚至可以在布局布线过程中的某个中间点提取(用 open_run 指定不同的 run)。
好了,网表提取的实战内容就这些。记住,脚本是死的,但用法是活的。你完全可以根据自己的项目需求,在脚本里加上 -cell 过滤、-include_xilinx_libs 选项,甚至写个循环批量提取多个模块的网表。