OpenSTA 第一次静态时序分析:liberty 加门级网表加 SDC 跑通 report_checks
更新时间:2026-11-03。概念篇 静态时序分析 STA 入门 讲过 STA 把电路展成时序图、逐条路径累加延迟算 slack,本篇解决手底下的问题:第一次打开开源工具 OpenSTA,到底喂哪几个文件、按什么顺序敲哪几条命令,才能把第一份
report_checks报告跑出来。本篇讲正常流程,命令不报错却结果不对的场景见 OpenSTA clock uncertainty 未生效排查,全簇索引在 EDA 知识目录。
本文要回答的问题
- STA 这一步要吃哪几个文件、吐出什么?为什么三个输入缺一不可?
read_liberty、read_verilog、link_design、read_sdc、report_checks各自干什么,顺序为什么不能乱?- 手里没有真实 PDK 库,能不能先跑起来?最小 liberty 要写哪些字段才够算一条路径?
- 报告里的 arrival、required、slack 三行数字和库表里的延迟值怎么逐项对上?
先给一张命令流水线卡,第一次 STA 的完整动作就是这六步:
读这张卡抓住一条主线:前两步解决「电路和门延迟从哪来」,中间两步解决「顶层是谁、时钟多快」,最后两步出结论。下文按「位置 → 命令 → 文件 → 易错点 → 真跑」展开。
一、STA 这一步在数字设计流程里的位置
STA 位于「综合之后、签核之前」,并且在布局布线后还要带着真实线延迟反复重跑。它的三个输入各有来历:liberty 是工艺厂给的标准单元库,门级网表是综合器把 RTL 映射到这套单元后的产物,SDC 是设计师写的时钟与接口约束。三者在 OpenSTA 里汇合,产出的 slack 报告决定设计是收敛还是打回重做。
图里绿色是三个输入,蓝色是本篇主角,灰色的综合是上游、橙色报告是产出。本篇的网表是手写的两级 DFF 小设计,真实流程里它由 Yosys 综合后 write_verilog 生成(见 综合是什么);库则换成 sky130 这类真实 PDK 库,本篇因为容器里没有 PDK,自己写一个三单元的教学库把流程跑通。
二、命令逐段解读:run_sta.tcl 五条命令
整条流水线只有一个 Tcl 脚本,逐行看:
read_liberty tiny.lib
read_verilog top.v
link_design top
read_sdc constraints.sdc
report_checks -path_delay max -digits 3
report_checks -path_delay min -digits 3read_liberty:读标准单元库。OpenSTA 由此知道每个单元的引脚、功能、引脚电容与延迟表;不读库,网表里的DFF_P只是一串无意义的名字。read_verilog:读门级网表。注意这里读的是结构网表(实例化单元),不是带always的 RTL;OpenSTA 不做综合,不会替你把行为描述展开成门。link_design top:参数top是顶层模块名。它把网表引用的每个单元到库里逐一解析,并展平层次。库单元对不上会在这里报liberty cell not found(排查见 OpenSTA link 缺单元)。read_sdc:读 SDC 约束文件。SDC 命令也可以直接写进 Tcl,二者等价;独立成文件是为了让综合、后端共用同一份约束。report_checks -path_delay max:报告最长路径,即 setup 检查。-digits 3让延迟保留三位小数,便于和库表逐值核对。report_checks -path_delay min:报告最短路径,即 hold 检查。2.0.17 没有-setup/-hold这两个旗标,写错会告警并仍按默认 max 出报告,所以必须用-path_delay max/min区分。
容器外那条 sta -no_init -exit run_sta.tcl 也值得说清:-no_init 表示不加载用户目录下的初始化脚本,保证同一份 Tcl 在任何机器上行为一致、可复现;-exit 表示脚本跑完立即退出,方便把整条命令塞进 Makefile 或 CI。不加 -exit,执行完会停在交互解释器里等输入,批处理场景会表现为命令「挂住不返回」。脚本是严格顺序执行的,把 read_sdc 挪到 link_design 之前、或把建时钟写在 read_liberty 之前,都可能让对象解析落在空集合上——顺序不是风格问题,是正确性问题。
三、三个输入文件长什么样
第一个输入是教学库 tiny.lib,单位声明、阈值、一维查表模板和三个单元:
library(tiny_teaching) {
time_unit : "1ns";
delay_model : table_lookup;
capacitive_load_unit (1,pf);
/* 输入/输出/转换阈值必须齐全,否则库被判无效 */
input_threshold_pct_rise : 50.0;
output_threshold_pct_rise : 50.0;
/* ... 其余六个阈值属性 ... */
lu_table_template(delay_1d) {
variable_1 : total_output_net_capacitance;
index_1 ("0.000, 0.010");
}
cell(NAND2_X1) {
pin(Y) {
function : "!(A B)";
timing() {
related_pin : "A";
timing_sense : negative_unate;
cell_rise(delay_1d) { values("0.120, 0.180"); }
cell_fall(delay_1d) { values("0.100, 0.150"); }
}
}
}
/* BUF_X1 同构;DFF_P 带 rising_edge 弧与 setup/hold 约束 */
}关键点有两个:组合单元的每个 timing() 组必须写 related_pin 与 timing_sense,声明延迟弧从哪个输入来、同相还是反相,NAND 有两个输入就要写两组;延迟是按输出负载在两个索引点之间线性插值得到的,不是定值。时序单元 DFF 的库描述结构不同,靠三组信息撑起一条时序路径:
cell(DFF_P) {
ff(IQ, IQN) { clocked_on : "C"; next_state : "D"; }
pin(C) { direction : input; clock : true; }
pin(D) {
timing() { related_pin : "C"; timing_type : setup_rising;
rise_constraint(scalar) { values("0.100"); } }
timing() { related_pin : "C"; timing_type : hold_rising;
rise_constraint(scalar) { values("0.020"); } }
}
pin(Q) { timing() { related_pin : "C"; timing_type : rising_edge;
cell_rise(scalar) { values("0.200"); } } }
}ff 组声明这是个时钟打拍单元;D 端的 setup/hold 约束决定要求时间,Q 端的 rising_edge 弧给出发射延迟(Tco)。三者缺任一个,STA 都无法把这个单元识别成路径的起点或终点。
第二个输入 top.v 是两级 DFF,中间穿过一个 NAND 和一个 BUF。这里要特别区分门级网表和 RTL:文件虽然都是 .v 后缀,但网表里只有模块实例和连线,每个实例名在库里都能查到;带 always、if、+ 的行为 RTL 不能直接拿来 STA——OpenSTA 不做综合,读进去也不会生成触发器实例,最后 link 出来是空设计。所以正确顺序永远是先经 Yosys 之类综合器映射成单元,再把网表交给 OpenSTA。网表本体很短:
DFF_P u_a (.D(d), .C(clk), .Q(s1));
NAND2_X1 g1 (.A(s1), .B(en), .Y(n1));
BUF_X1 g2 (.A(n1), .Y(s2));
DFF_P u_b (.D(s2), .C(clk), .Q(q));第三个输入 constraints.sdc 只有一行,在输入端口 clk 上建一个周期 1.0ns 的时钟:
create_clock -name clk -period 1.0 [get_ports clk]这一行同时定义了路径起点(时钟沿 0.0ns)和捕获要求(下一沿 1.0ns)。没有时钟,报告只会告诉你端点 unconstrained。
四、常见错与检查点
第一次跑最容易栽在三个地方,前两个本篇都实际踩到过:
| 现象 | 检查点 | 纠正 |
|---|---|---|
Error: Library ... is missing one or more thresholds,随后 No paths found | 库里只有单位没有八个阈值属性 | 补全 input/output/slew 的 rise/fall 阈值 |
零告警退出,但 No paths found | 组合单元 timing() 组漏写 related_pin,时序弧被静默丢弃 | 每个 timing 组补 related_pin 与 timing_sense,重跑到出现路径 |
| 想看 hold 却看到和 setup 一样的报告 | 用了不存在的 -hold 旗标 | 改 report_checks -path_delay min |
第二类最有迷惑性:库能读、网表能 link、时钟也建了,工具一个错不报,就是没有路径——因为对 OpenSTA 而言,没有 related_pin 的 timing 组构不成时序弧,组合单元在时序图里等于断路。排查信号与约束为什么「静默消失」的系统方法见 OpenSTA report_checks 全 unconstrained 排查,库文件根本没读到的场景见 OpenSTA liberty not found。
五、真实跑一遍:报告逐行对账
用镜像 geek-eda:0.3-sta 执行 sta -no_init -exit run_sta.tcl,setup 报告关键段如下:
读报告分上下两半。上半是数据到达时间:u_a/C → u_a/Q 是触发器 CLK 到 Q,标量延迟 0.200;g1/Y 的 0.126 和 g2/Y 的 0.088 来自库表插值——g1 的输出网只带 BUF 输入引脚电容 0.001pf,落在 NAND rise 表 0.120(负载 0)与 0.180(负载 0.01)之间,按 1/10 插出 0.126;g2 带 D 端电容 0.002pf,BUF 表 0.080 到 0.120 按 2/10 插出 0.088。三段相加 0.414,就是 data arrival time。
下半是数据要求时间:捕获沿在 1.000ns,减去库内 setup 0.100,要求 0.900。slack = 0.900 − 0.414 = 0.486 MET,周期 1ns 下这条路径余量约半个周期。hold 报告同一条路径取最短延迟:
min 分析里 NAND 取 fall 插值 0.105,最早到达 0.200 + 0.105 + 0.088 = 0.393;要求时间只等于库内 hold 0.020(同一沿、无周期项),故 hold slack 0.373 MET。
报告里还有三行新手常问的固定项:clock network delay (ideal) 恒为 0,是因为 OpenSTA 默认时钟树是理想的,没做时钟树综合前都该是 0;clock reconvergence pessimism 同理为 0,只有做了片上偏差或传播时钟后才出现非零修正;Path Group: clk 表示路径按时钟域分组,多时钟设计里每组各自出最差路径。最后的判读只有一句话:MET 是余量为正,VIOLATED 是负 slack,负 slack 的分级处置见 OpenSTA slack (VIOLATED) 负裕量解读。还要注意此时没有任何布线信息,网表电容只有引脚电容、线延迟为零;真实签核要读 SPEF 反标互连 RC,报告数字会明显变大。
实验环境
- 机器/系统:本地 Docker 容器,Ubuntu 24.04;宿主为 macOS。
- 工具版本:OpenSTA 2.0.17(镜像 geek-eda:0.3-sta,
sta -version实测输出 2.0.17)。 - 日期:2026-11-03。
- 完整命令:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta sta -no_init -exit run_sta.tcl,依次执行读库、读网表、link、读 SDC、max/min 两份报告,stdout/stderr 合并记录。 - 实验边界:
tiny.lib是教学用最小库,数值仅用于演示流程与报告读法;产线请替换为 sky130 等真实 PDK 库,结论不能外推为任何工艺的签核依据。
代码获取:完整的 tiny.lib、top.v、constraints.sdc、run_sta.tcl 与 Makefile 位于 geek-doc.cn/experiments/flow-opensta-first-sta,装好 OpenSTA 2.0.17 后执行 sta -no_init -exit run_sta.tcl,或在有镜像的机器上执行 make run 复现上述两份报告。
这一篇在知识体系里的位置
本篇是 STA 的「第一次跑通」操作页:setup/hold 与 slack 的概念直觉在 静态时序分析 STA 入门 和 时序为什么不满足;同一工具的真机深读可看 OpenSTA create_clock 与报告上手;跑通之后遇到的各类异常——负 slack 怎么分级处置见 OpenSTA slack (VIOLATED) 负裕量解读,时钟约束不生效见 OpenSTA clock uncertainty 未生效排查。
总结
OpenSTA 第一次静态时序分析只需要三个输入:带阈值与延迟表的 liberty 库、门级网表、一条 create_clock 的 SDC;命令顺序固定为 read_liberty → read_verilog → link_design → read_sdc → report_checks,前两者供电路与门延迟、中间两者定顶层与时钟、-path_delay max/min 分别出 setup 与 hold。两个最隐蔽的坑是库缺八个阈值属性直接判无效,以及组合单元 timing 组漏写 related_pin 导致零告警却无路径。实测在 1.0ns 周期下,Tco 0.200、NAND 与 BUF 延迟按引脚负载从库表插出 0.126/0.088,setup slack 0.486、hold slack 0.373 均 MET;教学库只演示流程,产线必须换成真实 PDK 库并反标布线 RC。