1. 时序路径分析到底在解决什么问题
数字芯片设计走到后端阶段,绕不开的一个核心动作就是静态时序分析(STA)。而STA里最基础、也最容易被忽视的概念,就是时序路径。很多人跑PrimeTime的时候,report_timing一敲,看到一串slack数字,觉得时序过了就万事大吉,但真到了流片回来发现芯片跑不起来,回头再查才发现——路径根本没抓对。
时序路径分析的本质,是在芯片的时序图上,找出所有从起点到终点的数据传播通路,然后逐一验证每条通路上的信号能否在时钟约束窗口内稳定到达。听起来简单,但实际操作中,路径的分类、约束的写法、分析模式的选择,每一步都有讲究。
这篇文章主要面向已经接触过STA基础概念、但在实际项目中经常遇到"路径漏抓""约束写了但没生效""setup和hold结果对不上"这类问题的数字后端工程师和IC设计验证人员。我会从时序路径的分类逻辑讲起,把每条路径为什么这样分、在PrimeTime里怎么被识别、SDC约束怎么对应到具体路径类型,一步步拆开讲清楚。上篇聚焦在路径类型和分析模式的底层逻辑,下篇会展开具体的约束编写和debug技巧。
先给一个直观的类比:你可以把芯片的时序路径想象成城市里的快递配送网络。触发器是配送站,组合逻辑是道路,时钟是交通信号灯。STA要做的就是检查每一个包裹(数据信号)能否在信号灯变色之前(时钟沿到来之前)送到下一个配送站。如果某条路太堵(组合逻辑延迟太大),包裹就会迟到,这就是setup违例;如果包裹到得太早,下一个配送站还没准备好接收,这就是hold违例。
2. 四条基本时序路径的拆解与识别
2.1 输入端口到触发器:容易被忽略的入口路径
输入端口到触发器(Input to Register)这条路径,起点是芯片的输入pad,终点是内部某个触发器的数据输入端D。信号从外部世界进入芯片,经过输入缓冲、可能还有一些组合逻辑,最终被第一个触发器捕获。
这条路径的特殊之处在于:外部信号的到达时间是未知的。你不知道上游芯片什么时候把数据发过来,所以在SDC里必须用set_input_delay来告诉工具"外部数据相对于时钟沿的到达时间是多少"。这个值给得准不准,直接决定了这条路径的时序分析结果是否可信。
我见过不少项目,前端工程师在约束文件里随便写了个set_input_delay 0,结果后端跑出来时序全过,但实际板级联调时数据根本采不到。原因就是输入延迟被低估了。正确的做法是查上游芯片的数据手册,找到它的输出延迟(Tco)加上PCB走线延迟,再考虑时钟树的偏斜,综合算出一个合理的input delay值。
在PrimeTime里,这条路径的识别标志是起点为input port,终点为flip-flop的D pin。用report_timing -from [all_inputs] -to [all_registers -data_pins]可以专门抓这类路径。
2.2 触发器到触发器:芯片内部的主战场
触发器到触发器(Register to Register)是芯片内部数量最多、也最核心的时序路径类型。起点是一个触发器的时钟引脚(CK),经过时钟到输出延迟(Tck2q)、组合逻辑延迟,到达下一个触发器的数据输入端(D)。
这条路径的时序分析完全由时钟约束决定。工具会计算:
- 发射触发器的时钟到达时间 + Tck2q + 组合逻辑最大延迟
- 接收触发器的时钟到达时间 + 建立时间要求
两者之差就是setup slack。如果为负,说明数据到得太晚。
这里有一个非常关键的细节:同一时钟域内的reg2reg路径和跨时钟域的reg2reg路径,分析方式完全不同。同频同相时钟域内,工具直接用时钟周期减去各种延迟来计算slack。但跨时钟域时,如果不加set_clock_groups或者set_false_path,工具会按照最悲观的相位关系去算,结果往往是一大堆假的违例。
我的经验是:在项目初期就要把所有时钟域的关系理清楚,该异步的异步,该分频的分频,该设false path的设false path。不要等到时序报告出来一堆红色再回头补约束,那时候你可能已经分不清哪些是真违例、哪些是约束没写对造成的假违例了。
2.3 触发器到输出端口:出口路径的约束逻辑
触发器到输出端口(Register to Output)这条路径,起点是内部触发器的CK引脚,终点是芯片的输出pad。数据从触发器出发,经过组合逻辑,最终驱动到外部世界。
和输入路径类似,这条路径的终点在芯片外部,所以需要用set_output_delay来描述外部接收器件对数据到达时间的要求。这个值通常包括:下游芯片的建立时间(Tsu)、PCB走线延迟、以及可能的时钟偏斜。
实际项目中,output delay的设置往往比input delay更容易出错。因为很多人只考虑了下游芯片的Tsu,忘了PCB走线的延迟。特别是高速接口,一根走线几厘米,延迟就是几百皮秒,足以让一条本来margin就不大的路径直接违例。
在PrimeTime里,这条路径的识别标志是起点为flip-flop的CK pin,终点为output port。用report_timing -from [all_registers -clock_pins] -to [all_outputs]可以抓取。
2.4 输入端口到输出端口:纯组合路径的处理
输入端口到输出端口(Input to Output)是一条纯组合逻辑路径,信号从输入pad进来,经过组合逻辑,直接从输出pad出去,中间不经过任何触发器。
这条路径的时序分析比较特殊:它没有时钟周期作为参考,完全由input delay和output delay的差值来决定。工具会检查:数据从输入端到达的时间 + 组合逻辑延迟,是否满足输出端的要求。
很多设计里,这类路径是feedthrough或者旁路逻辑,容易被遗忘。如果SDC里没有对这类路径做约束,工具默认会按照最严格的方式去分析,可能产生大量不必要的违例。正确的做法是用set_max_delay或者set_min_delay来显式约束这类路径的延迟范围。
注意:纯组合路径如果确实不需要时序约束(比如纯异步信号),一定要用
set_false_path明确告诉工具忽略它,否则工具会按照默认的最悲观方式分析,浪费大量运行时间。
3. 分析模式的选择:single、BC-WC还是OCV
3.1 三种分析模式的本质区别
PrimeTime支持多种时序分析模式,最常用的三种是:
| 分析模式 | 全称 | 核心逻辑 | 适用阶段 |
|---|---|---|---|
| single | 单一角分析 | 只在一种PVT条件下分析 | 初期快速评估 |
| BC-WC | 最好-最差角分析 | 分别在最差和最好角分析setup/hold | 中期signoff |
| OCV | 片上偏差分析 | 考虑同一芯片内不同位置的偏差 | 先进工艺signoff |
single模式最简单,就是选定一个工艺角(比如SS corner),跑一次分析。速度快,但结果不够全面。适合项目初期快速迭代,看看大概的时序情况。
BC-WC模式是分别用best case和worst case两个库来分析。setup用worst case(慢管),hold用best case(快管)。这样能覆盖到最极端的两种情况。但它的假设是:整个芯片要么全是慢管,要么全是快管。实际上同一颗芯片上,不同位置的管子速度是有差异的。
OCV模式就是来解决这个问题的。它通过derate因子来模拟同一芯片内不同路径之间的偏差。比如时钟路径用+10%的derate,数据路径用-10%的derate,这样就能覆盖到时钟偏斜最坏的情况。
3.2 什么阶段该用什么模式
我的建议是:
- RTL综合阶段:用single模式,快速跑通,看大概的时序趋势
- 布局布线初期:用BC-WC模式,开始关注setup和hold的平衡
- signoff阶段:必须用OCV模式,而且要加上AOCV或者POCV(如果工艺支持)
这里有一个常见的误区:很多人觉得OCV太悲观,跑出来一堆违例,就退回到BC-WC模式。但实际上,先进工艺下OCV是必须的,因为片上偏差的影响已经大到不能忽略了。正确的做法不是逃避OCV,而是通过优化时钟树、调整derate因子来让OCV结果收敛。
3.3 derate因子的设置逻辑
OCV模式下,derate因子的设置直接决定了分析的悲观程度。一般来说:
- 时钟路径的cell delay derate:+5%到+15%(根据工艺和金属层)
- 数据路径的cell delay derate:-5%到-15%
- 时钟路径的net delay derate:+10%到+20%
- 数据路径的net delay derate:-10%到-20%
这些值不是拍脑袋定的,而是根据foundry提供的偏差数据、结合芯片的实际工作条件算出来的。设得太松,signoff过了但硅片上跑不起来;设得太紧,过度设计,面积和功耗都浪费。
我通常的做法是:先用foundry推荐的derate值跑一版,看看违例情况。如果违例集中在某几条路径上,再针对性地分析是不是derate设得太悲观了。如果违例遍地开花,那说明设计本身就有问题,不是derate能解决的。
4. 时钟约束如何决定路径的分析结果
4.1 create_clock与create_generated_clock的区别
时钟约束是SDC的核心。create_clock定义的是根时钟,通常来自芯片的时钟输入引脚或者PLL输出。create_generated_clock定义的是衍生时钟,比如分频器输出、时钟门控后的时钟。
这两者的区别非常关键:generated clock会自动继承源时钟的抖动、延迟等属性,但可以有自己的频率和相位。如果分频时钟用create_clock重新定义,工具会认为它是一个全新的、和源时钟无关的时钟,导致跨时钟域路径的分析完全错误。
我踩过的一个坑:某个项目里,一个二分频时钟用create_clock定义了,结果工具认为它和源时钟是异步的,所有跨这两个时钟域的路径都被当成false path处理了。实际上它们是同步的,只是频率不同。后来改成create_generated_clock,问题才解决。
4.2 时钟不确定性(uncertainty)的组成
set_clock_uncertainty是SDC里最容易被滥用的约束之一。它由三部分组成:
- 时钟抖动(jitter):PLL本身的输出抖动,通常几十到几百皮秒
- 时钟树偏斜(skew):时钟树综合后的实际偏斜,signoff阶段应该用实际值
- 裕量(margin):人为留的余量,用于覆盖建模误差
很多人在项目初期就把uncertainty设得很大(比如200ps),然后一直不改。到了signoff阶段,时钟树已经做完了,实际skew可能只有50ps,但uncertainty还是200ps,导致大量不必要的违例。
正确的做法是:分阶段调整uncertainty。综合阶段可以用大一点的值(比如150-200ps),布局阶段降到100ps左右,时钟树综合完成后用实际skew加上jitter和少量margin(比如50-80ps)。
4.3 时钟组与false path的配合
set_clock_groups用来声明时钟域之间的关系。最常见的用法是:
set_clock_groups -asynchronous \ -group {clk_core} \ -group {clk_ddr} \ -group {clk_periph}这告诉工具:这三个时钟域之间是异步的,它们之间的路径不需要做时序分析。
但这里有一个陷阱:异步不等于不需要同步器。工具不做时序分析,不代表你可以直接把信号从一个时钟域拉到另一个时钟域。跨时钟域信号必须经过同步器(比如两级触发器),否则亚稳态问题会让芯片随机出错。STA工具不管亚稳态,那是CDC检查工具(比如SpyGlass CDC)的职责。
我的习惯是:在SDC里设完clock groups之后,一定会跑一遍CDC检查,确认所有跨时钟域路径都有正确的同步结构。STA和CDC是互补的,不能互相替代。
5. 路径分析中常见的误判与排查思路
5.1 为什么report_timing抓不到某些路径
有时候你明明知道某条路径存在,但report_timing就是抓不到。常见原因有:
- 路径被false path或多周期路径覆盖了:检查SDC里有没有
set_false_path或set_multicycle_path意外覆盖了这条路径 - 路径的起点或终点不在当前分析模式的有效范围内:比如在setup模式下抓hold路径,或者路径的时钟域被disable了
- 路径被case analysis屏蔽了:
set_case_analysis会把某些控制信号固定为常量,导致经过这些信号的路径被优化掉
排查方法:用report_timing -from <起点> -to <终点> -path_type full_clock_expanded,加上-crosstalk_delta和-input_pins等选项,把路径的每一个节点都打印出来。如果还是抓不到,用get_timing_paths配合-include_hierarchical_pins试试。
5.2 setup和hold结果矛盾的根因
有时候你会发现:同一条路径,setup过了但hold违例,或者反过来。这通常是因为:
- setup和hold用了不同的分析角:BC-WC模式下,setup用worst case,hold用best case,两者的cell延迟差异很大
- 时钟路径和数据路径的derate方向不同:OCV模式下,时钟路径加derate、数据路径减derate,导致setup和hold的悲观方向相反
- 约束本身有问题:比如
set_clock_uncertainty只设了setup没设hold,或者set_input_delay的min和max值设反了
排查时,先确认setup和hold用的是同一套时钟约束,然后检查derate设置是否合理。如果还是矛盾,用report_timing -delay_type min_max同时打印setup和hold的详细延迟,逐段对比。
5.3 时钟树综合前后的时序跳变
时钟树综合(CTS)是时序分析的一个分水岭。CTS之前,时钟是理想的(ideal clock),工具假设时钟到达所有触发器的延迟是相同的。CTS之后,时钟变成了传播的(propagated clock),实际的时钟树延迟和偏斜被纳入分析。
这会导致两个变化:
- setup可能变好也可能变差:如果发射触发器的时钟路径比接收触发器长,setup会变差;反之变好
- hold通常会变差:因为CTS后时钟偏斜变成了实际值,如果接收触发器的时钟到达得比发射触发器早,hold就会违例
我的经验是:CTS之前不要把setup压得太狠,留一些余量给CTS后的偏斜。同时,CTS之后要重点检查hold,因为hold违例通常需要在时钟树或者数据路径上插buffer来修复,比setup更难修。
提示:CTS之后如果hold违例很多,先别急着插buffer。检查一下时钟树的skew是否在预期范围内,有时候是时钟树本身没做好,而不是数据路径的问题。
6. 从路径分析到约束优化的实战思路
6.1 先分类,再约束
拿到一个设计,不要急着写SDC。先花时间把所有时序路径分类:
- 哪些是reg2reg?哪些是input2reg?哪些是reg2output?
- 哪些时钟域是同步的?哪些是异步的?
- 哪些路径是真正的功能路径?哪些是测试逻辑或者旁路路径?
分类清楚了,约束自然就好写了。比如异步时钟域之间的路径,直接设false path;同频同相时钟域之间的路径,用默认的setup/hold分析;分频时钟域之间的路径,用multicycle path来放宽要求。
6.2 约束的层次化管理
大型芯片的SDC往往有几千行,如果全部写在一个文件里,维护起来非常痛苦。我的做法是分层管理:
- 顶层约束文件:定义时钟、输入输出延迟、时钟组关系
- 模块级约束文件:定义模块内部的false path、multicycle path
- 特殊路径约束文件:定义跨模块的特殊路径约束
这样修改某一类约束时,只需要改对应的文件,不会影响其他部分。而且不同模块的约束可以并行开发和验证。
6.3 约束验证的闭环
写完约束不是终点,必须验证约束是否按预期生效。我通常用以下几个手段:
- check_timing:检查有没有未约束的路径、未定义的时钟、冲突的约束
- report_clock -skew:检查时钟树的偏斜是否在预期范围内
- report_timing -max_paths 100:抓出最差的100条路径,逐条确认是否合理
- 对比不同阶段的时序报告:综合后、布局后、CTS后、布线后,每个阶段的时序报告都要对比,看变化是否符合预期
如果发现某条路径的时序在某个阶段突然变差,就要查那个阶段做了什么操作影响了这条路径。比如CTS后某条路径的setup变差了200ps,那很可能是时钟树的偏斜导致的,需要回头优化时钟树。
6.4 时序违例的修复优先级
面对一堆时序违例,不要眉毛胡子一把抓。我的修复优先级是:
- 先修setup,再修hold:setup影响芯片的最高工作频率,hold影响芯片能否正常工作。通常先保证setup收敛,再修hold
- 先修关键路径,再修边缘路径:slack为-100ps的路径优先处理,slack为-5ps的可以后面再说
- 先修时钟路径,再修数据路径:时钟路径的问题会影响一大片路径,修好时钟往往能解决很多数据路径的违例
- 先修跨时钟域路径,再修同时钟域路径:跨时钟域路径的约束往往更复杂,问题也更容易被掩盖
修复手段上,setup违例可以通过:降低组合逻辑级数、插入pipeline寄存器、调整时钟树、放宽约束(如果是假违例)来解决。hold违例则通常通过:插入buffer、调整时钟树偏斜、降低驱动强度来解决。
7. 一些容易踩的坑和实操心得
7.1 不要迷信工具的默认设置
PrimeTime的很多默认设置是为了通用性,不一定适合你的设计。比如默认的set_clock_uncertainty是0,默认的set_input_delay也是0。如果你不显式设置,工具就按0来算,结果看起来很美,但实际上完全不可信。
我的习惯是:拿到一个新设计,先把所有默认值检查一遍,该改的改,该加的加。特别是时钟不确定性、输入输出延迟、derate因子这几个,一定要根据实际项目情况设置。
7.2 路径分析要结合物理信息
纯逻辑的时序分析有时候会骗人。比如工具报告某条路径的slack是正的,但实际布线后发现这条路径的线延迟比预估的大很多,slack就变负了。所以,时序分析一定要结合物理信息,特别是在先进工艺下,互连延迟占比越来越高。
我的做法是:在布局阶段就开始关注时序,不要等到布线完再看。布局阶段的时序报告虽然不如布线后准确,但能提前发现大的问题,留出足够的修复时间。
7.3 保持约束和设计的一致性
设计改了,约束一定要跟着改。我见过太多项目,RTL改了但SDC没更新,结果时序分析基于错误的约束,跑出来的结果完全没有参考价值。
建议把SDC纳入版本管理,和RTL、网表一起管理。每次设计变更,都要检查SDC是否需要同步更新。特别是时钟结构、端口定义、模块层次这些变化,很容易导致约束失效。
7.4 学会看时序报告里的细节
很多人看时序报告只看最后的slack数字,忽略了中间的细节。其实报告里的每一段延迟都有信息:
- cell delay:如果某个cell的延迟特别大,可能是驱动能力不够或者负载太大
- net delay:如果某条net的延迟特别大,可能是走线太长或者金属层选择不合理
- clock path delay:如果时钟路径的延迟异常,可能是时钟树结构有问题
- transition time:如果某个节点的transition time超标,会影响下一级cell的延迟计算
学会看这些细节,能帮你快速定位问题的根因,而不是盲目地插buffer或者改约束。
7.5 和前端工程师保持沟通
时序问题很多时候不是后端能单独解决的。比如某条路径的组合逻辑太深,后端再怎么优化也修不下来,这时候需要前端改RTL,插入pipeline寄存器。或者某个时钟域的设计本身就有问题,需要前端重新设计时钟结构。
我的经验是:在项目初期就和前端工程师对齐时序目标,明确哪些路径是关键路径、哪些时钟域是异步的、哪些接口有时序要求。这样后端在写约束和做优化时,才能有的放矢。
时序路径分析是STA的基础,但基础不等于简单。每一条路径的类型、每一个约束的设置、每一种分析模式的选择,背后都有其逻辑和适用场景。把上篇的这些底层逻辑搞清楚,下篇我们再展开具体的约束编写技巧和debug实战。