说实话静态时序分析这个系列写到第二篇我想换个切入点。上一篇我们把STA是什么、为什么需要它、建立时间和保持时间的基本定义聊了一遍。但很多刚接触时序分析的同学看完概念之后对着工具报出来的一屏时序报告还是会有一种每个字都认识连起来不知道在说什么的感觉。这一篇我就针对这个痛点把STA工具内部的建模逻辑、四种路径、时钟相关概念以及读报告时的拆解思路一层层讲清楚。这篇内容适合已经知道触发器有setup/hold概念、但还没系统搞懂时序报告是怎么算出来的人也适合准备做后端或时钟树综合的初级工程师。1. 时序弧一张延迟地图怎么决定路径快慢1.1 标准单元库里的延迟地图STA工具做时序分析时并不是像SPICE仿真那样把每个晶体管都建出来算一遍。它依赖的是标准单元库里预先算好的时序弧timing arc。你可以把库里的每个单元想象成公交线路图上的一个站点工具并不关心站点内部怎么运作只关心从哪个输入站上车、到哪个输出站下车要花几分钟、到达时候的状态稳定不稳定。时序弧就是这条线路上的一段公交线路。每一段时序弧都记录了一个单元引脚到另一个引脚之间的延迟或约束关系。单元库里同一个逻辑功能通常会有几十种驱动强度版本它们的区别往往就体现在这些时序弧的数值上。所以读时序弧等于是在读一张整个设计的延迟地图——地图做得好不好直接决定了后续时钟树综合和布线能不能收敛。1.2 延迟弧上升和下降不是一回事组合逻辑单元与门、或门、选择器等的时序弧叫延迟弧delay arc。它的特点是输入引脚的上升沿和下降沿到输出引脚的变化延迟是不一样的。原因很朴素——硅片上PMOS和NMOS的驱动能力本身就有差异再加上输出负载不一样充放电速度自然不同。延迟弧通常是一个查表模型横轴是输入信号的transition time信号从低到高或从高到低变化所需的时间纵轴是输出负载电容表里面填的数值就是这个条件下从输入到输出的延迟。所以你在时序报告里经常看到一长串延迟数字被拆成几段其中单元内部延迟就是通过这样一张二维表插值查出来的net延迟则是后端工具根据寄生参数提取出来的。两层加在一起才是一条完整的数据路径延迟。1.3 约束弧数据稳定区间的另一种延迟除了延迟弧时序弧还有一大类叫做约束弧constraint arc。触发器这类时序单元引脚之间不存在输入变化后输出经过多久变化的传播延迟取而代之的是一类你必须满足某个时间关系的约束比如数据引脚相对时钟引脚要有setup时间、hold时间异步复位引脚相对时钟要有recovery时间和removal时间时钟引脚自己还要求最小脉冲宽度防止窄到触发器无法正确翻转。我记得有次看到一位同学在调一个复位释放的问题他盯着组合逻辑延迟看了半天一直没想通为什么复位路径上的延迟明明不长时序却总不过。后来我们发现问题根本不在数据路径而在异步复位释放相对时钟的recovery/removal约束弧上——这个约束弧不是通了多长的路而是信号必须避开哪个时间窗口。理解约束弧很多看似奇怪的violation其实都能对号入座。1.4 读时序弧能迅速判断电路结构问题学习读时序弧最有价值的地方在于它能帮你判断一个电路结构是不是有浪费。举个例子多路选择器MUX的选择端S端到输出端的时序弧延迟通常比数据端A端或B端到输出端的延迟要大将近一倍。因为S端要同时控制两条支路的通断内部晶体管串联数量多RC延迟更大。所以当你在关键路径上有一长串MUX时把先到信号放在选择端、后到信号放在数据端往往能省下一大截路径延迟。相反你要是把晚到的信号放在选择端它就要穿过MUX内部最长的延迟弧路径立刻变慢。标准单元库里常见的时序弧类型大致可以整理成下面这张表时序弧类型含义主要影响的检查组合逻辑延迟弧rise/fall输入变化到输出稳定所需时间同时影响setup和hold触发器setup弧数据必须在时钟沿前保持稳定只影响setup检查触发器hold弧数据必须在时钟沿后继续稳定只影响hold检查recovery/removal弧异步信号相对时钟的释放与置位窗口复位/置位时序检查最小脉冲宽度弧时钟或复位信号的最窄脉冲宽度要求抗毛刺、时钟质量检查实际项目中拿到一个不熟悉的单元库我一般不会逐个看面积和功耗而是先挑几个关键单元比如触发器、MUX、AOI复合门打开lib文件翻一翻它们各引脚的timing弧。这样做的好处是心里对每级的延迟量级有个底后面看报告时能在几秒钟内判断某条路径是每级延迟都合理、只是级数太多还是某一级单元选择不当、延迟异常偏大。这两种情况的修复策略完全不同。2. 建立时间与保持时间发射沿和捕获沿之间的时间窗口2.1 为什么数据必须稳定两阵子D触发器在时钟沿到来的瞬间把数据采样进去但采样不是一个瞬时的动作。时钟有效时内部的传输门打开数据要穿过输入级的反相器和锁存节点让存储节点稳定到确定的电平。如果在这个期间数据还在变化触发器就可能采到一个中间电平输出端出现亚稳态。所以必须规定两个时间约束时钟沿之前数据要提前稳定一段时间这就是建立时间时钟沿之后数据还要保持稳定一段时间这就是保持时间。拿拍照来类比很贴切按下快门的瞬间被拍的人不能晃动快门开启之前和之后都要保持姿势。如果恰好在这期间动了一下照片就是糊的。触发器的setup/hold就是那一段不能动的窗口STA要保证的就是数据信号在到达每个触发器时都能落入这个窗口之外的安全范围。2.2 发射沿、捕获沿与数据传送时序检查从来不是单独看一个触发器而是看一段数据接力。时钟沿到达发射触发器时数据被推出来经过组合逻辑必须在下一个捕获沿到来之前到达捕获触发器的D端。这条路径上的时间关系是由发射沿和捕获沿共同定义的。setup检查关注的是从发射沿推出数据开始到数据到达捕获触发器D端为止加上捕获触发器要求的setup时间不能超过两个沿之间的时间距离——通常是一个时钟周期。hold检查关注的则反过来数据不能在捕获沿附近太早翻转以免把还没采完的数据冲掉。这两种检查窗口的参考点不同所以它们对路径延迟的要求也完全相反这才有了后面要说的min/max两套分析。2.3 最差情况怎么选慢最慢、快最快STA里一个重要思维习惯是最坏情况思维。做setup检查时我们要让数据路径跑最慢因为数据到达越晚给捕获触发器留下的建立余量就越少同时要让捕获时钟到达尽量早因为捕获沿越早留给数据传输的时间越短。反过来做hold检查时数据路径要取最快因为数据变化越早越可能在捕获沿之后立刻干扰锁存窗口时钟路径则要取最晚。对应到工艺角上这意味着慢角SS通常用来验证setup快角FF用来验证hold。有同学问为什么同一个路径要做两个工艺角不都是延迟吗根因就在这里——一组数据路径和时钟路径的延迟在不同的工艺条件下比值会变化setup和hold分别由最不利的比值决定必须分开覆盖。2.4 为什么hold violation不能靠降频解决这个问题我几乎每次培训都要强调。看到setup不满足第一位想到的往往是降频——因为setup slack跟时钟周期直接挂钩周期拉大留给数据的时间自然变多。但hold检查发生在同一个捕获沿附近它的时间窗口跟两个沿之间的距离即频率没有关系。哪怕你把时钟从1GHz降到1Hz只要数据在捕获沿之后立刻翻转hold一样会违例。hold违例的正确处理思路是让数据路径慢下来常用手段是在数据路径上插入延迟buffer或者调整时钟树让捕获时钟相对发射时钟稍微晚到制造正偏斜来缓解。跑后端流程时工具会在CTS阶段自动针对hold进行修正但前端设计阶段也要留个心眼——如果你在综合报告里看到hold违例先别急着改RTL逻辑要看看是不是异步路径没做对处理或者时钟约束写得太松。3. 四类时序路径约束命令为什么这样写3.1 四条路径先分清楚STA把所有需要检查的时序路径按起点和终点分成四类路径类型起始点终止点典型约束寄存器到寄存器发射触发器时钟引脚捕获触发器数据引脚时钟定义输入端口到寄存器输入端口寄存器数据引脚输入延迟寄存器到输出端口发射触发器时钟引脚输出端口输出延迟输入端口到输出端口输入端口输出端口输入延迟输出延迟这个分类是约束命令的骨架。很多人写约束文件觉得命令记不住其实就是没想明白每条命令对应哪条路径。把路径分类记清楚写SDC时你会发现自己能够顺着路径的走向翻译出该写什么。3.2 内部路径靠时钟定义覆盖寄存器到寄存器的路径是时序分析里最常见、也是工具自动完成覆盖的类型。只要用create_clock定义好主时钟工具就会自动在同一个时钟域的所有寄存器之间建立检查。命令很简单create_clock -period 10 [get_ports clk]但有一个很容易忽略的地方如果你通过PLL或者分频逻辑生成了内部时钟就必须额外定义生成的时钟工具才能正确分析两个时钟域之间的关系。如果设计里有多个异步时钟域还需要用set_clock_groups明确告诉工具哪些时钟之间不做时序检查。很多同学图省事看到跨时钟域路径有违例就直接设false path但设计里如果没有同步器这是把问题掩盖而不是解决。3.3 输入端口的延迟怎么借进来输入端口到寄存器这段路径约束核心是set_input_delay。它的意思是外部器件从外部时钟沿开始到信号落到本设计输入引脚上这中间花了多长时间。注意它描述的是外部时间不是本设计内部的时间。新手最容易在这里绕晕。set_input_delay -clock clk -max 2.5 [get_ports din] set_input_delay -clock clk -min 0.8 [get_ports din]可以这样理解用时钟沿当一把尺子的零点。max表示数据最晚到引脚的时刻min表示数据最早到引脚的时刻。这把尺子会把外部延迟借到本设计内部路径的起点上工具才能判断从输入引脚到寄存器之间的组合逻辑够不够快。如果input delay设得太大相当于外部数据很晚才来那你内部留给数据传输的时间就很少setup很容易红。3.4 输出端口要为下游预留什么寄存器到输出端口的路径约束核心是set_output_delay。它描述的是数据离开本设计输出端口后还要经过下游电路多久才能到下游触发器的数据端外加下游触发器自身要留多少建立时间。这些时间全都要从本设计的时钟周期里扣除算成本设计内部路径要保证多少时间。set_output_delay -clock clk -max 3.0 [get_ports qout] set_output_delay -clock clk -min 1.2 [get_ports qout]我见过一个项目后端反馈某个输出端口的路径一直收敛不了。排到最后发现是前端把set_output_delay的max设得比真实下游电路延迟大了将近1ns——不是代码问题是约束把必须达到的时间定得太严导致工具拼命优化一条本来并没有那么紧的路径。约束文件里的每一个数字都在给后端传递期望写错一个浪费的可能就是整个团队几天的时间。3.5 端口到端口路径与约束覆盖检查输入端口到输出端口的纯组合路径不经过任何寄存器。这种路径的检查本质上是把input_delay和output_delay两头一夹中间组合逻辑的总延迟必须落在这个区间里。约束方法就是上面两种命令同时配好工具会自动在输入端口和输出端口之间建立检查。约束写完以后我强烈建议看一遍工具的unconstrained endpoints报告查一查有没有没被覆盖到的端点。很多时序问题初看像是逻辑太慢实际是某些端点根本没被约束到工具默认用了一个非常宽松的虚拟时钟做检查问题被藏到了后端。写约束不是命令越多越好关键是覆盖得完整且合理。4. 时钟三兄弟latency、skew、uncertainty如何共同影响时序4.1 时钟延迟的两段式source和network时钟信号从芯片外部时钟源进入输入端再到每个触发器时钟引脚中间隔着长长的一段路。这段总延迟被STA拆成两段source latency是从芯片外部时钟源到时钟定义点的延迟network latency是从时钟定义点到触发器时钟引脚的延迟。在综合阶段时钟树还没长出来network latency只能用一个粗略估算值。布线完成之后报告里才会显示真实的时钟网络延迟。这就是为什么同一份设计综合报告和布线后报告的clock path delay差别可以很大。如果你不理解这两段的差别看到时钟路径从2ns变成5ns可能会以为出bug了其实只是估算变成了实际值。4.2 skew的双刃剑效应时钟偏斜clock skew指的是同一个时钟沿到达不同触发器的时刻差异。这块内容是个典型的双刃剑如果捕获触发器的时钟比发射触发器的时钟晚到对setup是有利的因为数据多了一点传输时间但对hold是有害的因为捕获沿后延数据保持的窗口也后延了数据更容易提前翻转冲掉旧值。反过来捕获时钟更早到的负偏斜对hold有利、对setup有害。时钟树综合的核心目标之一就是把skew压低到合理范围让setup和hold都有足够的余量。做时钟树收敛时我们常常盯着skew的大小看但比绝对值更重要的是理解它对两种检查方向的影响方向。4.3 uncertainty是时钟世界的模糊带时钟不确定性clock uncertainty包含了时钟抖动jitter、PLL相位误差、时钟树估算误差等一堆现实世界的不完美因素。它本质上是在时钟路径上扣掉一条模糊带表示时钟沿到底什么时候到不是精确的可能早可能晚。set_clock_uncertainty -setup 0.1 [get_clocks clk] set_clock_uncertainty -hold 0.05 [get_clocks clk]这里要特别提醒不要为了让时序通过而盲目把uncertainty调成0.05甚至0。它反映的是时钟源的真实质量不是你可以随便挥霍的余量。我之前接触过一个设计为了临时过综合有人把uncertainty设得很小结果后端跑了几天都收敛不了因为后端基于一个过紧的目标在布线。最后查下来问题就出在这个被改小的uncertainty上。4.4 一个简化检查表把三兄弟放在同一张表里看它们对slack的影响方向就很清楚了因素对setup的影响对hold的影响源端和网络延迟同时同向变化大部分相互抵消大部分相互抵消正时钟偏斜捕获沿更晚有利有害负时钟偏斜捕获沿更早有害有利时钟不确定性jitter等有害有害注意latency本身并不会直接拉大或减小slack因为它同时出现在发射和捕获两侧真正的差距来自skew和uncertainty。所以看到报告里的clock path delay数字很大时先别慌要计算发射侧和捕获侧的差值而不是盯着绝对值。5. 读时序报告的正确姿势从slack负号到路径拆解5.1 slack、WNS、TNS各代表什么时序报告最核心的数字是slack它是required time减去arrival time的结果。setup slack为正说明数据总是提前到达满足建立要求为负就是不满足。hold check有自己另一套slack含义类似。在整体汇总报告里还会看到WNS和TNS。WNS是所有违例路径里最差的那条slack反映的是单个最痛点有多严重TNS是所有负slack路径的总和反映的是违例面有多广。WNS很差说明有一条极其严重的路径需要单点攻坚TNS很差但WNS还行说明有一大堆轻微违例整体优化更有效。这两个数字一出来基本能判断优化策略该往哪个方向使劲。5.2 一条setup违例路径的拆解示例假设一条setup路径slack -0.35ns我们可以这样分步拆解先看startpoint和endpoint确认两个触发器是不是同一个时钟域有没有跨时钟域没处理。再看data path的总延迟把它除以单元平均延迟估算逻辑级数。如果级数超过10级高频率设计基本要拆流水或者优化逻辑结构。然后逐级看每个单元的延迟如果某一级延迟占比特别大优先看那级的输出负载和输入transition。输出负载过重就换更大驱动强度的单元fanout太大就手动复制逻辑。接着看clock path检查发射时钟和捕获时钟的latency差值确认skew没有异常。最后回头审视约束本身set_input_delay或set_output_delay是不是设置了不切实际的值。这套流程走下来90%的setup违例都能定位到具体原因。怕的是拿到报告就从第一级单元开始改逻辑改了半天slack纹丝不动其实问题在另一头。5.3 报告里不会明说的几个细节看报告时还要留几个心眼。首先是单位有的报告时间单位是ps有的是ns一不留神就把数值看大看小一千倍。其次是时钟不确定性在报告里的呈现方式有些工具已经把uncertainty从required time里扣掉了有些是单独列一列直接拿clock path delay和data path delay相减却对不上slack往往就是漏了这块。还有一个容易踩的坑是报告中的transition值超过了库里允许的上限这种情况即使路径级数不多也会造成延迟异常根源常常在超高扇出或长走线。6. 几轮实战下来我沉淀的检查套路6.1 先约束完整再谈收敛我的个人习惯是拿到一个新设计第一步不是看负slack有多惨而是先跑一遍约束完整性检查。读一遍SDC文件确认所有时钟、生成时钟都定义完整确认没有端口或触发器被落在约束覆盖范围外面。然后再看每个时钟域各自的WNS/TNS把最差的端点挑出来重点拆解。很多同学一上来就抓着一个负slack路径改RTL改了半天发现约束文件本身就少了一个生成时钟整个时钟域的检查方式都是错的白费力气。6.2 建立时间优化先看逻辑级数再看负载setup优化有一个优先级顺序。先把数据路径总延迟拆开看估算逻辑级数。如果级数过高优先考虑插流水寄存器或重构逻辑结构如果级数正常再看每级delay是否均衡找到瓶颈级——通常表现为延迟特别大、transition特别差的那一级。对瓶颈级换更强驱动单元、减少扇出、把高负载路径拆分并行都能立竿见影。网络延迟占比过大的时候还要考虑floorplan和布局的影响这就是前端的逻辑优化鞭长莫及的领域了。6.3 保持时间优化buffer要插在正确的位置hold违例的本质是数据路径太快。修法是在数据路径上插入延迟buffer但插的位置有讲究。一个原则是插在共享路径之外、尽量靠近发射端的位置避免因为多个分支共享这段插入的延迟导致其他本不违例的路径也变慢。实际操作里我会先确认这个违例是min delay violation再看目标捕获触发器附近能否借到可用的延迟单元。如果在数据已经快到捕获端时才插入buffer不仅效果差还可能把transition弄坏。6.4 对uncertainty保持敬畏最后想再强调一次uncertainty这件事。它代表的是现实世界里时钟源的物理抖动和不确定性不是给后端留出的余量旋钮。为了过时序而把uncertainty调到接近零本质上是在用一个理想时钟的假设骗过检查工具这个隐患最后会在芯片实测频率上暴露出来。我自己做设计的习惯是把每个时钟的uncertainty值写进设计文档任何改动都要备注理由。等到芯片回来频率上不去时查原因的第一件事就是核对这几项时钟约束是否被优化过。说实话静态时序分析入门不难难的是面对一份几百条violation的报告时还能不能冷静下来按顺序一步步定位。把时序弧、路径分类、时钟三兄弟这些基础在脑子里串成一条线之后你会发现报告里那些数字不再是天书而是每一段都有来路、都可以被追问。后面有机会我再接着写时钟约束文件的具体写法包括生成时钟和跨时钟域的细节。