ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

静态时序分析(STA)实战:从约束到修复的时序路径分析指南

静态时序分析(STA)实战:从约束到修复的时序路径分析指南 1. 时序路径分析到底在解决什么问题1.1 从一颗芯片跑不起来说起很多人第一次接触静态时序分析STA是因为流片回来的芯片在实验室里跑不到目标频率。板子焊好了电源正常复位也给了可频率一往上拉就出错。这时候后端工程师会甩过来一句话时序没收敛。于是问题就变成了到底哪条路径没收敛为什么没收敛怎么让它收敛时序路径分析就是回答这三个问题的过程。它不关心你的RTL写得漂不漂亮也不关心功能仿真过了多少条用例它只盯着一件事信号从起点到终点在时钟沿到来之前能不能稳定地被捕获。这个能不能被量化成一个具体的数字——裕量Slack。裕量为正路径安全裕量为负路径违例。我在实际项目里见过太多这样的情况功能仿真全绿FPGA原型也跑得好好的结果ASIC后端一跑STA报告里几百条负裕量路径。原因往往不是设计逻辑错了而是时序约束没写对或者综合阶段没有把关键路径优化到位。所以时序路径分析不是一个后端才需要关心的事情前端设计者在写RTL的时候就应该对关键路径有预判。1.2 四条基本路径类型决定了你分析的范围STA工具业界最常用的就是Synopsys的PrimeTime会把设计里所有的时序路径归为四类这个分类是理解后续所有分析的基础输入到寄存器数据从芯片外部引脚进来经过组合逻辑被内部触发器捕获。这类路径的起点是输入端口终点是触发器数据端。寄存器到寄存器数据从一个触发器出发经过组合逻辑到达另一个触发器。这是最核心、最常出问题的一类路径。寄存器到输出数据从内部触发器出发经过组合逻辑到达芯片输出引脚。这类路径的约束往往和外部器件的建立/保持时间有关。输入到输出数据从输入引脚直接经过组合逻辑到输出引脚不经过任何触发器。这类路径通常是纯组合逻辑馈通约束方式比较特殊。为什么要分这四类因为每一类的起点和终点不同约束方式不同优化手段也不同。寄存器到寄存器的路径可以通过插入流水线来优化但输入到输出的纯组合路径你没法插触发器只能靠逻辑重构或者约束放宽来解决。1.3 建立时间和保持时间一对永远在打架的兄弟时序分析的核心就是两个检查建立时间检查Setup Check和保持时间检查Hold Check。建立时间说的是数据必须在时钟有效沿到来之前提前稳定一段时间。这个提前量就是建立时间。如果数据来得太晚触发器采到的就是旧值或者亚稳态。保持时间说的是数据在时钟有效沿到来之后还必须再稳定一段时间。这个之后就是保持时间。如果数据变化太快触发器还没来得及正确锁存数据就变了。这两者的矛盾在于组合逻辑延迟越大建立时间越容易违例数据到得晚组合逻辑延迟越小保持时间越容易违例数据变化太快。所以你在优化时序的时候不能只顾一头。我见过有工程师为了修setup把组合逻辑拼命优化结果hold全挂了又得回头加buffer。这种来回折腾在项目后期非常消耗时间。提示在28nm及以下工艺节点hold违例的修复成本远高于setup。因为setup可以通过降频来临时规避但hold违例是功能性的降频也救不了。所以后端阶段一定要优先保证hold干净。2. PrimeTime读入设计前你必须搞清楚的几件事2.1 工艺库文件不是随便拿一个就能用PrimeTime工作时需要读入工艺库文件.db格式这个文件描述了标准单元的逻辑功能、时序信息、功耗参数等。很多新手容易犯的错误是综合用的库和STA用的库不是同一个版本或者用了错误的工作条件PVT Corner。PVT是Process、Voltage、Temperature的缩写。同一个芯片在不同工艺角下表现完全不同。慢速角SSSlow-Slow下晶体管驱动能力弱setup容易违例快速角FFFast-Fast下晶体管开关速度快hold容易违例。典型角TT用于常规分析。我在一个40nm项目上踩过一次坑综合时用的是SS角库STA时忘了切换结果setup报告看起来还行但hold报告一片红。后来发现是库文件的工作条件没设对工具默认用了TT角导致hold分析结果偏乐观。这个教训告诉我每次跑STA之前第一件事就是确认当前用的库文件对应哪个PVT角。# 设置工作条件 set_operating_conditions -max ss_0p99v_125c -max_library ss_0p99v_125c \ -min ff_1p10v_m40c -min_library ff_1p10v_m40c上面这段约束的意思是最大延迟分析setup用慢速角最小延迟分析hold用快速角。这是标准的OCVOn-Chip Variation分析思路后面会详细展开。2.2 读入网表时的链接问题PrimeTime读入网表通常是从综合工具导出的.v文件之后需要把网表中的单元和库文件中的单元对应起来这个过程叫链接Link。如果链接不成功工具会报unresolved reference错误。链接失败最常见的原因有三个一是库文件里没有这个单元比如用了某个特殊的IP核但没加载对应的库二是网表里的单元名和库里的名字大小写不一致三是网表里例化的模块没有被正确展开。# 读入网表和库文件 read_verilog ./netlist/top.v read_db ./lib/ss_0p99v_125c.db read_db ./lib/ff_1p10v_m40c.db current_design top link_designlink_design执行完之后一定要检查有没有报错。如果链接不干净后面所有的时序分析结果都不可信。我通常会在link之后跑一个check_design确认没有悬空端口、没有多驱动、没有未连接的输入。2.3 时钟定义是整个分析的基石时钟定义错了后面所有分析都是白费。PrimeTime里用create_clock来定义时钟create_clock -name clk_core -period 2.5 -waveform {0 1.25} [get_ports clk_core]这行命令定义了一个周期为2.5ns、占空比50%的时钟上升沿在0ns下降沿在1.25ns。看起来简单但实际项目中的时钟结构远比这个复杂。常见的问题包括时钟经过了分频器或倍频器PLL需要在PLL输出端重新定义生成时钟时钟经过了多路选择器MUX需要定义不同的时钟模式时钟有多个来源需要设置时钟组之间的关系。# 定义生成时钟 create_generated_clock -name clk_div2 -source [get_ports clk_core] \ -divide_by 2 [get_pins u_div/Q] # 设置时钟组 set_clock_groups -asynchronous -group {clk_core} -group {clk_usb}set_clock_groups这行命令告诉工具clk_core和clk_usb是异步的它们之间的路径不需要做时序分析。如果不设这个约束工具会默认所有时钟都是同步的然后报出一大堆跨时钟域的违例这些违例实际上是不需要修的。注意异步时钟组之间的路径虽然不需要做时序分析但跨时钟域的信号仍然需要做同步处理比如双触发器同步器。STA工具不会检查同步器是否足够这是设计者的责任。3. SDC约束文件里最容易写错的几类命令3.1 输入输出延迟和外部世界对话的桥梁芯片不是孤立存在的它要和外部器件通信。set_input_delay和set_output_delay就是用来描述芯片边界上的时序关系的。# 输入延迟数据相对于时钟沿提前/延后到达的时间 set_input_delay -clock clk_core -max 1.2 [get_ports data_in*] set_input_delay -clock clk_core -min 0.3 [get_ports data_in*] # 输出延迟数据需要相对于时钟沿提前/延后到达外部器件的时间 set_output_delay -clock clk_core -max 1.5 [get_ports data_out*] set_output_delay -clock clk_core -min 0.5 [get_ports data_out*]这两个约束的数值不是随便填的它们来自外部器件的时序手册。比如你的芯片要和一个DDR存储器通信那输入输出延迟就要根据DDR的建立/保持时间要求、PCB走线延迟、芯片引脚电容等参数计算出来。我见过有工程师直接填0觉得反正是芯片内部的事情。结果流片回来发现接口时序完全对不上。输入输出延迟填0意味着你假设外部器件的时序是理想的这在实际系统中几乎不可能。3.2 虚假路径和多周期路径该忽略的要果断忽略不是所有路径都需要按时钟周期来检查。有些路径在功能上根本不可能同时激活或者允许多个周期才能稳定。# 虚假路径功能上永远不会发生的路径 set_false_path -from [get_ports test_mode] -to [get_ports scan_out] # 多周期路径允许多个时钟周期稳定的路径 set_multicycle_path -setup 2 -from [get_pins u_multiplier/*] -to [get_pins u_accumulator/D*] set_multicycle_path -hold 1 -from [get_pins u_multiplier/*] -to [get_pins u_accumulator/D*]虚假路径的典型场景是测试逻辑、配置寄存器、上电初始化电路等。这些路径在正常工作模式下不会被激活如果不对它们设false_path工具会浪费大量时间去优化它们甚至可能因为优化这些路径而恶化了真正关键的路径。多周期路径的典型场景是乘法器、除法器、复杂状态机等。这些逻辑的组合延迟天然就超过一个时钟周期强行要求一个周期内稳定是不现实的。但是设多周期路径要特别小心setup设了Nhold通常要设N-1。如果只设setup不设hold工具会在错误的位置检查hold导致hold违例。提示每设一条false_path或多周期路径都要在文档里写清楚理由。我见过项目交接时后人不知道前人为什么设了某条false_path不敢删也不敢改最后成了技术债。3.3 时钟不确定性给时序分析留足余量真实芯片里的时钟不是理想的。时钟源有抖动Jitter时钟树有偏斜Skew这些因素都会影响时序。set_clock_uncertainty就是用来给这些非理想因素留余量的。# 设置时钟不确定性 set_clock_uncertainty -setup 0.15 [get_clocks clk_core] set_clock_uncertainty -hold 0.08 [get_clocks clk_core]setup uncertainty通常包括时钟抖动、时钟树偏斜的预估、以及一些设计余量。hold uncertainty通常只包括时钟抖动和偏斜因为hold检查对时钟抖动更敏感。这个数值怎么定如果是前端综合阶段时钟树还没建可以设大一点比如周期的5%~10%如果是后端签核阶段时钟树已经建好了可以根据实际的时钟树报告来设通常可以小一些。我在一个项目中见过setup uncertainty设了0.3ns而时钟周期只有2ns相当于15%的余量。结果综合出来的面积比预期大了30%因为工具为了满足这个过于保守的约束拼命加buffer和upsize单元。后来把uncertainty降到0.1ns面积立刻降下来了时序也没有问题。4. 用report_timing读懂时序报告4.1 一条路径的完整报告长什么样report_timing是PrimeTime里最常用的命令它会把一条路径的详细时序信息打印出来。很多人看到报告里密密麻麻的数字就头疼其实只要抓住几个关键点就行。report_timing -from [get_pins u_reg_a/Q] -to [get_pins u_reg_b/D] -delay max报告通常包含这几部分起点信息Startpoint、终点信息Endpoint、路径类型Path Type、时序检查类型Setup/Hold、数据路径延迟Data Path、时钟路径延迟Clock Path、裕量Slack。数据路径延迟是从起点触发器时钟引脚到终点触发器数据引脚的信号传播时间。时钟路径延迟是从时钟源分别到起点和终点触发器时钟引脚的延迟。裕量的计算公式是Setup Slack 时钟到达时间 时钟周期 - 建立时间 - 数据到达时间Hold Slack 数据到达时间 - 时钟到达时间 - 保持时间如果Slack是负数说明违例了。负得越多问题越严重。4.2 从报告里定位瓶颈单元一条路径上可能经过几十个甚至上百个单元哪个是瓶颈看报告里的Incremental Delay列。这一列显示的是每个单元或线网的增量延迟。增量最大的那个单元就是主要瓶颈。常见的瓶颈类型包括大扇出的线网net delay大、驱动能力弱的单元cell delay大、经过多级缓冲器的路径累积延迟大。# 报告路径上延迟最大的10个单元 report_timing -from [get_pins u_reg_a/Q] -to [get_pins u_reg_b/D] \ -delay max -max_paths 1 -nworst 1 -input_pins -nets加上-input_pins和-nets选项后报告会显示每个单元的输入引脚延迟和线网延迟更容易定位问题。4.3 用表格对比不同优化策略的效果定位到瓶颈之后通常有几种优化手段。我用一个实际项目中的例子来说明优化手段操作方式对Setup的影响对Hold的影响面积代价单元Upsize换驱动能力更强的单元改善明显可能恶化增加插入Buffer在长线中间插缓冲器改善线延迟可能恶化增加逻辑重构改变组合逻辑结构改善明显不确定可能减少插入流水线在组合逻辑中插触发器大幅改善需要重新平衡增加降低频率增大时钟周期直接解决无影响无在实际项目中我通常优先考虑逻辑重构和插入流水线因为这两种手段对面积的代价相对可控而且效果持久。Upsize和插Buffer虽然简单但会带来面积和功耗的上升而且可能引入新的hold问题。5. 时序违例的排查链路与修复思路5.1 先确认约束是否正确再怀疑设计这是我最想强调的一点遇到时序违例第一反应不应该是设计有问题而应该是约束有没有写错。我排查时序违例的标准流程是这样的检查时钟定义是否正确周期、波形、生成时钟关系检查输入输出延迟是否合理和外部器件手册对照检查false_path和多周期路径是否覆盖了所有不需要检查的路径检查时钟不确定性是否设置合理确认以上都没问题后再看设计本身的逻辑延迟这个顺序很重要。因为约束错误的概率远高于设计错误而且约束错误的修复成本低得多。我曾经花了整整两天去优化一条路径最后发现是时钟定义的时候把周期写错了实际周期应该是2ns我写成了1.5ns。5.2 跨时钟域路径的违例怎么判断跨时钟域CDC路径的违例是最容易被误判的。如果两个时钟是异步的它们之间的路径本来就不应该做时序分析。但如果你忘了设set_clock_groups工具会按同步时钟来检查报出一大堆违例。判断方法很简单看报告里的Launch Clock和Capture Clock。如果两个时钟的频率比不是整数倍或者相位关系不固定那它们大概率是异步的。# 查看设计中所有的时钟 report_clocks # 查看时钟之间的路径 report_timing -from [get_clocks clk_a] -to [get_clocks clk_b] -max_paths 5如果确认是异步时钟域加上set_clock_groups -asynchronous即可。但要注意加了异步时钟组之后工具不再检查这些路径的时序但跨时钟域的信号仍然需要同步器。同步器的正确性要靠CDC检查工具如SpyGlass CDC来验证STA工具管不了这个。5.3 修复Setup违例的实战顺序假设你已经确认约束没问题确实存在setup违例。修复的顺序应该是第一步看违例路径是否在关键模块上。如果违例路径在测试逻辑或配置逻辑上考虑加false_path。如果在数据通路上那就必须修。第二步看违例的严重程度。如果Slack是-0.05ns可能通过轻微的单元调整就能解决。如果是-0.5ns那就需要较大的结构改动。第三步选择合适的优化手段。优先考虑逻辑重构比如把长组合链拆成两级流水线其次考虑单元Upsize最后考虑插Buffer。第四步修复后重新跑STA确认没有引入新的违例。特别是holdsetup修完之后hold往往会变差。# 修复setup的常用命令 set_max_delay 1.8 -from [get_pins u_path/*] -to [get_pins u_reg/D] # 或者用compile_ultra -retime让工具自动做寄存器重定时5.4 修复Hold违例的注意事项Hold违例的修复相对直接在数据路径上插入延迟单元Buffer或Delay Cell。但有几个坑要注意不要在有false_path的路径上修hold因为工具可能把延迟单元插到了不该插的地方。修hold时要注意不要恶化了setup。插延迟单元会增加数据路径延迟可能让原本勉强满足的setup变成违例。Hold修复通常在时钟树综合之后进行因为时钟树的偏斜会影响hold结果。# 修复hold的常用命令 insert_buffer [get_pins u_path/net] BUF_X2 # 或者用set_delay_insertion让工具自动插入延迟我在一个项目中遇到过这样的情况hold违例修完之后setup出现了新的违例。原因是插入的buffer增加了数据路径延迟。后来改用驱动能力更弱的buffer延迟增加得少一些两边就都满足了。这个经验告诉我修hold的时候要选延迟增量最小的方案不要一上来就用大驱动。6. 几个容易被忽略但很致命的细节6.1 不要忽略线延迟模型PrimeTime支持三种线延迟模型零线延迟Zero Wire Load、单位线延迟Unit Wire Load、实际线延迟Back-annotated Delay。在综合阶段通常用线负载模型来估算线延迟在后端签核阶段必须用实际提取的寄生参数SPEF文件来做分析。# 读入寄生参数文件 read_parasitics ./spef/top.spef如果忘了读SPEF工具会用线负载模型来估算结果和实际差距可能很大。我见过一个项目综合阶段时序报告显示Slack有0.3ns的余量后端提取寄生参数后重新分析Slack变成了-0.2ns。原因就是线负载模型低估了长线的延迟。6.2 多模式多角MMMC分析不是可选项现代芯片通常支持多种工作模式比如高性能模式、低功耗模式、测试模式每种模式下的时钟频率、电压、使能信号都不同。同时还要考虑不同的PVT角。这就是MMMCMulti-Mode Multi-Corner分析。# 定义分析场景 create_scenario -name func_ss -specific_data func.sdc create_scenario -name test_ff -specific_data test.sdc # 分别激活不同场景进行分析 set_active_scenarios {func_ss test_ff}如果只分析一种模式一个角很可能漏掉某些场景下的违例。比如功能模式下时序没问题但测试模式下扫描链的时序违例了。这种问题在流片后才发现代价就大了。6.3 时序报告里的Unconstrained Path要重视跑完STA之后除了看违例报告还要看有没有未约束的路径。report_analysis_coverage可以报告约束覆盖率。report_analysis_coverage如果有大量路径没有被约束覆盖说明你的SDC文件有遗漏。未约束的路径不会被检查也就意味着它们可能存在问题但你不知道。常见的未约束路径包括没有定义时钟的寄存器、没有设置输入输出延迟的端口、被错误地设了false_path的路径。我通常会把约束覆盖率作为STA签核的一个检查项覆盖率低于95%就不允许签核。6.4 时钟树综合后的时序变化在综合阶段时钟树是理想的所有触发器的时钟到达时间相同。但在后端时钟树综合CTS之后时钟树有了实际的延迟和偏斜时序结果会发生变化。这个变化可能好也可能坏。如果CTS做得好关键路径上的时钟偏斜有利于setup捕获时钟比发射时钟晚到那setup会改善。如果CTS做得不好偏斜方向不利setup会恶化。所以在CTS之后必须重新跑STA不能拿综合阶段的报告来签核。我见过有团队为了赶进度用综合阶段的时序报告去签核结果流片回来芯片频率达不到标称值。这种教训非常深刻。7. 一些个人体会时序路径分析这件事说到底是一个约束驱动的工作。你的约束写得越准确、越完整工具的分析结果就越可信你修复违例的效率就越高。反过来如果约束写得含糊不清工具要么漏报违例你以为没问题实际有问题要么误报违例你花大量时间去修本来不需要修的路径。我在带新人的时候通常会让他们先做一件事拿一个简单的设计手动写一份完整的SDC然后逐条命令解释为什么这么写。这个过程比看任何教程都有效。因为写SDC的过程会强迫你去思考这个时钟的周期是多少这个端口的输入延迟怎么算这条路径为什么可以设false_path另外STA不是一次性的工作而是一个迭代的过程。综合后跑一次布局后跑一次CTS后跑一次布线后跑一次每次的结果都可能不同。每次跑完都要和上一次对比看看哪些路径变好了哪些变差了为什么。这种对比分析的能力是区分普通工程师和资深工程师的重要标志。最后说一个很实际的问题STA工具跑一次可能要好几个小时大芯片甚至要跑一整天。所以不要等到所有约束都写完了才跑第一次。我的习惯是先把基本的时钟和输入输出约束写好跑一次快速分析看看有没有明显的违例。然后再逐步添加false_path、多周期路径等约束每加一批就跑一次观察结果的变化。这样既能及时发现问题又能避免一次性跑完发现结果完全不对、又不知道是哪条约束导致的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进