EDA Tcl 基础:OpenSTA 与 Yosys 里真正用得到的最小语法集
OpenSTA、Yosys 这一代开源 EDA 工具几乎都内嵌同一个 Tcl 解释器:命令行交互敲的是 Tcl,批处理脚本写的也是 Tcl。但 EDA 工程师不需要学完整本 Tcl 语法书——真正每天用到的只有五六件东西:变量、列表、foreach、过程、命令查询。本篇是 EDA Tcl 基础操作篇,每个语法点都不讲通用编程例题,而是直接落在「遍历多个角点出报告」「批量读入 RTL」「封装一段报告流程」这类真实 EDA 场景,同一份脚本分别在 OpenSTA 2.0.17 与 Yosys 0.33 中真跑。第一次跑 STA 的完整命令序列见 OpenSTA 第一次静态时序分析,约束本身怎么写见 SDC 编写流程。
本文要回答的问题
- EDA 工具里的 Tcl 脚本和普通 Tcl 教程讲的东西,差别到底在哪?
- 变量、列表、
foreach、proc在 OpenSTA/Yosys 里各自解决什么实际问题? - OpenSTA 的对象集合(collection)能不能当普通 Tcl 列表遍历?
- 为什么
yosys -c script.tcl报invalid command name "read_verilog",正确入口是什么? - 忘了一条命令的名字或参数时,不翻手册怎么在 shell 里直接查?
读这张卡抓住一条主线:左边五件全是通用 Tcl 语法,右边挂着的全是 EDA 原生命令。学 EDA Tcl 不是学一门新语言,而是学会用五个语法件把工具命令编排起来。
一、Tcl 在 EDA 流程里的位置
上游是网表、库与 RTL 文件,下游是给人读、给回归脚本解析的文本报告,中间这一步就是 Tcl 脚本:它本身不计算延迟、不做综合,只负责把工具原生命令按顺序、按循环、按参数组织起来。
图里绿色的报告是唯一产物,蓝色两色分别是「你写的控制流」和「工具给的词汇」。同一个 foreach 关键字在 OpenSTA 里可以遍历角点,在 Yosys 里可以遍历 RTL 文件——语法只有一套,动词随宿主工具而变。这也是本篇与 SDC 内容篇的分工:SDC 编写流程讲约束「写什么」,本篇讲这些命令「用什么语法组织起来」。
二、五个语法件逐个解读
变量:set 定义,$ 取值。 Tcl 没有声明语法,set period 10.0 即定义即赋值,$period 即替换成字符串。EDA 脚本的惯例是把环境相关的量集中放在文件头:库名、网表名、顶层名、时钟周期,换设计时只改这几行:
set lib_file tiny.lib
set netlist top.v
set top top
set period 10.0
read_liberty $lib_file
read_verilog $netlist
link_design $top
create_clock -name clk -period $period [get_ports clk]列表与 foreach:批量驱动同一条命令。 list 构造列表,lappend 追加元素,foreach 逐项绑定变量。典型场景是 setup/hold 两个角点各出一份报告——角点列表只有一处定义,报告主体只写一遍:
set corners [list max]
lappend corners min
foreach c $corners {
report_checks -path_delay $c -digits 3
}proc:给一段命令序列起名字、加参数。 过程体里默认看不到外层变量(Tcl 是严格作用域),需要共享时用 variable 引用全局变量,更干净的做法是像下面这样全部走参数。参数化的过程让主流程变成一句可读的调用:
proc report_corner {corner digits} {
variable period
puts "----- path_delay $corner, period=${period}ns -----"
report_checks -path_delay $corner -digits $digits
}
foreach c $corners { report_corner $c 3 }命令查询:info commands 与 help。 不记得命令全名时,info commands report_* 按通配符列出当前解释器里真实存在的命令;知道命令名但忘了参数时,help report_checks 直接打印完整签名,包括 -path_delay、-corner、-digits 等所有开关。两者的分工是:一个负责「有哪些命令」,一个负责「这条命令怎么用」。
三、输入输出文件长什么样
本篇实验吃两类输入、在两个工具里跑同一套语法。OpenSTA 侧:教学库 tiny.lib(BUF/NAND2/DFF 三个单元、标量查表延迟,产线替换为 PDK 库)、两级寄存器门级网表 top.v、脚本 sta_flow.tcl,用 sta -no_init -exit sta_flow.tcl 执行。Yosys 侧:两个可综合 RTL 文件 rtl/gates.v、rtl/cnt.v 与脚本 ys_flow.tcl。两边的输出都是纯文本报告:STA 给出 setup/hold slack,Yosys 给出每个模块的 wire/cell 统计。脚本文件本身就是输入清单与执行流程的合体,新增一个角点或一个 RTL 文件只改列表行,不动执行逻辑。
四、常见错与检查点
- Yosys 里
yosys -c x.tcl报invalid command name "read_verilog"。-c的 tcl 前端只给纯 Tcl,综合命令没有导入。正确写法是走 tcl pass 并在脚本首行导入:入口用yosys -p "tcl ys_flow.tcl",脚本第一行写yosys -import。检查:导入后再跑,日志出现Executing Verilog-2005 frontend才说明命令真的接上了。 - Tcl 的
proc与 Yosys 的procpass 撞名。yosys -import不覆盖 Tcl 内置命令,Yosys 的过程优化 pass 被改名成procs,同时cd/eval/exec/read/trace五个命令因碰撞跳过导入(日志会显式提示)。检查:脚本里定义过程照常用proc,想跑 Yosys 过程优化要写procs。 - 把 OpenSTA 集合当普通列表。
all_registers、get_ports返回的是 STA 对象集合,不是 Tcl 字符串列表;OpenSTA 2.0.17 也没有 PrimeTime 的foreach_in_collection。正确写法是直接用普通foreach逐项取出对象句柄,再用get_property $reg full_name取属性。检查:能直接puts $reg打印、但取属性必须经过get_property,就说明手里是对象不是名字。 - 过程里读不到外层变量。 现象是
can't read "period"。检查变量是在proc外定义还是过程体里;纠正办法优先走参数传入,确需共享全局量时在过程体里声明variable period(OpenSTA)或global(标准 Tcl)。
五、真实跑一遍
OpenSTA 脚本先把角点列表构造成 {max min},再用 foreach 调 report_corner 过程出两份报告。setup 报告的真实输出:
读这张终端卡:标题行是 proc 里 puts 拼出来的,period=10.0 来自 variable period 对全局变量的引用;到达时间 0.414 = DFF 的 C→Q 0.200 加 NAND/BUF 两级门延迟,要求时间 9.900 = 周期 10 − 库 setup 0.100,slack 9.486 MET。同一过程第二次以 min 调用,产出 hold 报告 slack 0.373 MET。循环跑完后,脚本用 foreach 遍历 all_registers 集合,并演示两种命令查询:
这一段证明三件事:集合可以直接 foreach,两个寄存器全名逐行打出(顺序由工具内部决定,本例是 u_b、u_a);info commands report_* 一次性列出 28 个报告类命令,想不起名字时不用翻手册;help report_checks 打印的参数签名里,本篇用到的 -path_delay 与 -digits 都在其中。
Yosys 侧,首行 yosys -import 后,同样的列表加 foreach 批量读入两个 RTL,proc show_stat 封装「标题加 stat」,再逐模块调用:
gate_demo 被拆成 4 个逻辑单元($and 两个、$not、$or 各一个),后面的 cnt4 则有 14 个 wire bit、1 个 $add 单元和 1 个未综合的 process(always 块)。末尾 info commands synth* 列出 synth 与各厂商的 19 个变体共 20 条——命令查询语法在两个工具里一字不差。Yosys 报告各字段怎么读见 Yosys stat 报告解读。
代码获取:完整脚本、教学库、网表与 RTL 位于 geek-doc.cn/experiments/flow-tcl-basics-eda,执行 bash run.sh,或用 docker run --rm -v "$PWD":/work -w /work geek-eda:0.4-flow bash -lc 'bash run.sh' 一次复现 OpenSTA 与 Yosys 两侧全部输出。
实验环境
- 机器/系统:本地 Docker 容器,镜像 geek-eda:0.4-flow;宿主为 macOS。
- 工具版本:
sta -version输出2.0.17;yosys -V输出Yosys 0.33 (git sha1 2584903a060)。 - 日期:2026-11-04。
- 完整命令:在实验目录执行
bash run.sh,依次运行sta -no_init -exit sta_flow.tcl与yosys -p "tcl ys_flow.tcl",完整输出见同目录run1.log。 - 实验边界:
tiny.lib仅 3 个单元、标量延迟,数字只用于演示 Tcl 语法如何驱动 EDA 命令,不能外推为签核结论。
这一篇在知识体系里的位置
本篇讲 EDA 工具的脚本语言机制本身,是所有命令式流程的语法底座:第一次按顺序跑通 STA 读 OpenSTA 第一次静态时序分析,约束内容与落地顺序读 SDC 编写流程,STA 在整个数字设计中的位置读 静态时序分析 STA 入门;Yosys 侧的第一次综合读 Yosys 第一次综合,看懂 stat 输出读 Yosys stat 报告解读。
总结
EDA Tcl 基础的全部日常就是五件语法:set 与 $ 把库名、周期等环境量参数化,list/lappend 构造角点与文件清单,foreach 把同一条 EDA 命令批量施加到每个元素,proc 给一段命令序列起名字并参数化,info commands 与 help 负责在 shell 里查命令名和参数;OpenSTA 2.0.17 里对象集合直接用 foreach 加 get_property 遍历(没有 foreach_in_collection),Yosys 0.33 里要经 yosys -p "tcl x.tcl" 入口并先 yosys -import,且 Tcl 的 proc 与 Yosys 的 proc pass 共存时后者改名 procs——实测两侧脚本各一次跑通,setup slack 9.486、hold slack 0.373,28 个 report_* 命令与 20 个 synth* 命令均可由同一套查询语法列出。