ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CTS自动分组引发大量hold violation?skew group排查与修复指南

CTS自动分组引发大量hold violation?skew group排查与修复指南 最近做一个数模混合芯片的后端CTS跑完check_timing一片红hold violations上百条而且全都集中在同一个数字子系统的三块逻辑里。我一开始按老套路查congestion、查data path delay没发现异常绕线也没堵到那个程度。后来把skew group report翻出来一行行看才意识到是Innovus的自动分组把两个完全不需要对齐的时钟域硬凑到了一起而罪魁祸首恰恰是设计里以_clock_gen命名的那些时钟生成模块。这个坑让项目组多花了大半个月今天把这个过程的来龙去脉和解决办法完整梳理一遍。1. 先搞明白CTS为什么要分skew group_clock_gen为什么又特别敏感1.1 skew group不是给你看的是给balance pass用的按我自己的理解skew group就是工具在时钟树综合阶段划出的必须对齐的一组sink。CTS引擎不会去修任意两条路径之间的偏差它只会针对同一个group内部的sink集合做delay balancing。也就是说skew group直接决定了工具在哪些寄存器之间做平衡、往哪些路径上插buffer和delay cell这个范围划错了后面时序优化再怎么使劲也白搭。很多人觉得skew group就是时钟域clock domain的别名其实不完全一样。同一个时钟域内部可以按逻辑层次或物理位置分成多个group不同时钟域也可以通过配置被放进同一个group。工具的目标不是让某个时钟自己的树平衡而是让这个group里所有sink的clock arrival time尽量一致。可以打个比方公司团建分组组内的人要按同一节奏完成游戏任务。如果分组本身不合理——把两条完全独立的任务线强行分到一组成员就得做一堆无意义的协调反过来必须同步完成的任务被拆到不同组节奏又会乱。CTS的分组逻辑就是这个感受。1.2_clock_gen命名触发的启发式逻辑Innovus在识别时钟拓扑时会基于层次化单元的命名做一些启发式推断。如果一个hier cell名字里有clock_gen、clkgen、clock_generator这类关键字工具会倾向于把它当做一个时钟生成位置也就是时钟源头或generated clock的source point来处理。这种处理带来的直接效果是从这个模块输出或经过的时钟在自动构建skew group的时候被特殊关注。多个从该模块出来的generated clock只要源头都能回溯到同一个PLL或者同一个master clock工具就默认它们适合做inter-clock balancing于是会把它们的sink自动归到同一个大group。问题在于工具根本不知道你的设计意图。它只看到命名和时序关系看不到这个时钟其实只在测试模式下工作这两个时钟是异步的那条路径是CDC不需要对齐。命名启发式只是统计学上的大概率正确落在具体design上经常会帮倒忙。1.3 分组过粗和过细都会出事关键在平衡范围一个真正合理的skew group划分原则是在任意一个有效的function mode下需要彼此对齐的时钟才放进同一组。理想状态下时序上互相exclusive或者说在功能上永远不会同时活跃的时钟不应该被强制平衡。但自动分组往往不区分这些。分组过粗时工具为了满足组内所有sink对齐会在原本八竿子打不着的两条时钟树上疯狂插buffersetup和hold被强行拖累功耗和面积也跟着涨。分组过细时该平衡的路径没平衡比如MUX选择后的寄存器与两路输入时钟之间的skew失控模式切换瞬间hold崩盘。所以理解这个问题的核心不是学会某个命令而是理解平衡范围这个边界哪些时钟必须对齐哪些时钟只是共享了同一个source但功能上根本不需要对齐。自动分组恰恰是在这个边界上看不清。2. 自动分组最容易翻车的三个真实场景2.1 MUX选择时钟两组时钟被拆开切换路径上的寄存器skew失控这是我实际项目里碰到最多的情况。某个逻辑模块的时钟来自一个clk_mux两个输入分别是pll_div2_clk和test_clk输出给一组寄存器。正常情况下功能模式走pll_div2_clk测试模式走test_clk两个模式不会同时工作。CTS自动分组时工具把这两路时钟当成两个完全独立的group因为它们的clock root不同。这会导致MUX输出端的寄存器在launch clock是pll_div2_clk、capture clock也是pll_div2_clk的内部路径上没问题但在跨MUX的选择路径上——也就是一个寄存器在MUX前用pll_div2_clk、另一个寄存器在MUX后用输出时钟这种结构——两边的skew差距可能非常大。为什么因为自动分组没有把MUX前两个输入时钟的sink和MUX后的sink纳入同一个平衡范围。工具认为既然你是两个时钟那就各自平衡自己的树但真实电路里MUX输出后的那一组寄存器在模式切换瞬间要同时跟两路输入逻辑打交道它们的时钟到达时间必须由同一个参考点来决定。表现到timing上就是hold violation集中在MUX周围的路径上而且这类violation有一个特点如果你去查single-mode timing看不到任何问题非得在同时check两个mode的时候才会暴露。这也是为什么很多人被这个问题坑了很久都找不到原因。2.2 同PLL分频的多个generated clock被归并工具盲目插buffer另一个典型场景是一个PLL分出多个时钟比如clk_100m、clk_200m、clk_400m分别给CPU、DMA、外设总线。这些时钟源头相同但功能上完全没有对齐的必要它们的sink分布在不同模块负载差异很大。自动分组看到同源就把四个时钟的sink全部塞进同一个inter-clock group。CTS引擎为了让四个时钟在终点上的latency一致就得用buffer去填平路径延迟差。低频时钟的树很短高频时钟的树很长工具硬是在低频时钟树上插了一大串delay cell让四个树的到达时间强行对齐。结果就是本来只需要保证各自时钟域内部skew收敛最后变成跨时钟域的全局对齐。CLOCK latency整体变大OCV margin消耗严重面积和功耗多出不少更麻烦的是这些额外插入的delay cell在后续ECO中特别容易变成hold违例的雷。这类问题在report里的特征非常明显skew group里同时出现了cpu_clk、dma_clk、bus_clk这些名字完全不同的时钟而它们的sink分布在floorplan的四个角上。2.3 异步时钟域的端点被错误平衡问题却表现为hold变差还有一种情况最隐蔽两个时钟域在SDC里已经声明了set_clock_groups -asynchronous时序检查时也确实不会去check跨域路径。但CTS工具做自动分组的时候并不会严格遵守SDC的异步关系——至少在很多版本里不会。工具只看时钟拓扑发现两个时钟都从同一个divider逻辑下来就认为它们共享common path可以做inter-clock balancing。这一平衡不要紧它改变了两个时钟域内部的局部skew结构。原本不检查跨域路径所以两个域之间的clock arrival差多少都无所谓现在工具为了对齐把一个域的时钟树整体往后推了一段这个域内部原本hold裕量刚好够的路径因为树深了、uncertainty变了直接变成hold violation。这类问题的迷惑性在于你看到的violation全是同域路径的hold跟异步时钟关系八竿子打不着但追根溯源就是工具在异步域之间做了多余的平衡导致的。3. 定位问题的排查链路从大面积hold到坐实自动分组3.1 先翻CTS log和skew group report比对异常条目如果你也遇到full chip hold大面积崩但绕线和data path都查不出毛病第一步不是继续清violation而是回头去看CTS到底划了哪些group。在Innovus的ccopt流程里可以用这个命令拉出实际生成的groupreport_ccopt_skew_groups或者针对单个group看详细成员report_ccopt_skew_groups -group cts_skew_group_xxx重点看几个信息字段含义异常信号Clock(s)group里包含哪些时钟多个功能无关的时钟同时出现Num Sinksgroup包含的终点数量数量异常庞大或分布跨模块Target Skew目标skew值过小的target迫使工具过度插bufferSource Pin时钟源头位置多个source来自同一_clock_gen模块Associated Mode关联的工作模式不同mode的时钟被合并我那次定位时就是发现一个group的clock列表里同时躺着mac_tx_clk和mac_rx_clk而且source都是mac_clock_gen/divider。这两个时钟在功能上是完全独立的收发域SDC里还专门设过异步工具照样给它们做平衡。同时还要去CTS log里搜skew group、balance、inter-clock这几个关键字看工具在构建group时做了哪些合并决策。ccopt引擎通常会把自动合并的过程打印出来里面能看到add xxx to skew group yyy之类的信息这对定位哪一步开始错的很有帮助。3.2 回到GUI里核对sink分布和group成员仅看报告还不够因为报告里只是文字信息很难直观感受到这两个模块相距多远。我会在Innovus GUI里打开CTS相关面板把可疑group的sink在floorplan上高亮出来。操作很直接在界面左侧的CTS栏里找到Skew Group标签页选中有问题的group所有sink会高亮。如果两个模块一个在东边一个在西边中间隔着一条大的IP block它们却被放在同一个group里基本可以断定这个group的建立有问题。与此同时打开Timing Debugger看那些hold violations的launch/capture时钟对。如果violated path的launch clock是A域的capture clock也是A域的但路径整体穿过了一个小组的中间时钟域——这种绕远路的物理关系往往是因为跨模块的时钟树被强制平衡后局部data path的相对关系被破坏了。3.3 用一个小实验验证自动分组是不是元凶排查到最后我通常不会急着加约束而是先做一个快速的对照实验手动创建一个更合理的skew group把不应该平衡的时钟踢出去然后重新跑一遍ccopt_design。具体操作是先备份当前的结果然后手动建立groupcreate_ccopt_skew_group -name mac_tx_only -sinks [get_pins mac_tx_inst/reg*/ck]或者更简单粗暴一点直接关掉跨时钟平衡set_ccopt_property balance_inter_clock false重新跑CTS之后对比两件事一是hold violating endpoint数量有没有大幅下降二是时钟树总延迟和buffer数量变化大不大。如果violation数量明显下降同时时钟树延迟也降了那基本可以实锤——之前的时序问题确实来自自动分组过度平衡。这个实验成本很低跑一轮CTS可能也就几十分钟但能帮你省下后面几天的无用功。别一上来就盲调constraint先验证根因。4. 主动控制skew group的落地做法4.1 在SDC阶段就把时钟关系理清楚很多人以为skew group是CTS阶段的事跟SDC没关系这是最大的误区。自动分组在很大程度上是读SDC然后猜意图的结果SDC里写清楚工具就不需要猜。对于MUX选择的时钟一定要用set_clock_groups -logically_exclusive声明它们是逻辑互斥的关系。这样工具知道两个时钟不会同时活跃不会硬把它们和MUX后的sink做跨时钟平衡set_clock_groups -logically_exclusive \ -group {pll_div2_clk} \ -group {test_clk}对于同源但功能上异步的时钟用set_clock_groups -asynchronous声明set_clock_groups -asynchronous \ -group {mac_tx_clk} \ -group {mac_rx_clk}还有一个容易被忽略的点create_generated_clock的source pin要定义准确。如果你把_clock_gen模块内部的某个中间pin指定为generated clock的source工具会认为该模块是时钟源头进而增强对它做特殊分组的倾向。但如果实际源头应该是PLL的输出最好把source定义往上提让工具看到更完整的拓扑减少启发式误判。4.2 CTS阶段显式建group、踢人、调balance开关SDC约束写完后CTS阶段还需要做一些显式控制不能完全依赖自动分组。第一个动作是主动创建skew group把你认为必须对齐的sink明确指定进去create_ccopt_skew_group -name sdram_clk_group \ -sinks [get_pins sdram_ctrl_inst/reg*/ck]显式创建的group优先级高于自动生成的group工具会按照你给的sink集合去构建平衡策略。第二个动作是处理某些自动group里混入的无关人员。如果报告显示某个时钟不应该在group里可以使用update_ccopt_clock_tree_spec的排除选项或者在group创建之后用命令把特定sink移除。具体命令在不同版本里略有差异但思路一致把不该对齐的sink从group摘出来让它们自己单独平衡。第三个动作是设置跨时钟平衡开关。如果你确认某些时钟之间不需要对齐可以这样配置set_ccopt_property balance_inter_clock false如果只想针对个别时钟对关闭平衡用set_clock_group_options -no_balance \ -group [get_clocks clk_a] \ -group [get_clocks clk_b]反过来如果确实需要两个时钟对齐用-allow_balanceset_clock_group_options -allow_balance \ -group [get_clocks clk_a] \ -group [get_clocks clk_b]这些配置在Innovus里会写进CTS spec文件所以也建议生成spec之后审查一遍create_ccopt_clock_tree_spec -file ccopt_spec.tcl打开文件检查balance_inter_clock、skew_group相关的配置是否符合预期再把它作为最终CTS的输入。这一步很像代码review能挡住绝大多数自动分组埋下的雷。4.3 改完之后的回归验证别只看violation数量调整完skew group策略后验证工作不能只盯着hold violating endpoint数量这一个指标。我通常会同时比较三组数据指标改动前改动后关注点Hold Violations15012是否大幅收敛Worst Hold Slack-0.42ns0.01ns最差路径是否修复Total Inserted Delay Cells45k32k是否减少了无效插bufferClock Tree Latency1.8ns1.1ns树是否更短Total Power85mW79mW功耗是否下降如果violation少了但buffer数量暴增说明你可能从一个坑跳进了另一个坑——手动group没做好导致工具花了更大成本去平衡。所以一定要交叉看延迟和面积数据。另外如果项目是多mode多corner改动group后所有mode都要重新跑一遍CTS或至少做DMSA验证。因为一个group的调整会影响全局的时钟树结构某个mode修好了另一个mode可能恶化。我曾经在单mode下把hold全清完一跑DMSA又冒出来40条setup violation就是吃了没做多mode验证的亏。5. 这组坑的底层逻辑命名、SDC与CTS工具的三角关系5.1_clock_gen命名不是原罪隐式契约才是经历这次排坑之后我最大的感触是_clock_gen这个命名本身没有错但RTL设计者在起名时根本不会意识到后端工具会拿模块名做启发式推断。一个在RTL里看起来只是方便阅读的层次命名到了后端就成了CTS分组策略的建议标签。这就是一个隐式契约工具作者为了提升自动化程度把一些统计规律固化成启发式规则但规则碰上了routing、placement、SDC等各种变量结果就变得不可预测。你没法改变RTL设计者的习惯也没法要求工具不做启发式推断唯一的办法是在后端流程里显式地把意图说清楚。所以我现在的做法是收到一个新netlist后先扫一遍层次名把带clock_gen、clkgen、clk_gen这类关键字的模块全部找出来然后逐个检查它们的时钟输出有没有在SDC里被完整约束。如果哪个模块的时钟关系没理清我会直接发邮件给前端要求补充约束说明而不是等CTS跑完再被动救火。5.2 把skew group review变成流程里的一步很多团队跑CTS只看log有没有error只看时序收敛没有几乎没有人会把skew group report单独拿出来审查。但经过这次问题我认为report_ccopt_skew_groups应该被列为CTS之后、时序优化之前的必经检查项。具体来说我会在每次CTS跑完后固定执行以下动作导出全量skew group报告。用脚本筛选出group里包含多个时钟名称的条目。对比SDC中的set_clock_groups声明找出SDC说异步但group里却被合并的时钟对。在地图上高亮这些group的sink分布确认没有跨floorplan大区域的不合理平衡。把这几个检查结果写进项目checklist作为sign-off前的固定审查项。这套动作做下来后来几个项目再没被自动分组坑过。成本很低但收益非常高。5.3 最后分享一个排查小技巧如果你在CTS结果里看到大量hold violation又怀疑是自动分组的问题可以先别去碰skew group配置而是去检查这些violated paths的clock latency报告。如果同一group内不同域之间的clock arrival time差异很小但每个域内部的skew又比预期大不少这个组合拳基本就是工具在做跨域平衡时牺牲了域内平衡的铁证。这时候你去关掉或调整balance设置通常都能直接看到改善。另外DMSA跑完多mode之后如果每个mode单独看都是收敛的但带mode的full chip timing里有跨mode的路径出问题优先怀疑skew group而不是data path。这类问题有一个共同特征violated path在单mode下根本不存在只有在mode组合之后才出现。这些经验听起来不复杂但排查的时候非常容易被时序数据淹没。记住一条主线时钟树综合是全局行为任何局部优化都可能被另一个域的平衡拖下水。把group划分的逻辑彻底看清很多看似无解的时序问题其实只是分组边界划错了而已。
返回列表