4、DC综合脚本基础:Tcl语言入门、DC常用Tcl命令、综合脚本的基本结构
好,咱们进入正题。这一章聊的是DC综合脚本的基础。说白了,就是教你如何跟DC这个工具“对话”。
很多新手工程师拿到DC,第一反应是打开GUI界面点点点。我个人习惯是,能用脚本绝不用GUI。为什么?因为脚本可重复、可版本管理、可自动化。你想想看,一个项目几百个模块,每个都要手动点一遍,那不得疯掉?
4.1 Tcl语言入门——DC的“母语”
DC的脚本语言是Tcl(Tool Command Language)。你不需要成为Tcl专家,但基础语法必须掌握。我在项目中遇到过不少同事,因为Tcl基础不牢,写出来的脚本bug百出,debug花的时间比综合本身还长。
4.1.1 变量与赋值
Tcl里一切皆字符串。变量用set赋值,用$引用。
set clk_period 10.0
set clk_name "clk_core"
puts "时钟周期是 $clk_period ns"
puts "时钟名字是 $clk_name"
嗯,这里要注意:变量名区分大小写。$CLK_PERIOD和$clk_period是两个不同的变量。我曾经因为这个问题,查了半小时脚本为什么报错。
4.1.2 列表操作
DC里经常要处理一组文件或一组路径。列表操作是基本功。
set rtl_files [list \
"top.v" \
"sub_module1.v" \
"sub_module2.v" \
]
# 遍历列表
foreach file $rtl_files {
puts "读取文件: $file"
}
这里[list ...]是Tcl的命令替换语法。说白了,就是先执行list命令,把结果赋给变量。
4.1.3 字符串处理
综合脚本里经常要拼接路径、提取文件名。Tcl的字符串命令很强大。
set design_name "top_module"
set output_dir "./results/${design_name}"
# 结果: ./results/top_module
# 提取文件扩展名
set filename "design.v"
set ext [file extension $filename]
puts "扩展名是: $ext"
${var}这种花括号引用变量,在拼接字符串时更清晰,不容易出错。
4.1.4 条件与循环
脚本里免不了要做判断。比如根据不同的工艺角选择不同的约束。
set corner "ss_0p9v_125c"
if {$corner == "ss_0p9v_125c"} {
set clk_period 10.0
} elseif {$corner == "ff_1p1v_m40c"} {
set clk_period 8.0
} else {
set clk_period 9.0
}
循环除了foreach,还有for和while。但说实话,我在DC脚本里90%的情况用foreach就够了。
4.2 DC常用Tcl命令——你真正需要记住的
DC提供了几百个Tcl命令。但你不需要全记住。我整理了一份“高频命令清单”,这些是每个综合脚本里几乎都会用到的。
| 命令 | 功能 | 使用频率 |
|---|---|---|
analyze |
分析RTL文件,检查语法 | ⭐⭐⭐⭐⭐ |
elaborate |
将设计实例化到DC内存中 | ⭐⭐⭐⭐⭐ |
link |
连接设计中所有模块 | ⭐⭐⭐⭐ |
current_design |
设置当前要操作的设计 | ⭐⭐⭐⭐⭐ |
create_clock |
定义时钟约束 | ⭐⭐⭐⭐⭐ |
set_input_delay |
设置输入延迟 | ⭐⭐⭐⭐ |
set_output_delay |
设置输出延迟 | ⭐⭐⭐⭐ |
compile_ultra |
执行综合(优化版) | ⭐⭐⭐⭐⭐ |
write |
输出网表、报告等 | ⭐⭐⭐⭐⭐ |
report_timing |
生成时序报告 | ⭐⭐⭐⭐ |
analyze再elaborate,然后link,最后current_design。这个顺序不要搞反。我见过有人跳过analyze直接elaborate,结果综合出来的网表跟RTL对不上,查了一天才发现是语法错误被忽略了。
4.2.1 文件读取命令详解
analyze和elaborate是DC读取设计的标准流程。为什么分成两步?
analyze负责检查语法,把RTL文件转换成DC内部的中间格式(GTECH)。elaborate则把这些中间格式实例化成一个完整的设计层次。
# 第一步:分析所有RTL文件
analyze -format verilog [list \
./rtl/top.v \
./rtl/sub_module.v \
./rtl/ram_wrapper.v \
]
# 第二步:实例化顶层设计
elaborate top -architecture verilog
# 第三步:连接所有模块
link
# 第四步:设置当前设计
current_design top
这里有个坑:elaborate时指定的顶层模块名,必须跟RTL里的module名字完全一致。大小写、下划线都不能错。我曾经因为顶层模块名写成了TOP(RTL里是top),结果DC报错说找不到设计。
4.2.2 约束命令详解
约束是综合的灵魂。没有约束,DC不知道你要优化成什么样。
# 创建时钟
create_clock -name clk_core -period 10.0 [get_ports clk]
# 设置时钟不确定性(clock uncertainty)
set_clock_uncertainty -setup 0.2 [get_clocks clk_core]
set_clock_uncertainty -hold 0.1 [get_clocks clk_core]
# 设置输入延迟
set_input_delay -clock clk_core -max 2.5 [get_ports data_in*]
set_input_delay -clock clk_core -min 1.0 [get_ports data_in*]
# 设置输出延迟
set_output_delay -clock clk_core -max 3.0 [get_ports data_out*]
set_output_delay -clock clk_core -min 1.5 [get_ports data_out*]
get_ports支持通配符*。但通配符匹配的是端口名,不是信号名。如果你要约束内部寄存器,需要用get_pins或get_cells。
4.3 综合脚本的基本结构——一个模板走天下
好了,前面铺垫了这么多,现在咱们看看一个完整的综合脚本长什么样。我习惯把脚本分成几个清晰的段落,每个段落做一件事。
4.3.1 脚本的“骨架”
# ============================================
# 脚本名称: syn_top.tcl
# 功能描述: 综合顶层模块 top
# 工艺库: smic55ll
# 作者: [你的名字]
# 日期: 2024-01-15
# ============================================
# ---------- 1. 设置库文件 ----------
set target_library "smic55ll_ss_0p9v_125c.db"
set link_library [concat "*" $target_library "smic55ll_io.db"]
set symbol_library "smic55ll.sdb"
# ---------- 2. 读取设计 ----------
analyze -format verilog [list \
./rtl/top.v \
./rtl/sub_module.v \
]
elaborate top
link
current_design top
# ---------- 3. 设置约束 ----------
create_clock -name clk -period 10.0 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_input_delay -clock clk -max 2.0 [all_inputs]
set_output_delay -clock clk -max 2.0 [all_outputs]
# ---------- 4. 设置综合策略 ----------
set_compile_directives -constant_propagation true
set_flatten true -design top
# ---------- 5. 执行综合 ----------
compile_ultra -no_autoungroup -retime
# ---------- 6. 输出结果 ----------
write -format verilog -hierarchy -output ./results/top_netlist.v
write -format ddc -hierarchy -output ./results/top.ddc
report_timing > ./reports/timing.rpt
report_area > ./reports/area.rpt
report_power > ./reports/power.rpt
这个模板我用了好几年,基本没大改过。每次新项目,复制一份,改改库文件路径和模块名就能用。
4.3.2 脚本的“血肉”——变量化
好的脚本应该是“参数化”的。把经常变的东西提取成变量,改起来方便。
# 参数定义区
set DESIGN_NAME "top"
set CLK_PERIOD 10.0
set RTL_DIR "./rtl"
set OUTPUT_DIR "./results"
set REPORT_DIR "./reports"
# 读取设计时使用变量
analyze -format verilog [list \
${RTL_DIR}/${DESIGN_NAME}.v \
${RTL_DIR}/sub_module.v \
]
# 约束时使用变量
create_clock -name clk -period $CLK_PERIOD [get_ports clk]
# 输出时使用变量
write -format verilog -output ${OUTPUT_DIR}/${DESIGN_NAME}_netlist.v
4.3.3 脚本的“灵魂”——错误处理
脚本跑着跑着出错了怎么办?不加错误处理,脚本会继续往下跑,最后生成一堆没用的结果。
# 在关键步骤后检查是否成功
if {[link] == 0} {
error "Link failed! 请检查模块连接。"
}
# 或者用 catch 捕获错误
if {[catch {compile_ultra} result]} {
puts "综合失败: $result"
exit 1
}
嗯,这里要强调一下:link命令返回0表示成功,返回1表示失败。这个跟很多其他命令的返回值习惯不一样,别搞混了。
4.4 实战经验:脚本调试的“三板斧”
脚本写好了,第一次跑大概率会报错。别慌,我总结了三步调试法:
- 检查库文件路径:80%的错误是库文件没找到。用
list_libs命令查看当前加载了哪些库。 - 检查设计名称:
current_design设置对了没?用list_designs查看所有已加载的设计。 - 检查约束:用
report_constraint查看当前约束是否生效。
我曾经接手过一个项目,脚本跑了三天三夜没出结果。最后发现是compile_ultra的参数写错了,导致综合进入了死循环。从那以后,我每次跑长脚本都会先加个timeout机制。
下一章,咱们聊聊综合约束的详细设置。到时候我会分享一些“压箱底”的约束技巧,保证让你少走弯路。