2. Tcl基础语法(上):变量与数据类型、列表与字典、字符串操作、数学运算
说实话,Tcl 这门语言第一眼看上去有点「怪」。但只要你摸清它的脾气,就会觉得——写 Vivado 自动化脚本,真离不开它。我最早接触 Tcl 也是因为要做工程批处理,那时候觉得它语法松散,后来才发现,它的灵活恰恰是效率的保证。
2.1 变量与数据类型
Tcl 里一切皆字符串——嗯,至少表面上是这样。你用 set 赋值,用 $ 取值,简单直接。
set freq_mhz 200 ;# 整数,其实存成字符串 "200"
set design_name "top" ;# 字符串
set is_ready false ;# 布尔值,也是字符串
我个人习惯在变量名里用下划线,比如 clk_period_ns,看着清楚。你想想看,在 Vivado 里我们经常要存工程名、版本号、约束文件路径,用 set 一把梭,后面用 $ 引用,多省事。
${var} 括起来。比如 ${freq_mhz}MHz,这样解析不会出错。我曾经在生成文件名时忘了加花括号,结果变量名被吞了一半,排查了半天……
2.2 列表与字典
列表就是一组有序元素。Vivado 里经常用它来管理文件列表、IP 列表。字典则是键值对,适合存属性信息。
# 列表
set src_files [list "led.v" "top.v" "pll.xci"]
lappend src_files "uart.v"
puts "文件数量: [llength $src_files]"
# 字典
set ip_info [dict create name "clk_wiz" vendor "xilinx.com" version "6.0"]
dict set ip_info status "out_of_date"
puts "IP状态: [dict get $ip_info status]"
我记得有一次做工程重构,需要从 30 多个 IP 里批量更新版本号。用 dict 把每个 IP 的名字、版本、路径存起来,一个 foreach 循环搞定。要是手动改,估计得加班到半夜。
lindex $src_files 0 取第一个元素。字典的键是大小写敏感的,写脚本时最好统一风格。
2.3 字符串操作
字符串处理是 Tcl 的强项。在 Vivado 里,我们经常要解析文件名、拼接路径、提取版本号。
set full_name "top_module.vhd"
puts "扩展名: [string range $full_name [string last . $full_name] end]"
puts "大写: [string toupper $full_name]"
puts "替换: [string map {.vhd .sv} $full_name]"
我个人特别喜欢 string map,做批量重命名或者替换约束文件里的关键词,一行搞定。你想想看,如果要在 20 个 XDC 文件里把 clk_100m 改成 clk_125m,用 Tcl 字符串操作几秒钟就跑完了。
string trim 去掉字符串首尾空格,结果没注意中间的空格也被我误删了……后来才发现 string trim 只删首尾,string map 或 regsub 才能处理中间。读文档要仔细啊。
2.4 数学运算
Tcl 的数学运算靠 expr 命令。支持整数、浮点、位运算。Vivado 里算时钟周期、分频系数、地址偏移都用得上。
set period_ns 5.0
set freq_mhz [expr {1000.0 / $period_ns}]
puts "频率: $freq_mhz MHz"
set addr_base 0x4000
set offset 0x1C
set addr_target [expr {$addr_base + $offset}]
puts "目标地址: [format 0x%X $addr_target]"
用 expr 时我建议用花括号 {} 把表达式包起来,这样性能更好,也避免一些奇怪的解析问题。嗯,这里要注意:位运算用 <<、>>、&、|,跟 C 语言一样。
incr 做整数自增,比 set i [expr {$i + 1}] 更简洁。比如循环计数器:incr i 或 incr i -1 递减。
2.5 综合示例:一个简单的工程配置片段
下面我把这四块知识串起来,模拟一个 Vivado 工程初始化的小脚本。你可以直接复制到 Vivado Tcl Console 里试试。
# 工程配置 —— 变量 + 列表 + 字典 + 字符串 + 数学运算
set proj_name "eth_controller"
set part "xc7k325tffg900-2"
set src_dirs [list "./src/rtl" "./src/ip"]
set constraints [list "./constr/timing.xdc"]
dict set board_info name "KC705"
dict set board_info rev "C"
# 计算需要的时钟周期
set target_freq 156.25
set period_ns [expr {1000.0 / $target_freq}]
puts "目标时钟周期: [format %.3f $period_ns] ns"
# 拼接报告文件名
set report_name "${proj_name}_timing.rpt"
puts "报告将保存为: $report_name"
你看,变量存参数,列表管文件,字典记板卡信息,字符串拼文件名,数学算周期——全用上了。我在实际项目里就是这么搭脚本骨架的,清晰又好维护。