做时序签核(STA signoff)最怕的不是一两条violation,而是整个分析结果根本不可信。我见过不少项目跑完PR后,时序报告看着挺干净,结果到了交付前复查,发现约束漏写了一大片、寄生参数只标了85%、覆盖率低得没法看——这种情况下前面所有优化动作都等于在沙子上盖楼。所以我现在每接手一个block,交付前必跑四个命令:check_timing、report_annotated_parasitics、report_analysis_coverage、report_delay_calculation。这四个命令单独拎出来都不算复杂,串起来就是一套完整的数据质量体检流程,今天就把这套组合拳的用法和踩坑经验一次讲清楚。
这套流程适合谁?后端工程师、STA工程师、以及刚入门想弄明白“为什么时序报告不能全信”的数字IC学生。读完你会知道:约束质量问题怎么定位、寄生参数标注完整度怎么看、分析覆盖率低该怎么查,以及一条路径的延迟到底是怎么被工具“算”出来的。
1. 先把四个命令串成一条验收链
1.1 这四个命令各自看什么
很多人习惯上来就抓violation,盯着某条path改到天荒地老,却忽略了更基础的问题:分析环境本身是不是可信的。这四个命令分别守住了不同关口,我列个表说明:
| 命令 | 关注点 | 解决的问题 |
|---|---|---|
| check_timing | 约束质量 | 有没有路径根本没被约束、有没有组合环、有没有误设常量 |
| report_annotated_parasitics | 寄生参数标注 | 延迟计算用的RC数据是全的还是缺的 |
| report_analysis_coverage | 分析覆盖率 | 时序引擎到底分析了几成endpoint,多少路径是盲区 |
| report_delay_calculation | 延迟计算细节 | 单条路径的cell delay和net delay是怎么算出来的 |
你可以这么理解:check_timing查的是“题目有没有出对”,寄生参数报告查的是“给定的物理信息够不够”,覆盖率查的是“有多少题根本没人做”,delay_calculation查的是“某一道题的具体解题步骤”。如果前三个没过关,后面看多少violation都是白费劲。
1.2 建议的执行顺序与组合套路
我自己的习惯是固定按这个顺序跑:check_timing → report_annotated_parasitics → report_analysis_coverage → report_delay_calculation。逻辑很简单:先保证约束是完整的,否则谈覆盖率没有意义;再确认物理数据是完整的,否则看delay没意义;然后评估整体盲区,最后才针对具体路径抠细节。
实际项目里,我已经把下面这段脚本固化成了回归的一部分,每次交block前必须跑一遍并留档:
# 1. 约束质量检查 check_timing -verbose > check_timing.log # 2. 寄生参数标注汇总 report_annotated_parasitics > annotated_parasitics.log # 3. 覆盖率统计 report_analysis_coverage -verbose > coverage.log # 4. 关键路径延迟计算明细(比如 setup critical path) report_delay_calculation -from [get_pins regA/CK] -to [get_pins regB/D] -delay_type max -nosplit > delay_calc.log这套顺序跑下来的好处是:每一步的异常都能作为下一步分析的线索。比如覆盖率低,往往会追溯到check_timing报出来的一批unconstrained endpoints;寄生标注有缺漏,又会导致后面delay_calculation里看到某些net delay异常。四个命令彼此印证,排查效率会高很多。
2. 先跑 check_timing:把约束问题挡在门外
2.1 check_timing 输出里最该盯的几类问题
check_timing的输出项在不同工具版本里略有差异,但核心检查项基本一致。下面这些是我每次必看的:
| 检查项 | 含义 | 危险程度 |
|---|---|---|
| unconstrained_endpoints | 有endpoint根本没有被任何时序约束覆盖 | 高,直接导致漏报 |
| constant_value | 某些pin被set_case_analysis或常量逻辑固定,相关路径不分析 | 中,误设会掩盖真实violation |
| combinational_loops | 组合逻辑环 | 高,会导致计算不收敛 |
| generated_clocks | generated clock来源缺失或配置错误 | 高,时钟关系全错 |
| no_input_delay / no_output_delay | 端口没设input/output delay | 中,端口路径成盲区 |
| disabled_timing_arcs | 某些timing arc被disable | 低,但要确认理由 |
这里最坑的是 unconstrained_endpoints。它意味着时序引擎根本不会去分析到达这些点的路径,不管真实延迟多大,报告里都不会出现。我见过一个项目,某模块的输出端口漏了一组约束,结果那部分路径晚了一个周期才稳定,硬是在后仿真阶段才暴露出来,改版代价非常大。
2.2 实战:怎么区分真问题和“噪音”
check_timing报出来的东西不一定都要改,关键是要有判断力。举个例子,scan_mode信号如果在top层用set_case_analysis固定为0,那么所有扫描链相关的路径在功能模式下确实不需要分析,这在check_timing里不会报unconstrained。但如果你只固定了scan_mode,忘了固定scan_en,那扫描逻辑可能残留一部分不受约束的路径,这时候就要补约束。
还有一种常见“噪音”:tie cell(tie high/tie low)的输出。这类cell输出电平恒定,连接它的逻辑永远处于常量状态,相关路径本来就不需要做时序分析。面对这种情况,正确做法是确认逻辑确实被tie死,然后显式用set_case_analysis或者set_constant处理,并把依据写在约束注释里。
check_timing跑完不要只看最后有没有“Error”,要看具体条目。我的经验是,把每一项的数量都记下来,和上一次跑的结果对比。如果某个检查项的数量突然暴增,那大概率是近期约束改动引入了新问题,比一条条翻报告快得多。
2.3 约束质量不达标,后续分析全是白搭
很多人忽略了一点:check_timing和report_analysis_coverage的结果是强相关的。约束漏得越多,覆盖率自然越低,因为大量endpoint没被约束嘛。我曾经见过一个block,report_analysis_coverage只有85%,一查check_timing,发现有一整组bus的output delay没设,endpoint从这一侧全部变成unconstrained。所以如果覆盖率低,我一般都会先回头翻check_timing,而不是急着去检查哪条路径。
约束这块没什么捷径,只能老老实实补。但补的时候有个原则:能补真实约束就补真实约束,实在不能补的再用set_false_path,而且set_false_path必须写明理由。否则三个月后没人记得这条false_path是“真伪路径”还是“懒得约束”,这才是最危险的。
3. report_annotated_parasitics:寄生参数没标完,延迟就是猜的
3.1 寄生参数从哪来,标注完整度怎么看
post-layout阶段,工具做时序分析的RC数据通常来自SPEF(Standard Parasitic Exchange Format)文件,由StarRC、QRC这类寄生提取工具生成。PR工具在优化时用的寄生参数和signoff时用的寄生参数可能来自不同的提取条件,所以我们到了签核阶段要做两件事:读入正确的SPEF,然后确认它被完整地标注到了每个net上。
report_annotated_parasitics的输出会分成几个统计块,核心信息是net的标注情况,类似下面这样:
Net Annotation Summary ---------------------- Total nets : 120000 Nets annotated : 119000 Nets annotated with RC: 118700 Nets annotated without RC: 300 Nets not annotated : 1000注意“annotated without RC”和“not annotated”的区别:前者是工具知道这个net存在,但没有收到RC值;后者是工具压根没拿到这个net的寄生信息。不管哪一种,对应的net delay计算都不是基于真实版图数据的。
最理想的情况是100%的net都有RC标注,但实际项目中这并不容易做到,特别是大型SoC的top层或ECO之后的重叠金属层。通常的项目验收标准会要求在99%以上,但对时钟网络和高频关键路径所在的net,必须做到一个都不能缺。
3.2 如何快速定位缺失的 net
只看summary肯定不够,一定要配合详细模式跑一遍,把没标注的net拉出来逐个过目。在PrimeTime里可以用下面这类命令:
report_annotated_parasitics -format detailed -net -not_annotated > missing_rc.log也可以结合工具提供的API,把所有未标注net的名字打出来,再和时钟树、关键路径做交叉比对:
foreach net [get_annotated_parasitics -net -not_annotated] { set net_name [get_attribute $net full_name] puts "Missing RC on net: $net_name" }一旦定位到缺失的net,原因通常就那几类:
| 缺失原因 | 典型场景 |
|---|---|
| SPEF版本与版图不一致 | 金属层叠信息更新后忘记重新提取 |
| ECO新增net未覆盖 | 逻辑ECO后新增的绕线没有重新提寄生 |
| 特殊网络被排除 | power/ground或某些模拟宏的net在提取时被过滤 |
| 层次不匹配 | top-down工作流里子模块和top的SPEF合并出问题 |
3.3 标注缺失时的处理套路
处理流程一般是这样:先确认SPEF是不是最新的,确认版图数据没有改动过,然后重新读入寄生参数。PrimeTime里的典型操作是:
remove_parasitics read_parasitics -format spef top_latest.spef update_timing -full重新读入后,再跑一次report_annotated_parasitics看结果。重点检查两个时刻:update_timing用的是不是最新的RC值,以及读入时有没有被工具自动忽略的net。
有些ECO场景下,整份SPEF重新提取成本太高,也可以对局部net做标注修复,但我不建议把局部标注作为常规手段。因为工具在计算delay时,对标注方式很敏感,部分net用SPEF、部分net靠估算,会造成延迟数据的口径不一致,后期排查问题会非常痛苦。我的原则是:要么不用新数据,要用就全量统一。
4. report_analysis_coverage:看看到底分析多少路径
4.1 覆盖率到底在统计什么
report_analysis_coverage(千万别拼成coverge,这是个单词错误)输出的是setup、hold还有clock gating等检查的覆盖率信息。它的输出通常长这样:
Coverage Summary ---------------- Total Endpoints: 10000 Constrained Endpoints: 9500 Unconstrained Endpoints: 500 Setup Coverage: 95.0% Hold Coverage: 95.0% Clock Gating Coverage: 95.0%首先要搞清楚“endpoint”在这里指的是什么。简单说,所有需要被时序分析覆盖的终点,包括寄存器的D端、输出端口,以及其他需要检查时序的单元pin。工具会统计有多少endpoint被完整约束并参与了分析,有多少因为各种原因被排除,二者相除就是覆盖率。
覆盖率低绝对是个危险信号。如果只有90%,意味着每20条路径里就有1条完全没被分析。而这个比例放到真实芯片上,可能就是一颗功能失效的样片。
4.2 最常见的“未覆盖”路径与处置方式
导致覆盖率不足的因素,大部分来自约束问题。我总结了几类高频原因和对应处理办法:
- 输出端口没设output delay:端口完全没约束。处理方式是补set_output_delay,如果端口确实是伪路径,再set_false_path并写明原因。
- 寄存器没有时钟:寄存器D端在功能模式下没有时钟到达。通常是时钟约束缺失,补clock或generated clock。
- 异步信号路径:被set_false_path或set_clock_uncertainty等排除的路径会进入“排除”名单,如果排除数量过多,覆盖率也会下降,但这类属于有意排除。
- 常量逻辑路径:set_case_analysis把某些信号固定后,相关路径不参与分析。这类和check_timing里的constant_value是对应的。
处置逻辑分三步:能补约束就补约束;不能补的确认是伪路径再set_false_path;所有排除理由都要留痕。最忌讳的就是为了把覆盖率数字拉上去,看到unconstrained endpoint就随手set_false_path,这样数字好看了,风险全埋进去了。
4.3 覆盖率卡在90%不动的排查经验
我遇到过最头疼的情况是覆盖率卡在某个值上不去,不是95%,也不是97%,永远是90.2%左右。查来查去,最后发现是一个lockup latch的D端和Q端都接到了同一个常量信号上,连续十几个实例全是这样。这类单元在功能上确实是冗余的,但如果你不显式处理,它就是会占用endpoint名额。
处理这类问题的正确姿势并不是把所有lockup latch全部set_false_path。锁存器本身也是有时序要求的,随意排除会丢掉时钟沿竞争信息。正确做法是确认这些latch在功能模式下是否真正处于常量状态,然后对特定输入pin用set_case_analysis,把原因写清楚。
另一个容易遗漏的点是异步复位的释放路径。复位释放的时序检查往往被单独约束或排除,如果约束写得不完整,也会产生一批unconstrained endpoints。建议在做覆盖率清理时,专门把异步复位相关的endpoint拉出来核对一遍。
5. report_delay_calculation:把延迟计算掰开揉碎
5.1 一条路径的延迟是怎么被算出来的
先讲点底层原理,不然你不知道这个命令输出的是什么。标准单元的数字电路路径延迟,主要分两部分:cell delay和net delay。
cell delay是信号从cell输入端到输出端的延迟,工具通常用查找表来算。以经典的NLDM(Non-Linear Delay Model)为例,延迟大小是输入transition时间和输出负载电容(output load)的二维函数。输入信号上升得越慢、负载电容越大,cell delay就越大。输出port还有CCS(Composite Current Source)模型,考虑的东西更细,但本质思想一致。
net delay是信号从cell输出pin到负载pin的传输延迟,计算依赖net的RC信息。常见算法是Elmore延迟模型和有效电容模型。这些算法会综合考虑driver的驱动电阻、net自身的电阻电容、以及接收端的负载电容。
所以你在report_delay_calculation里看到的每一个delay数字,都不是工具拍脑袋猜的,而是基于transition、capacitance、resistance这些物理量,通过库模型和RC模型计算出来的。这也是为什么check_timing和寄生参数标注的问题,最终都会反映到延迟计算上。
5.2 report_delay_calculation 命令怎么用
这个命令最核心的用法是拿一条具体路径来审视,看它每一级的延迟组成。典型命令如下:
report_delay_calculation -from [get_pins u_regA/CK] -to [get_pins u_regB/D] -delay_type max -nosplit -transition_time -capacitance这里解释一下选项:
-from/-to:指定起点和终点。起点通常是时钟pin或输入port,终点通常是数据pin或输出port。-delay_type max/-min:max对应setup分析,min对应hold分析。-nosplit:默认工具可能会把路径按cell边界分段展开,加上这个选项会保持一条完整路径,不拆段。-transition_time:在report里额外显示每个pin的transition时间。-capacitance:显示每个pin的负载电容。
输出内容会按从起点到终点的顺序,列出每一级cell的延迟组成、net的延迟组成、以及累加的arrival time。我截图里的一个典型输出,每一步都标明了driver pin、load pin、input transition、output load、cell delay、net delay。看到这些数据后,你就能判断延迟到底被谁吃掉了。
5.3 延迟异常时该往哪几个方向查
依据我自己的排障经验,碰到delay_calculation结果异常,优先查这四点:
| 现象 | 排查方向 |
|---|---|
| 某级cell delay偏大 | 是不是input transition太大或output load过高 |
| 某段net delay异常偏大 | 是不是net没标注RC,或绕线特别长 |
| net delay为0 | 是不是寄生数据缺失,net被当成理想线 |
| transition值异常大 | 是不是驱动cell驱动能力不够或扇出过大 |
有一次我排查hold violation,发现某条路径的net delay算出来是0.5ps,明显不对劲。翻寄生标注报告,发现那个net恰好就是报告里“annotated without RC”的那批之一。重新补了RC数据后,net delay变成15ps,hold violation直接变了性质。由此可见,delay_calculation和前三个命令是联动的,排查问题时要养成交叉验证的习惯。
再补充一个细节:工具在做delay calculation时,如果cell delay超出了查找表范围,会采用外推方式估算,误差会显著变大。所以看到transition或者load离库模型边界很近时,要特别注意,最好回查constraints或者物理实现,是不是驱动cell选小了或者扇出过大了。
6. 常见问题排查实录与经验清单
6.1 四个命令的异常输出速查表
我平时排查问题基本按下面这个表来快速定位,节省了很多时间:
| 症状 | 可能原因 | 处理建议 |
|---|---|---|
| check_timing报大量unconstrained endpoints | 约束缺失或set_case_analysis没做全 | 补真实约束,不能伪修 |
| 覆盖率低 | 未被约束endpoint过多 | 配合check_timing清理约束盲区 |
| 覆盖率卡住不动 | lockup latch、常量信号、异步复位清理不彻底 | 逐个确认后set_case_analysis或set_false_path |
| 寄生标注缺net | SPEF过期、ECO未重新提取 | 重新读入最新SPEF再update_timing |
| 某net delay异常偏低 | RC数据缺失 | 检查report_annotated_parasitics对应net状态 |
| cell delay外推 | transition或load超出库查找表范围 | 优化驱动强度或减小扇出 |
6.2 一次真实项目的时序数据质量QA复盘
最后分享一个具体案例。上个项目里有颗中小规模的子芯片,交付前我按老规矩把四个命令全跑了一遍。
第一步check_timing报出47个unconstrained endpoints。逐一核对后发现,其中31个和scan_en/scan_mode的case_analysis没设全有关,补上约束后消失;12个是tie cell输出连着的常量路径,确认后按常量处理;剩下4个是真漏约束,出在某个调试接口的output delay上,补约束解决。
第二步report_annotated_parasitics显示net标注完成率99.2%,但缺了8个net。查下来全是ECO之后新加的网,没有重新提取寄生。解决方法是让后端重新出SPEF,然后统一读入,更新后完成率回到100%。
第三步report_analysis_coverage从最初的88%一路处理到97.6%。剩下的2.4%主要是异步复位的释放路径和几个模拟宏接口,都是经过确认的合理排除项。
这个流程走下来,最直观的感受是:整个timing报告的可信度完全不一样了。之前看到的violation我们敢去改,因为知道约束完备、物理数据齐全、分析覆盖到位;不会出现改了半天结果发现是约束坑了的尴尬情况。
以我个人的习惯,现在每次做signoff前都会把这四个命令按顺序完整跑一遍,结果归档到项目记录里。这套检查看着简单,但它能拦住绝大多数“分析环境不可信”导致的大坑。如果你正在为某个timing问题反复折腾却找不到根源,不妨先退一步,把这套体检做一遍,多数时候问题就自己浮出来了。