1. 先搞清楚:PrimeTime到底在解决什么问题
1.1 从一条关键路径说起
在数字IC后端流程里,时序签核这件事,不管你做了多少年项目,最后跑PrimeTime的时候手心还是会微微冒汗——因为这是流片前最后一道闸门。PrimeTime是Synopsys家的静态时序分析工具,圈内习惯直接叫PT,几乎所有做数字芯片的公司,不论工艺节点是180nm还是3nm,signoff时都认它出的报告。
先说一个最简单的场景:两个寄存器之间串联了几级组合逻辑,数据在第一个时钟沿从reg1/Q出发,经过一堆与非门、反相器、或非门,最终要稳定落在第二个时钟沿到来之前的reg2/D输入端。这个过程中,组合逻辑延迟够不够短、时钟到达两个寄存器的时刻差了多少、寄存器本身要求数据提前多久建立,任何一个环节不满足,芯片跑到某个电压温度下就会出功能错误。
我把这个场景比喻成接力赛:数据是接力棒,第一个时钟沿是发令枪,第二个时钟沿是终点线。reg1把棒交出去以后,中间的每个逻辑门就是接力的运动员,任何一个运动员跑慢了、摔倒了,棒就不能在终点线前交到reg2手里。PrimeTime干的事情,就是把芯片上所有这样的接力赛全部检查一遍,告诉你哪一棒慢了、慢了多少、慢在哪段赛道上。
1.2 为什么动态仿真代替不了静态时序分析
很多刚入行的朋友会问:我都跑了那么多testbench仿真了,为什么还要单独做时序检查?这个问题背后的逻辑,恰恰是理解STA价值的关键。
动态仿真,也就是用VCS、Verilog-XL这类工具给设计灌激励,本质是“抽查”。你写了1000个用例,它就跑1000个用例覆盖到的情况。但芯片里寄存器有几十万个,组合逻辑路径有几百万条,不同输入组合、不同工艺角、不同电压温度下,每一条路径的延迟都在变。想靠testbench把所有情况都覆盖到,在数学上就不现实。
静态时序分析走的是另一条路:它不需要激励,而是把电路拆解成一条条时序路径,用工艺库里的延迟模型,把每条路径从起点到终点的延迟计算出来,再和约束要求逐一比对。听起来好像计算量很大,但因为是纯结构性分析,实际跑一轮也就几十分钟到几小时。
我用个更直白的例子对比:
| 对比项 | 动态仿真 | 静态时序分析 |
|---|---|---|
| 工作方式 | 灌激励、看波形 | 穷举所有时序路径、计算延迟 |
| 覆盖范围 | 取决于激励覆盖率 | 结构性全覆盖,无死角 |
| 是否依赖输入向量 | 依赖 | 完全不依赖 |
| 检查对象 | 功能逻辑正确性 | 时序是否满足约束 |
| 速度 | 慢,门级仿真尤其慢 | 快,纯计算不需要仿真时间 |
所以业界标准做法是:动态仿真管功能,静态时序分析管时序,两者谁也替代不了谁。PrimeTime在其中的角色,就是那个把全芯片几百万条路径全部算一遍、最后给出“能不能signoff”结论的工具。
1.3 在整个芯片设计流程中的位置
PrimeTime在流程里不是孤立存在的,它前面连着综合和布局布线,后面连着物理验证和流片。一般项目的时序签核会跑两轮:
第一轮在逻辑综合之后。这时候还没有版图寄生参数,用的是wire load model估算互连延迟,精度粗一些,主要目的是尽早发现明显的时序问题,给后端布局布线一个合理的起点。综合工具DC自己也会做时序优化,但很多公司还是会用PrimeTime再核一遍,因为PT的延迟计算引擎和综合工具不同,签核标准和实现标准分开,这是行业惯例。
第二轮在布局布线完成之后。有版图了,有真实的RC寄生了,把SPEF文件提出来,灌进PrimeTime做带寄生参数的时序分析。这一轮的分析结果最接近芯片实际工作状态,也是signoff最权威的依据。
这里要注意一个词:signoff。PrimeTime的报告不仅仅是“参考”,它是流片前必须满足的硬性条件。Foundry和EDA厂商之间有一整套关于PrimeTime如何配置、如何设置derate、如何报时序的默契,你用别家工具跑出来的结果,代工厂不一定认。这也是为什么哪怕TCL脚本写得再难用,大家还是老老实实在PT里做最终签核。
2. 跑通第一次STA:输入准备与工具启动
2.1 五类输入文件,缺一个都不行
我第一次用PrimeTime的时候,最大的困惑就是“我该给它喂什么东西”。后来被师父带着跑了几个项目,才把整个输入体系理清楚。跑一次完整的PT分析,至少需要以下五类输入:
第一类:门级网表。就是综合或布局布线之后输出的网表文件,格式一般是Verilog。注意必须是门级网表,不是RTL。PT分析的是真实的逻辑门和触发器,RTL里的always块、assign语句它不认。还有个小细节,网表里如果还有未映射的工艺库单元,比如还留着GTECH或者LIBM开头的名字,说明综合没跑干净,PT读进去会报unresolved reference。
第二类:标准单元库。这包括link_library和target_library。时序库里定义了每个单元在不同输入转换时间、输出负载下的延迟查表,还有setup time、hold time、时序弧、功耗信息。Synopsys环境里通常是.db格式,文本格式的.lib也可以用,但PT读.db更快。关键是link_library要把所有用到的库都列上,我见过有人漏了某个IO库,结果link完一堆模块解析不了。
第三类:约束文件SDC。这是整个STA里最“人味儿”的部分。时钟定义、输入输出延迟、false path、multicycle path全在这份文件里。SDC既是约束,也是你和工具沟通的语言。约束写得不合理,后面所有报告都是错的,这个我等下专门讲。
第四类:寄生参数文件SPEF(可选但强烈建议)。布局布线完成以后,工具会从版图提取出每根互连线的RC寄生,存成SPEF文件。PT读入SPEF以后,互连延迟的计算就不再靠估算,而是基于实际版图。做signoff分析时这文件必须有,否则只能算pre-layout估算。
第五类:UPF电源意图文件(低功耗设计时用)。如果芯片有多个电压域、有电平转换单元、有隔离单元,就需要UPF告诉PT哪些cell在哪个电压下工作、什么时候断电。没有UPF,PT默认整颗芯片一个电压,低功耗设计根本没法分析。
2.2 从目录规划到工具启动
跑PT的项目目录,我建议按这个结构来建:
case/ ├── rtl/ # 综合后的网表或布局布线后网表 ├── cons/ # SDC约束文件 ├── lib/ # 工艺库 .db / .lib ├── spef/ # 寄生参数文件 ├── script/ # PT脚本 ├── out/ # 报告输出 └── work/ # PT运行时产生的临时文件这个习惯帮我省了太多事。尤其是work目录,PT运行的时候会在当前目录生成pt_session、pt_log之类的中间文件,如果不隔离出来,跑几个case就会把项目根目录搞得一团乱。报告归档也方便,每次跑完按日期命名,后面查问题直接翻目录就行。
启动PT有两种方式。交互式调试用pt_shell,直接进TCL环境,一行行敲命令;批量跑case用脚本模式:
pt_shell -f script/run_sta.tcl | tee out/run_pt.log注意tee这里会同时往终端和日志文件里输出,方便后面排查。脚本内容最简版长这样:
set search_path [list ./lib ./netlist] set link_library [list slow.db fast.db standard_cell.db] set target_library [list slow.db] read_verilog ./netlist/TOP.v link_design read_sdc ./cons/top.sdc set_operating_conditions -analysis_type on_chip_variation \ -library slow read_parasitics -format spef ./spef/top_max.spef update_timing report_timing -nworst 100 -delay_type max > ./out/timing_max.rpt report_constraint -all_violators > ./out/violators.rpt report_analysis_coverage > ./out/coverage.rpt我给新人的建议是:第一次跑通不要贪多,就把上面这段脚本原样跑一遍,能出报告就算环境OK了。至于里面每条命令在干什么、有什么坑,我们下面一节慢慢拆。
2.3 我踩过的库文件版本坑
在输入准备这个环节,我要专门用一个小节讲库文件,因为我在这里栽过好几次跟头。
第一次是在一个28nm项目上,PT跑完报告全是花屏级别的乱码——不是真的花屏,是约束路径全乱套。查了半天,发现是link_library里同时放进了两个不同版本的std cell库,一个带金属填充、一个不带,两个库里同名cell的延迟差了一倍。PT默认会解析到第一个,结果整个分析基于了错误库的延迟。从那以后我养成了习惯:每次跑PT之前,先敲一句report_library看看当前环境下到底link了哪些库、版本是多少。
第二个坑是hold分析用了max库。很多人setup分析用慢库(slow corner),hold分析用快库(fast corner),这个没错。但有个细节:link_library和set_operating_conditions要同步切换。如果link_library里只放了slow库,你后面再怎么设置-library fast,PT也找不到fast库的模型,会直接报错或者用了错误的默认值。
第三个坑相关性更强。不同PVT条件下的库,内部的查找表边界不一样。有的库在低电压下过渡时间索引范围很窄,你读进去的net transition稍微大一点,PT就报“out of table range”的warning。这种warning不能忽略,因为它意味着延迟计算是外推的,精度没有保障。我在一个0.8V的低功耗项目上遇到过transition超过库最大索引的情况,后来是回后端把驱动cell换大了一档,transition降下来才算干净。
库这块你只要记住一个原则:库里所有的cell、所有的corner、所有的查找表,必须和当前分析场景严格匹配。跑之前花两分钟用report_library确认一遍,能省掉后面一整天的排查时间。
3. 核心概念拆解:从时序弧到slack
3.1 时序弧:路径延迟的最小单元
静态时序分析的最小计算单元,叫时序弧(timing arc)。这个概念很基础,但很多人一开始没当回事,结果读报告的时候对不上号。
标准单元库里,每个cell都定义了一组时序弧。组合逻辑门,比如NAND2,从输入A到输出Y有一条时序弧,从输入B到输出Y也有一条。每条弧不仅分上升沿和下降沿,还分了rise transition和rise delay、fall transition和fall delay。为什么分这么细?因为CMOS电路里PMOS和NMOS的驱动能力天然不对称,一个信号从0变到1和从1变到0,经过同一个门的延迟差异可能达到20%甚至更多。
时序单元更复杂一点。D触发器的CK到Q之间有clock-to-Q弧,D到Q之间有约束关系弧,也就是建立时间和保持时间。建立时间约束是数据必须在时钟沿之前多长时间稳定,保持时间约束是数据必须在时钟沿之后保持多长时间不变。这两条约束不是物理延迟,而是单元内部锁存结构的工作条件,不满足的话触发器采到的值可能是亚稳态,逻辑直接乱掉。
除了单元内部的时序弧,还有互连延迟。信号从A门的输出脚到B门的输入脚,中间要穿过一段金属线,线上有电阻和电容。这段路径的延迟由RC决定,和线长、线宽、层数、相邻线的耦合都有关系。PT把单元延迟和互连延迟加在一起,就得到了从一个端口到另一个端口的path delay。
我习惯把所有延迟来源画成一句话:路径延迟 = 单元内部延迟 + 互连RC延迟。前者从库的查找表查表得到,后者从SPEF或wire load model算出。理解了这句话,后面看报告里的Incr列就能对号入座。
3.2 路径类型:合法路径到底有哪几类
STA不是把电路里所有节点间都算一遍,它只关心合法路径。所谓合法路径,就是从起点到终点、物理上存在并且有明确时序要求的路径。PrimeTime默认把路径分成四类:
| 路径类型 | 起点 | 终点 | 典型场景 |
|---|---|---|---|
| input-to-register | 输入端口 | 触发器D端 | 片外信号到内部寄存器的setup/hold |
| register-to-register | 触发器CK端 | 触发器D端 | 核心时序路径 |
| register-to-output | 触发器CK端 | 输出端口 | 内部信号到片外的时序 |
| input-to-output | 输入端口 | 输出端口 | 组合直通路径 |
为什么一定要区分路径类型?因为它们的约束来源完全不同。register-to-register的约束由时钟周期决定;input-to-register的约束由set_input_delay描述,这个值反映了片外器件多久能给出数据;register-to-output的约束由set_output_delay描述,反映了片外器件要求数据多早到位。input-to-output的约束则是两头的IO delay叠加。
我见过不少新人查时序,看到一个violation就慌,尤其看到input-to-output路径违例,以为自己的组合逻辑写崩了。实际上先看清楚路径类型,如果是input-to-output,大概率是IO约束没写对,可能是set_input_delay和set_output_delay重复算了同一个interface的延迟,也可能是时钟不同步导致约束边界对不上。路径类型先分清楚,排查方向就对了。
3.3 深入理解setup和hold:一个时钟周期的博弈
这是STA的核心中的核心,也是面试官最爱问的两个字:setup,hold。
建立时间(setup time)的定义是:在时钟有效沿到来之前,数据输入必须保持稳定的最短时间。为什么要有这个要求?因为触发器的内部结构——主锁存器和从锁存器——需要一小段时间来采样和锁存输入。如果你在时钟沿快到的时候才把数据变化,主锁存器还没来得及稳定,从锁存器就会采到一个不确定的值,也就是亚稳态。
保持时间(hold time)的定义正好反过来:时钟沿到来之后,数据输入必须保持稳定的最短时间。这是为了防止数据变化得太快,在触发器内部还没来得及完全锁存之前就消失。
在单周期路径的默认情况下,setup检查的逻辑是:数据在第0个时钟沿发出,必须在第1个时钟沿之前准备好。中间可用的时间就是一个时钟周期T。数据路径上消耗的时间越少,setup余量越大。而hold检查的逻辑是:数据在第0个时钟沿发出后,不能在第0个时钟沿附近就变掉,它必须保持足够长的时间,让捕获触发器完成锁存。
我解释到这里,聪明的读者应该已经看出来了:setup关心的是“数据要足够早到”,hold关心的是“数据不能太早变”。这两个要求是互相矛盾的——想让setup好,路径延迟要短;想让hold好,路径延迟又不能太短。所以后端工程师在修时序的时候,经常是按下葫芦浮起瓢:setup违例了,你插入buffer增大驱动,结果hold又违例了,因为路径变短了。
这里把公式写清楚。setup检查的slack按下式计算:
data arrival time = launch_edge + clock_path_to_launch + data_path_delay data required time = capture_edge + clock_path_to_capture - clock_uncertainty - setup_time setup slack = data required time - data arrival timehold检查的公式类似:
data arrival time = launch_edge + clock_path_to_launch + data_path_delay data required time = capture_edge + clock_path_to_capture + clock_uncertainty + hold_time hold slack = data arrival time - data required time注意看,公式里的clock_path_to_launch和clock_path_to_capture是不同的。在理想情况下,时钟应该同时到达所有触发器,但实际时钟树有skew,有的触发器早到、有的晚到。在setup分析中,如果捕获时钟比发射时钟晚到,那等于变相多给了数据时间,这是有利的;反过来在hold分析中,晚到的捕获时钟就是不利的。这也是为什么时钟树综合要控制skew,skew太大会直接恶化setup或hold。
3.4 slack到底是什么,为负了会怎样
slack的翻译很妙,叫“余量”。它衡量的是数据到达时刻和约束要求时刻之间的差值。slack为正,说明有余量,时序满足;slack为负,说明欠了时间债,时序违例。
很多初学者以为slack只要大于0就万事大吉。做过实际项目的人都知道,slack的大小本身就是一种优化空间。一个路径的setup slack有1ns,意味着你有1ns的余量可以牺牲掉——可以把某些单元换成低功耗的HVT单元,可以让布局布线工具更激进一点,甚至可以把电压调低一点来省功耗。反过来,如果一条关键路径的slack只有10ps,即便它是正的,也是一个随时可能爆炸的风险点——因为OCV的影响、电压波动、工艺偏差稍微一动,10ps的余量就没了。
所以看报告的时候,重点不是看哪条路径slack为负,而是看“最差的slack是多少”。APR工具在布局布线的时候,目标之一就是让所有路径的slack都高于一个阈值,这个阈值内部叫slack margin,通常设50ps到100ps。
这里要顺带讲一下OCV和它的修正。片上工艺偏差(On-Chip Variation)意味着同一颗芯片上,不同位置的晶体管延迟特性不完全一样。为了安全,PrimeTime在做时序分析的时候会对路径加一个derate因子,悲观地认为发射路径偏慢、捕获路径偏快,这样算出来的setup会偏保守,更容易暴露潜在问题。但这样做的代价是过度悲观——发射和捕获两个时钟路径其实共享了一段公共的时钟树,那段公共路径不可能同时既快又慢。
所以PT提供了CRPR(Clock Reconvergence Pessimism Removal),也叫CPPR,原理是在公共路径上取消重复的悲观derate。CRPR在深亚微米工艺下尤其重要,因为时钟树很长,公共路径占的比例很大。如果不开CRPR,3nm工艺下有些路径的setup会虚报几千个violation,实际上真正有问题的没几个。
4. 常见问题与排查技巧实录
4.1 一路红灯:unconstrained怎么办
新手跑PT最常见的现象,不是满屏violation,而是满屏的“unconstrained”。这个词比违例还可怕,因为它意味着路径根本没被约束,工具压根没把它当成一条需要检查的路径。
我一次带新人跑项目,他跑完PT说“怎么报告里全是MET(满足)”,我过去一看,report_analysis_coverage只有40%——超过一半的路径都没有被约束到。原因很简单,SDC里create_clock的端口名字写错了,PT没找到那个时钟端口,于是所有和它相关的路径全部变成了unconstrained。
排查思路其实很固定。第一步,用report_clock确认所有时钟都创建成功,名字、周期、波形、端口对应关系都对不对。第二步,用report_analysis_coverage看整体路径覆盖率,如果低于95%,说明有大量路径没有约束,要么是约束缺失,要么是约束写错。第三步,用report_constraint -all_violators看违例汇总,确认所有violation都是真实存在的,而不是因为约束不对导致的假违例。
还有一个隐蔽的场景,是时钟groups的问题。SDC里set_clock_groups -asynchronous用在两个没有相位关系的时钟之间,是合理的。但如果你对两个其实同源的时钟也这么设了,PT就会把它们当成完全不相干,两组时钟之间跨时钟域的路径全部被忽略。这种问题在报告里不直接报错,但覆盖率会明显下降。
4.2 时序违例分级处理经验
等到你终于拿到了第一份真正有violation的报告,别急着修。我总结了一套分级处理流程,按这个来能省不少时间。
第一级:路径分类。先看违例路径的类型是setup还是hold。如果是setup,通常是数据路径太慢;如果是hold,通常是数据路径太快。两种修法完全不同。setup的修法是优化逻辑级数、换大驱动、插buffer加速过渡时间;hold的修法是插delay buffer增加延迟。
第二级:路径真实性判断。拿到一条violation路径,别急着改电路,先回答三个问题:这条路径是真路径还是假路径?约束是不是合理的?路径上的单元是不是真的存在于网表里?我见过最离谱的一次,是约束文件里用set_false_path把一个原本必须检查的路径设成了false,但那个路径在实际电路里真实存在,只是SDC写错了端口名。这种虚假的false path让一条真正的违例路径溜走了,直到流片前才在report_analysis_coverage里发现覆盖率只有80%。
第三级:物理问题还是逻辑问题。如果路径是真实存在的、约束是合理的、那就要深入分析慢在哪。是时钟树delay太大?是组合逻辑级数太多?是transition太差?是线太长导致RC太大?这些问题的修法完全不同,后面一节我详细讲。
4.3 那些年我们误用的命令和配置
PT的命令很多,但真正把人带进沟里的就那么几个。
第一个是set_false_path。这个命令的本意是把不需要检查的路径抹掉,比如复位信号、测试逻辑。但很多人用它来“消掉”那些难以满足的约束。我见过有项目为了跑完PT,把一整个模块的时序都设成了false path——因为模块还没做后端,路径延迟估不准,就干脆不检查了。结果到了signoff阶段,整个模块的时序完全没评估过,流片风险直接拉满。我的原则是:false path可以设,但必须在脚本里注释清楚为什么、对应的是哪条路径、最后谁review过。
第二个是set_multicycle_path。这是用来声明数据不需要在一个时钟周期内到达、可以跨多个周期的命令。但参数容易搞混——-setup对应的值表示需要几个周期,-hold对应的值表示相对setup提前多少个周期释放检查。如果hold的路径设得不对,会直接把hold检查放宽到完全失效。
第三个是set_clock_groups。这个命令比false path更隐蔽,因为它的覆盖范围大。一条set_clock_groups -asynchronous可能会把两个时钟域之间的所有路径全部忽略,即使这些路径其实是有功能需求、需要被检查的。同类问题在跨时钟域的异步FIFO设计中尤其危险。所以用这个命令之前,一定要确认两个时钟域之间没有需要同步的路径,或者已经用同步器处理过了。
还有一个更细的坑:read_sdc和source约束文件。PT是TCL环境,SDC文件本质上也是TCL脚本。read_sdc会对SDC做语法解析和检查,而source则完全当作TCL脚本执行,不会检查SDC语义。有的人喜欢用source,因为可以在SDC里写条件判断、循环、变量计算,但代价是约束文件里的语法错误很难被早期发现。我推荐的做法是:纯约束内容用read_sdc,需要复杂逻辑处理的脚本用source,但两者别混用在一份文件里。
5. 从能跑到会看:报告解读进阶
5.1 report_timing报告真的看懂了吗
我一直认为,跑PT不难,读懂报告才算入门。PrimeTime最常用的报告命令是report_timing,但很多人拿到报告只关心最后一行slack是正还是负,中间的细节从来没仔细看过。
一份典型的max delay路径报告结构如下:
Startpoint: reg1/CK Endpoint: reg2/D Path Group: clk Path Type: max Point Incr Path -------------------------------------------------------- clock clk (rise edge) 0.00 0.00 clock network delay 0.50 0.50 r reg1/CK (DFF) 0.00 0.50 r reg1/Q (DFF) 0.30 0.80 r buf1/A (BUF) 0.10 0.90 r buf1/Y (BUF) 0.25 1.15 r nand2_1/A (NAND2) 0.15 1.30 r nand2_1/Y (NAND2) 0.40 1.70 f ... reg2/D (DFF) 0.12 5.30 r data arrival time 5.30 clock clk (rise edge) 10.00 10.00 clock network delay 0.60 10.60 r clock uncertainty -0.05 10.55 reg2/CK (DFF) library setup time -0.08 10.47 data required time 10.47 -------------------------------------------------------- data required time 10.47 data arrival time - 5.30 -------------------------------------------------------- slack (MET) 5.17读这份报告,我建议按四个层次来看。
第一层看Startpoint和Endpoint。搞清楚这条路径从哪个触发器出发、到哪个触发器结束。如果路径的起点或终点是一个之前没预料到的寄存器,那可能是一条跨越了你认知边界的路径,需要警惕。
第二层看Path Type。max对应setup,min对应hold。看到max不能忽略min,看到min也不能忽略max。签核时必须两个都查。
第三层看Incr列里的关键项。时钟树延迟、单元延迟、线延迟,每一项都是可优化空间。clock uncertainty反映了你给时钟预留的抖动余量,library setup time是库定义的约束。
第四层看slack的正负和大小。只有这一层直接影响你能不能signoff。
report_timing的选项也要掌握。实际项目里我常用的组合是:
report_timing -nworst 100 -path_type full_clock_expanded -delay_type max -max_paths 100 report_timing -nworst 100 -path_type full_clock_expanded -delay_type min -max_paths 100-path_type full_clock_expanded会把时钟路径完全展开,方便看时钟树内部的结构;-delay_type max看setup、min看hold;-nworst 100和-max_paths 100配合,能一次看足够多的路径,不至于漏掉关键违例。
5.2 学会把timing慢的原因拷问到底
报告读懂了还不够,关键是从报告里找到“为什么慢”的根因,然后才能对症下药。
我把路径慢的原因归纳成五类,且看报告时从后往前倒推:
第一类是clock tree delay过大。如果报告中clock network delay特别大,比如超过了周期的一半,那说明时钟树综合有问题。时钟树太长、buffer级数太多,都会恶化setup。这时候优化的方向在CTS(时钟树综合)环节,而不是数据路径。
第二类是组合逻辑级数太多。数据路径里如果一串串联了十几个逻辑门,每一级就算只有20ps,加起来也有200多ps。级数太多说明逻辑结构可能需要优化,比如把串行逻辑改成并行结构、把某些计算提前到前一级流水线。
第三类是transition太差。如果报告中某个节点记录了较长的transition time,比如大于500ps,说明这个点的驱动能力不足,信号上升下降特别慢。问题出在驱动cell太小、线上负载太大。解决方法是换更大尺寸的驱动cell,或者在这段路径上插入中继buffer。
第四类是线延迟过大。如果SPEF读进来以后,某段互连的延迟明显高于同类路径,可能是线绕了远路、走线换到了高阻层、或者存在明显的耦合电容。这个问题需要到布局布线工具里看版图才能定位。
第五类是负载不平衡。一个cell驱动了很多个扇出,总负载超过它的驱动能力,延迟自然增大。这种情况可以在工具里查到扇出列表,看看是不是需要对扇出做复制或者插入buffer来分担驱动压力。
我每次拿到violation报告,都会先花十分钟做这个分类,而不是盲目地往后端工具里灌derive命令。分类清楚以后,每类问题都有自己对应的优化手段和工具,效率天差地别。
另一个实用小技巧是,用report_clock -skew看时钟偏斜。时钟树虽然做了平衡,但不同路径的skew还是会在几十ps量级波动。在修复hold的时候,时钟skew往往是最大的变量。有的路径hold的violation只有20ps,查了半天数据路径没问题,最后发现是clock skew超了预期。把时钟树的skew压下来,violation立刻消失。
写在最后的一点个人经验
说了这么多,最后分享几个我这些年积累的实操习惯。
第一,跑PT一定要多换corner看。signoff不是单看一个corner,而是要在最差setup的慢corner、最差hold的快corner分开跑。有的项目还有低电压low voltage corner,功耗分析和时序分析用的数据不一样,别指望一份报告走天下。
第二,报告文件名要规范。我吃过名字乱七八糟的亏,一个月前跑的报告,回头想查当时的slack,翻半天不知道哪份对应哪个版本、哪个corner。后来强制规定了命名格式:模块名_时钟名_路径类型_corner.rpt,比如core_clk_setup_ss_0p72v_125c.rpt,一目了然。
第三,也是最重要的,永远不要为了报告好看而妥协安全性。无论是误设false path掩盖问题,还是把clock uncertainty调低到不真实的程度,最后买单的都是芯片。PT是最后一道防线,防线上的每一个配置,都要经得起团队review。
如果你准备接触PrimeTime,别怕那些复杂的命令和满屏的英文报告。从最简单的脚本开始,跑通一个case,读懂一份报告,处理完一条真实的违例路径,你就已经入门了。剩下的,都是在一次次流片教训里积累出来的经验。