静态时序分析核心:Setup/Hold不等式与签核避坑 简介《静态时序分析与逻辑设计》PDF 文档面向数字集成电路、专用集成电路与现场可编程门阵列设计的学习者围绕静态时序分析如何保障电路时序正确展开适合希望建立分析流程、理解时序收敛与逻辑优化关系的中级读者。内容从时序约束、网表生成、时序分析到报告输出梳理完整路径并展开建立时间、保持时间、时钟到输出时间、关键路径、流水线与重定时等概念同时提及常用时序分析工具及芯片设计场景。压缩包仅含 1 个 PDF 文件约 592KB轻量便于按主题查阅和做批注。已有 710 人学习可作为课程配套阅读或项目前的概念速查材料帮助读者把时序报告中的问题与逻辑设计优化动作对应起来形成较完整的知识框架。1. 静态时序分析不施加任何激励凭什么敢判定电路会崩有个现象挺反直觉动态仿真跑上千万个周期、覆盖率报告很漂亮流片回来还是在某个批次上翻车静态时序分析一个激励都不给连输入波形都没有却敢在签核报告上写“时序通过”。这两件事不矛盾——仿真在抽样静态时序分析在解不等式。时序正确性本质上就一句话数据到得太早或太晚都会出错。而“所有输入组合乘上所有工作条件”的空间大到没法枚举仿真只能撒点。静态时序分析换了个方向把电路拆成起点到终点之间的路径逐条算延迟再跟时钟周期比。核心不等式 T tCO tDELAY tSU最大工作频率 f 1/T。吃透这条不等式后面约束怎么写、报告怎么读、为什么修完 setup 会反噬 hold、为什么逻辑设计和 ASIC 签核都绕不开它全都能往下推。它是个能手工验算的算术问题不是黑匣子。2. 时序不等式Setup 与 Hold 到底在卡哪个时间窗口2.1 采样窗口的两条边建立时间与保持时间触发器采样不是“时钟沿一到就把 D 端电平抓走”这么理想的动作。真实的触发器内部是主从锁存器结构时钟沿到来时主锁存器关闭、从锁存器开启数据在这两个状态之间转移。这个过程要求在时钟沿关闭主锁存器之前的一小段时间内输入不能发生翻转否则锁存器可能把一次跳变采样成中间电平。这段时间就是建立时间 tSU——数据必须在采样沿到来之前稳定保持的最短时长。反过来主锁存器关闭之后、从锁存器完全接手之前如果输入立刻翻转翻转能量可能通过寄生通路耦合进已经锁存的值把刚写进去的数据冲掉。这段就是保持时间 tH——采样沿之后数据还必须继续保持稳定的最短时长。两条线一夹就在时钟沿前后各挖出一段“禁止翻转区”。注意这个窗口是触发器本身的物理属性跟时钟频率没关系跟设计意图也没关系。工艺越先进、单元尺寸越小窗口的绝对宽度越小但它相对于信号翻转速度的比例反而更敏感——这也是为什么先进节点上时序收敛越来越难做血泪经验都堆在这里。还有一个参数必须放在一起看tCO时钟到输出时间。它是时钟沿有效之后触发器输出 Q 端稳定到可以被下一级采信所需的时间。可以把它理解成“发起端的入场费”——数据不是时钟沿一到就立刻从 Q 端出发的得先交这笔钱。三者合起来tCO、tDELAY、tSU就是一条完整路径上的三段固定开销出发要时间路上要走到达还要提前量。2.2 从 tCO tDELAY tSU 推到最高工作频率把发起寄存器和捕获寄存器放在一起看一个时钟周期内必须完成的事情是前一个时钟沿让发起寄存器输出数据数据穿过组合逻辑和走线赶在下一个时钟沿之前到达捕获寄存器并稳定 tSU。写成不等式就是T_clk t_skew - t_jitter tCO t_comb_max tSUT_clk 是时钟周期t_skew 是捕获时钟相对发起时钟的到达时间差正的 skew 相当于白送时间t_jitter 是时钟抖动要从周期里扣掉。t_comb_max 是组合逻辑加走线的最坏延迟。把它整理成频率f_max 1 / (tCO t_comb_max tSU - t_skew t_jitter)分子里任何一项变大频率就掉。逻辑级数多、扇出大、走线长全都会顶到 t_comb_max 上。这就是为什么综合工具拼命做逻辑重组、布局布线工具拼命压缩关键路径的走线长度——它们动的都是这一项。Hold 检查是另一个方向的不等式tCO t_comb_min tH t_skew注意右边没有 T_clk。这就是 hold 违例和 setup 违例最本质的区别检查项数据出发数据到达要求与时钟周期的关系Setup前一个时钟沿下一个沿之前 tSU周期越长越容易满足可降频缓解Hold同一个时钟沿同一个沿之后 tH与周期无关降频完全无效降频救得了 setup救不了 hold。这个结论在流片之后尤其致命——芯片回来发现 hold 违例软件层面没有任何办法绕过去只能改版。这也是为什么 hold 必须在签核阶段用最悲观的快 corner 反复确认。2.3 不等式被破坏之后亚稳态与 tMET如果数据真的在禁止翻转窗口里发生了变化触发器输出会怎样它会停在一个既不是 0 也不是 1 的中间电平上而且不知道什么时候才会落到某个确定值。这个状态叫亚稳态。亚稳态不会永久保持它会被正反馈逐渐放大到某个稳定电平但需要时间。留给它衰减的时间叫 tMET。可靠性与 tMET 大致成指数关系MTBF ∝ e^(tMET / τ) / (T0 · f_clk · f_data)τ 是触发器的亚稳态衰减常数跟工艺和单元结构有关f_clk 是采样时钟频率f_data 是数据翻转频率。这三个量里唯一能人为拉大的就是 tMET——办法就是串联两级触发器让第一级有整整一个周期去衰减第二级再去采。这里有个容易误解的点STA 工具根本不检查亚稳态。它只验证建立/保持时间不等式成不成立亚稳态是概率事件得靠 MTBF 公式单独算。所以跨时钟域路径上那句set_false_path的作用仅仅是“让工具别报违例”它不提供任何安全性保证。真正的保证来自同步器结构和算出来的 MTBF 数字。3. STA 的流水线约束、网表、遍历、报告四步怎么咬合3.1 约束先于分析时钟定义与输入输出延迟工具本身不知道你的时钟是 100MHz 还是 500MHz也不知道外部器件相对时钟是什么相位关系。这些信息全靠时序约束喂进去。约束写错后面所有分析都建在错误前提上——垃圾进垃圾出。一份最小可用的约束至少要覆盖五件事时钟定义、输入延迟、输出延迟、时钟不确定度、时序例外。下面是一段示意性的 SDC 片段# 定义主时钟周期 10ns上升沿在 0ns下降沿在 5ns占空比 50% create_clock -name core_clk -period 10 -waveform {0 5} [get_ports clk] # 时钟抖动 偏斜预算统一从周期里扣掉 # setup 用较大的余量hold 用较小的余量 set_clock_uncertainty -setup 0.15 [get_clocks core_clk] set_clock_uncertainty -hold 0.08 [get_clocks core_clk] # 输入端口外部器件在时钟沿之后 2ns 才送出数据板级走线再加 0.5ns set_input_delay -clock core_clk -max 2.5 [get_ports data_in*] # 输出端口下游器件要求数据在捕获沿前 3ns 就到达 set_output_delay -clock core_clk -max 3.0 [get_ports data_out*] # 两个时钟之间没有可分析的相位关系必须显式声明为异步 set_clock_groups -asynchronous \ -group [get_clocks core_clk] \ -group [get_clocks aux_clk]create_clock里的-waveform {0 5}同时定义了上升沿和下降沿的位置这个细节直接影响跨边沿路径的检查结果写错了不会报错只会静悄悄地算错。set_input_delay和set_output_delay的数值不是拍脑袋填的要对着外部器件的时序手册和板级走线延迟算出来而且 max 和 min 要分开给——只给 max 的话输入路径的 hold 检查就无从谈起。最后一句set_clock_groups是最容易被滥用的地方。它的语义是“这两个时钟域之间不存在需要检查的同步关系”工具会跳过所有跨域路径。如果两个时钟其实是同源的、只是经过了不同的分频器那这句声明就是把真实存在的慢速路径从分析里放走了。3.2 网表与时序图工具眼里没有“电路”只有节点和边综合或布局布线之后产出网表里面只有实例和连线。STA 工具把网表、标准单元库、寄生参数三者合起来构建一张有向图节点是引脚边是延迟。单元库提供的是二维查找表——每个单元在不同输入转换时间和不同输出负载下的延迟。工具根据实际驱动能力和负载去插值得到这条边的延迟。寄生参数走线电阻电容则决定连线的延迟。有了这张图剩下的事情就是在图上做两遍遍历# 正向传播算每个节点的最晚到达时间arrival time # 拓扑序保证所有前驱都算完之后才算当前节点 for node in topological_order(graph): if node.is_start_point: # 起点发起时钟沿的到达时间 该寄存器/端口的输出延迟 arrival[node] start_clock_edge(node) output_delay(node) else: # 非起点在所有前驱里挑最慢的那条路径 # max 对应 setup 检查因为 setup 关心数据最晚什么时候到 arrival[node] max( arrival[pred] edge_delay(pred, node) for pred in graph.predecessors(node) ) # 反向传播算每个节点的最晚要求时间required time for node in reversed(topological_order(graph)): if node.is_end_point: # 终点捕获时钟沿到达时间 - 建立时间 - 时钟不确定度 required[node] capture_clock_edge(node) - setup_time(node) - uncertainty(node) else: required[node] min( required[succ] - edge_delay(node, succ) for succ in graph.successors(node) ) # 两者一减就是 slack全图最小值所在的那条就是关键路径 slack {n: required[n] - arrival[n] for n in graph.nodes}这段伪代码里最关键的不是代码本身而是 max 和 min 的选择。正向传播取 max是因为 setup 要抓最慢的那条路径反向传播取 min是因为捕获端要求的时间要按最紧的那个算。如果换成 hold 检查正向传播取 min数据最早什么时候到反向传播取 max最早必须什么时候之后才允许到两个方向刚好对调。时间复杂度是 O(VE)跟图的规模成线性关系。这一点下面会展开说它是 STA 能“检查所有路径”的根本原因。3.3 报告怎么读slack 的符号、关键路径的起点与终点两个数一减就得到 slack。Setup 的 slack 等于 required 减 arrivalhold 的 slack 等于 arrival 减 required。为负就是违例负得越多越严重。报告里的路径一般按起点和终点的类型分组寄存器到寄存器、输入到寄存器、寄存器到输出、输入到输出。分组的意义在于它们的约束来源不同——reg2reg 的 required time 由时钟周期决定in2reg 的起点时间由 input_delay 决定reg2out 的终点要求由 output_delay 决定。读一条具体路径的报告重点看三样东西起点寄存器和终点寄存器分别属于哪个时钟域公共时钟路径有多长数据路径上每一级的延迟贡献。如果某一段的延迟占比特别大那才是真正的优化对象。这里有个反直觉的细节slack 最差的路径不一定延迟最长。因为不同路径的 required time 可能不一样——跨时钟域的路径、多周期路径、跨边沿路径它们的可用时间各不相同。一条 8ns 延迟的路径在 10ns 周期下 slack 是 2一条 3ns 延迟的路径在 2.5ns 的周期约束下 slack 是 -0.5后者才是要命的那条。只看延迟排序会抓错重点。4. 路径穷举为什么 STA 敢说“我检查了所有情况”4.1 四类时序路径与它们的检查条件按起点和终点的组合分数据路径有四类每一类的检查条件都不一样输入到寄存器起点是芯片外部器件用set_input_delay建模。arrival time 等于输入延迟加上片内走线延迟required time 等于捕获时钟沿减去建立时间和不确定度。这类路径的难点在于外部延迟你只能估估得乐观就是给自己埋雷。寄存器到寄存器最典型的一类两端都在时钟域内。公共时钟路径的延迟会部分抵消剩下的就是组合逻辑要背的账。寄存器到输出终点是芯片外部用set_output_delay描述下游器件的要求。这类路径的 slack 直接反映芯片能不能跟外部器件对上。输入到输出纯组合路径中间没有寄存器。这类路径的存在感最低但漏约束的概率最高——很多人只盯着寄存器之间的路径忘了还有直通的组合逻辑。除了数据路径还有几类“非数据”检查同样重要时钟门控检查要求门控信号在时钟有效沿之前稳定恢复时间和移除时间检查异步复位信号的释放时机必须远离时钟沿最小脉冲宽度检查时钟的高低电平持续时间不能短到触发器反应不过来。这几类检查在报告里通常只占几行但漏掉任何一类都可能让芯片上电就挂。4.2 跨边沿检查上升沿发、下降沿收会怎样如果发起寄存器用上升沿触发捕获寄存器用下降沿触发那留给数据的时间就只有半个周期假设占空比 50%。required time 变成 T/2 减 tSU而不是 T 减 tSU。这种结构在 DDR 接口、时钟分频电路、多相时钟系统里非常常见。工具会自动识别这种关系前提是时钟波形定义正确。create_clock里-waveform {0 5}这个参数就是在告诉工具上升沿在 0下降沿在 5。如果写成{0 6}占空比变成 60/40所有跨边沿路径的 required time 会跟着全错而且不会报任何警告。还有一类更隐蔽的路径时钟到建立时间路径。它描述的不是数据从哪来到哪去而是时钟信号本身从时钟源出发、经过时钟树、到达捕获寄存器时钟端的延迟再加上建立时间必须早于下一个有效沿。时钟树综合之前这条路径用的是理想时钟模型延迟估算是乐观的只有时钟树真正长出来、寄生参数提取完成之后这个数字才可信。所以签核必须在布局布线之后做用综合阶段的报告签核是自欺欺人。4.3 最长路径与最短路径为什么 setup 找最慢、hold 找最快Setup 关心数据最晚什么时候到所以沿路径取延迟最大值用的是慢 corner慢工艺、低电压、高温。Hold 关心数据最早什么时候到所以要取延迟最小值用的是快 corner快工艺、高电压、低温。同一条物理路径setup 和 hold 用的是两套完全不同的延迟数字。这也是为什么时序签核必须跑多个 corner只跑一个 corner 得到的结论没有意义。慢 corner 下 setup 通过不代表快 corner 下 hold 也通过反过来也一样。现在回到那个核心问题路径数量会不会爆炸考虑一个每级都有多个分支的组合逻辑树从起点到终点的路径条数随分支数指数增长。如果工具真的去枚举每一条路径规模稍大的设计就跑不动了。但工具不枚举。它只做一次拓扑遍历在每个节点上保留一个最优值后面的节点只需要看前驱的最优值就够了。这就是动态规划的思路——路径数量是指数的但求解过程是多项式的复杂度 O(VE)。这个性质是 STA 能够成立的根本前提它不靠覆盖靠归纳。只要有向图是 DAG没有组合环就能保证拓扑序存在就能保证每个节点的最优值被正确算出。反过来说一旦设计里出现组合逻辑环拓扑排序就做不了工具只能报个警告然后把环上的路径忽略掉——这条路径从此不在分析范围内而你可能永远不知道。5. 避坑报告全绿却上板翻车的几种典型5.1 约束层的坑假路径、漏约束与周期写错现象签核报告全绿板子上电跑一段时间就复位换一块板子又能跑。原因set_false_path设得太宽把一条真实存在的慢速路径从分析里放走了。最常见的情形是为了消掉一条跨时钟域路径的违例直接下一句 false path但那条路径其实是同源的同步路径只是经过了不同的分频器。解决每一条 false path 都必须有出处——对应设计里的哪一段逻辑、为什么可以不同步、由谁评审过。约束文件进版本库跟 RTL 一起管理改动要留痕。宽泛的通配符比如-from [all_registers]一律不允许出现在 false path 里。现象报告里某个模块的路径一条都没有时序看起来干净得不正常。原因端口没约束工具认为该端点没有时序要求直接跳过分析。解决跑一遍未约束端点检查命令把所有没有时序要求的端点列出来。这一步应该做成流程里的强制项而不是出了问题才想起来查。现象所有分析结果都偏乐观实际频率上不去。原因时钟周期写错或者从别的项目脚本复制过来没改。10ns 写成 8ns所有 slack 都凭空多出 2ns。解决约束和 RTL 版本绑定跑之前先回读实际的时钟定义跟设计规格逐条对一遍。这个动作花不了几分钟但能挡掉最难查的一类事故。5.2 模型与场景层的坑corner、跨时钟域与组合环现象STA 通过实测在低温或高压条件下出现 hold 违例。原因只跑了慢 corner 看 setup没跑快 corner 看 hold。或者用了理想时钟模型签核没加片上偏差的降额系数。解决setup 看慢 corner、hold 看快 corner两边都要跑时钟树长出来之后加降额系数重新签核。理想时钟下的报告只能用来做早期评估不能用来签核。现象跨时钟域路径一堆违例全设成 false path 之后报告干净了但实测误码率奇高。原因STA 不检查亚稳态。false path 只是让工具闭嘴不代表电路安全。两级同步器有没有做、MTBF 够不够STA 一个字都不会告诉你。解决跨时钟域单独用同步器结构加 MTBF 计算来验证或者引入专门的跨时钟域检查流程。false path 只是声明“这里不用工具管”不是声明“这里没问题”。现象工具报告里有组合环警告环上的路径没被分析。原因组合逻辑里存在反馈回路。除了时钟门控这种有意为之的结构组合环本身在设计上就该避免。解决把组合环警告当成错误处理逐条查清楚。时钟门控结构要用专门的命令声明让工具知道这是合法的环。现象设了多周期路径之后hold 检查突然报出一堆违例。原因set_multicycle_path -setup 2之后没配对应的 hold 约束工具默认把 hold 检查也推到了第二个时钟沿变得过度悲观。解决多周期 setup 和 hold 必须成对出现hold 的周期数一般设为 setup 周期数减一。这个规则工具不会替你检查只能靠约束评审。5.3 方法层的坑把 STA 当成时序验证的全部现象时序全绿、功能仿真也全绿芯片回来还是有 bug。原因STA 只证明时序不证明功能。它不检查复位逻辑是否正确、状态机初值是否合理、异步接口的物理效应是否存在。动态仿真只覆盖被激励到的场景形式验证只对写出来的性质负责。三者证明的东西完全不一样方法证明什么不证明什么覆盖方式静态时序分析路径延迟满足时序约束功能正确性、亚稳态概率图遍历全路径动态仿真特定激励下功能正确未覆盖的输入组合、未跑的 corner抽样形式验证特定性质在全部输入下成立实际延迟、物理效应数学证明解决三条腿一起走缺一条都不行。STA 通过只说明“在假设约束正确、模型正确的前提下延迟满足要求”这个前提本身就需要别的环节来保证。现象时序收敛卡在最后几条路径上怎么优化都过不去。原因只盯着综合和布局布线反复迭代没回到逻辑设计层面。逻辑级数摆在那里换单元、加驱动只能挤出零点几个纳秒治标不治本。解决回到 RTL看这几条路径是不是可以拆流水线、能不能改算法结构、是不是有可以用寄存器换逻辑的地方。真正难啃的时序问题答案往往在综合之前。6. 时序收敛的取舍为什么修 setup 的手段往往反噬 hold修 setup 的路子就那么几条插流水线、做重定时、换更强的驱动单元、拆逻辑、降频率。每一条都有代价而且代价经常落在 hold 头上。插流水线是把长组合逻辑切成几段、段间加寄存器。setup 立刻变好但延迟多一个周期、面积增加、功耗增加。更麻烦的是新增的寄存器本身带来新的 hold 检查——相邻两级之间的数据路径变短了hold 反而更容易违例。重定时是移动寄存器的位置来平衡各级延迟功能不变。理论上很优雅实际用起来很别扭RTL 和网表的对应关系被打破调试和后期改动都变难而且工具做重定时依赖约束正确约束写得含糊它会把寄存器搬到“看起来更优”但违反设计意图的位置。换驱动、拆逻辑只能解决走线和负载问题逻辑级数摆在那儿还是过不去。降频率最省事但那是产品定义层面的让步不是设计层面的解决。反过来修 hold 的手段是往数据路径上加延迟——插缓冲器、插延迟单元。这个方向和 setup 完全相反数据路径变长setup 就更紧。所以典型流程是先修 setup、后修 hold因为修 hold 一定会破坏已经收敛好的 setup。这里藏着一个更靠后的陷阱。修 hold 插进去的缓冲器在慢 corner 下延迟更大、在快 corner 下延迟更小而 hold 检查用的恰恰是快 corner。如果插入延迟的时候用典型模型去估实际快 corner 下可能根本不够。等芯片回来发现 hold 违例这时候没有任何补救手段——hold 跟时钟周期无关降频救不了软件绕不过去只能改版。这是唯一一种真的没有后悔药的时序违例。所以我的习惯是每跑完一轮 setup 优化不管报告多漂亮都强制再跑一遍快 corner 的 hold 检查再回一遍所有 false path 和多周期约束确认没有哪条路径是被约束“放走”的。约束文件单独做版本管理每次改动写清楚理由和评审人。时序收敛的本质不是把 slack 修成正数是让“工具看到的时序”和“硅片上真实发生的时序”尽量重合。约束写错、corner 漏跑、跨时钟域放过这三件事里任何一件都能让一份漂亮的报告变成一张废纸。希望帮到你。本文还有配套的精品资源点击获取