create_clock 用法:OpenSTA 最小闭环实测,report_checks 报告逐列怎么读
更新时间:2026-10-28。本文是「集成电路大类 · 芯片设计」入门层(beginner)的一篇工具实跑文。概念侧的两篇先导已经把地基打好:静态时序分析 STA 入门 讲清 STA 为什么不靠仿真就能遍历亿万条路径,时序为什么不满足:建立时间与保持时间 讲清 setup 检查的物理直觉。概念懂了之后,新手最常卡住的是一个很具体的问题:SDC 里那句 create_clock 到底喂给谁、工具算出来的报告每一列是什么账?本篇用开源工具 OpenSTA 跑一个最小闭环——读库、读网表、定义时钟、出报告——把 report_checks 的输出逐列拆开读。
本文要回答的问题
- create_clock 的最小语法是什么,它给 STA 工具注入了哪些信息?
- 一个最小的 OpenSTA 流程需要几条命令,各自的职责是什么?
- report_checks 报告里 Delay、Time、Description 三列怎么读,arrival time 与 required time 怎么对账出 slack?
- slack 显示 8.76ns MET 意味着什么,什么情况下会变成 VIOLATED?
先把本次实测的完整报告摆上来,后文所有数字都来自这份输出:
这份实录分三段看:开头 Startpoint: _37_ 与 Endpoint: _44_ 说明工具挑出的最关键路径是两个上升沿触发器之间的一条;中间上半段是「数据到达路径」从 0.00ns 累加到 1.17ns;下半段是「时钟需求路径」,在 10.00ns 的捕获沿上减去 0.08ns 的库 setup 时间得到 9.92ns 需求时间。最后一行 8.76 slack (MET) 就是全文的核心结论——这条路径离时序违例还有 8.76ns 的余量。
一、为什么 STA 的一切从 create_clock 开始
静态时序分析的本质是一道减法:数据最晚必须什么时候到(required time)减去数据实际什么时候到(arrival time),差值就是 slack——正数安全、负数违例。问题是「必须什么时候到」这个数字从哪来?答案只有一个来源:时钟约束。没有 create_clock,工具不知道你的芯片打算跑多快,也就没有任何一条路径存在「要求」,报告无从谈起。这也解释了为什么 SDC 文件里 create_clock 永远写在最前面——其余所有约束(输入输出延迟、false path、multicycle)都是在这口「时钟基准」之上做的加减法。
create_clock 的最小语法长这样:
create_clock -name clk -period 10.0 [get_ports clk]三个要素缺一不可:-name 给时钟起个名字(报告里 Path Group 一栏会用它分组);-period 10.0 声明周期 10ns,也就是目标频率 100MHz;最后的 [get_ports clk] 把这个时钟挂到网表的哪个源头节点上。这一句执行完,工具就知道了三件事:所有由 clk 驱动的触发器在每个 10ns 边界有一次启动沿和一次捕获沿、setup 检查按「启动沿 0ns、捕获沿 10ns」对账、路径按 clk 这个 group 归类报告。
这里有个新手高频坑值得点名:忘记定义时钟时,STA 工具的表现是「所有路径 unconstrained」——不是报错拦你,而是静默地不给任何结论,报告空空如也。这与仿真侧同构的事故互为镜像,比如 Verilator 时钟约束缺失 里排查的「定时逻辑不前进」,根源同样是时钟没喂进去。时钟是数字电路的空气,少了它所有时序语义都悬空——仿真器里信号不翻转,STA 里路径没有要求,两类工具的症状不同、病根是同一个。
还有一个理解上的要点:create_clock 定义的时钟同时参与 setup 与 hold 两类检查。同一条路径上,setup 检查用「启动沿 0ns、捕获沿 10ns」对账(Path Type: max),hold 检查用「同一个沿、数据不许变得太快」对账(Path Type: min)。report_checks 默认只打印 setup 最差路径,hold 要加 -path_delay min 才出来。本文的计数器路径 hold 余量极大(数据 1.17ns 才到,远大于 hold 要求),所以正文只展开 setup 一侧;两类违例的根源分野(一个嫌慢、一个嫌快)见 建立时间违例和保持时间违例根源不同。
二、实验设计:一个 8 位计数器走完全流程
被测对象刻意选得极小——一个带使能与异步复位的 8 位计数器,RTL 如下:
module sta_demo (
input wire clk,
input wire rst_n,
input wire en,
output reg [7:0] q
);
always @(posedge clk) begin
if (!rst_n) q <= 8'h00;
else if (en) q <= q + 1;
end
endmodule先用 Yosys 把这段 RTL 综合成 sky130 单元库(sky130_fd_sc_hd,典型角 tt_025C_1v80)的门级网表——STA 分析的对象是门级网表而不是 RTL,因为报告里每一级延迟都要查库里的真实单元时序。综合命令里两个关键动作值得记:dfflibmap -liberty 把通用触发器映射到库里的具体 DFF 单元,abc -liberty 让组合逻辑综合以库单元的时序面积为目标做优化。选 sky130 不是因为要流片,而是它是开源生态里唯一时序信息完整、无需签 NDA 就能拿到的工艺库,做教学实验的账本最干净。然后 OpenSTA 侧的全部脚本只有六行:
read_liberty $env(STA_LIB)
read_verilog sta_demo_net.v
link_design sta_demo
create_clock -name clk -period 10.0 [get_ports clk]
set_propagated_clock [all_clocks]
report_checks六行各自的职责:前三行把「库 + 网表 + 顶层设计」装配进工具,link_design 之后电路结构才完整;create_clock 是本文主角,注入时序约束;set_propagated_clock 要求按时钟网络的实际传播延迟计算(而不是理想零延迟假设),让报告数字更接近物理真实——两者的区别在于,理想模式下工具假设时钟沿同时到达所有触发器(skew 为零),propagated 模式下沿时钟网络逐单元累加延迟,时钟树综合之后两种模式算出的 slack 可以差出几百皮秒,签核一律用 propagated;最后 report_checks 触发时序检查并打印最差路径。整个闭环没有图形界面、没有工程文件,一条命令跑完,这就是开源 STA 的最小形态。
需要如实说明工具入口的一个细节:本实验环境里 OpenSTA 走 OpenLane 镜像内的独立二进制 /build/bin/sta。如果改用 openroad 入口跑同样的脚本,link_design 会报 ORD-1010「no technology has been read」——openroad 入口要求先 read_lef 读工艺物理信息,而纯 sta 入口只做逻辑级时序分析、不需要 LEF 文件。只关心时序账的读者走 sta 入口最省事。
三、create_clock 的完整语法:最小三要素之外还能说什么
本文只用到了最小三要素,但 create_clock 的实际语法还有几个常用参数,按使用频率排:
create_clock -name clk -period 10.0 -waveform {0 5} [get_ports clk]-waveform {rise fall}:声明时钟在一个周期内的上升沿与下降沿时刻,缺省是{0 period/2}即 50% 占空比。DDR 接口或占空比不对称的时钟必须显式给。-add:在同一个节点上叠加第二个时钟(比如同一引脚在不同模式下跑不同频率),不加-add会被后定义的时钟覆盖。- 挂在内部引脚而非端口:
[get_pins pll/CLKOUT]之类写法用于时钟源头在芯片内部的场景,比如 PLL 输出。 -comment:给时钟加注释,审查 SDC 时留痕用。
与 create_clock 配套、但本篇没用到的还有两个命令,先知道名字即可:create_generated_clock 用来描述分频/倍频出来的派生时钟(工具会自动从源时钟推导波形,手写 create_clock 挂内部分频点反而容易错);set_clock_uncertainty 给时钟加抖动与偏斜预算,是在「理想时钟」之上叠一层悲观量。这些都属于 时序分析(STA) 里时钟树与 skew 章节的领地,等基础闭环跑顺了再逐层加码——约束写作的正确顺序永远是「先把主时钟定义对,再谈精细化」。
四、从时钟定义到 setup 检查:一条路径的时间轴
create_clock 声明的 10ns 周期,落到具体一条触发器到触发器路径上,就是下面这张时间对应关系:
读这张图把两条时间线对齐:上面蓝色 clk 每个周期 10ns,0ns 的上升沿是启动沿——源触发器 _37_ 在这一拍把旧值推出 Q 端;数据穿过四级组合逻辑(与门、与非门、或门、AOI 复合门),在 1.17ns 时稳定到达目标触发器 _44_ 的 D 端。而捕获沿要等下一个 10ns 上升沿才来,且库要求数据比捕获沿提前 0.08ns(setup time)就稳定。于是这条路径的账就是:10.00 − 0.08 − 1.17 = 8.76ns 富余。waveform 里从「数据到达」到「捕获沿」之间那段长长的空白,就是 slack 在时间轴上的形状——slack 不是抽象分数,是数据到齐之后干等捕获沿的那段闲置时间。
五、report_checks 报告逐列解读
把报告正文原样摘录,逐段对账:
Delay Time Description
---------------------------------------------------------
0.00 0.00 clock clk (rise edge)
0.00 0.00 clock network delay (propagated)
0.00 0.00 ^ _37_/CLK (sky130_fd_sc_hd__dfxtp_1)
0.33 0.33 ^ _37_/Q (sky130_fd_sc_hd__dfxtp_1)
0.23 0.56 ^ _21_/X (sky130_fd_sc_hd__and4_1)
0.15 0.71 v _29_/Y (sky130_fd_sc_hd__nand4_1)
0.32 1.03 v _35_/X (sky130_fd_sc_hd__or3_1)
0.14 1.17 ^ _36_/Y (sky130_fd_sc_hd__a21boi_0)
0.00 1.17 ^ _44_/D (sky130_fd_sc_hd__dfxtp_1)
1.17 data arrival time三列的分工:Delay 是「这一级本身花了多少时间」(增量),Time 是「走到这里累计是第几纳秒」(绝对时刻),Description 是「当前在哪一级、哪个引脚」。每行末尾的 ^ 与 v 是信号跳变方向——上升沿与下降沿,用来核对单元库里查的是哪个方向的延迟表(门的上升延迟与下降延迟通常不相等,比如 and4 输出升 0.23ns,而 nand4 输出降只要 0.15ns)。逐行读下来,源触发器自己的 clock-to-Q 是 0.33ns,四级组合逻辑合计 0.84ns,数据 1.17ns 到达。这就是 静态时序分析 STA 入门 里说的「用门延迟逐级累加出到达时间」的真实长相。
报告的下半段是需求侧的账:
10.00 10.00 clock clk (rise edge)
0.00 10.00 clock network delay (propagated)
0.00 10.00 clock reconvergence pessimism
10.00 ^ _44_/CLK (sky130_fd_sc_hd__dfxtp_1)
-0.08 9.92 library setup time
9.92 data required time
---------------------------------------------------------
9.92 data required time
-1.17 data arrival time
---------------------------------------------------------
8.76 slack (MET)捕获侧从 10.00ns 的时钟沿出发,减去库规定的 setup 时间 0.08ns(dfxtp 触发器要求数据提前 0.08ns 稳定),得到需求时间 9.92ns。最后三行是总结算:9.92 − 1.17 = 8.76,标注 MET 表示满足。
下半段里有两行「零值」值得专门解释,因为它们在真实大设计里不会是零。clock network delay (propagated) 为 0.00,是因为这个网表里没有显式的时钟树缓冲器——时钟从端口直通触发器,传播路径上没有可计算延迟的单元;做完 CTS(时钟树综合)的设计里,这一行会是几百皮秒甚至纳秒级,且启动侧与捕获侧的时钟延迟往往不相等,差值就是 skew。clock reconvergence pessimism(时钟重收敛悲观量)为 0.00 同理:当启动与捕获路径共享一段时钟网络时,工具会把共享段算出的悲观差值退回来,避免重复计罚;本例两条时钟路径完全同构,没有可退的量。这两行现在安静地躺着零,恰恰是你日后在真实报告里认出它们非零时的参照系。
各列含义汇总成一张表,方便速查:
| 报告字段 | 含义 | 本次实测值 |
|---|---|---|
| Startpoint / Endpoint | 路径的起点与终点触发器 | _37_ → _44_ |
| Path Group | 路径归属的时钟组(来自 create_clock -name) | clk |
| Path Type | 检查类型,max=setup、min=hold | max |
| Delay 列 | 本级单元的增量延迟(ns) | 每级 0.14–0.33 |
| Time 列 | 累计到达时刻(ns) | 0.00 → 1.17 |
^ / v | 该级输出的跳变方向,决定查库哪张延迟表 | 升/降交替 |
| data arrival time | 数据实际到达时刻 | 1.17ns |
| data required time | 数据最晚允许到达时刻 | 9.92ns |
| slack (MET/VIOLATED) | 需求减到达,正为 MET、负为 VIOLATED | 8.76ns MET |
六、slack 8.76 MET 意味着什么,以及余量怎么花掉
8.76ns 的 slack 说明这条 8 位计数器路径在 100MHz 下几乎没有任何压力——数据只用了周期的 11.7% 就到齐了。这不意外:四级逻辑实在太短。slack 的价值不在「大」,而在它是设计修改的预算表:如果把这个计数器换成 32 位加法器级联更多逻辑,arrival time 会从 1.17ns 往 9.92ns 逼近;一旦 arrival 超过 required,slack 变负、报告尾部从 MET 翻成 VIOLATED,就是 建立时间违例 里讲的「数据太慢」场景——修法是砍组合逻辑级数、插流水线,或者干脆把 create_clock 的 period 放宽。读报告时还要养成一个习惯:先看 Path Group 与 Path Type 再进细节,确认自己看的是哪个时钟域、哪类检查的账——多时钟设计里不同 group 的最差路径可能差一个数量级,混着看很容易误判收敛状态。
反过来,如果有人把 -period 10.0 改成 -period 1.0(目标 1GHz),required time 变成 0.92ns,同一条 1.17ns 的路径立刻 VIOLATED——同一份网表、同一条路径,只是时钟约束变紧,结论就翻转。这正说明 create_clock 不是「告诉工具时钟是什么」,而是「告诉工具你的性能目标是什么」:STA 报告永远是对照约束打出来的分数,约束本身就是设计意图的一部分。更细的时钟语义(时钟延迟、不确定性、多时钟)可以参考 时序分析(STA) 里的 skew 与时钟树章节,本篇只把最小闭环钉死。
最后补一组 report_checks 的常用选项,实战中几乎天天用:report_checks -path_delay min 切到 hold 检查视角;-through、-from、-to 把报告过滤到指定路径(大设计里全量报告动辄几万行,不过滤根本没法看);-format full 展开每个单元的输入引脚,定位具体是哪一级逻辑贡献了延迟;-slack_max 0 只打印违例路径,时序收敛后期全靠它刷红清单。入门阶段先把默认输出读熟——本文逐列拆的那张表,就是所有更花哨用法底下共用的账本格式。
给读者的延伸练习:把 run.tcl 里的周期从 10.0 逐步收紧到 2.0、1.5、1.2,观察 slack 从正转负的临界点(理论上在 1.25ns 附近,即 arrival 1.17 加 setup 0.08);再把计数器位宽从 8 改到 32 重新综合,看最差路径的级数与 arrival 怎么变。这两组对照各花不到一分钟,但对「约束即目标、路径即账本」的直觉养成比读十页文档都管用。
这一篇在知识体系里的位置
- 概念先导:静态时序分析 STA 入门(STA 为什么可行、slack 的定义)与 时序为什么不满足:建立时间与保持时间(setup/hold 的物理直觉);
- 违例排查侧:建立时间违例和保持时间违例根源不同(slack 变负之后的修法分野);
- 体系视角:时序分析(STA)——建立时间、保持时间、slack(时钟偏斜、时序路径分类等完整图景);
- 同构事故:Verilator 时钟约束缺失(仿真侧「时钟没喂进去」的对照案例)。
一句话总结
create_clock 是 STA 的一切起点:它把「芯片打算跑多快」这个设计意图变成可计算的捕获沿时刻,report_checks 则把每条路径拆成 Delay/Time/Description 三列流水账——到达时间逐级累加、需求时间从时钟沿倒减 setup 时间,两者相减就是 slack。本篇在 OpenSTA 2.4.0 上实测最小闭环:sky130 库 + 8 位计数器网表 + 10ns 时钟,最差路径 arrival 1.17ns、required 9.92ns、slack 8.76ns MET;同一份网表把周期压到 1ns 即变 VIOLATED,可见约束即目标。
实验环境
- 机器/系统:云主机 16C32G / Ubuntu 22.04(eda-golden-v1 镜像)
- 工具版本:OpenSTA 2.4.0(41a51eaf4c,OpenLane docker 镜像内
/build/bin/sta独立入口)+ Yosys 综合(sky130_fd_sc_hd 单元库,tt_025C_1v80 典型角) - 日期:2026-10-28
- 完整命令:
yosys -p "read_verilog sta_demo.v; synth -top sta_demo; \
dfflibmap -liberty $LIB; abc -liberty $LIB; clean; write_verilog sta_demo_net.v"
sudo docker run --rm -v $PWD:/w -w /w \
-e STA_LIB=$LIB efabless/openlane:<sha>-amd64 /build/bin/sta -exit run.tclrun.tcl 内容为第二节的六行脚本。若改用 openroad 入口,link_design 会因未读 LEF 报 ORD-1010,纯时序分析请走 sta 入口。
代码获取
RTL、综合脚本与 run.tcl 已同步到公开仓库:geek-doc.cn/experiments/opensta-create-clock-report,克隆后进入该目录,按 README 先跑 Yosys 综合生成网表、再执行 sta 脚本即可复现本文报告。