什么是 TCL?
TCL,Tool Command Language,是一种脚本语言,主流的 EDA 工具都支持 TCL 进行操作。TCL 经常被用于 快速原型开发,脚本编程, GUI 和测试等方面。
TCL 学习
接下来以一个实际的 tcl 脚本为例,简单介绍 TCL 的使用。这个脚本是用于 modelsim/qusetasim 仿真的脚本,mentor 将 tcl 进行拓展,称之为 do 文件,实际语法还是 TCL。脚本来自链接:
/zixunimg/eefocusimg/blog.csdn.net/k331922164/article/details/50001035
# testbench name without .v , .vt , .vhd , .vht
set testbench_name conv
# simulation time ; ns/us/ms/min
set sim_time 1us
# auto add files when $auto_add_file==1; add file list when $auto_add_file==0
quietly set auto_add_file 1
# project path
set prj_path E:/conv_sim
# backup path
set bkp_path E:/conv_sim/backup
# monitor resolution ratio , for example : 1920 x 1080
quietly set m_width 1920
quietly set m_height 1080
if 0 {
my first program in Tcl program
Its very simple
}
首先,第一行,TCL 采用#进行单行注释,多行注释使用判断句注释,就像末尾的 if 那样。TCL 是一种弱语言类型,不需要声明类型,直接通过 set 声明变量并进行赋值。字符串变量直接输入即可,不需要额外的声明。这里直接 set 的变量是全局变量。
proc new_prj {new_prj_name} {
global prj_path
set new_prj_path E:/conv_sim/$new_prj_name/
quit -sim
project close
if { [file exists $new_prj_path]==0 } {
file mkdir $new_prj_path
}
project new $new_prj_path $new_prj_name work
if { [file exists $new_prj_path/src]==0 } {
file mkdir $new_prj_path/src
}
file copy -force -- $prj_path/sim.do $new_prj_path
puts "create new project successfully!"
puts "current project path : "
pwd
}
这里定义了一个用于创建工程的命令 new_prj。
proc 为 procedure 的缩写,TCL 使用 proc 声明过程,用 proc 声明后,过程就会成为脚本的一个命令,直接使用。对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为全局变量。TCL 中,局部变量和全局变量可以同名,两者的作用域的交集为空:局部变量的作用域是它所在的过程的内部;全局变量的作用域则不包括所有过程的内部。如果我们想在过程内部引用一个全局变量的值,可以使用 global 命令。这样在 new_prj 中就能访问到全局变量 prj_path。
变量的引用通过$进行引用,会直接将表达式进行替换。
quit 是 qusetasim 的命令,用于退出软件,加上选项 -sim 将只退出仿真而不会关闭软件。project close 也是 qusetasim 的命令,用于关闭工程。
file exists path,判断是否存在指定文件夹。file mkdir path,创建文件夹。file copy -force — source targetDir 复制文件到指定目录,这个命令把 source 中指明的文件或目录递归的拷贝到目的地址 targetDir,只有当存在 -force 选项时,已经存在的文件才会被覆盖。试图覆盖一个非空的目录或以一个文件覆盖一个目录或以一个目录覆盖一个文件都会导致错误。
注意,- 表示开关 / 选项,-- 表示这后面再没有开关(switchs)了,即后面以’-‘开头的参数将不作为 switch。
puts 用于打印,和 sv 中的$display 一个作用。
proc encrypt_src {} {
global prj_path
set encrypt_path $prj_path/encrypt_src/
file mkdir $encrypt_path
set paths {}
set paths [glob -nocomplain -directory $paths */]
lappend paths $prj_path
foreach one_path $paths {
set vfiles [glob -nocomplain -directory $one_path *.v *.vt *.sv]
foreach one_file $vfiles {
set one_file_path $prj_path
vencrypt [append one_file_path $one_file] -d $encrypt_path
}
}
puts "encrypt HDL design file(s) successfully!"
}
这里利用 questasim 的 vencrypt 将工程内的文件全部进行加密。
set paths {}创建一个列表,列表是 TCL 中的一个数据类型。
glob 和 file 是 TCL 提供的两个进行文件名操作的命令,用来操作文件或获取文件信息。glob 命令采用一种或多种模式作参数,并返回匹配这个(些)模式的所有文件的列表,其语法为:glob switches pattern pattern 。-nocomplain 是一个开关,使用这个开关允许 glob 返回空列表,不开启的话,如果结果是空,则会返回错误。-directory,用于指定目录,让 glob 在这个目录下进行匹配。pattern 代表正则匹配的模式,匹配成功的文件名将添加进返回的列表。
lappend 在一个列表后添加一个元素。
foreach 遍历整个列表的元素,中间变量为 one_path。
vencrypt 是 questasim 的命令,用于将文件进行加密,加密后的文件可以被 questasim 识别编译,但是无法阅读。比如下面的代码的后半段就是前半段进行加密后的内容。-d 用于指定加密后的文件生成加密文件的存放位置。
module relu_core (
input clk, //clk
input rst_n, //reset
input [255:0] pooling_in,
input relu_bypass,
output [255:0] relu_out //output of pooling
);
wire [255:0] relu_result;
genvar i;
generate
for(i=0; i<32; i=i+1) begin
assign relu_result[i*8+7:8*i] = pooling_in[i*8+7] ? 8'h00 : pooling_in[i*8+:8];
end
endgenerate
assign relu_out = relu_bypass ? pooling_in : relu_result;
endmodule
`pragma protect begin_protected
`pragma protect version = 1
`pragma protect encrypt_agent = "QuestaSim" , encrypt_agent_info = "10.6c"
`pragma protect key_keyowner = "Mentor Graphics Corporation" , key_keyname = "MGC-VERIF-SIM-RSA-2"
`pragma protect key_method = "rsa"
`pragma protect encoding = ( enctype = "base64" , line_length = 64 , bytes = 256 )
`pragma protect key_block
NROTnsk8pETOxl4QiAgkLU/279V7O329exd+zzLCVEaVhofiC+ppJtC4dQapAZkK
4CZaxp/gcINr0jlEzm2pBtmqhJ5Kd71mtMNPuUCpphC+J/VJ0JNsOVsVnG6dGztB
+2p8OzoWDWF4F+yAF7L6O7Vksq5afDXEwJDlkIfxzxg3PfwWjDbssR5lq731UMSE
a9cfMZZxe8OO7itVsCz/zAK+WKSZvpAlArpT2mHg76CMXN5ybxCzwXtjeAZf0piM
61waCYBncD+dloA+5uhHJxOj10CAE7jsF8rgi3bfMG/XqmneMRzL8ODqaLtolytR
h2xcuR63Zyi3tgTOeUvXSg==
`pragma protect data_method = "aes128-cbc"
`pragma protect encoding = ( enctype = "base64" , line_length = 64 , bytes = 528 )
`pragma protect data_block
8v6OffgbV/rkWBfwQvGwkfKmdYvvLkohFDLb+Qy+qVnoZgIFOFAFomkEaZ5b32l7
Qmw1d80d+hxDgn4XFA3qbNpn80H3EDx8+HhZL8GeoZw1Wkp4dlFpUcQLEJ3tNh4d
j314qMHeKj8Uu/4928YoJVMzojlFbn+vWh0ZTyzs5jLJa+EspgVZCSIvlPY9WYOQ
1HmF3kaj/0rJ35hmJXvy+ZMshKS031slUm/7jSxVj0kzdrNLGNYdgKIOvKTTu9vT
lYCjfkZSbLEGAXKWFlWSGCfqgb4sGc6uB0j+oKpE2yVOrMJaQ4rqHg0ykJiwn1fM
MwByU4SVRJK024OPjQXEL4aSQHgi0gvw30Ihxvcr4Do15HVZCBn43R6SVNvQNa0s
Nj6PLcT3jYPk9HZs0ZiykRR+LMLLlfFD9NFaN1s1PNZugAywB9TmYh8Vq1CY5w0Y
GxucPCAqYDn+jet1wFh3Rk/ybjj8ageR/4W+U0eKXF+Uu7YWfUApRkGH75Ru3yE/
f9JiC/xdmH6As5aWGzvNZk2g2BLzamLp7d4tmCQbizQSby38hKW8VOJmOzEQPWHg
UxKcN/VZzW6yPVrJxPRHRIb/qarIrx75IdFRZjYrCI9FFchJ3TASPSviufKyAWOj
jWIkYe8Z9yQ5fTMZwe7NhT27PfRe46HR9YuMP9lCCGcYplKVbG5xFwX6bRdqAqwK
`pragma protect end_protected
proc create_file_list {} {
global prj_path
set prj_files [project filenames]
set paths {}
set paths [glob -nocomplain -directory $paths */]
lappend paths $prj_path
set fd [open $prj_path/hdl_design_file_list.tcl w+]
foreach one_path $paths {
set vfiles [glob -nocomplain -directory $one_path *.v *.vt *.vhd *.vht]
foreach one_file $vfiles {
set exists_num 0
set one_file_path {}
append one_file_path $prj_path
append one_file_path $one_file
foreach one_prj_file $prj_files {
if {$one_file_path == $one_prj_file} {
incr exists_num
}
}
if {$exists_num == 0} {
puts -nonewline $fd "# "
}
puts $fd $one_file_path
}
}
close $fd
puts "create file list successfully!"
}
这里定义了一个创建文件列表的过程。
set fd [open $prj_path/hdl_design_file_list.tcl w+],open 命令以特定模式打开文件,返回一个文件标识符。这与 c 语言类似,w+ 读写方式打开文件,如文件存在则清空文件内容,否则创建新的空文件。puts -nonewline $fd \”# “,向 fd 中写入内容,不开启 -nonewline 则会在末尾添加换行符。close 关闭文件。
proc add_file {} {
global prj_path auto_add_file
set add_count 0
set del_count 0
set prj_files [project filenames]
if {$auto_add_file == 1} {
set paths [glob -nocomplain -directory $prj_path */]
lappend paths $prj_path
foreach one_path $paths {
set vfiles [glob -nocomplain -directory $one_path *.v *.vt *.vhd *.vht]
foreach one_file $vfiles {
set exists_num 0
foreach one_prj_file $prj_files {
if {$one_file == $one_prj_file} {
incr exists_num
}
}
if {$exists_num == 0} {
project addfile $one_file
incr add_count
}
}
}
} elseif {$auto_add_file == 0} {
if {[file exists $prj_path/hdl_design_file_list.tcl] == 0 || \
[file size $prj_path/hdl_design_file_list.tcl] <= 1} {
create_file_list
}
set fd [open $prj_path/hdl_design_file_list.tcl r]
set old_content [read -nonewline $fd]
close $fd
regsub -all " " $old_content {} new_content
regsub -all \t+ $new_content {} new_content
foreach one_file [split $new_content \n] {
if {[regexp ^# $one_file] == 0 && $one_file != "\n"} {
set exists_num 0
foreach one_prj_file $prj_files {
if {$one_file == $one_prj_file} {
incr exists_num
}
}
if {$exists_num == 0} {
project addfile $one_file
incr add_count
}
} elseif {[regexp ^# $one_file] == 1 && $one_file != "\n"} {
set one_file [string trim $one_file "#"]
foreach one_prj_file $prj_files {
if { $one_file == $one_prj_file} {
project removefile $one_file
incr del_count
}
}
}
}
}
puts -nonewline "add "
puts -nonewline $add_count
puts " file(s) successfully!"
puts -nonewline "remove "
puts -nonewline $del_count
puts " file(s) successfully!"
}
创建了添加源文件的过程。
project filenames 是 questasim 的命令,返回当前工程的文件列表。
regsub -all “ “ $old_content {} new_content,-all 没有这个开关时,regsub 只替换第一个匹配,有了这个开关,regsub 将把所有匹配的地方全部替换。第二项为正则匹配表达式,第三项为目标字符串,第四项为替换内容。regsub 通过第二项进行匹配,将第三项中匹配的目标,替换为第四项,存到第五项中。
proc wave_style_set {} {
global testbench_name default_color
foreach sig [find signals $testbench_name/*] {
switch -regexp -nocase -- $sig {
/*/*rst+ {add wave -radix binary -color [lindex $default_color 0] $sig}
/*/*clk+ {add wave -radix binary -color [lindex $default_color 1] $sig}
/*/*en+ {add wave -radix binary -color [lindex $default_color 2] $sig}
/*/*cnt+ {add wave -radix decimal -color [lindex $default_color 3] $sig}
/*/*data+ {add wave -radix hex -color [lindex $default_color 4] $sig}
/*/*flag+ {add wave -radix binary -color [lindex $default_color 5] $sig}
default {add wave -radix hex -color [lindex $default_color 6] $sig}
}
}
}
创建了一个修改波形格式的过程。
switch -regexp -nocase — $sig ,和 c 语言的 switch 一样。TCL 支持三种匹配方式:-exact 方式, -glob 方式, -regexp 方式,缺省情况表示 -glob 方式。-exact 方式表示的是精确匹配, -glob 方式的匹配方式和 string match 命令的匹配方式相同, -regexp 方式是正则表达式的匹配方式。-nocase 是 regexp 的匹配选项,是大写字母也被当作小写来处理,相当于不区分大小写字符。最后一个变量就是被用来作测试的值 。每行的开头,比如 /*/*rst+ ,用这个正则匹配进行选择。
lindex $default_color 0 引用列表 default_color 的第 0 项。
alias ne "new_prj"
alias es "encrypt_src"
alias af "add_file"
alias br "backup_prj"
alias cf "create_file_list"
alias cc "compile_changed"
alias cr ".main clear"
alias rr "run"
alias ra "run_all"
alias sw "save_wave"
alias aw "load_wave"
alias wr "write_report"
alias df "display_info"
alias dh "do_help"
为过程创建别名。通过别名也能够调用过程