1. 天线效应违例到底在报什么错
天线效应(Antenna Effect)这个词,做数字后端的兄弟应该都不陌生。简单说,就是在芯片制造过程中,金属层或通孔在刻蚀时积累了电荷,这些电荷如果找不到泄放路径,就会击穿栅氧,导致器件失效。工艺越先进,栅氧越薄,这个问题就越致命。
我在最近一个项目里,跑完route之后做verifyAntenna,一口气报了60多个违例。这个数量不算特别夸张,但也不少了。如果一个个手动去修,加跳线、加buffer、改绕线,没个两三天搞不定。而且手动修的过程中很容易引入新的DRC或者timing问题,改完还得重新跑一遍验证,来回折腾。
天线效应的本质是什么?Foundry在工艺规则里会定义一个“天线比率”,通常是金属面积与栅极面积的比值。如果这个比值超过阈值,就判定为天线违例。常见的修复思路有三种:一是打断长金属线,在中间加一个二极管把电荷泄放掉;二是通过跳层(layer hopping)把长线分散到不同金属层;三是插入buffer把一条长线拆成两段短线段。
这三种方法各有适用场景。加二极管最直接,但会占用面积,而且有些工艺不允许在某些位置加二极管。跳层需要改绕线,可能影响timing和congestion。插buffer则是最“温和”的方式,因为它本质上是在逻辑上把一条net拆成两条,天线比率自然就降下来了。
我这次选择的是用ecoRoute来批量修复。为什么不用手工?因为60多个违例分布在不同的模块和不同的金属层上,手工修效率太低,而且容易漏。ecoRoute的好处是它可以自动识别违例net,自动插入buffer或者做跳层,然后增量绕线,不影响已经收敛的timing和DRC。
注意:ecoRoute修复天线效应之前,一定要先确认你的design已经完成了route和post-route优化,否则修完天线之后还要重新跑route,前面的工作就白费了。
2. 为什么选ecoRoute而不是手工修
2.1 ecoRoute的核心机制
ecoRoute是Innovus里专门用于增量绕线的引擎。它的工作方式是:你给它一个eco place或者eco netlist的变更,它只对受影响的net重新绕线,其他区域保持不动。这个特性对于天线修复来说简直是量身定做的。
天线修复的本质是什么?就是在某条net的中间插入一个buffer,把一条长线拆成两条短线。对于ecoRoute来说,这就是一个标准的ECO操作:你在netlist里加一个buffer实例,连接好输入输出,然后让ecoRoute去绕这两条新net。它不会动到其他任何东西。
我试过用ecoPlace加ecoRoute的组合,效果很稳。ecoPlace负责把新加的buffer摆到合理的位置,ecoRoute负责绕线。整个过程是增量的,不会触发全局reroute。
2.2 和手工修相比的优势
手工修天线最大的问题是效率。60多个违例,每个都要看它的net走向、金属层分布、周围有没有空间加buffer或者做跳层。一个熟练的后端工程师,修一个天线违例大概需要5到10分钟,60个就是5到10个小时。这还不包括修完之后重新验证的时间。
ecoRoute批量修复的话,从写脚本到跑完,大概30分钟到1小时就能搞定。而且脚本可以复用,下次遇到类似问题直接改改参数就能用。
另一个优势是一致性。手工修的时候,不同的人可能有不同的修法,有的人喜欢加buffer,有的人喜欢跳层,导致最终的netlist风格不统一。用脚本修的话,所有违例都走同一套逻辑,结果可预测、可复现。
2.3 什么情况下不适合用ecoRoute
ecoRoute也不是万能的。如果天线违例集中在某个特别congested的区域,ecoRoute可能找不到合法的绕线路径,这时候就需要手工介入。还有一种情况是,如果违例net上有时序特别紧的路径,插buffer可能会引入额外的delay,需要仔细评估。
我一般会先跑一遍ecoRoute,看看它能修掉多少。如果剩下几个实在修不掉的,再手工处理。这样既保证了效率,又保证了质量。
3. 修复前的准备工作
3.1 确认违例报告和net清单
第一步肯定是拿到准确的天线违例报告。在Innovus里,跑完verifyAntenna之后,会生成一个antenna violation report。这个report里会列出所有违例的net名字、违例的pin、当前的antenna ratio、以及要求的threshold。
我一般会把这个report解析一下,提取出所有违例net的名字,存到一个文件里。这个文件后面会作为ecoRoute的输入。
# 提取天线违例net列表 set fp [open "antenna_violations.rpt" r] set net_list {} while {[gets $fp line] >= 0} { if {[regexp {Net:\s+(\S+)} $line match net_name]} { lappend net_list $net_name } } close $fp puts "Total violation nets: [llength $net_list]"这段脚本的逻辑很简单:逐行读report,用正则匹配出net名字,存到list里。实际项目中,report的格式可能不太一样,你需要根据自己项目的report格式调整正则表达式。
3.2 检查物理资源是否充足
在跑ecoRoute之前,一定要确认design里有足够的空间放buffer。如果congestion已经很严重了,再往里塞buffer只会让情况更糟。
我一般会看两个东西:一是congestion map,看看违例net所在的区域是不是已经爆了;二是free space的报告,看看还有多少可用的placement site。
# 检查congestion情况 reportCongestion -hotSpot # 检查free site数量 checkFPlan -reportUtil如果发现某个区域特别congested,可以考虑先把一些不关键的buffer挪走,或者调整floorplan给这个区域多留点空间。
3.3 备份当前design
这一步千万别省。ecoRoute虽然说是增量的,但万一脚本写错了,或者ecoRoute的行为不符合预期,你还能回退。我一般会用saveDesign存一个checkpoint,命名上带上时间戳,方便回溯。
# 保存当前design状态 saveDesign ./checkpoints/before_antenna_fix_${timestamp}.enc提示:备份的时候最好把当前的timing report和DRC report也一起存下来,修完之后可以对比,确认没有引入新的问题。
4. 核心脚本拆解与实操
4.1 脚本整体结构
我写的这个脚本分四个部分:第一部分是读取违例net列表,第二部分是给每个违例net插入buffer,第三部分是跑ecoPlace和ecoRoute,第四部分是重新验证天线效应。
整个脚本大概100行左右,核心逻辑就是遍历net列表,对每个net执行ecoAddRepeater或者ecoInsertBuffer,然后统一跑ecoRoute。
# antenna_fix.tcl # 读取违例net列表 set net_file "antenna_violations.txt" set fp [open $net_file r] set violation_nets {} while {[gets $fp line] >= 0} { if {[string trim $line] ne ""} { lappend violation_nets [string trim $line] } } close $fp puts "INFO: Total [llength $violation_nets] nets to fix"4.2 插入buffer的关键参数
给每个违例net插buffer的时候,有几个参数需要特别注意。
第一个是buffer的类型。我一般会选驱动能力适中的buffer,太小的驱动能力不够,太大的面积浪费。具体选哪个,要看你的library里有哪些可用的buffer。我通常会选一个中等驱动的,比如BUF_X4或者BUF_X8。
第二个是插入位置。ecoAddRepeater可以指定插入位置,也可以让工具自动选。我一般会让工具自动选,因为它会考虑congestion和timing。但如果某个net特别长,我会手动指定一个大概的位置,让工具在那个附近找。
第三个是是否允许跳层。有些工艺里,跳层比插buffer更有效。ecoRoute支持在绕线的时候自动做layer hopping,你只需要在命令里加一个选项。
# 对每个违例net插入buffer foreach net $violation_nets { # 获取net的driver和load set driver [get_db [get_db nets $net] driver] set loads [get_db [get_db nets $net] loads] if {$driver eq "" || [llength $loads] == 0} { puts "WARN: Net $net has no driver or load, skip" continue } # 插入buffer ecoAddRepeater -net $net \ -cell BUF_X4 \ -location_aware \ -relative_location 0.5 puts "INFO: Inserted buffer on net $net" }这段脚本里,-relative_location 0.5表示在net的中间位置插入buffer。这个值可以根据实际情况调整,比如0.4或者0.6。-location_aware让工具考虑物理位置,避免把buffer放到太远的地方。
4.3 ecoPlace和ecoRoute的执行
插完buffer之后,需要先跑ecoPlace把buffer摆到合法位置,再跑ecoRoute绕线。
# 执行ecoPlace ecoPlace -buffer # 执行ecoRoute ecoRoute -target # 检查ecoRoute结果 set eco_route_status [get_db ecoRouteStatus] puts "INFO: ecoRoute status: $eco_route_status"ecoPlace -buffer是专门针对buffer的placement,它会尽量把buffer放在net的附近,减少绕线长度。ecoRoute -target会让ecoRoute只绕那些需要修的net,不动其他net。
跑完ecoRoute之后,一定要检查一下status。如果status不是“completed”,说明有些net没绕通,需要进一步处理。
4.4 重新验证天线效应
修完之后,重新跑一遍verifyAntenna,看看还有没有违例。
# 重新验证天线效应 verifyAntenna -report antenna_after_fix.rpt # 统计剩余违例数量 set fp [open "antenna_after_fix.rpt" r] set remaining 0 while {[gets $fp line] >= 0} { if {[regexp {Net:\s+(\S+)} $line match net_name]} { incr remaining } } close $fp puts "INFO: Remaining antenna violations: $remaining"如果remaining是0,恭喜你,收工。如果还有几个,那就需要手工看看是什么原因。常见的原因包括:buffer插不进去(没空间)、绕线绕不通(congestion太严重)、或者违例net本身有特殊约束。
5. 实操中踩过的坑和排查技巧
5.1 buffer插不进去怎么办
最常见的问题是ecoPlace找不到合法的位置放buffer。这时候可以试试几个方法:一是换一个更小的buffer,比如从BUF_X4换成BUF_X2;二是放宽placement的约束,允许工具把buffer放得远一点;三是手动指定一个位置,让工具在那个附近找。
# 换更小的buffer ecoAddRepeater -net $net -cell BUF_X2 -location_aware # 或者手动指定位置 ecoAddRepeater -net $net -cell BUF_X4 -location {100 200}如果还是不行,那就只能手工在版图上找个空位,手动摆一个buffer进去,然后手动连上线。这种情况一般出现在特别congested的区域。
5.2 ecoRoute绕不通怎么办
ecoRoute绕不通的原因通常是congestion太严重。这时候可以试试调整绕线层,让ecoRoute优先用上层金属,因为上层一般比较空。
# 设置ecoRoute的绕线层偏好 setEcoRouteMode -layer_preference {M5 M6 M7 M8}或者可以临时放宽DRC的约束,让ecoRoute先绕通,然后再手工修DRC。但这个方法要慎用,因为放宽约束可能会导致后续DRC修不完。
5.3 修完天线之后timing变差了
插buffer会引入额外的delay,这是不可避免的。如果修完天线之后发现某些路径的timing变差了,可以考虑几个方法:一是换驱动能力更大的buffer,减少delay;二是调整buffer的位置,让它更靠近driver或者load;三是在buffer后面再加一级buffer,做timing的补偿。
我一般会在修完天线之后跑一遍timing report,看看有没有新的setup或者hold违例。如果有,就针对性地修一下。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| ecoPlace找不到位置 | congestion太严重 | 换小buffer,放宽约束,手动指定位置 |
| ecoRoute绕不通 | 绕线资源不足 | 调整绕线层偏好,放宽DRC约束 |
| 修完timing变差 | buffer引入delay | 换大驱动buffer,调整位置,加补偿buffer |
| 天线违例没减少 | buffer没插对位置 | 检查net的driver和load,调整插入位置 |
| 脚本跑一半报错 | net名字有特殊字符 | 检查net名字,加转义或者过滤 |
提示:脚本跑之前,先用一两个net试一下,确认流程没问题再批量跑。我吃过这个亏,脚本写错了,跑了一半发现不对,回退又花了不少时间。
6. 脚本的复用和扩展
6.1 把脚本做成可配置的
我现在的做法是把脚本里的关键参数抽出来,放到一个配置文件里。这样下次遇到不同的项目,只需要改配置文件,不用改脚本本身。
# config.tcl set BUFFER_CELL "BUF_X4" set RELATIVE_LOCATION 0.5 set LAYER_PREFERENCE {M5 M6 M7 M8} set MAX_ITERATIONS 3然后在主脚本里source这个配置文件,用变量替换硬编码的值。这样脚本的通用性就强多了。
6.2 加入迭代修复的逻辑
有些天线违例一次修不掉,需要迭代几次。我一般会加一个循环,每次修完之后检查剩余违例,如果还有就再跑一遍,最多跑三次。
set max_iter 3 set iter 0 while {$iter < $max_iter} { # 读取当前违例 # 插入buffer # ecoPlace和ecoRoute # 重新验证 # 如果违例为0,break incr iter }这个逻辑对于那种违例分布比较分散的情况特别有用。第一遍可能只修掉80%,第二遍再修掉剩下的15%,第三遍基本就干净了。
6.3 和其他ECO流程的整合
天线修复只是ECO流程中的一环。实际项目中,可能还有timing ECO、DRC ECO、IR-drop ECO等等。我一般会把这些ECO整合到一个统一的脚本里,按优先级依次执行。
天线修复的优先级通常比较高,因为它直接影响芯片的可靠性。我一般会先修天线,再修timing,最后修DRC。这样顺序的好处是,天线修复引入的buffer可以在后续的timing修复中被优化掉。
7. 一些个人体会
这个脚本我用了大概有两年了,从28nm到7nm的项目都跑过,整体来说稳定性还是不错的。但有几个点我想特别强调一下。
第一,不要迷信自动化。ecoRoute再智能,它也只是个工具。有些违例就是需要人工判断,比如那种特别敏感的模拟net,或者有时序特紧的路径。脚本能修掉80%到90%的违例,剩下的还是要靠人。
第二,脚本要经常更新。不同的工艺节点,library不一样,DRC规则不一样,congestion的情况也不一样。我一般会在每个新项目开始的时候,花半天时间把脚本调一遍,确认它能正常工作。
第三,验证一定要做全。修完天线之后,不仅要重新跑verifyAntenna,还要跑DRC、LVS、timing。我见过有人修完天线之后DRC爆了,结果又花了两天修DRC,得不偿失。
最后分享一个小技巧:如果你发现某个net的天线违例特别顽固,怎么修都修不掉,可以试试把它拆成多条net,每条net单独绕线。这个方法有点暴力,但有时候确实管用。具体做法是在net的中间加一个buffer,把一条net变成两条,然后分别绕线。如果还不行,就再加一个buffer,变成三条。一般来说,拆成三条之后,天线比率肯定能降下来。