早上刚到工位,隔壁同事就火急火燎地喊:Innovus里的PR跑了一整夜,到现在还没跑完,日志停在placeDesign就不动了,CPU也不高了,这算不算卡死?这问题我在数字后端项目里遇到过太多次了。今天就把"innovus跑pr跑不下去"这类情况的排查思路整理出来,从现象判断、根因分析到应急恢复,整理成一份能直接抄作业的排查手册,给刚上手Innovus做物理实现的工程师做参考。不管你是刚把Innovus装好准备跑第一个案例,还是已经在一颗复杂SoC上挣扎了很久,这篇文章里的很多排查方法都能直接用上。
1. "跑不下去"到底长什么样:先判断是真死还是假死
很多人一看到工具卡住就慌了,随手就kill -9,结果丢了几个小时甚至一整天的结果。我在实际排障中,有接近一半的情况其实是"假死"——工具没死,只是干了一个需要很久的步骤还没有反馈。所以拿到"跑不下去"这个问题,第一件事不是查代码、不是改配置,而是先搞清楚:它到底是活着但没动静,还是已经彻底没救了。
1.1 真死、假死的区分方法
给你几个我一直在用的实操判断方法,每一步都不需要额外装工具:
- 看CPU。在终端执行
top -p <pid>,如果Innovus进程的CPU稳定在比较高的水平(比如100%以上),说明它在计算,大概率是假死;如果CPU几乎为0但进程没退,多半是锁死了,或者正在等待外界资源。 - 看日志增长。
ls -l看innovus.log最后修改时间,或者tail -5 innovus.log看内容是否在持续更新。日志还在涨,说明工具还在干活。 - 在交互shell里敲命令测试。如果你启动时带了交互界面,敲一个
help或者set a 1看有没有响应。有响应说明shell是活的,只是当前stage没有输出。 - 看系统级状态。
vmstat 5连续看几轮,重点看swpd列和si/so列。如果swap的换入换出持续很高,说明内存和换页正在打架,系统已经濒临瘫痪,工具可能只是跑得极慢,不是死掉。
这里有一个我自己的血泪教训:有一次placeDesign跑了三个多小时,日志最后停在"Starting global placement phase 2",我等了两个小时实在没耐心,直接kill -9。后来恢复snapshot重跑才发现,其实再等40分钟就能过。从那以后,我再也不急着kill,而是先用上面的方法判断完再说。
1.2 挂死、报错终止、死循环三种形态
"跑不下去"至少可以分成三种形态,处理方式完全不同:
- 挂死:进程还在,但不做任何事,不写log。常见原因是等待license、等待集群上其它进程释放文件、线程死锁。这种需要用系统层面的手段排查,比如看进程状态是不是D状态(不可中断睡眠)、有没有僵尸进程占着锁文件。
- 报错终止:日志里有明确的
Error、Fatal、CRITICAL WARNING,随后出现*EXIT*。这种其实最友好,因为信息是明确给出的,直接搜日志里的关键字就行。很多新手怕报错,其实报错是工具在告诉你它发现了问题,远比闷头卡死更值得庆幸。 - 死循环/不收敛:流程一直在跑,CPU高,log也在写,但迭代指标不下降,甚至上升。最常见的就是congestion overflow一直不降、optDesign的violation数量反复横跳。这种最麻烦,需要结合具体阶段去判断。
这三种形态放在一起看,你会发现"跑不下去"其实不是一个单一问题,而是一类问题。后面每一个根因,我都尽量把对应的现象和处置方法放到一起说。
2. 硬资源问题:内存爆了、磁盘满了、线程打架
这一层是我排查"跑不下去"时最先检查的,不是因为它的技术含量高,而是因为它的定位成本最低、出现频率极高。而且这一层的坑,很多时候用系统命令一分钟就能确认。
2.1 内存不足的典型征兆与判断方法
Innovus是内存大户,尤其在大规模的CTS和routeDesign阶段,内存占用轻松超过几十GB。内存不足时,轻则工具慢到像死机,重则直接被系统OOM killer杀掉,或者因为swap反复交换导致几乎停摆。
判断内存问题的几个方法:
- 用
free -g看总内存、已用内存和swap占用。如果swap used持续在涨,说明物理内存已经顶不住了。 - 用
dmesg -T | grep -i oom看有没有Out of Memory的记录。如果有,说明系统已经在杀进程了,你的Innovus可能侥幸活着,但已经被挤兑到极限。 - 观察日志:如果log精确地停在某个大数据量操作(比如写def、构建routing database、CTS balance阶段)之后长时间不动,优先怀疑内存。
解决内存问题,加机器是最直接的办法,但工程上常常没那么好运气。几个实测有效的降内存手段:
- 减少同时跑的Innovus实例。我见过有人一台机器同时挂三个大设计,结果三个全卡死。错峰启动、一次只跑一个大型设计,是最简单有效的办法。
- 降低线程数。
setMultiCpuUsage -numThreads 4,把线程从8降到4,内存峰值可能下降20%到40%。代价是速度慢一些,但总比跑不下去强。 - 清理中间文件。
*.bak、*.gz、前一个run留下的database目录,看着不起眼,加起来可能占掉几十GB缓存空间。 - 换用层次化流程。对超大规模设计,平铺式PR的内存消耗非常高,分层做可以显著压低峰值内存。这是进阶操作,但值得提前了解。
2.2 临时目录与工作目录写满的问题
跑着跑着没反应,很多新手根本不会想到磁盘满了。Innovus会在工作目录下产生大量中间文件,addStripe、routeDesign、optDesign都会生成很多.dat、.txt、.enc、.db文件。工作目录所在分区一旦写满,工具的表现常常是直接挂起,日志有时连明显的"Disk full"都打不出来。
排查方法:
df -h看当前目录所在文件系统的使用率。du -sh *看一下是哪个子目录或文件占了大头。- 特别留意
/tmp。有些环境中TMPDIR指向/tmp,而Innovus会把临时文件写到那里。/tmp被占满时,Innovus的表现就是卡在某个phase不动,日志上毫无线索。
处理方案也很直接:定期清理历史run目录里的旧文件,只保留最终输出和数据库;给/tmp或者工作目录所在分区预留充足空间;更稳妥的做法是在启动脚本里先做一次磁盘余量检查,低于设定阈值就拒绝启动,而不是等跑了一半再挂。
2.3 线程、多开与license等待
Innovus的每一步都有多线程配置。线程数开得太多,会成倍增加内存占用;在共享机器上,多核抢内存甚至会导致性能倒退。我的经验值是:在16核机器上跑一个大设计,线程开8到12个是上限,再往上升,内存就崩了。用top -H可以看实际线程数。
另一个隐蔽的问题是多个Innovus进程跑在同一个目录。它们会互相抢锁文件、抢database,谁都在等谁,全部卡住。这个问题在ps -ef | grep innovus里一眼就能看到,但如果没往这个方向想,会白等很久。
再就是license等待。log卡在Waiting for license或者某一行checking license的信息上半天不动,十有八九是license被占光了,或者license server不可达。处理方式:
- 用
lmstat -a -c <port>@<server>看一下license占用。 - 用
ping验证本机到license server的网络连通性。 - 实在不行,把其它机器上挂着的僵尸innovus进程清掉,释放license。
资源问题这一层,整体思路就是"先系统后工具"。任何工具都依赖CPU、内存、磁盘、网络这些硬资源,它们出问题时的表现往往比工具自身的问题更像"卡死"。
3. 数据与流程配置:database、floorplan、constraint的隐患
资源层排掉之后,就要看数据了。很多"跑不下去"其实是前面哪一级流程埋的雷,到当前这一步才爆出来。这一层的排查往往要花更多时间,因为问题不在当前阶段,而在上游。
3.1 数据导入与数据库一致性检查
Innovus跑的每一步,几乎都要基于完整的design database。如果你是从ICC/ICC2、Fusion Compiler或者其它工具导过来的数据,Lef/Def/Verilog/SDC之间只要有一丁点不一致,后面就可能出问题。
最常见的几个坑:
- 属性没迁移干净。比如clock单元的
is_integrated_clock属性没有传过来,CTS阶段工具会走一些奇怪的默认路径,表现为报一堆warning然后卡住。 - 缺少tie cell/decap cell。
read_verilog之后,如果厂商库里的tie hi/tie lo cell没有正确mapped,place之后工具去找这些cell找不到,会报错卡住。 - LEF和GDS里unit不一致、Filler cell缺失,导致legalization反复失败。
- SDC里对象引用了设计中不存在的pin/port,工具在clock propagation时反复warn并尝试修复,消耗大量时间。
数据层面我的建议非常明确:每次导入完设计后,先跑一遍checkDesign -type all(不同版本命令名略有差异),把所有error和critical warning清零后再往下跑。这一步不能省,省了迟早要在更深的步骤里加倍偿还。有一次我急着试一个floorplan方案,省了这步直接往下跑,结果route阶段挂了半天,最后发现是SDC里一个clock pin的path名字写错了,回头花十分钟改掉,立刻恢复。
3.2 Floorplan与pin assignment的隐患
Floorplan做得不合理,是"跑不下去"的重灾区。这里最典型的几个问题:
- Macro放得过于集中,行不成行、列不成列,导致大量区域无法放置cell,place阶段工具在legalization时反复尝试。
- Blockage把某些区域封死,cell被挤到其它区域,局部density爆表,工具会在place阶段死磕。
- IO pin assignment与芯片引脚方向不匹配,外部信号进来要大绕路,congestion map直接爆红。
- Power domain划分不合理,多个domain交错的区域routing资源严重不足。
处理经验:在做place之前,用reportCongestion或者GUI里的congestion map先看热点区域。如果某个区域已经红色甚至深红色,别急着往下跑。先调整macro摆放、删掉不必要的blockage、改pin assignment,或者用placeInstance把几个关键IP的位置先固定下来,再让工具处理剩余区域。这一步确实花时间,但比跑一半挂掉再回来省时间得多。
3.3 约束文件引发的卡点
约束是"跑不下去"的隐藏杀手,尤其optDesign阶段卡着不收敛,很多是SDC里写出来的矛盾约束。
举几个实际碰到过的例子:
set_max_transition设得过于激进,比如0.05ns,工具为了满足这个值疯狂插buffer,transition确实改善了,但area和congestion爆炸,后续route阶段根本跑不动。set_clock_uncertainty和set_clock_transition互相矛盾,导致每个buffer插入后的时序预测都在变,工具在同一个区域反复调整。- 生成时钟的
clock_leaf没有定义,CTS阶段工具不认识某些pin,构建时钟树时反复warn并卡住。 set_multicycle_path设错,比如应该2cycle设成10cycle,工具在优化时会接受本来不该接受的路径,跑出来的timing毫无意义,而且跑得极慢。
排查约束问题的方法并不复杂:如果optDesign卡住,先看最近的violation报告,找一个violation数量在反复横跳的指标。如果setup和hold violation之间来回转化,基本就是约束自洽出问题了。先把约束简化,单独跑optDesign -setup -effort low验证能否收敛,再逐项把约束加回去,就能定位到是哪一条约束在捣乱。
3.4 流程脚本与checkpoint设计
很多人喜欢写一大段tcl脚本一次性跑完place、opt、clock、route,中间不设任何checkpoint。这种写法一旦跑到后面挂掉,恢复成本极高。更稳妥的写法是用procs分段,每段结束保存一次design,并输出一次summary。
流程脚本里还有一些隐性的坑:
- 用
source加载多个版本工艺库时,优先级顺序错了,导致后面读到的是旧LEF,和当前design不匹配。 - 在route之前重新load了一次floorplan,但没有重跑place阶段,数据库状态混乱,工具可能直接挂起。
- 用了不存在的命令名或选项名,工具只是warning但不会停止,后续行为在警告之下不可预测,跑到后面才爆。
所以脚本里我会加上catch和明确的分段标记,每段结尾打一行puts "STEP_X DONE at [clock format [clock seconds]]"。日志里能看到进度标记,挂的时候一眼就知道是哪一步,而不是翻几百行log找最后一条信息。
4. place/opt/route各阶段的经典卡点与处置
讲完资源和数据,接下来是重头戏:各个阶段各自的"跑不下去"。这里的经验更贴近工具的使用细节,也是大家在社区里问得最多的部分。
4.1 placeDesign阶段的congestion与density
place阶段跑不下去,最常见的原因就是congestion高、density高,工具在全局布局和legalization之间反复迭代。
判断方法:place的log里会持续打印overflow值(比如Total Overflow),如果这个值在多个iteration里没有明显下降,或者一直停留在一个高位数,基本就是congestion问题。
几个处置方法,按推荐顺序排列:
- 调整congestion effort。
setPlaceMode -place_global_cong_effort high,让工具更早关注拥挤区域。 - 手动摆均匀硬宏。工具不是万能的,它自己摆macro时往往只考虑时序,不怎么管拥挤度。你手动把几个大IP打散,congestion问题经常立刻缓解。
- 用placement blockage划出禁区。某些区域宁可空着,也不要让工具硬塞cell,否则它会一直尝试摆放。
- 加cell padding。这里就涉及到很多人问的"Innovus加cell padding"。通过
set_cell_padding或者set_pad_pin_cell_padding给热点区域的cell加上padding,让cell之间留出空隙,反而能缓解局部拥挤。但注意,padding加多了density会直线上涨,效果适得其反。我一般控制在3%到5%以内,具体得看设计规模。
还有一个非常实用的经验:place卡住不代表真的卡死。工具在非常拥挤的设计里可能会跑出几十个iteration,每经过一个iteration都把逻辑重新摆一遍。如果不想等,可以用place_design -run_place_global false先跳过全局布局,只做legalization和optimization,先看看能不能趟过去。这样至少能快速拿到一个大概的congestion报告,再决定要不要调整floorplan。
4.2 optDesign阶段setup/hold来回振荡
optDesign卡住的剧本往往是这样的:开始修setup,很大的negative slack修成positive了,但hold又崩了;改修hold,hold好了,setup又变差。两个指标反复横跳,工具停在某个迭代里不出来。
造成这种现象的根源通常是约束自洽性差,以及工具一次修的力度太大。处置方法:
- 分步优化,不要指望一次性搞定。先单独跑
optDesign -setup,跑完后report_timing看关键路径是否收敛;再单独跑optDesign -hold,两者交替验证。这样做虽然慢,但每一步的目标明确,不会陷入来回振荡。 - 给工具设上限。比如
set_db opt_design_hold_effort low,或者限制一次插入buffer的规模,减少来回振荡的幅度。具体选项名因版本而异,但思路是一致的。 - 把不需要修得太狠的路径先例外掉。例如
set_false_path、set_multicycle_path,让工具集中火力在真正的关键路径上。 - 回头找前端要约束。如果后端怎么opt都收敛不了,很可能是synthesis阶段的约束和后端不一致。这种情况下继续苦修没有意义,及时和前端沟通,把约束对齐,往往比在Innovus里折腾几个小时更有效。
我在实际项目里见过一个极端案例:一个设计的setup violation数量在5000到8000之间反复横跳,修了一下午都没收敛。后来把SDC里一条原本应该false path但误设成0 cycle的路径改掉,整个问题十分钟解决。这类问题光靠工具是调不出来的,只能靠对设计的理解。
4.3 CTS与电源网络(addStripe)的坑
CTS阶段跑不下去,首先要查时钟网相关的特殊net定义。Innovus的CTS会优先处理所有clock net,如果clock net的routing规则定义有问题,比如non-default rule设置成禁止使用,工具在构建时钟树时会卡在某一步。
电源网络也是在这一步开始处理的,而addStripe本身就是一个高频卡点。常见原因:
- 电源ring/strap的layer设置和PR routing layer重叠,metal direction冲突,工具无法生成合法的tracks。
- Stripe间距设置过密,导致routing resource被大量消耗,后面route阶段基本没法跑。
- PG net connection没有先定义好,执行
addStripe -nets {VDD VSS}时工具找不全pg pin,挂在那里。
经验之谈:电源网络的处理要放在place之后、CTS之前。正式开始前,先用预检查命令看一下生成的stripe文件是否符合预期,再真正执行addStripe。如果直接跑完addStripe后发现电源环和逻辑routing冲突,后面的route基本跑不通,不如一开始就慢一点。
还有一点容易被忽略:如果设计中存在多个电源域,不同domain之间的isolation cell和level shifter如果没有正确摆放,工具在CTS阶段处理PG net时也会卡住。
4.4 routeDesign阶段的DRC收敛问题
routeDesign阶段"跑不下去",更多是detail route之后的DRC修不干净。工具会在Post-route optimization里反复尝试消除short和violation,如果congestion已经高到一定程度,或者某些区域被堵死,它会一直耗在那里不退出。
排查思路:
- 先单独跑全局布线。执行
routeDesign -global_route,看congestion report里的overflow是否归零。如果全局溢出不收敛,先解决congestion再往下走。 - 分类统计DRC。detail route跑一次后,看DRC violation summary按类别统计。如果short集中在某几个区域,可以用
addRoutingBlockage把一些实在没救的区域封掉,引导绕线。这招看着粗暴,但很实用。 - 检查电源网络占用的routing资源。如果PG网络太密、间隔太小,会给signal留出的空间不够。适当放宽电源strap间距,反而能让整体routing更快收敛。
route阶段的卡点,很多时候要回溯到floorplan和place阶段的决策。比如我遇到过detail route在某个角落反复修short,修了三个小时都没清干净,最后发现是那个区域下方埋了一个尺寸写错的hard macro,导致上方所有的routing layers都被堵死。把macro挪开之后,route半小时就结束了。
5. 跑挂之后的应急恢复与后续加固
最后一节,讲点"事后的功课"。因为跑不下去这件事,很难保证永远不发生,关键是挂掉之后能不能尽快恢复,以及以后怎么少挂。
5.1 应急恢复的标准动作
遇到跑不下去,我自己的标准动作是:
- 先判断真死假死,绝不轻易kill(方法见第1节)。
- 确认是真死或者不可接受的不收敛后,在交互模式里按Ctrl+C。Innovus通常会进入暂停态,这时先执行
saveDesign,把当前状态落盘。注意,有些版本里Ctrl+C可能直接终止流程,需要看日志确认是否还活着。 - 从最近的snapshot恢复。强烈推荐在每个大阶段结束时保存一次:place阶段结束保存
place.enc,CTS结束保存cts.enc,route结束保存route.enc。这样无论挂在哪一步,最多丢失一个阶段的运算。 - 恢复之后,把刚才出问题的阶段改成"轻量档"先验证一遍。能过,再逐步恢复全effort跑。
这里有个细节:saveDesign本身也会花时间,在大设计上可能要十几分钟甚至更久。所以保存的时机要合理,不能每个小步骤都存,否则保存本身就成了瓶颈。我的习惯是:planning、floorplan、place、CTS、route完毕,这五个节点必存;其它零碎步骤不存。
5.2 让流程更健壮的工程化手段
分享几个我在实际项目中验证过有效的做法:
- 在流程脚本里加后台监控模块。每隔5分钟把
ps -o pid,pcpu,pmem -p <pid>、free -g、df -h追加到一个监控log文件。下次跑挂了,先翻监控log,一眼就能看出是不是内存或磁盘导致的,不用瞎猜。 - 每个阶段开头打印日期和STEP标记,阶段结束输出
report_summary。这样日志里能快速定位到底挂在哪一步。 - 对关键阶段设置超时保护。比如用Linux的
timeout 7200 innovus -no_gui ...,超过2小时自动退出。虽然粗暴,但至少不会让一个已经没救的流程白占机器资源。超时退出后,还能自动把当前状态dump下来,方便诊断。 - 跑大设计之前先做快速验证:把effort降到最低、关闭部分优化开关,用最小迭代跑一遍全流程,确认数据链路是通的,再按正常effort跑。这有点像新工艺节点试片,能提前发现90%的数据问题和文件路径问题。
5.3 积累自己的卡点黑名单
最后分享一个习惯:每遇到一个"跑不下去"的问题,我都会把它记到自己的排障笔记里,记录现象、根因、解法。时间久了会发现,很多问题是有共性的:某个版本的工具在某个effort下对某类约束的兼容性差、某个工艺库的某个cell在CTS里容易引发问题、某个macro摆放位置会导致congestion爆表……这些经验攒多了,排查时间会指数级下降。
我自己的笔记里已经有几十条这样的记录了,从"LEF单位不一致导致legalization死循环",到"时钟树综合阶段因为clock_leaf定义缺失卡了整整一天",每一条都是拿加班时间换来的。这次整理出来的内容,就是这个"黑名单"里最常见的那一批。如果你手里有其它怪异的"跑不下去"的场景,欢迎带着log来一起讨论,互相攒经验,比一个人硬啃手册效率高太多了。