第五章Tcl 脚本编程

Tcl 基础

Tcl 三大公理

  1. Tcl 中一切都是字符串
  2. 每个字符串都是一个列表
  3. 每个列表由空白分隔的零个或多个元素组成

基本语法

# 变量
set x 42
set name "hello"

# 变量替换 $
puts $x           ;# 输出 42
puts "$name world"  ;# 输出 hello world

# 命令替换 []
set y [expr $x + 1]

# 字符替换(转义) \
puts "Line1\nLine2"

# 大括号 {}:阻止替换
set z {$x + $y}   ;# z 是字符串 "$x + $y",不替换

# 双引号 "": 允许替换
set w "$x + $y"   ;# w 是字符串 "42 + 43"

Procedures(函数)

proc add {{a 0} {b 0}} {
    # 过程定义,a 和 b 有默认值 0
    return [expr $a + $b]
}

add 3 4    ;# 返回 7
add 5      ;# 返回 5

Return Values 和 Return Status

# catch 的语法:catch {some_command ...} variable
# 第一个参数是要执行的命令(含其参数);
# 第二个参数是一个变量,会被设为 some_command 的返回值;
# catch 本身的返回值是 some_command 的执行状态。
if {[catch {some_command} result]} {
    puts "Error: $result"
}

Standard Tcl vs Jasper Tcl 的差异

JasperGold Apps 会更改、重命名或不支持标准 Tcl 中的某些命令。官方列出的差异只有两条:

Standard TclJasper Tcl 中的解释
clocktcl_clock(被重命名,因为 clock 在 JasperGold 中是设置时钟的命令)
pidJasperGold Apps 支持 pid 命令。它返回的是 analysis session 的进程 ID,而不是 JasperGold 控制台的 pid

JasperGold 脚本常用任务

-silent 开关

许多 JasperGold Apps 命令提供可选的 -silent 开关。使用该开关时,命令不打印任何结果,而是把结果作为普通的 Tcl 返回值返回。在脚本中,只要命令支持这个开关,你几乎总是应该使用它。如果某个命令没有提供该开关,说明它的结果本来就是以 Tcl 结果值的形式返回的。

-silent单条 Tcl 命令的开关,不是 jg 的命令行选项——写成 jg -fpv script.tcl -silent 是错误的。

Structure Queries(结构查询)

脚本中最常见的任务之一,是获取当前已 elaborate 的设计的信息。get_design_info 命令提供了对大量信息的访问。默认情况下它既返回值又打印信息;在脚本中你通常只想要返回值,加上 -silent 即可禁止打印。

# 获取设计中所有模块
set modList [get_design_info -list module -silent]
# Mod_A1 Mod_B1 Mod_B2 Mod_B3 top

# 获取某个模块类型的所有实例
set instList [get_design_info -module Mod_B2 \
                -list instance -silent]
# inst0_B2 inst1_B2 inst2_B2 inst3_B2 inst4_B2 inst5_B2

# 获取信号的位范围
set sigInfo [get_signal_info -indexes wdata0 -silent]
# 1D_Array 31 0

# 获取信号的位宽
set sigInfo [get_signal_info -width wdata0 -silent]
# 32

Cone of Influence (COI) Queries

# 获取某个属性 COI 中的模块
set coiModules [get_design_info -property myTask::addr_conn0 \
                  -list module -silent]
# maint top

# 获取不在 COI 中的模块
set notCOImods ""
foreach module [get_design_info -silent -list module] {
    if { [lsearch -exact $coiModules $module] < 0 } {
      lappend notCOImods $module
    }
}
set notCOImods
# arbiter bridge egress ingress port_select

常见脚本模式

筛选属性用 get_property_list,它通过 -include / -exclude属性的 attribute 取值来筛选,而不是靠专门的开关。注意排除优先于包含:同一个 attribute 值若同时出现在 -include-exclude 中,该属性会被排除。

# 对所有 cex 属性做处理
foreach prop [get_property_list -include {status cex}] {
    puts "Failed: $prop"
}

# 取出所有未决的 assert/cover:包含 type 为 assert 或 cover
# 且未被 disable 的,排除已经 proven/cex/unreachable/cover 的
get_property_list -include {type {assert cover} disabled 0} \
                  -exclude {status {proven cex unreachable cover}}

# 数值型 attribute 需要带比较运算符
get_property_list -include {trace_length <20}

# 用正则匹配属性名
get_property_list -include {name top.inst0*(_vld|_valid)} -regexp

# 每属性时间限制是全局设置,只接受一个时间参数,
# 不能按属性单独指定
set_prove_per_property_time_limit 10s
get_property_list 的完整语法为 get_property_list [-include {...}] [-exclude {...}] [-regexp] [-task <task_name>] [-exclude_env] [-no_task_prefix]——没有 -status-tag-silent 开关。属性状态是通过 -include {status ...} 传入的 attribute 之一。

脚本化方法论

为什么要写 Tcl 脚本而不是只用 GUI?脚本让验证流程可复现、可回归、可自动化

为什么需要脚本

标准脚本模板结构

# ===== 标准 FPV 脚本模板 =====
clear -all

# 1. 设置路径
set RTL_PATH ../source/design
set PROP_PATH ../source/properties

# 2. 分析设计和属性
# 语言开关:-sv | -sv05 | -sv09 | -sv12(VHDL 用 -vhdl 系列)
analyze -sv ${RTL_PATH}/top.v
analyze -sv ${PROP_PATH}/bindings.sva

# 3. 展开设计
elaborate -top top

# 4. 时钟和复位
clock clk
reset ~rstN

# 5. 获取设计信息
get_design_info

# 6. 第一轮:快速证明
set_max_trace_length 20
prove -all

# 7. 第二轮:深度证明
set_max_trace_length 50
set_prove_per_property_time_limit 30s
# 专注找证明:N 和 Tri 性能最好
set_engine_mode {N Tri I}
prove -all

# 8. 报告
report
report -file proof_results.rpt

# 9. CI 检查:取出所有还没有 proven/unreachable 的 assert 和 cover
set failed [get_property_list -include {type {assert cover} disabled 0} \
                             -exclude {status {proven unreachable}}]
if {[llength $failed] > 0} {
    puts "ERROR: [llength $failed] properties not proven!"
    exit 1
}

常用脚本技巧

批量操作断言

# 批量操作断言:用 -regexp 按属性名匹配
assert -disable {*}[get_property_list -include {name top.debug.*} -regexp]
prove -property {top.submodule.*}

# 保存数据库
set proj_dir [get_proj_dir]
save -jdb $proj_dir/session.jdb -capture_setup
脚本最佳实践
  • 总是从 clear -all 开始,确保脚本独立运行
  • 使用变量管理路径,不要硬编码绝对路径
  • 脚本末尾用 exit code 通知 CI 系统是否通过
  • 保存数据库(save -jdb),后续可用 GUI 打开调试
  • 注释每个主要步骤的意图,方便团队理解

来源文档

  • AN_scripting.pdf