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,还有forwhile。但说实话,我在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 生成时序报告 ⭐⭐⭐⭐
核心原则:analyzeelaborate,然后link,最后current_design。这个顺序不要搞反。我见过有人跳过analyze直接elaborate,结果综合出来的网表跟RTL对不上,查了一天才发现是语法错误被忽略了。

4.2.1 文件读取命令详解

analyzeelaborate是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_pinsget_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 实战经验:脚本调试的“三板斧”

脚本写好了,第一次跑大概率会报错。别慌,我总结了三步调试法:

  1. 检查库文件路径:80%的错误是库文件没找到。用list_libs命令查看当前加载了哪些库。
  2. 检查设计名称current_design设置对了没?用list_designs查看所有已加载的设计。
  3. 检查约束:用report_constraint查看当前约束是否生效。

我曾经接手过一个项目,脚本跑了三天三夜没出结果。最后发现是compile_ultra的参数写错了,导致综合进入了死循环。从那以后,我每次跑长脚本都会先加个timeout机制。

一句话总结: 综合脚本不是写出来就完事的,它是你跟DC沟通的桥梁。脚本写得好,综合效率高;脚本写得烂,debug到天亮。

下一章,咱们聊聊综合约束的详细设置。到时候我会分享一些“压箱底”的约束技巧,保证让你少走弯路。