做数字IC后端的朋友但凡接触过DFT可测试性设计大概率会对OCC电路不陌生。OCCOn-Chip Clock Controller片上时钟控制器是扫描测试里负责产生捕获时钟的关键模块尤其在后端做时钟树综合时它几乎是最容易出问题、也最考验经验的一类结构。一方面功能时钟要满足时序收敛另一方面测试时钟又必须保证shift和capture模式都能正常工作两边交叉、状态机切换、门控逻辑混杂在一起光是看CTS报告就能看出一堆不合理的树。很多刚转做后端的人拿到带OCC的网表后第一反应是“直接按功能时钟做不就行了吗”结果实际place、clock_opt下来要么捕获时钟沿不对要么shift和capture之间的skew超标最后回头查居然是约束和时钟树起点没设置好。这篇文章我就以Synopsys工具链为主把OCC电路时钟树综合里最值得注意的5个技巧完整拆开讲里面会附上具体的约束命令和ICC2/ICC实现阶段配置适合正在做或者准备做DFT相关后端项目的工程师参考。1. OCC时钟树为什么难做核心背景与设计思路1.1 OCC电路的基本结构与时钟域想把这个话题聊透得先回到电路本身。OCC控制器的典型结构里时钟输入一般有两路一路是从芯片引脚直接进来的测试时钟比如TCK另一路是来自PLL或其他数字模块的功能时钟。控制器内部用状态机常由TAP控制器驱动和一组组合逻辑、门控单元配合在capture模式产生一个或多个精确的捕获脉冲在shift模式则让扫描链直接拿到测试时钟。说白了OCC就是测试时钟和功能时钟的一个“交通枢纽”负责在正确的时间把正确的时钟沿送到扫描触发器。这个结构放在后端视角里问题一下就出现了两个时钟域本身是异步的但在OCC输出端汇合了。功能时钟从PLL到OCC要经过长距离的片上网络测试时钟从外部引脚进来路径短、延迟小。CTS要做的事不是简简单单平衡一棵树而是要把这两段“来源完全不同的时钟”合理建模让工具理解它们是互斥的、或者需要在某个节点上合并然后在时序引擎里老老实实算路径。否则工具要么以一种极其保守的方式处理导致大量路径违例要么干脆把两个时钟当成同一个建出的树根本没法用于流片。1.2 后端CTS的挑战点我在实际项目里体会到OCC相关CTS的难点主要集中在三块。第一块是时钟树起点clock source的定义。OCC内部的MUX或者门控单元的输出到底该认成哪个时钟的起点如果直接从原始端口create_clock工具自动传播到OCC输出时经常把门控逻辑当做树的一部分建出的树又大又慢。第二块是shift和capture两条工作模式下的时钟延迟不一样。功能时钟路径天然比TCK引脚路径长如果不在CTS阶段做延迟匹配捕获模式下的hold timing很难收。第三块是约束的“互相打架”。功能时序约束要求PLL时钟正常传播测试约束又要求TCK能穿过OCC两者如果不加明确的例外关系工具会陷入大量的时钟冲突报告。因此后端工程师做OCC设计时最重要的不是一上来就调工具而是先把“时钟模型”想清楚。我习惯在项目一开始就把OCC相关时钟分成功能时钟、shift时钟、capture时钟三种角色分别定义它们从哪个端口进入、在哪个节点汇合、哪些路径是false path。这个思路捋顺了后面所有步骤都会顺很多。2. 五个关键技巧逐个拆解2.1 技巧一先养成画“时钟拓扑图”的习惯让SDC建模有据可依很多后端工程师拿到网表和SDC就直接开工我建议先停下来把OCC模块内部的结构在纸上画一遍哪怕只是简单标注出mux引脚、门控单元输入输出和寄存器的时钟端。因为OCC这种混合逻辑结构工具默认行为未必符合你的预期只有你对“真实拓扑”有数才能知道工具报出来的每一段skew、每一条时钟路径到底对不不对劲。具体操作上我会先在逻辑综合阶段就要求前端/dft工程师把OCC例化层级和关键pin列清楚然后基于网表手动做一次时钟传播推演。举一个常见的例子假定OCC内部有一个两输入MUX选通端由scan_en控制输入端分别是TCK和功能时钟FCLK输出端OCC_CLK。那么在逻辑上OCC_CLK是TCK和FCLK两个时钟的“逻辑互斥时钟汇合点”。SDC建模时我要保证后端的时钟树综合工具把这段路径识别出来而不是把TCK直接贯穿到所有扫描链——因为扫描链在正常功能中是不工作的。实际项目中我喜欢把这类信息整理成一张简单的表格方便自己和前后端团队对齐节点/信号时钟来源工作模式后端处理策略TCK芯片引脚外部测试时钟shift/capture端口create_clock设置树起点FCLKPLL输出内部功能时钟functional从PLL输出create_clockOCC内部MUX输出TCK/FCLK互斥测试模式设为时钟门控/汇合节点添加逻辑互斥关系扫描链时钟端OCC输出或TCK直连测试模式真实时钟树终点这张表的价值在于它逼着你在开始做CTS之前把所有时钟来源、工作模式、处理策略对齐一遍很多前期遗漏的约束问题在纸上就已经暴露了。2.2 技巧二用set_clock_groups把互斥时钟“摘干净”别省这一步在做Synopsys流程时set_clock_groups是我处理OCC相关时钟的第一优先级命令。很多新人觉得“反正后面有set_false_path”但实际上set_clock_groups和set_false_path的作用范围不同尤其对于时钟树综合set_clock_groups可以让工具在CTS阶段直接理解哪些时钟不会同时生效从而在做树时更合理。以TCK和FCLK汇合到OCC_MUX的场景为例约束写法大致是这样# 功能模式 create_clock -name FCLK -period 10.0 [get_ports fclk] # 测试模式 create_clock -name TCK -period 100.0 [get_ports tck] # OCC内部mux会选择其中一个时钟作为输出二者逻辑互斥 set_clock_groups -logically_exclusive \ -group {FCLK} \ -group {TCK}这里有个容易踩坑的点如果OCC输出之后还连接了PLL旁路逻辑、门控单元、分频器等单纯在端口层面加set_clock_groups不一定够工具可能无法自动识别精确的汇合节点。我通常会在SDC里再补充set_clock_sense明确告诉工具OCC的某个输出引脚上感知到的是哪个时钟的脉冲沿这样工具在做CTS时就能更准确地把树建到OCC输出后的负载上。# 告诉工具OCC_MUX输出端在测试模式下是TCK的脉冲 set_clock_sense -pulse -clock TCK [get_pins occ_u/mux_out]这也呼应了前面说的“拓扑先行”——你得知道MUX的确切例化pin名命令才有地方落。实际芯片里OCC结构比这个复杂可能有多个MUX、多路门控但只要坚持“先识别汇合点再声明互斥再补时钟感知”这个顺序基本不会出大岔子。2.3 技巧三选对门控单元管好ICG的树入口OCC电路里经常出现时钟门控单元Synopsys实现阶段最推荐的还是集成时钟门控单元ICG而不是用离散的AND/OR门拼出门控逻辑。原因很直接ICG内部带锁存和使能同步逻辑对时钟毛刺和使能竞争天然免疫而离散门控单元在CTS阶段会产生大量亚稳态和hold问题且时序分析异常复杂。从CTS角度看ICG的CLK引脚通常被工具默认当作“树传播节点”也就是时钟会穿过ICG继续往下游长树。这在OCC场景里有好有坏。好的方面是工具可以把ICG之后的时钟树和ICG之前的时钟网络分开处理方便对准skew坏的方面是如果ICG所在的树分支和普通寄存器路径混在一起工具可能为了平衡某个无关叶子节点把OCC内部的时钟路径拉得又弯又长。我在项目里常用的做法是对OCC结构内的ICG时钟引脚显式设置CTS例外让工具明确哪些节点应该当作树终点哪些应该继续传播。比如# 如果OCC内部ICG的CLK引脚不需要继续长树可以设成non_stop set_clock_tree_exceptions -non_stop_pins [get_pins occ_u/icg_u/CLK] # 或者反过来指定某个ICG的CLK引脚当作树终点的stop pin set_clock_tree_exceptions -stop_pin [get_pins occ_u/icg_u/CLK]为什么要这么做因为OCC内部的时钟路径有一部分是“测试专用”的并不需要在功能模式下保持严格平衡。把这些点设成non_stop可以让工具尽量减少不必要的缓冲插入节省面积和功耗反过来对于必须精确对齐的捕获时钟脉冲路径设置好stop pin能防止工具乱插入延迟单元破坏时序。值得注意的是不同工艺库里的ICG单元命名、引脚属性和SDC识别方式略有差别建议在项目早期先拿一块小模块试跑一遍CTS确认工具对ICG的处理方式符合预期再全面铺开。2.4 技巧四主动匹配shift/capture路径延迟别把问题全丢给hold fixing时钟树综合里很多人的习惯是“先做树再看timing最后插buffer修hold”。但OCC电路如果也这么干后期很可能修出几百条hold违例修完还容易造成DRV问题。原因在于shift和capture模式的时钟路径天然不平衡。TCK从测试引脚进来板级路径短片上延迟小FCLK从PLL输出绕一大圈到达OCC延迟大。当OCC切换到capture模式一个由FCLK产生的捕获脉冲要去采扫描链上的数据而shift模式下扫描链数据是由TCK推入的两边时钟延迟差就变成了时序路径上的“系统性偏差”。你不主动处理这个偏差工具只能在每条路径上疯狂加延迟单元。所以我在做OCC相关CTS时会在CTS阶段提前用set_clock_tree_exceptions或工具自带的延迟匹配选项给TCK到OCC这段路径一个合理的delay目标让它尽量向FCLK路径看齐。Synopsys ICC2里可以用set_clock_delay或clock tree exception设置相对延迟# 给TCK到OCC_mux路径设置一个额外交付延迟用于匹配FCLK到达时间 set_clock_tree_exceptions -delay 0.8 \ -clock TCK \ -source [get_ports tck] \ -dest [get_pins occ_u/mux_out/CLK]这里0.8这个数值不是随便拍的我是参考同一项目中FCLK从PLL到OCC的预估延迟减去TCK原始路径延迟估算出来的。更讲究一点的做法是先做一个不含延迟匹配的初步CTS从report_clock_tree里读出两个时钟到达OCC的时间差再把这个差值填进延迟匹配约束重新做CTS。当然如果项目时间紧我一般会留一个保守的余量让修hold的阶段不会太吃紧。2.5 技巧五配合工具检视报告确认树结构“长对”了Synopsys工具提供了很丰富的时钟树报告但很多人只是看一眼total skew够不够小就关掉了。实际上对于OCC电路单纯看skew没有意义因为这条树里的节点分属不同工作模式有些节点甚至不在功能模式下翻转。我更关注的是树是否在正确的节点上停止、是否穿越了不该穿越的门控逻辑、两个互斥时钟是否各自形成了独立且完整的树分支。ICC2里有一个常用命令report_clock_tree -detail -skew_group {FCLK OCC_CLK}它会打印出时钟树的关键节点、每个节点的clock latency和插入的buffer链这些信息能帮你判断工具是否在OCC内部或外部多插了buffer。另外我会额外加一条报告report_clock_groups -verbose专门确认set_clock_groups的声明是否完整传播到了所有相关路径。很多时候做完了时钟树综合发现某些路径没有被当成异步路径检查就是因为这步没查清楚。还有一个技巧是看CTS log里的“clock tree reference”。如果工具报出很多warning比如某个单元同时被多个时钟驱动、某个ICG被综合工具等价交换了这些都要逐一确认。OCC电路里最怕的就是综合阶段工具“聪明”地把两个不同时钟域下的门控合并了导致后端CTS怎么建都不对。遇到这种情况我会第一时间回到netlist和SDC层面把逻辑等价交换logic optimization导致的时钟结构变化还原出来。3. Synopsys工具配置从DFT到CTS的完整流程3.1 综合阶段必须提前做的约束工作很多人以为OCC是后端的事等网表出来了再想约束就来得及。我吃过亏之后才明白OCC相关约束必须从综合阶段就开始铺垫。DFT插入完成后通常工具会生成一个带测试逻辑的网表但SDC里面关于OCC时钟的约束往往很简单甚至只有create_clock。后端接手后第一件事就是把与OCC相关的时钟例外补上。我在综合阶段一定会检查的点包括OCC模块内部是否有时钟和数据的组合环combinational loopICG的使能信号来自TAP控制器还是普通寄存器OCC输出时钟有没有在顶层被再次门控。这三项如果存在必须在综合阶段让前端DFT工程师处理掉不然后端CTS会非常痛苦。实际项目里我还会在综合阶段写一段专门的“DFT约束检查脚本”把OCC关键引脚打印出来确保SDC里的时钟定义和网表例化名称一致。因为OCC命名在不同版本Tessent/DFTMAX中可能会带前后缀工具默认匹配不到时后面CTS阶段根本不会按你的意图去建树。3.2 实现阶段CTS的典型命令序列到了后端实现阶段ICC2或ICC里做OCC相关的CTS核心流程和普通CTS类似但要注意几个开关和属性。下面我以ICC2为例列一个比较完整的命令序列参考# 步骤1读入网表和约束后先确认时钟树相关属性 set_ccopt_property -clock_nets [get_nets -hier fclk_net] route_type top set_ccopt_property -clock_nets [get_nets -hier tck_net] route_type top # 步骤2设置CTS例外OCC内部关键点 set_clock_tree_exceptions -non_stop_pins [get_pins occ_u/icg_u/CLK] set_clock_tree_exceptions -stop_pin [get_pins occ_u/mux_out/CLK] # 步骤3进行时钟树综合 clock_opt -only_cts # 步骤4查看时钟树报告重点关注OCC相关节点 report_clock_tree -detail report_clock_timing -type skew -group {FCLK TCK} # 步骤5时序修整 clock_opt -only_psyn -area_recover这里面最容易被忽视的是第一步的route_type设置。OCC相关时钟往往是跨越较大区域的全局时钟网络如果让工具默认当成普通信号网络处理做出来的树延迟会偏大后期修起来也麻烦。显式指定top层的route类型是很多老工程师的隐藏技巧。另外如果在CTS过程中发现某个OCC单元的时钟输入没有按预期长树可以检查一下综合阶段是否把它标记成了ideal_clock或者dont_touch。这类属性在ICC2中会影响时钟传播行为尤其是来自DFT工具的dont_touch属性有时会导致tree完全建不出来。3.3 不同工艺角下的OCC CTS差异做先进工艺项目时我还会额外关注不同工艺角对OCC CTS的影响。比如SS corner下hold变差、FF corner下setup变差这在OCC场景会更明显因为shift和capture两条路径的偏差在不同corner下变化趋势不一样。我的做法是针对OCC相关路径单独提取corners分析而不是只看单一corner的report。具体到工具上可以在ICC2里创建额外的delay corner把OCC相关路径的时序单独报告。例如create_delay_corner -name occ_check_ss \ -lib_corner {ss_0p72v_125c} set_timing_path_delay_corner -delay_corner occ_check_ss \ -path_type full_clock_expanded这样分析出来的时序结果更贴合测试模式下的真实情况。不过要注意这种方法会增加运行时间和内存开销一般只在OCC相关路径大量违例、需要专项优化时使用。4. 常见问题与排查技巧实录4.1 问题一捕获时钟沿数量不对仿真时少了一个脉冲这是我见过最多的一类问题。现象是后端做完CTS后DFT工程师跑测试仿真发现OCC输出的捕获脉冲少了一个或者第一个脉冲长度不对。最常见的原因是OCC内部用于产生捕获脉冲的触发器在CTS阶段被工具当成普通寄存器处理时钟到达时间和功能逻辑信号到达时间没对齐导致状态机输出被亚稳态吃掉。排查思路先用report_clock_tree看OCC内部这几个关键触发器的clock latency是不是明显大于相邻逻辑的data arrival time。如果确实存在明显差距大概率是时钟树终点设置不对。修法是在CTS例外里把OCC内部关键触发器的时钟引脚设为stop pin同时检查这些触发器的clock pin是否被工具错误地合并到别的树分支。还有一次我发现是综合阶段把OCC控制器里的latch优化掉了DFT仿真过不了。这类问题不是CTS的锅但也容易在CTS阶段暴露。经验是一旦发现OCC相关仿真失败先确认网表里OCC单元结构完整再回头查CTS。4.2 问题二shift和capture之间的时钟延迟差过大如果时序报告里TCK到达OCC输出与FCLK到达OCC输出的延迟差超过0.5ns以上而且你已经在CTS阶段做了延迟匹配但效果不佳可以考虑是不是树结构本身有问题。我最开始遇到过一种情况TCK端口到OCC之间的路径被工具当成普通信号网络绕了很远才到达导致延迟比预期大很多。查了半天发现是综合阶段给这棵网络打了ideal_network属性CTS完全没处理它。解决方法是删除这些ideal属性或者在CTS前强制指定该网络为时钟网络。另外也要检查TCK端口有没有被设置成set_driving_cell如果驱动强度太弱CTS插入的buffer会异常多延迟也跟着飙高。对于FCLK这边常见问题是PLL输出到OCC的路径上有多余的门控单元比如DFT插入的observability logic导致时钟树被分叉。这种结构我建议前端DFT工程师处理后从物理上避免“功能时钟经过OCC时又被不必要的门控”的拓扑。4.3 问题三OCC内部DRV违例扎堆CTS后修不干净DRVmax_capacitance/max_transition违例在OCC电路中很突出因为OCC输出往往要驱动大量扫描链时钟端扇出可能几百甚至上千。如果工具在CTS阶段没有合理插入buffer树单靠一个ICG驱动所有扫描链DRV怎么修都修不完。我的做法是在CTS前就给OCC输出设置max_fanout约束让工具自动分级缓冲。例如set_clock_tree_options -max_fanout 32这个数值不是固定的一般取决于标准单元库里的buffer驱动能力和时钟频率。我会先用一个较小值跑一遍观察面积和功耗变化再逐步放宽。注意太小的fanout会让buffer树层数剧增面积变大、功耗上升反而拖累时序收敛太大则DRV修不动。32到64这个区间是我常用的起点。如果工具已经完成了CTSDRV还是很多可以尝试命令repair_clock_nets -power这个命令会对时钟网络做DRV修复和功耗优化但要注意它可能改变已有树的延迟跑完后要重新做时序检查。4.4 问题四工具不认OCC内部时钟节点报告里全是clock not propagated有时ICC2会报很多“clock not propagated”或“clock sense unknown”的警告集中在OCC相关节点。这通常不是工具坏了而是SDC里没有正确描述OCC内部时钟的传播方式。以OCC中MUX为例如果MUX两个输入分别是TCK和FCLK但SDC只定义了TCK工具在MUX输出端会不知道该怎么传播。解决办法就是我前文提到的set_clock_sense显式指定MUX输出端在测试模式下感知到的时钟。如果用的是Tessent生成的OCC结构有些厂商库还自带专门的OCVMOn-Chip Clock Voltage Module模型此时要注意在综合阶段选对lib cell工具的识别度会高很多。排查这类问题时我习惯先把OCC模块所有input/output pin和内部信号的时钟域逐一打印出来all_connected [get_pins occ_u/mux_out]然后对照SDC找出哪些pin没有时钟定义。通常补完这些定义工具就能正常处理了。4.5 问题五CTS后功能模式时序收敛但测试模式hold大量违例这个问题在OCC项目中特别常见。功能模式没问题说明FCLK构建的树基本合理但测试模式hold大量违例多半是因为TCK和FCLK两条树在OCC汇合后的公共路径common path太短导致OCV片上偏差效应被放大。解决办法有两个方向。一是尽量加长两棵时钟的公共路径让它们在OCC之前就有一段“共用”的树减小clock reconvergence的悲观度。二是针对测试模式单独设置更紧的hold margin但这个方法治标不治本后期会花大量时间去修hold buffer。公共路径的加长需要OCC内部MUX结构允许“先长树再选通”的拓扑。有些OCC设计里MUX位置太靠前公共路径天然就短这时只能请DFT工程师帮忙微调OCC结构或者在后端把TCK和FCLK的源点位置在物理上拉近。总之后端能做的调整有限早期发现这类问题要尽早反馈。5. 一些个人经验总结做OCC相关的时钟树综合和做普通功能时钟CTS最大的区别就是你不能只盯着skew和latency数值而要把“模式”这个概念时刻放在脑子里。同一根树在功能模式下要求平衡在测试模式下却可能要求不穿过某些节点同一个MUX正常工作时不翻转测试时却决定了捕获时钟的沿。这种多模式叠加的复杂度正是OCC电路让人头疼的根源。我个人实际操作中比较受益的一个习惯是把OCC相关检查和DFT仿真验证绑定起来——每次做完CTS版本都同时跑一遍功能仿真的时钟沿检查和测试模式下的仿真冒烟测试哪怕只是粗略看看时钟脉冲数量和沿位置对不对也能提前拦住大量后期才会暴露的问题。毕竟时钟树综合做得好不好最终要看的是芯片能不能在测试机上正常工作而不仅仅是report里那几个数字是否漂亮。