4、综合脚本基础:Tcl语言入门、DC的Tcl命令风格、变量定义与基本运算、常用Tcl数据结构(列表、数组)

好,咱们进入第四讲。说实话,很多做综合的工程师,脚本能力是短板。你想想看,DC(Design Compiler)这个工具,说白了就是个听话的机器。你给它什么指令,它就干什么活。但如果你连指令都说不利索,那机器再强也白搭。

我个人习惯,学任何EDA工具,第一件事就是搞懂它的脚本语言。DC用的是Tcl,一种看起来简单、但用起来很灵活的语言。嗯,咱们今天就把Tcl在DC里的那点事儿,掰扯清楚。

4.1 Tcl语言入门:为什么是它?

Tcl,全称Tool Command Language。名字很直白,就是给工具用的命令语言。我刚开始接触时觉得这语言太简陋了,连个像样的循环都要写半天。但后来我发现,它的设计哲学就是「一切皆命令」。你写的每一行,都是发给工具的一条指令。这跟DC的工作模式完美契合。

在DC里,你敲的每一行Tcl,DC都会立刻执行。没有编译,没有链接。你写错了,它当场报错。这种交互式的方式,其实很适合调试。我在项目中遇到过好几次,写了一个复杂的约束脚本,跑起来全是红的。要是没有Tcl这种即时反馈的机制,我估计得疯掉。

小提示: 在DC的Tcl环境下,你可以随时用 help 命令查看某个命令的用法。比如 help set,它会告诉你set命令的所有选项。这个习惯,我建议你从一开始就养成。

4.2 DC的Tcl命令风格:跟标准Tcl有啥不同?

标准Tcl的命令风格,是 命令 参数1 参数2 ...。DC呢?它基本遵循这个规则,但加了很多自己的私货。比如,DC里大量使用 - 开头的选项。

举个例子,标准Tcl里定义一个变量:

set my_var 10

在DC里,你定义一个时钟:

create_clock -name clk -period 10 [get_ports clk]

看到了吗?-name-period 这些都是选项。DC的命令风格,就是「命令 + 一堆带短横线的选项 + 参数」。这种风格的好处是,可读性强。你一看就知道 -period 10 是周期为10。

我曾经犯过一个低级错误,把 -name 写成了 -names。DC直接报错,说找不到这个选项。嗯,这里要注意,DC对选项名称非常敏感,多一个s都不行。

4.3 变量定义与基本运算

变量是脚本的灵魂。在Tcl里,定义变量用 set 命令:

set frequency 100
set period [expr {1000 / $frequency}]

这里有个关键点:$ 符号用来取值。你定义变量时不用 $,但你要用它的值时,必须加 $。我第一次写Tcl时,老忘记加 $,结果DC把变量名当成了字符串,死活不对。

基本运算,用 expr 命令。注意,expr 后面要跟花括号 {},这是Tcl的规范写法:

set a 5
set b 3
set sum [expr {$a + $b}]
set product [expr {$a * $b}]

DC里常用的运算,无非就是加减乘除、取模这些。但有个坑:除法。Tcl里的除法,如果两个数都是整数,结果也是整数。比如 [expr {5/2}] 结果是2,不是2.5。要得到小数,你得写成 [expr {5.0/2}] 或者 [expr {double(5)/2}]

警告: 在DC脚本中,如果你用除法来计算时钟周期,一定要小心整数除法的问题。我曾经因为这个,算出来的周期差了0.5ns,导致综合出来的时序全是红的。排查了半天才发现是除法的问题。

4.4 常用Tcl数据结构:列表(List)

列表,是Tcl里最常用的数据结构。说白了,就是一堆东西排成一排。定义列表很简单:

set lib_list {slow.lib fast.lib typical.lib}
set cell_list [list AND2 OR2 INV NAND2]

花括号 {}list 命令都可以创建列表。我个人习惯用 list 命令,因为更清晰。

操作列表的常用命令:

  • llength $lib_list:获取列表长度。比如这个例子返回3。
  • lindex $lib_list 0:获取第0个元素,返回 slow.lib
  • lappend lib_list extra.lib:在列表末尾追加元素。
  • lsearch $lib_list "fast.lib":搜索元素,返回索引。找不到返回-1。

在DC里,列表用得最多的场景,就是管理库文件。比如:

set search_path [list . /home/libs /tools/tsmc28]
set target_library [list slow.lib typical.lib fast.lib]

你想想看,如果没有列表,你得一个一个地设置库文件,那得多麻烦。

4.5 常用Tcl数据结构:数组(Array)

数组,跟列表不一样。列表是线性的,数组是键值对。定义数组:

array set timing_constraints {
    clk_period 10
    clk_name   sys_clk
    input_delay 2.5
    output_delay 3.0
}

取值用 $timing_constraints(clk_period)。注意,数组的取值语法是 $数组名(键名)

我建议你在DC脚本里,用数组来管理一组相关的约束。比如:

array set clk_info {
    clk1 {name clk_a period 10}
    clk2 {name clk_b period 15}
    clk3 {name clk_c period 20}
}

foreach clk [array names clk_info] {
    set info $clk_info($clk)
    # 这里可以解析info,创建时钟
}

你看,用数组加列表,可以很优雅地管理多个时钟。我在一个项目里,需要处理12个异步时钟域。要是没有数组,我估计得写12遍 create_clock,那脚本就太臃肿了。

核心要点: 列表适合管理「同类型、有序」的数据,比如库文件列表。数组适合管理「不同类型、有名称」的数据,比如时钟参数。选对数据结构,你的脚本会清晰很多。

4.6 实战小例子:一个简单的DC初始化脚本

咱们把今天学的串起来,写一个简单的DC初始化脚本:

# 设置库路径
set search_path [list . /home/techlibs]
set target_library [list typical.lib]
set link_library [list * typical.lib]

# 设置时钟参数
array set clk_config {
    period 10.0
    name   main_clk
    port   [get_ports clk]
}

# 创建时钟
create_clock -name $clk_config(name) \
             -period $clk_config(period) \
             $clk_config(port)

# 设置输入输出延迟
set_input_delay 2.0 -clock $clk_config(name) [all_inputs]
set_output_delay 2.5 -clock $clk_config(name) [all_outputs]

puts "脚本初始化完成。时钟周期: $clk_config(period) ns"

这个脚本虽然简单,但包含了变量、列表、数组、运算和DC命令。你把它跑一遍,就能看到DC是怎么工作的。

嗯,今天就到这儿。下一讲,咱们会深入DC的约束命令,比如 set_max_delayset_multicycle_path 这些。到时候你会发现,今天的Tcl基础,就是那些高级命令的基石。