2. Tcl语言基础(上):语法规则、变量、列表、字符串操作、数学运算
各位同学,咱们今天开始啃Tcl这块硬骨头。说实话,我刚开始接触仿真脚本时,也觉得Tcl这语言有点「怪」,跟C、Python这些主流语言长得不太一样。但干了几年的自动化脚本搭建后,我越来越喜欢它——简洁、直接、没那么多花架子。
这一章,咱们把Tcl最核心的基础过一遍。你只要把这几个点吃透了,后面写脚本就会顺手很多。
2.1 Tcl语法规则——其实就三条
Tcl的语法,说白了就三条核心规则。我当年刚入行时,带我的老工程师跟我说:「你把这三条记住,Tcl就学会一半了。」我当时还不信,后来发现还真是这么回事。
- 每条命令占一行——命令之间用换行符或分号分隔
- 命令由单词组成——第一个单词是命令名,后面是参数,空格分隔
- 替换机制——变量替换用
$,命令替换用[],反斜杠转义
来看个最简单的例子:
set a 10 # 设置变量a为10
puts $a # 输出10
puts [expr $a + 5] # 输出15,这里用了命令替换
嗯,这里要注意:Tcl里一切皆命令。连set、puts这些看起来像关键字的东西,本质上也是命令。你甚至可以自己重新定义它们——当然,我不建议你这么做,除非你想把同事搞疯。
2.2 变量——Tcl里只有字符串
这一点可能会让你有点意外:Tcl里所有变量都是字符串。没错,包括数字、列表、甚至后面要讲的对象引用,底层全是字符串。
为什么会这样?因为Tcl的设计哲学就是「简单」。把所有东西都当字符串处理,解释器就不用操心类型转换了。我在项目中遇到过好几次,从C语言转过来的同事,死活不理解为什么Tcl里1 + 2会报错——因为Tcl不认识+这个命令,你得用expr。
变量操作三件套
| 操作 | 命令 | 示例 |
|---|---|---|
| 设置变量 | set |
set name "Hello" |
| 获取值 | $ |
puts $name |
| 删除变量 | unset |
unset name |
set myVar "仿真脚本"
puts "我正在写$myVar" # 输出:我正在写仿真脚本
# 变量名可以包含字母、数字、下划线
set var_1 100
set var_2 "hello world"
-!Tcl会把my-var解析成my命令加上参数-var。我当年写一个自动化脚本时,就因为变量名里带了个减号,排查了整整一个下午。从那以后,我所有变量名都只用下划线。
2.3 列表——仿真脚本的基石
做仿真脚本,列表是你打交道最多的数据结构。无论是信号列表、参数列表、还是测试用例列表,几乎全是列表操作。
我个人觉得,Tcl的列表操作比Python还灵活——当然,这是个人偏好。咱们来看几个最常用的:
创建列表
set signals {clk rst data valid} # 用花括号
set params [list freq 100 phase 0] # 用list命令
set empty {} # 空列表
列表操作命令
| 命令 | 作用 | 示例 |
|---|---|---|
lindex |
取第n个元素 | lindex $signals 0 → clk |
llength |
列表长度 | llength $signals → 4 |
lappend |
追加元素 | lappend signals enable |
linsert |
插入元素 | linsert $signals 1 reset |
lreplace |
替换元素 | lreplace $signals 0 0 clk_in |
lsearch |
搜索元素 | lsearch $signals data → 2 |
# 实战:管理仿真信号列表
set test_signals {clk rst_n data_in data_out}
puts "信号数量:[llength $test_signals]"
puts "第一个信号:[lindex $test_signals 0]"
# 追加新信号
lappend test_signals valid ready
puts "追加后:$test_signals"
# 输出:clk rst_n data_in data_out valid ready
lindex时,索引从0开始。但如果你访问不存在的索引,Tcl不会报错,而是返回空字符串。我曾经因为这个特性,在调试一个信号比对脚本时,发现数据对不上——原来是索引写错了,但Tcl默默返回了空值,导致比对结果全是「通过」。从那以后,我每次取列表元素都会先检查长度。
2.4 字符串操作——文本处理利器
仿真脚本里,字符串操作太常见了。解析波形文件、处理日志、格式化输出……哪样都离不开字符串。
常用字符串命令
set str "Hello, Tcl World!"
# 字符串长度
string length $str # 返回:17
# 字符串比较
string equal "abc" "ABC" # 返回:0(不相等,默认区分大小写)
string equal -nocase "abc" "ABC" # 返回:1
# 字符串匹配(支持通配符)
string match "*.tcl" "script.tcl" # 返回:1
# 提取子串
string range $str 7 9 # 返回:Tcl
# 大小写转换
string toupper $str # 全部大写
string tolower $str # 全部小写
# 去除空白
string trim " hello " # 返回:hello
string trimleft " hello " # 去除左侧空白
string trimright " hello " # 去除右侧空白
格式化输出——format命令
这个命令跟C语言的printf很像。我在写仿真报告时,经常用它来对齐数据:
# 格式化输出
set freq 100.5
set phase 45.2
puts [format "频率:%8.2f MHz, 相位:%6.1f°" $freq $phase]
# 输出:频率: 100.50 MHz, 相位: 45.2°
string range配合string first,可以快速定位并提取关键信息。比如:
set log_line "Time: 12.345 ns, Signal: clk"
set start [string first "Time: " $log_line]
set time_str [string range $log_line [expr $start + 6] [expr $start + 12]]
puts "提取的时间:$time_str ns"
2.5 数学运算——expr命令
Tcl本身不认识加减乘除,你得用expr命令来算。这个命令会解析表达式,然后返回计算结果。
基本运算
# 算术运算
set a 10
set b 3
puts [expr $a + $b] # 13
puts [expr $a - $b] # 7
puts [expr $a * $b] # 30
puts [expr $a / $b] # 3(整数除法!)
puts [expr $a / 3.0] # 3.33333(浮点除法)
puts [expr $a % $b] # 1(取模)
# 数学函数
puts [expr sin(3.14159/2)] # 1.0
puts [expr sqrt(16)] # 4.0
puts [expr abs(-5)] # 5
puts [expr int(3.7)] # 3(取整)
puts [expr round(3.7)] # 4(四舍五入)
expr 10 / 3,结果是3,不是3.333。为什么?因为两个操作数都是整数,Tcl就做整数除法。只要有一个是浮点数,结果就是浮点数。我建议你养成习惯:涉及除法时,至少把一个数写成浮点形式,比如10.0 / 3。
位运算和逻辑运算
# 位运算(常用于寄存器配置)
set reg_val 0x0F
puts [expr $reg_val << 4] # 左移4位:0xF0
puts [expr $reg_val & 0x03] # 按位与:0x03
puts [expr $reg_val | 0xF0] # 按位或:0xFF
# 逻辑运算
puts [expr 1 && 0] # 0(逻辑与)
puts [expr 1 || 0] # 1(逻辑或)
puts [expr !1] # 0(逻辑非)
2.6 知识体系总览
说了这么多,咱们用一张图来梳理一下本章的知识结构。这是我做培训时常用的方式——先看整体,再抠细节。
这张图把咱们今天讲的内容串起来了。你看,Tcl的基础其实就这五大块。语法规则是骨架,变量和列表是血肉,字符串操作和数学运算是工具。把这五块吃透了,你就能写出像模像样的仿真脚本了。
expr的整数除法陷阱,自己踩一次坑,比我说一百遍都记得牢。
好了,这一章的内容就到这儿。记住:Tcl没那么神秘,它就是一套简洁的规则加上一堆实用的命令。多用、多练、多踩坑,你很快就能上手。
可可精品资料库