PrimeTime 约束不生效:SDC 读进去了但 report_timing 没体现速查
更新时间:2026-10-23。FAQ 速查页,属于 EDA 工具排错系列。PrimeTime(Synopsys 静态时序签收工具,交互壳
pt_shell)有一类最磨人的事故:SDC 脚本跑完了、没有报 error,可report_timing里的路径和没约束前一模一样——set_false_path写了,路径还在违例;set_multicycle_path写了,余量纹丝不动。这类「约束读进去但没生效」几乎都不是语法问题,而是对象没匹配上、被后面的约束覆盖、或被更底层的状态掐断。时钟层面的同类问题(约束缺失而非失效)见 PrimeTime 时钟/约束缺失,本篇聚焦「约束在、效果不在」;约束里引用的对象整个找不到的姊妹问题见 PrimeTime 未解析引用。
本文要回答的问题
set_false_path写了,路径为什么还在report_constraint -all_violators里?Warning: No pins matched这条告警到底意味着什么,为什么它之后约束全部失效?- 层次综合(hierarchy 变化)之后,实例名失配的约束怎么批量定位?
set_case_analysis在顶层配了,为什么子模块里的路径行为没变?- 两条例外约束互相覆盖时,哪条赢?怎么验证最终生效的是哪条?
一、报错原文与现象
pt_shell 里「约束不生效」最典型的三帧现场。
形态一,对象匹配为空,只给一条 Warning 就放行:
pt_shell> set_false_path -from [get_pins u_core/u_regfile/rd_reg*/Q]
Warning: No pins matched 'u_core/u_regfile/rd_reg*/Q'. (UID-109)
Information: Constraint accepted with empty collection. (UITE-416)形态二,约束成功加载、集合非空,但时序报告毫无变化:
pt_shell> set_multicycle_path 2 -setup -to [get_pins u_mac/acc_reg*/D]
pt_shell> report_timing -to u_mac/acc_reg_0/D
...
data arrival time 9.83
data required time 9.90 # 仍按 1 拍算
slack (VIOLATED) -0.07形态三,例外约束「写了前一条被后一条顶掉」,reset_case 之后一切恢复:
pt_shell> report_exceptions
Warning: Exception overridden by later exception on same path. (UITE-502)判读口径:UID-109(No pins matched)这类告警是「约束不生效」的头号信使——PrimeTime 的设计哲学是空集合不报错,只警告,一条匹配不到对象的约束会被「接受」然后什么都不做。所以排查的第一反应永远不是改约束内容,而是先确认约束指向的对象在网表里真实存在。
二、报错 → 原因 → 处置速查表
| 报错/现象 | 最可能原因 | 一条命令确认 | 处置 |
|---|---|---|---|
No pins matched 'xxx' (UID-109) | 实例名/层级写错,或综合后层次被摊平 | get_pins xxx 直接看返回 | 按门级网表实际层次改对象名 |
| false_path 写了路径仍违例 | 集合为空,或方向写反(from/to 颠倒) | report_exceptions -ignored | 修对象;方向对齐路径实际走向 |
| multicycle 不生效 | 只配了 setup 没配 hold,或时钟域对不上 | report_timing 看 required time | max/min、setup/hold 成对配置 |
| 约束前脚写后脚没 | 后面的通配约束覆盖了前面的精确约束 | report_exceptions 全量看 | 调整 SDC 顺序,精确约束放后面 |
| 子模块 case analysis 无效 | 顶层配了常量但端口方向/层次错 | report_case_analysis | 在正确层级 pin 上配,或换 propagate 方式 |
| 整条路径不分析、无违例也无通过 | set_disable_timing 把弧断了 | report_disable_timing | 删掉误配的 disable,恢复弧 |
| get_ports 返回空但端口明明在 | 网表摊平后端口改名或加前缀 | get_ports *xxx* 通配探测 | 用通配定位真实名再改 SDC |
| 同一例外时而生效时而不生效 | 不同 mode 场景 SDC 加载顺序不一致 | 对比两个场景的 report_exceptions | 场景脚本同源、顺序固化 |
三、排查决策图
整张图按「先确认对象在不在,再看约束被谁盖了,最后查底层状态」三段分流:空集合治对象名,非空但没效果治覆盖顺序与方向,全都对还不行就查 disable timing 和 case analysis 把路径掐了没有。
读图抓住三色主线:红色是问题现场与根因(约束失效、对象失配),蓝色是三个排查动作(验证集合、看覆盖、查状态),橙色是对应修复。绿色 verify 是收口标准——不是「不报错」,而是 report_exceptions 里能看到这条例外、report_timing 的 required time 真的变了。紫色 guard 是防复发:把「空集合即失败」写进 SDC 加载脚本。
四、逐条排查展开
根因一:集合为空——实例名在门级网表里对不上。 这是「约束不生效」里占七成以上的根因。RTL 阶段写的 u_core/u_regfile/rd_reg_0/Q,经过综合可能被摊平成 u_core/rd_reg_0/Q,也可能被分组加前缀变成 u_core/u_regfile/rd_reg_0_Q_reg/Q。PT 对 get_pins 匹配失败只给 UID-109 一条 Warning,约束照样「接受」,然后静默什么都不做。确认方法极其简单:把 SDC 里那行 get_pins 原样敲进 pt_shell,看返回是不是空。定位真实名字用通配探测:
pt_shell> get_pins *regfile*/Q
Warning: No pins matched '*regfile*/Q'. (UID-109)
pt_shell> get_pins u_core/rd_reg*/Q
{"u_core/rd_reg_0/Q", "u_core/rd_reg_1/Q"}拿到真实名字后改 SDC。批量定位整个文件的失配约束,可以逐条 sizeof_collection 检查:
foreach line $constraints {
set c [eval $line]
if { [sizeof_collection $c] == 0 } { puts "EMPTY: $line" }
}根因二:约束覆盖顺序——后写的赢。 PrimeTime 的例外约束遵循「同一路径上后加载的覆盖先加载的」。常见翻车:先写了一条精确的 set_false_path -from A -to B,后面又来一条粗放的全局约束或 set_max_delay,把前一条顶掉,report_exceptions 里会带 UITE-502 提示。排查用:
report_exceptions
report_exceptions -ignored-ignored 专门列出被覆盖而没有生效的例外,是这类问题的照妖镜。修法不是删约束,而是调整 SDC 文件内的书写顺序:粗放约束在前、精确例外在后,或者干脆把两类约束拆到两个块里,中间加注释说明覆盖意图。多场景(mode)下尤其要核对每个场景的加载顺序一致,否则同一 SDC 在功能模式有效、在测试模式失效。
根因三:multicycle 只配了一半。 set_multicycle_path 2 -setup 单配 setup 是经典半吊子写法——setup 放宽到 2 拍,hold 没跟着移,PT 按默认规则检查 hold,结果要么余量没变,要么冒出一堆新违例。正确写法成对出现:
set_multicycle_path 2 -setup -to [get_pins u_mac/acc_reg*/D]
set_multicycle_path 1 -hold -to [get_pins u_mac/acc_reg*/D]验证生效与否不要看「没报错」,要看 report_timing 里 data required time 是否真的从 1 拍变成了 2 拍——required time 没变就是没生效,回头查 -to 指向的集合和时钟域。
根因四:set_disable_timing 把时序弧掐断。 有些库里带默认 disable,或者历史脚本里留了一句 set_disable_timing [get_cells u_sync],效果是相关时序弧整段退出分析:路径既不违例也不通过,report_timing 直接找不到它。表面看「没有违例」,实际是路径根本没被检查——比违例更危险。排查:
report_disable_timing凡是不是你亲手写进签收 SDC 的 disable 条目,一律查清来源(库默认、设计约束、脚本遗留)再决定去留。
根因五:case analysis 没传到子模块。 签收按 mode 分析时常在顶层配 set_case_analysis 0 [get_ports test_mode],指望它沿端口传播进所有子模块。传播失败通常是两个原因:配值的 pin 层级写错(实际要选 MUX 的 sel 脚而不是顶层端口),或者中间有一层常数传播被 set_case_analysis 的隔离写法挡住。确认用 report_case_analysis 看最终落到每个 pin 上的常量值,再对关键时钟 MUX 的 sel 脚逐个核对——case 配错不只是约束不生效,还会把时钟分支整个判成不可达,与 PrimeTime 时钟/约束缺失 里的「时钟被掐断」症状汇流。
根因六:通配符写法在 PT 与 DC 之间语义漂移。 get_ports data_in[*] 这种总线写法在两边工具里展开规则有细微差别,DC 能匹配 32 位,PT 可能一位都匹配不到。跨工具复用 SDC 时,凡带 [*]、* 的 collection,进 PT 后第一件事是 sizeof_collection 核对数量与预期一致。这是「DC 侧好好的、PT 侧不生效」的最常见解释。
五、防复发纪律
- 空集合即失败:SDC 加载脚本对每条关键
get_pins/get_ports/get_clocks做sizeof_collection非空断言,UID-109 类告警升级成脚本中止,不让「匹配为零的约束」混进签收。 - 生效验证双口径:每写完一组例外约束,先
report_exceptions(含-ignored)确认条目在且未被覆盖,再report_timing看 required time / slack 实际变化——两个口径都对上才算生效。 - SDC 顺序固化:粗放约束在前、精确例外在后,多场景脚本同源加载;改顺序必须连带改注释说明覆盖意图。
- disable 白名单:
report_disable_timing输出纳入签收检查单,非白名单条目一律视为事故。 - 改名可追溯:综合摊平/分组改名规则记录在案,SDC 里的实例名以门级网表为准,不从 RTL 凭记忆抄写。
一句话总结
PrimeTime 约束不生效十有八九是「对象没匹配上」或「被后面的约束盖掉」:UID-109 空集合告警是头号信使,先用 get_pins/sizeof_collection 验证对象真实存在,再用 report_exceptions -ignored 查覆盖顺序、用 report_disable_timing 与 report_case_analysis 查底层状态;multicycle 要 setup/hold 成对、false path 方向对齐路径走向;生效与否以 report_exceptions 加 report_timing 双口径复核为准,防复发靠 SDC 加载脚本的空集合非空断言与约束顺序固化。