OpenSTA report_checks 全 unconstrained:时钟链三路断点真实复现
report_checks 跑出来满屏 unconstrained endpoints、结尾 No paths found.,退出码却是 0——这是 OpenSTA 新用户最容易误判为「工具过了」的一类故障。它和单条路径落入 unconstrained 组不是一个量级:前者是整份报告没有任何端点被时钟罩住。单一端点视角的排查见 unconstrained endpoint 排查,create_clock 缺失与端口名拼错的对照见 OpenSTA no clocks found;本篇只处理「整份报告全 unconstrained」,并用时钟传播链把三种根因一次性区分开。
本文要回答的问题
- 整份
report_checks没有一条带 slack 的路径,端点全部 unconstrained,问题出在哪一层? report_clock_properties明明列出了 10ns 时钟,为什么寄存器还是 unclocked?- 时钟建在端口上,经过内部缓冲后为什么传不到 DFF 的时钟脚?
- SDC 文件写对了也可能完全没生效,怎么从输出里抓出来?
- 用哪几条命令能沿「时钟对象 → 时钟源点 → 时钟网络逐脚」逐级定位断点?
这张漏斗对应本文的排查主线:不要盯着 report_checks 的空白输出猜原因,而是沿时钟链路连问三级。每一级拦住一类根因,三级都通过才配看 slack。
一、现象原文:整份报告没有一条受约束路径
实验设计是一个三级时钟结构:顶层端口 clk → 内部时钟缓冲 u_clkbuf(CLK_BUF)→ DFF 时钟脚 u_stage/C,D 端接输入 d,Q 端经数据缓冲到输出 q。先看最彻底的故障形态——SDC 根本没被加载时,OpenSTA 2.0.17 的真实输出:
这段输出有三个关键证据:check_setup -verbose 列出 u_stage/C unclocked 和 q、u_stage/D 两个 unconstrained endpoints;时钟属性表只有表头一行、下面空白;report_checks 收尾是 No paths found.。最后一行 CASE_EXIT case3_nosource=0 说明 OpenSTA 认为脚本正常执行完毕——退出码 0 在这里只代表「命令跑完了」,不代表「时序查过了」。自动化流程若只看退出码,就会把一次零检查当成全绿。
注意一个迷惑性更强的变体:根因 2 的缓冲断传场景里,report_checks 反而会打出一条 slack (MET) 8.500 的路径,但它的起点写的是 Startpoint: u_stage (rising edge-triggered flip-flop)——没有 clocked by clk。这条 MET 只由输出端口的 set_output_delay 撑起,发射沿根本不存在,不能当作修复证据。这个坑后面单独拆。
二、工具与版本
全部实验在 Docker 镜像 geek-eda:0.3-sta 内运行,OpenSTA 2.0.17(Debian 官方独立包,Ubuntu 24.04)。网表与库都是教学用最小件:DFF_P 带建立/保持约束,BUF_X1 是数据缓冲,CLK_BUF 是时钟缓冲。配套两份 Liberty:good.lib 中 CLK_BUF 带完整的 A→Y 组合时序弧;broken.lib 中同一个单元只有功能描述、没有 timing 弧——这是手工裁剪库或第三方库交付不全时的真实形态,数值只用于演示,不能外推为任何工艺结论。
另一个版本细节:本文用的时钟清单命令是 2.0.17 里的 report_clock_properties;习惯用 report_clocks 的读者把它当作同一张表即可,真正可靠、不依赖命令改名的是下面反复用到的 all_clocks 加 get_property 组合。
三、三种根本原因:同一现象,断点在三层不同位置
三种根因的共同结果都是 unconstrained endpoints,但断点分别位于「对象层、网络层、文件层」。实验用 all_clocks 打印每个时钟的 sources,再用 get_pins 沿 u_clkbuf/A → u_clkbuf/Y → u_stage/C 打印每一站实际挂着的时钟对象(内部对象句柄形如 _..._p_Clock,同一条链上句柄相同才说明是同一个时钟传到了)。
根因 1:时钟根本没建在时钟网络上。 最常见的不是整行漏写(那是 OpenSTA no clocks found 的场景),而是 create_clock 作用到了错误的对象,比如随手把内部脚当端口:
CASE 1: create_clock attached to wrong net (u_buf/A instead of port clk)
DIAG all_clocks:
CLOCK=clk sources=_c0e9fb16c7550000_p_Pin
DIAG clock network pins (clk -> u_clkbuf -> u_stage/C):
PIN=u_clkbuf/A clocks=
PIN=u_clkbuf/Y clocks=
PIN=u_stage/C clocks=
Warning: There is 1 unclocked register/latch pin.
u_stage/C
clk 10.00 0.00 5.00
No paths found.时钟对象真实存在、属性表也看得到 10ns 波形,但它的源点是数据缓冲的内部脚,时钟链三站的 clocks 属性全部为空。时钟「建了」和时钟「建在 clk 上」是两件事,这正是只看属性表会误判的原因。同类变体还包括 [get_ports clck] 拼错后造出悬空时钟,端口名对不上网表的对照见前述姊妹篇。
根因 2:时钟建在端口上,但传播被内部缓冲截断。 用 broken.lib 跑同一份正确 SDC,时钟源点这次没问题:
CASE 2: clock on clk port but CLK_BUF has no timing arc (broken.lib)
DIAG all_clocks:
CLOCK=clk sources=_b07ed4043f560000_p_Pin
DIAG clock network pins (clk -> u_clkbuf -> u_stage/C):
PIN=u_clkbuf/A clocks=_0061d3043f560000_p_Clock
PIN=u_clkbuf/Y clocks=
PIN=u_stage/C clocks=
Warning: There is 1 unclocked register/latch pin.
u_stage/C这是本篇最有信息量的一段证据:时钟到达了缓冲输入端 u_clkbuf/A,却在 u_clkbuf/Y 消失,下游 u_stage/C 自然为空。STA 工具靠 Liberty 里的时序弧沿网络传播时钟,CLK_BUF 缺了 A→Y 的 timing arc,传播在这一站终止。真实工程里的同款机理还包括门控单元(ICG)使能端约束缺失、层次展平后实例名对不上、以及对时钟缓冲误用了 set_disable_timing。此时 report_checks 会出现开头说的假 MET 路径——起点不带 clocked by clk,因为发射沿从未到达,那条路径的 required time 全部来自输出延迟,不构成寄存器到输出的完整检查。
根因 3:SDC 文件根本没有生效。 又分两种现场。其一是命令行只跑了一个 tcl,而那个 tcl 里压根没有 source constraints.sdc,输出与第一节完全相同:all_clocks 一节连一个时钟都列不出,且没有任何与约束相关的报错——没执行的文件不会报错,这是它最隐蔽的地方。其二是顺序写反,在 read_liberty/link_design 之前就 source 了 SDC:
CASE 3b: source constraints.sdc BEFORE read_liberty/read_verilog
Error: no network has been linked.
Error: no network has been linked.
Error: no network has been linked.
LINK_OK
DIAG all_clocks:
...
No paths found.三条约束在网表存在之前执行,各吃一个 no network has been linked 后全部落空,之后 link 成功也不会补执行。判别要点是看报错与 LINK_OK 的先后:约束报错出现在链接完成之前,就是加载顺序问题;从头到尾没有任何约束相关输出,则要回去查命令行的 tcl 里是否真的 source 了 SDC。
三种根因的判别信号汇总如下:
| 层级 | 根因 | all_clocks | 时钟源点 | 逐脚 clocks | 特征报错 |
|---|---|---|---|---|---|
| 对象层 | create_clock 建错网络 | 有 clk | 不是 clk 端口 | 全空 | 无,或 port not found |
| 网络层 | 缓冲缺时序弧/门控断传 | 有 clk | clk 端口 | 断在缓冲输入之后 | unclocked register pin |
| 文件层 | SDC 未 source / source 过早 | 空 | 无 | 全空 | 无报错,或 no network linked |
四、排查清单:沿时钟链走三级,不要跳级
红是现象与三条分叉根因(写在边标签上),蓝是三级排查命令,橙是修复,绿是复验。落地时三条命令照此顺序执行:先 all_clocks(配合 report_clock_properties 看周期波形)确认对象层;再对每个时钟取 sources 属性比对真实时钟端口;最后用 get_pins -hierarchical 找到时钟通路上每一级单元脚,逐站打印 clocks 属性,时钟在哪一站从句柄变成空,断点就在哪一站。逐级核对的脚本化做法与更大设计的覆盖率统计见 unconstrained endpoint 审计方法;约束写得越来越多后,常规 lint 见 SDC 约束 lint。
五、修复代码与修复后的真实 slack
针对三种根因分别修:根因 3 保证在 link_design 之后 source constraints.sdc(并确认命令行跑的 tcl 里确实有这一行);根因 1 把时钟建回真实端口;根因 2 在 Liberty 中给 CLK_BUF 补回 A→Y 组合时序弧(或去掉误加的 set_disable_timing、修对门控连接):
read_liberty good.lib
read_verilog top.v
link_design top
source constraints.sdc# constraints.sdc:时钟必须建在真实时钟端口上
create_clock -name clk -period 10.0 [get_ports clk]
set_input_delay 1.0 -clock clk [get_ports d]
set_output_delay 1.0 -clock clk [get_ports q]/* good.lib 中 CLK_BUF 的时序弧:broken.lib 缺这一整段 timing() */
pin(Y) {
direction : output;
function : "A";
timing() {
related_pin : "A";
timing_type : combinational;
timing_sense : positive_unate;
cell_rise(scalar) { values("0.10"); }
cell_fall(scalar) { values("0.10"); }
}
}修复后复跑,时钟链三站挂着同一个时钟句柄,check_setup 不再输出任何警告,路径起点恢复 clocked by clk,拿到真实 slack:
FIXED: good.lib, source constraints.sdc after link, clock on port clk
PIN=u_clkbuf/A clocks=_a0fcf78978550000_p_Clock
PIN=u_clkbuf/Y clocks=_a0fcf78978550000_p_Clock
PIN=u_stage/C clocks=_a0fcf78978550000_p_Clock
clk 10.00 0.00 5.00
Startpoint: u_stage (rising edge-triggered flip-flop clocked by clk)
Endpoint: q (output port clocked by clk)
Path Group: clk
0.500 data arrival time
9.000 data required time
8.500 slack (MET)四个条件要同时成立才算修复:时钟链三站句柄一致、check_setup 零警告、起点与端点都标注 clocked by clk、Path Group: clk 下出现 8.500ns 的真实 slack。任何一条不满足,都不要拿退出码 0 交差。
代码获取:完整网表、两份 Liberty、五个场景 Tcl 与运行脚本位于 geek-doc.cn/experiments/ts-opensta-report-unconstrained,安装 OpenSTA 后执行 bash run.sh(或在 geek-eda:0.3-sta 容器内执行同一命令)即可复现全部对照。
实验环境
- 机器/系统:本地 Docker 容器,Ubuntu 24.04;宿主为 macOS。
- 工具版本:OpenSTA 2.0.17,Debian 官方独立二进制包(镜像 geek-eda:0.3-sta)。
- 日期:2026-11-02。
- 完整命令:在实验目录执行
bash run.sh,依次运行 case1_wrong_net、case2_broken_buf、case3_nosource、case3b_early_source 与 fixed,合并 stdout/stderr 并记录各组退出码。 - 实验边界:Liberty 为仅含 DFF_P/BUF_X1/CLK_BUF 最小时序弧的教学库,未做物理实现,本文数值不能外推为任何工艺的签核结论。
这一篇在知识体系里的位置
本篇属于开源 EDA 排错簇的「约束未生效」分支,上游是 SDC 数据格式与生命周期。单条路径落进 unconstrained 组、clock not found 的入门场景见 unconstrained endpoint 排查;时钟对象缺失与端口名拼错的两案对照见 OpenSTA no clocks found;时钟恢复后时序不满足是下一阶段问题,见 slack (VIOLATED) 排查。
总结
OpenSTA report_checks 全 unconstrained 的断点只可能在三层:时钟对象建错了网络、时钟被缺时序弧的内部缓冲或门控截断在半路上、SDC 压根没被执行或在 link 之前执行。排查沿 all_clocks 查对象、sources 查源点、get_pins 逐站查 clocks 属性三级走,时钟在哪一站消失根因就在哪一站;退出码 0 与不带 clocked by 的假 MET 都不能作数,修复必须同时看到时钟链贯通、零 unclocked 警告、Path Group: clk 和真实 slack。