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这种即时反馈的机制,我估计得疯掉。
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}]。
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_delay、set_multicycle_path 这些。到时候你会发现,今天的Tcl基础,就是那些高级命令的基石。