静态时序分析入门:从时序路径到setup/hold违例排查 做数字芯片的不管是前端写RTL还是后端做物理实现时序路径分析这道坎儿早晚都要过。尤其到了流片前的静态时序分析STA环节一条setup违例或hold违例查上大半天是很多工程师的日常。这篇文章先把时序路径这层窗户纸捅破一条路径是怎么被定义的、建立时间和保持时间到底在算什么、SDC约束里的时钟和延迟信息又是怎么变成一串串时序报告数字的。内容定位在入门到进阶之间适合刚转数字IC的工程师、在校学生也适合做过一段时间后端但没系统梳理过STA的老手。为什么非要揪着时序不放说白了数字电路里的所有功能都建立在时钟边沿对数据的正确采样上。信号从寄存器出发经过组合逻辑再进入下一个寄存器这中间每一段都有延迟。这些延迟叠加起来如果超过一个时钟周期数据来不及稳定下一拍采到的就是错误电平。芯片频率上不去、功能跑飞、功耗异常很多时候根源都在时序。所以时序路径分析不是EDA工具跑个报告那么轻松它决定了一块芯片能不能真正工作。这一篇是上篇先老老实实把概念和计算拆清楚。等后面再聊更深入的时钟树综合、工程修复和ECO那些都是建立在今天这些基础之上的。往后每个实操环节我都会把“为什么”也讲一遍毕竟干这一行光会跑工具不叫懂能解释清楚结果才算真正能上手。1. 时序路径分析到底在解决什么问题1.1 一个触发器一整天都在等数据数字电路里最核心的存储单元是触发器时钟上升沿来的时候它把输入端的电平“拍”到输出上。很多人刚接触时序时只记住了这一点却没意识到一个关键前提数据必须在时钟沿到来之前就已经稳定并且在时钟沿之后的一小段时间内不能变化。这两条时间窗口一条叫建立时间setup time一条叫保持时间hold time。打个比方时钟沿就像上课铃数据就是学生。学生必须在打铃前坐到座位上这叫建立时间铃声打完老师开始讲课学生也不能立刻跑掉至少得坐稳一小会儿这叫保持时间。如果一个学生总是踩着铃冲进教室甚至铃响了还没进教室那课堂秩序就乱了。对应到芯片里数据到达太晚触发器采到的可能是上一拍的旧值逻辑直接错乱。数据变化太早又会破坏时钟沿附近需要的稳定窗口结果同样不可靠。很多初学者有个误区以为只要逻辑表达式正确信号就一定是对的。实际完全不是这样。一个两输入与非门如果输入端A和B几乎同时变化输出中间可能先出现一个不该出现的窄脉冲这就是毛刺。放到时序分析里我们要关心的不是“稳态下输出是什么”而是“在时钟沿那个瞬间稳态是否已经达到”。这个思路贯穿整个静态时序分析想明白了后面所有的计算、报告、修复逻辑都顺理成章。1.2 真正要算的是slack每一条时序路径上工具都会算出两个关键时间数据实际到达时间以及数据必须到达时间。两者相减得到的就是时序裕量业内叫slack。Slack为正说明这拍数据有富余Slack为负说明路径不满足要求也就是violation。STA工具会遍历整个设计里成千上万条路径把所有违例路径和关键路径全找出来工程师要做的就是把这些负slack一条条消掉。这里要强调一个概念静态时序分析和动态仿真完全不同。动态仿真需要给激励、看波形跑一条路径验证一条路径速度慢得吓人而且只能验证你想到的场景。时序路径分析则是把所有可能路径都抽象成延迟网络用数学方式计算每个端点的到达和需求不依赖输入向量所以叫“静态”。也正因为不依赖具体激励它能覆盖到很多仿真很难触发的极端路径比如异步信号最坏情况下的到达。理解了slack再回头看整个时序收敛流程就清晰了综合阶段工具约束住逻辑级数和单元负载布局布线阶段工具优化线长和负载时钟树综合之后clock skew成为主导项最后签核阶段用准确的寄生参数重新计算。每一步跑出来的报告核心都只有一件事——看slack。后续文章的案例也都是围绕负slack怎么出现、怎么修复展开。2. 时序路径的组成与分类2.1 四类路径一个都不能漏静态时序分析不是只分析寄存器到寄存器这一种情况。一颗芯片的边界包含输入引脚、输出引脚内部有成千上万个触发器它们组合起来一共形成四类标准路径工具在约束文件的基础上挨个分析。路径类型起点终点典型关注点寄存器到寄存器触发器的时钟引脚下一级触发器的数据引脚时钟偏斜、组合逻辑延迟、setup/hold输入到寄存器芯片输入端口触发器的数据引脚外部输入延迟、板级接口时序寄存器到输出触发器的时钟引脚芯片输出端口输出延迟要求、外部负载输入到输出芯片输入端口芯片输出端口纯组合直通路径、组合逻辑是否合理很多刚学STA的人拿到时序报告看到cleanup path或edge path这些词会懵其实就是把寄存器时钟端当作起点来追踪时钟树的延迟。做input-to-reg分析时输入端口没有一个真正的时钟引脚所以约束里必须通过set_input_delay告诉工具外部数据相对于时钟沿什么时候到如果漏了这条约束工具会默认数据有无限充裕时间分析结果全都乐观到失真。同样输出路径上没有真实采样寄存器工具需要靠set_output_delay来模拟外部器件的建立时间要求。这四类路径的约束差异就是STA入门时最容易踩坑的地方。我见过不少人把板上外部存储器的接口时序分析得一团乱根源往往就是输入输出延迟的方向和数值搞反了。后面第3节会专门展开这些命令的因果逻辑。2.2 延迟不是简单相加一条数据路径上的总延迟可以粗略拆成单元延迟加线网延迟。单元延迟指信号经过逻辑门产生的延后线网延迟指信号在金属连线上传输的延后。这俩都不是固定数字跟输入信号的转换时间slew有关也跟输出端挂了多大的负载有关。同一个反相器接一个负载和接十个负载延迟能差出好几倍。所以现代EDA工具做时序分析时用的不是“查个常数”的办法而是查标准单元库里的非线性延迟模型NLDM。库里针对每个单元、每个输入转换时间、每个输出负载都存了一张二维查找表工具通过查表和插值得到更贴近工艺真实的延迟。这就是为什么同一颗工艺库用不同版本的库文件或不同corner跑STA结果会有肉眼可见的差异。流片前的签核分析通常还要跑慢速低温、慢速高温、快速低压等多个corner原因就在这里。时钟路径的延迟则单独算。时钟信号从时钟源出发经过PLL缓冲、时钟树上的各级buffer再到达每个触发器的时钟端这个延迟叫clock insertion delay也叫source latency加network latency。问题在于时钟到达两个不同触发器的时间往往不一样这个时间差就是clock skew。Skew如果控制不好会同时恶化setup和hold因此后端的时钟树综合CTS本质就是一门控制skew的手艺。理解了延迟的来源和统计方式后面看时序报告里的delay components那一栏就不会被一堆数字牵着走了。3. 时序约束把“正确”这件事量化3.1 SDC静态时序分析的宪法没有约束STA工具什么都干不了——它不知道时钟周期、不知道哪些端口输入何时有效、不知道哪些路径不需要分析。EDA工具能跑全靠一份叫SDCSynopsys Design Constraints的约束文件相当于整个时序分析的宪法。后端工程师写的所有约束最终都会汇成这份文件跟网表、库文件一起送去时序签核。最基础的约束是定义时钟。芯片里可能同时存在好几个时钟每个时钟都要用create_clock命令明确周期和波形。比如一个100MHz的时钟create_clock -name clk_100m -period 10 [get_ports clk]这条命令告诉工具每个时钟周期是10ns起点是端口clk。更精细的情况要定义占空比可以用-waveform {0 5}也可以定义生成时钟用divide_by倍频分频。不管是哪种目的都是让工具知道“时间基准是什么”。如果一颗芯片里的时钟没定义清楚后面所有路径分析都会跟着崩。除了时钟SDC里还有成堆的约束命令set_input_delay、set_output_delay、set_clock_uncertainty、set_false_path、set_multicycle_path、set_case_analysis、set_max_delay、set_min_delay等。很多新手喜欢把网上的模板约束一把抄过来结果约束文件比RTL还难懂跑出来的时序报告千奇百怪。这里我的建议很明确每一条约束命令都要能解释清楚“它约束的是哪条路径”“它的数值从哪里来”。如果解释不了这条约束多半是有问题的。3.2 输入输出延迟的设置逻辑输入输出延迟约束是整个SDC里最容易出错的地方因为涉及芯片外部接口的时序关系建模必须准确。set_input_delay表示数据从外部源到达芯片输入引脚相对于时钟沿的延迟时间。例如set_input_delay 2.0 -clock clk_100m [get_ports data_in]意思是data_in上的数据在时钟沿之后2ns到达芯片引脚。既然有到达时间那么setup分析时工具就会用10 - 2 8ns作为内部允许的最大数据路径延迟。如果不设置这条约束工具默认输入数据在时钟沿之前无限早到达内部逻辑怎么连都算满足真正的接口时序风险会被完全掩盖。输出延迟约束的建模方向则反过来。芯片输出引脚的数据要送到外部系统外部系统有自己的建立时间要求。比如外部器件要求数据在时钟沿前3ns稳定那么set_output_delay 3.0就表示芯片内部路径必须保证在那之前把数据送到引脚。很多工程师做接口时序老是不收敛多半是没搞清楚外部建立时间对应的方向。这类约束还有max和min两个版本。max版本对应setup要求min版本对应hold要求。实际项目中输入输出延迟要根据板级实际情况手动计算不是随便填个数。严谨一点的做法是从芯片数据手册的AC时序特性表里提取参数再考虑PCB走线延迟、驱动芯片输出延迟等最终形成约束。3.3 时钟不确定性把悲观提前算进去时钟信号在芯片内部传输时会受到电源噪声、温度变化、片上工艺波动的影响同一个时钟沿到达各个寄存器的精确时刻永远不可能完全相同。除了前面说的skew还有一部分是时变的不确定性jitter以及PLL本身输出的相位误差。为了在设计中留出余量SDC里会用set_clock_uncertainty命令把这些提前扣掉。set_clock_uncertainty 0.1 -setup [get_clocks clk_100m] set_clock_uncertainty 0.05 -hold [get_clocks clk_100m]很多团队的约束库里clock uncertainty的取值是根据芯片具体架构反复调出来的。设太小工具分析过于乐观流片后可能翻车设太大工具会觉得每条路径都难收敛面积和功耗白白浪费。实际项目里setup和hold的uncertainty常常是不同值原因很简单两者的安全裕量要求不一样。看时序报告时如果发现一条路径的slack刚好是负0.05ns先不要急着动逻辑检查一下uncertainty设置很可能就有收获。4. 建立时间与保持时间的路径分析实操4.1 setup time计算实例前面铺垫了这么多现在来点硬核的。静态时序分析中的setup检查本质上是比较数据路径到达终点的时间和采样时钟沿到达终点的时间二者之间必须留出一个setup time。标准计算公式可以写成data arrival time 时钟源到达起点触发器的延迟 TCQ 组合逻辑延迟 线网延迟data required time 采样时钟沿到达终点触发器的延迟 时钟周期 - setup time - clock uncertaintyslack data required time - data arrival time举一个具体数字。假设时钟周期为10ns起点寄存器TCQ时钟到Q输出延迟为0.5ns中间组合逻辑延迟为2.5ns线网延迟0.3ns终点触发器的setup time为0.2ns时钟从起点到终点的skew为0.2ns终点时钟比起点晚到uncertainty设为0.1ns。那么data arrival time 0.5 2.5 0.3 3.3nsdata required time 10 0.2 - 0.2 - 0.1 9.9nssetup slack 9.9 - 3.3 6.6nsslack为正说明这条路径很轻松。如果数据到达时间超过9.9ns就会变成负slack。实际项目里逻辑级数动辄十几级时钟频率动辄几百兆赫兹每个0.1ns的裕量都弥足珍贵。这里特别提醒公式里的clock skew符号取决于终点时钟相对起点是早到还是晚到。如果终点时钟比起点更晚到相当于采样沿被推后那么数据可用的时间窗口变长对setup有利。反过来如果终点时钟提前到setup就会变差。很多新人看工具报告发现skew对setup有正有负很容易搞混。建议自己手动搭一个两触发器模型按上述公式推一遍比死记符号强得多。4.2 hold time计算实例保持时间检查处理的是另一个时间窗口数据在采样时钟沿之后不能太快发生变化。Hold分析对比的是数据最快到达时间和保持时间要求。标准逻辑是数据在采样时刻到来之后必须再稳定一小段时间而它最快到达终点的路径不能提前覆盖掉这个稳定窗口。hold分析里的关键参数是数据路径的最小延迟不是最大延迟。芯片制造出来之后受到工艺偏差影响路径延迟会有变化最快情况fast corner下TCQ可能变小、组合逻辑延迟也可能变小。所以hold检查通常用min corner下的延迟来计算目的是保证任何条件下数据都不会变得太快。继续用上边的例子。假设最小TCQ为0.2ns最小组合逻辑延迟为0.5ns最小线网延迟为0.1nshold time为0.1ns时钟skew同样是终点晚到0.3ns。那么最快数据到达时间 0.2 0.5 0.1 0.8nshold required time hold time clock skew 0.1 0.3 0.4nshold slack 0.8 - 0.4 0.4nsslack为正说明数据不会变化太快。但如果clock skew变成0.9nshold required time就成了1.0nshold slack变成-0.2ns出现hold违例。这也是为什么时钟树偏斜太大时hold修复会非常痛苦。在实际设计中hold违例往往发生在数据路径特别短的寄存器对之间比如两个并排放置的触发器TCQ很快、中间几乎没有组合逻辑再叠加时钟晚到非常容易触发违规。4.3 为什么setup和hold的修复思路完全不同同样是时序违例setup和hold的修复方向几乎相反。Setup违例通常是因为路径太长、延迟太大解决办法是减少组合逻辑级数、插入流水线、增大驱动强度、减少扇出、或者优化时钟偏斜。Hold违例则是因为路径太短、数据太快解决办法是插入延迟单元delay cell、增大缓冲器、增加线长甚至人为把两个寄存器之间的距离拉远。这种对立让不少新手很崩溃给一条路径加了buffer修hold结果setup又被拖坏了。这正是后端实现复杂的地方。实际工作中工程师通常先修setup再做时钟树综合最后专门修hold。因为时钟树综合会改变每级寄存器的时钟到达时间如果先修holdCTS之后往往又要重来。先保证数据路径总体延迟满足setup再通过CTS调整skew最后把hold的死角逐个清掉这个顺序可以省下大量重复劳动。5. 时序报告怎么读、违例怎么排查5.1 一张报告的阅读顺序工具跑出来的时序报告密密麻麻但核心信息其实很固定。以主流的report_timing为例先看Startpoint和Endpoint确认这条路径是从哪个寄存器或端口出发通向哪里。再看Path Group和Path Type确认它是setup还是hold检查。很多团队会按时钟域把路径分组Path Group就是时钟名方便定位是哪条时钟频率下的问题。中间最重要的一段是延迟列表。工具会把整条路径上每个单元的延迟、每段网络的延迟、时钟到达时间全部列出来。工程师要做的不是看第一个数而是从起点开始像剥洋葱一样逐级往后看找到哪一级延迟占比最大、哪一段线网延迟异常。比如一个反相器输出带了一大堆负载线网延迟0.8ns这在深亚微米工艺下非常可疑大概率是布局扇出没做好。数据到达时间和要求时间之间差的每一个0.1ns都可能对应一个真实的物理问题。最后看slack数值。如果setup slack是负的报告一般会同时提示要求的时钟周期和路径延迟。我习惯先把报告里的时钟周期和SDC里的create_clock对一遍——如果工具算出来的周期跟约束定义不一致说明约束定义里可能藏着重复定义或相位问题。5.2 新手最容易翻车的约束坑总结这么多年见过的时序问题真正由电路延迟本身造成的violation其实只占一部分。另一大批是约束写错或者没写全导致报告出现“幽灵违例”或掩盖真实风险。最常见的有这么几类。第一类false path没设。跨时钟域的路径如果不做同步处理工具会按同频时钟去分析自然全是违例。正确的做法是识别出真正的异步时钟域用set_false_path把这些路径从时序分析里关掉。但必须确认这些路径确实有同步器保护不能为了出绿色报告就乱设。第二类multicycle path设错。DDR接口、读数据回传这类路径经常需要两三个周期才算有效如果不设set_multicycle_path工具会按一拍分析大量false violation会淹没真实问题。这个命令用起来很简单关键是要理解它到底调整的是setup捕获沿还是hold检查沿改错方向可能比不改还危险。第三类reset信号没做case analysis。异步复位信号在没有实际复位时通常处于无效电平但它毕竟是一个逻辑信号。如果不通过set_case_analysis固定复位端电平工具会认为复位随时会翻转在复位路径上产生大量不存在的时序路径。很多新手看到复位相关的setup违例第一反应是加大驱动结果越修越乱实际只是缺少一条约束。6. 常见问题速查与实操习惯6.1 常见问题速查表把这几年最常被问到的问题整理成一个速查表遇到类似情况可以直接对着排查。现象可能原因排查方向关键路径全集中在某个模块模块本身逻辑级数高、扇出大检查RTL关键路径考虑重新综合或流水线大量路径setup违例但偏离很小时钟uncertainty或约束过紧对照约束文件确认uncertainty是否合理时钟路径延迟特别大CTS没做好或时钟源驱动弱检查时钟树报告确认buffer级数和位置hold违例集中在短路径寄存器对时钟skew大、寄存器距离近看CTS结果调整skew或插入delay cell报告显示高扇出网络延迟异常驱动单元过载、布线拥塞优化扇出约束对宽扇出信号做复制时序报告和动态仿真结论不一致约束遗漏或误设异步路径未设false path逐条回读SDC确认每个约束对应的路径换一个corner后大量violation库文件或corner配置不对重新检查库和PVT配置确认用对了corner6.2 几条能让时序收敛提速的实操习惯第一每一轮跑STA只改一件事。这个习惯看上去简单实际做起来很难。很多工程师看到整屏violation恨不得同时修三五个问题结果下一轮报告出来根本分不清到底是谁起到了作用。我的做法是改约束单独跑、改源码单独跑、改布局策略单独跑每轮报告都留档对比这样定位问题才有依据。第二先看全局概览再看单条报告。用report_analysis_summary或者report_qor这类命令拉出全局表格看总违例数、最差slack、WNS和TNS。如果全局TNS非常负说明整个模块都需要重新审视不是修一两条路径能解决的。如果只是个别路径违例再逐条深挖。第三学会用时钟域分类思路。跑到签核阶段路径数量动辄上百万不可能一条条翻。按时钟域、按模块、按path group切分把问题聚拢到几个热点区域再集中处理效率能高一个数量级。比如一颗SoC里CPU和GPU频率不同时序难收敛的往往是高频率的那个域先把频率域的顶层约束吃透其他域的问题往往能自动缓解。到了实际流片阶段尤其做RK3588那种规模的大SoC时序分析要处理的数据量和约束复杂度完全是另一个层级但核心思想仍然是这些路径分类、延迟计算、约束正确性、setup/hold两张网。基础打不牢后面用再高级的灌入式ECO也救不回来。做时序这几年我最大的体会是拿到violation报告先别急着改电路把那几百行SDC逐条读一遍往往能多救回来半个晚上。上篇先把概念和计算讲透下一回再拿真实案例把修复流程走一遍。