做芯片后端的人一定绕不开寄生参数提取这道工序,90nm芯片的RC参数提取更是后端流程里最不能偷懒的一环。StarRC作为Synopsys的主力寄生提取工具,负责把版图中的互连电阻电容提取出来,输出成SPEF文件交给PrimeTime做时序分析。但问题在于,一个芯片往往有几十个模块、好几个工艺角,手工一个个敲StarRC命令不仅效率低,还容易漏提、错提,出片了才后悔就来不及了。所以我做了这套用Shell脚本自动化批量提取SPEF的方案,实际用在90nm项目上,把原来要跑两三天的提参工作压缩到半天,还顺手把一批经典报错给趟平了。这篇东西不绕弯子,直接讲思路、贴脚本、列报错,适合正要接触后端提参流程的同学,也适合被StarRC折腾到头秃的同行参考。
1. 项目背景与方案设计思路
1.1 90nm节点为什么必须认真提参
180nm往上走的时候,很多人还能靠前仿真和粗略的线负载模型蒙混过关,但到了90nm节点,互连线的寄生效应已经完全压不住。线宽变细之后,金属线的电阻显著增加,线间距离缩小让耦合电容急剧上升,这两者叠加起来,直接改变了信号从发射端传到接收端的延迟,也让串扰噪声成为时序收敛的拦路虎。
具体到90nm工艺,我举几个实际影响:时钟树上的走线电阻如果估算不准,时钟偏斜很容易超出budget;数据路径上的耦合电容如果不提取,hold time检查会冒出一堆违反;电源网络上如果少了动态RC分析需要的SPEF,IR drop和电迁移分析结果也站不住脚。所以后端signoff阶段,寄生提取已经不是“可选项”,而是必须把实际版图中的每一根wire、每一个via的RC值都提出来。
StarRC在这一环节的角色,是把来自Milkyway数据库或者GDSII的版图,结合工艺厂提供的TLUPlus文件,计算出一张包含电阻、电容、电感的寄生网络表,最终写成SPEF格式。SPEF是行业标准格式,PrimeTime、RedHawk、Voltus这些工具都能直接读,它就像芯片后端各工具之间的通用语言。
1.2 为什么要用Shell脚本做自动化
最开始在这个90nm项目上,提参是手工做的,一次提一个模块、一个corner,流程大概是:准备好Milkyway库和tluplus文件,手写一个StarRC命令文件,运行,检查log,再手工整理SPEF。听着不难,但一旦模块数量超过十个、corner有三个以上,这套流程就变成了体力活,而且手工操作最大的问题是不可控。
有一次我因为赶进度,在某个corner上漏改了tluplus路径,跑出来的SPEF是错的数据,后面PR和STA全被带偏,排查了两天才定位到是提参环节的问题,教训极其深刻。那次之后我认真想了一件事:提参流程的输入输出非常固定,无非是设计名字、corner名字、工艺文件路径、输出目录这几种变量,完全适合用Shell脚本把整个流程包起来。
选择Shell而不是Perl或者Python,原因也很实在:后端服务器上Shell是必有的,不依赖额外解释器;StarRC是命令行工具,Shell对这类外部命令的调用、返回码判断、log重定向有着天然优势;整个流程不需要复杂的数据结构,用变量、数组、for循环足以覆盖绝大部分场景。写起来快,跑起来直接,排错也直观。
1.3 输入输出与目录结构规划
自动化方案启动之前,目录结构要先定清楚,否则脚本跑起来之后文件散落各处,找起来会想哭。我在这套方案里用的是下面这段布局:
$PROJECT_ROOT/ ├── setup/ │ ├── 90nm_typ.tluplus │ ├── 90nm_slow.tluplus │ ├── 90nm_fast.tluplus │ ├── star_mapping.mapping │ └── layermap.txt ├── input/ │ ├── block1/ │ │ └── mw_library/ │ ├── block2/ │ │ └── mw_library/ │ └── block3/ │ └── mw_library/ ├── script/ │ └── run_rc.sh ├── output/ │ ├── spef/ │ ├── log/ │ └── summary/ └── run_rc.logsetup目录放工艺相关文件,input目录按模块放各自的Milkyway数据库,script目录放脚本,output目录按SPEF、log、summary分开存。这样设计的原因很简单:工艺文件是只读的,放一起方便维护版本;设计数据按模块隔离,避免不同模块的tluplus或mapping串了;output分三个子目录,是为了后期检查时能快速从summary总表到具体log再到SPEF逐层定位问题。
2. StarRC提取流程的核心要素梳理
2.1 StarRC命令文件的基本结构
StarRC本身需要一个命令文件来告诉它本次要提哪个设计、用什么工艺参数、输出什么格式。不同版本命令语法略有差异,但核心内容不外乎这几类:
/* 设计名与输入数据库 */ basename = block1 milkyway_library = /path/to/input/block1/mw_library netlist_file = /path/to/input/block1/block1.v /* 工艺文件与映射 */ source -e /path/to/setup/90nm_typ.tluplus set_mapping_file /path/to/setup/star_mapping.mapping set_tlu_plus_files -layermap /path/to/setup/layermap.txt \ -tluplus_typ /path/to/setup/90nm_typ.tluplus /* 提取选项 */ extract -design block1basename就是本次提取的设计名,它会出现在输出的SPEF文件的DESIGN字段里;milkyway_library指定版图数据库;netlist_file是门级网表,用于把版图几何信息映射到逻辑单元上。set_mapping_file和set_tlu_plus_files这两行,是StarRC把物理layer与工艺层关联起来的关键,后面我会专门展开。
extract这一行是真正触发提取的命令,后面还可以加一些选项,比如提耦合电容时用-coupling_cap yes,提电阻时用-reduce_res,输出噪声参数用-out_noise等。具体选项按项目需求开,但默认把R、C提准是第一优先级的。
2.2 tluplus和mapping file为什么是重灾区
说句实话,StarRC运行时的报错,至少一半都出在tluplus和mapping file的配合上。tluplus是工艺厂提供的经过压缩和加密的工艺互连参数文件,里面定义了每层金属的方块电阻、单位面积电容、边缘电容等物理值;mapping file则负责告诉StarRC:版图里的layer1对应tluplus里的M1,layer2对应M2,等等。
这两个文件任何一个跟设计不匹配,StarRC就会在初始化阶段倒下一片或者提出一堆错误数据。比如,把用于10M工艺的tluplus用在了8M工艺的设计上,或者mapping里把M4映射到了M5,工具不会直接报错,但它输出的SPEF里的数值就是错的。这种错误比直接报错更可怕,因为很少有人会去复核SPEF里的每一条R、C是否合理。
我的经验是,在每个corner批量跑之前,先挑一个小模块做一次单点验证:提取完成后,打开SPEF,用命令统计一下电阻、电容的量级,跟同工艺节点的历史数据比对,量级不对就赶紧查tluplus和mapping,别等整批跑完再复盘。
2.3 SPEF文件关键字段解读
自动化脚本跑完,我们最终拿到的就是一堆SPEF文件。看懂SPEF的结构对排查脚本问题非常有帮助。下面是一段典型的SPEF头部:
*SPEF "IEEE 1481-1998" *DESIGN "block1" *DATE "Tue Apr 8 15:30:00 2025" *VENDOR "Synopsys StarRC" *PROGRAM "StarRC" *VERSION "1.0" *DIVIDER / *DELIMITER : *BUS_DELIMITER [ ] *NAME_SCOPE LOCAL *UNITS R K M L H C T P这几个字段值得注意:*DIVIDER是层次路径分隔符,比如top/sub_inst/ff_inst/Q;*DELIMITER是实例名与信号名之间的分隔符;*BUS_DELIMITER是总线的方括号;*UNITS这行定义了电阻单位、电容单位、时间单位等,R代表欧姆、K是千欧、M是兆欧,C后面跟的是电容单位,FA一般表示皮法。
这些东西在自动化流程里有一个容易踩坑的地方:如果StarRC输出的SPEF里DIVIDER是/,而后续PrimeTime脚本里预期的是.,或者BUS_DELIMITER是[]但STA环境用的是<>,那么SPEF读进去之后,大量pin会映射不上,报一堆“Cannot find pin”的错。最稳妥的办法是在StarRC命令文件里显式指定这些与网表命名一致的分隔符,而不是依赖工具默认值。
3. Shell脚本自动化核心实现
3.1 主控脚本框架设计
整个自动化方案的中枢是一个主控Shell脚本,它负责遍历模块列表和corner列表,为每一个组合生成StarRC命令文件、调用StarRC、记录日志、汇总结果。我的脚本框架大致如下:
#!/bin/bash # run_rc.sh - StarRC SPEF自动化提取脚本 set -euo pipefail PROJECT_ROOT="/prod/90nm_rc" SETUP_DIR="$PROJECT_ROOT/setup" INPUT_DIR="$PROJECT_ROOT/input" OUTPUT_DIR="$PROJECT_ROOT/output" SPEF_DIR="$OUTPUT_DIR/spef" LOG_DIR="$OUTPUT_DIR/log" SUMMARY_FILE="$OUTPUT_DIR/summary/run_summary.txt" BLOCKS=(block1 block2 block3 block4) CORNERS=(typ slow fast) export STARRC_HOME="/home/eda/synopsys/starrc/VERSION" export PATH="$STARRC_HOME/linux64/bin:$PATH" export LM_LICENSE_FILE=27000@license_host export SNPSLMD_LICENSE_FILE=27000@license_host mkdir -p "$SPEF_DIR" "$LOG_DIR" "$(dirname "$SUMMARY_FILE")" : > "$SUMMARY_FILE" run_one_extraction() { local block="$1" local corner="$2" local cmd_file="$OUTPUT_DIR/${block}.${corner}.cmd" local log_file="$LOG_DIR/${block}.${corner}.log" local spef_file="$SPEF_DIR/${block}.${corner}.spef" cat > "$cmd_file" <<EOF basename = $block milkyway_library = ${INPUT_DIR}/${block}/mw_library netlist_file = ${INPUT_DIR}/${block}/${block}.v source -e ${SETUP_DIR}/90nm_${corner}.tluplus set_mapping_file ${SETUP_DIR}/star_mapping.mapping set_tlu_plus_files -layermap ${SETUP_DIR}/layermap.txt \\ -tluplus_typ ${SETUP_DIR}/90nm_${corner}.tluplus extract -design $block EOF echo "[$(date '+%F %T')] Running $block / $corner ..." | tee -a "$SUMMARY_FILE" starrc -64 -s "$cmd_file" > "$log_file" 2>&1 local ret=$? if [ $ret -eq 0 ] && [ -s "$spef_file" ]; then echo "[OK] $block / $corner -> $spef_file" | tee -a "$SUMMARY_FILE" else echo "[FAIL] $block / $corner. See $log_file" | tee -a "$SUMMARY_FILE" return 1 fi } for block in "${BLOCKS[@]}"; do for corner in "${CORNERS[@]}"; do run_one_extraction "$block" "$corner" || true done done这个脚本里值得说明的细节有几个。set -euo pipefail里的u如果没设,脚本里引用未定义变量时不会报错,而一旦变量名拼错,它就会静默地拿空值去生成路径,这种错误在批量运行时会非常隐蔽;加上u之后,变量拼错会在第一时间暴露。|| true的作用是单个模块失败时不中断整个循环,但因为它会把失败信息写进summary,所以检查汇总文件就能知道哪些组合挂了。
starrc -64 -s中的-64表示以64位模式运行,90nm规模和以上的设计基本都需要开64位,32位模式很容易在提大模块时内存耗尽。-s表示后跟的是StarRC命令文件。不同版本参数名称可能叫-cmds或者-f,以你本机starrc -help输出为准。
3.2 多corner和多模块循环的逻辑
芯片项目里通常要提typ、slow、fast三个基本corner,有时还会加一个某个电压下的特殊corner。对应到tluplus上,不同corner会使用不同的工艺参数文件,比如90nm_typ.tluplus、90nm_slow.tluplus、90nm_fast.tluplus,它们反映的是不同工艺波动条件下互连线的电阻电容值。
循环里动态拼接corner相关路径,是我刻意为之的:set_tlu_plus_files -tluplus_typ ${SETUP_DIR}/90nm_${corner}.tluplus。这样写虽然看起来只是字符串拼拼凑凑,但能够保证如果我想新增一个corner,只需要在CORNERS数组里加一个名字,同时保证setup目录下有对应的tluplus文件,其他逻辑完全不需要动。
模块列表在脚本里维护成数组,好处是顺序可控、可注释、可增删。项目早期可能只有block1、block2,后来加入了新的block3,只需要在数组里追加一项,批量任务就会自动包含它。如果某次只想重跑某几个模块,可以把其他模块临时注释掉,或者把这个数组改成从外部参数读取,这部分根据个人习惯来就好。
3.3 日志与结果归档策略
自动化流程里,日志管理是做不做得好后期的分水岭。每个模块每个corner的运行log,我用${block}.${corner}.log命名,放在统一的log目录里;如果StarRC运行失败,log文件会保留下来,方便事后查原因,我绝不自动清理。
summary文件之所以单独设置,是为了在几十上百个运行组合里快速看到整体状态。每次运行结束,脚本会把时间、模块名、corner、成功失败、SPEF路径写成一行,最终汇总文件就像一张流水账,配合grep就能快速找出所有失败项。比如想看这次批量运行有哪些失败,一行命令就够了:
grep "FAIL" output/summary/run_summary.txt另外,每天跑完一批,我会顺手把summary文件按日期归档一份,防止第二天开跑新一批时把前一天的结果覆盖掉。后面排查问题时,如果发现是某个工艺角的数据异常,可以直接翻那天的归档记录,看看当时用的是什么版本的tluplus,非常管用。
4. 典型报错与排查技巧实录
4.1 头号报错:connected database layer does not have a valid itflayer
这个报错我在网上搜过很多次,应该是用StarRC的人最常撞见的问题之一,报错原文比较长,核心一句话就是某个database layer在itf/tluplus里找不到有效的itflayer定义。出现这个报错,基本可以断定是物理层映射链路出了问题。
我详细拆解一下这个报错的成因。StarRC在初始化阶段,会把Milkyway数据库里的layer和techfile中的信息加载进来,然后通过mapping file把这些layer映射到tluplus里定义的itflayer上。但凡其中一环接不上,比如tluplus里根本没有某个layer、mapping文件里把M7写成了M8、或者Milkyway库里的layer名字与tluplus中的命名规则不一致,StarRC就会在“connected database layer”这一步检查时给出这个错误。
针对90nm项目的排查,我建议按下面的顺序走:
- 第一步,打开log文件,用grep定位到第一条报错前后的layer名字,确认是哪个金属层或通孔层出了问题。比如报错信息里如果提到
M5,那就优先检查M5相关的映射和定义。 - 第二步,核对tluplus文件来源。90nm工艺的tluplus,不同代工厂、不同版本差异很大。确认当前tluplus是工艺厂针对这个项目坐标点和金属层版本提供的那个,而不是从其他项目随手拷贝的。
- 第三步,打开mapping file,逐行核对有问题的layer的映射关系。mapping里一般长这样:
M5 M5 3,意思是指定的milkyway层名、tluplus层名、以及层编号。如果第二列和tluplus中的itflayer名字不一致,就会触发这个报错。 - 第四步,检查Milkyway库的完整性。有时候库是从旧节点复制过来或者只导入了一部分层次,导致某些layer在库里有几何数据但techfile里没有对应定义,这种情况同样会报这个错。
我在实际项目里遇到这个报错,最后定位到是mapping file里M6和M7两行写反了,StarRC在映射M6时发现对应tluplus里的itflayer描述不合法,直接抛错。修正mapping之后同一套tluplus一次通过。这里要特别提醒:改mapping file之前一定先确认工艺文件和tluplus的版本,别在错误的地基上修修补补。
4.2 其他高频报错速查表
除了itflayer问题,StarRC实战中还有几类高频报错,我把它们整理成速查表,方便批处理时遇到了直接对症下药。
| 报错关键字 | 常见原因 | 处理建议 |
|---|---|---|
| Can't open milkyway library | 库路径写错或读权限不足 | 检查命令文件里的路径,确认当前用户对库文件夹有读权限 |
| Layer xxx not found | techfile或mapping里缺少该层 | 核对techfile的layer定义,补充mapping映射 |
| netlist file not found | 门级网表路径不对 | 确认网表文件存在且没有拼错名称 |
| Memory allocation failed | 设计规模大而32位模式不够用 | 改用starrc -64,检查服务器内存和swap |
| Cannot open tluplus file | tluplus路径错误 | 检查变量拼接后的完整路径,corner名是否拼写正确 |
| License checkout failed | license不可用 | 查license服务器,确认StarRC功能feature未被占用满 |
这张表看起来朴素,但每一条都是我或身边同事实打实踩过的坑。特别是netlist file not found,在批量脚本里特别容易出:因为模块的网表命名不统一,有的叫block1.v,有的叫block1_typ.v,脚本一拼路径就找不到了。后来我把输入文件命名规范统一成模块名.v,这类报错减少了很多。
4.3 排查方法论:先看log再猜原因
在做自动化脚本的时候,我还踩过一个心态上的坑:一看到报错就埋头改脚本,改来改去发现其实问题压根不在脚本,而在StarRC本身的输入数据上。后来我给自己定了一条规矩:任何报错,先看log,再看输入文件,最后才动脚本。
StarRC的log文件通常会打印出它加载每一个文件的过程,包括tluplus路径、mapping内容、layer统计信息。这些信息是判断问题根因的第一手资料。grep -i "error\|warning" extract.log可以快速抓高亮信息,但不要只盯着error看,warning有时候才是真凶,比如某个layer匹配不上它可能只是个warning,但最后输出的SPEF里就少了那一层的RC。
批量运行时代价最大的排查方式,是跑完一整批之后才发现某一个corner的所有模块都错在同一处。所以我在主循环里额外做了一步:一个corner跑完一个小模块之后,如果log里error关键字非常多,立刻停掉整个批次。这个“先试跑一个,再批量展开”的思路,在提参这种计算密集任务里能省下大量机时。
5. 避坑指南与提效技巧
5.1 让脚本更健壮的几个细节
脚本能不能真正落地到项目里,健壮性比花哨的写法重要得多。我吃过几次亏之后,总结了下面几条经验:
- 不要在Windows下编辑脚本再传到Linux跑。CRLF换行符会让Shell脚本在运行时报出一堆莫名其妙的
$'\r': command not found,排查起来非常痛苦。用dos2unix转一下就好。 - 路径不要写死。90nm这个项目的工具有可能明天就升级、工艺文件有可能从别的目录迁移,脚本里用相对路径加
PROJECT_ROOT这种顶层变量来引用,能降低后续维护成本。 - 变量拼接处加引号。几乎每一条路径和文件名都要用双引号包起来,防止路径里有空格或者特殊字符时被Shell拆开。
- 失败重跑要能接着跑。如果某个模块失败后修复了输入文件,重跑时最好只跑失败的组合,而不是整个矩阵重来。我一般把summary文件里的成功项过滤出来,和完整矩阵做一次差集,再只跑差值。
这些细节看起来零碎,但一个脚本要连续跑几个小时甚至一整晚,任何一个细节出问题,浪费的都是宝贵的项目时间。
5.2 提升批量提取效率的经验
提参流程本身是计算密集的,90nm节点上一个中等规模的模块,跑一个corner可能要几十分钟到几个小时不等。批量自动化之后,虽然不用人守着了,但机时规划还是要做。我的做法是先用小模块把三个corner都试跑一遍,确认tluplus、mapping、输出SPEF都没有问题,再把大模块全部丢进队列,排成串行或者按license余量开两三个并行。
StarRC并行度不是越高越好,最明显的瓶颈是license数量。如果公司license只有两三个StarRC feature,开十个并行作业,大部分时间都花在“checkout license失败后不断重试”上,反而比串行还慢。所以在脚本里我加了一个简单的并发控制,限制同时运行的StarRC进程数,比如用xargs -P 3或者一个计数信号量控制后台进程数。
另外,批量跑的时候我一般把nice命令加上,nice -n 10 starrc -64 -s ...,让提参作业让出一些CPU优先级给其他在跑PR的同事,这个习惯在多人共用的服务器上非常重要,能少很多同事间的摩擦。
5.3 输出SPEF的快速质检方法
拿到SPEF之后,不要直接丢给PrimeTime。哪怕StarRC退出码是0、SPEF文件也不是空的,也不能保证数据就一定合理。我习惯在每个批次结束后对生成的SPEF做一次快速质检,主要看三类指标:文件大小是否在合理范围、电容总量是否和版图面积匹配、电阻数量是否为0。
电阻数量为0是初学者最容易忽略的问题。如果extract命令里没有正确开启电阻提取选项,或者mapping里没有定义通孔电阻,StarRC输出的SPEF里可能只有电容没有电阻。在90nm节点,不提电阻等于白提,后续时序分析对线路延迟的估算会过于乐观,hold time可能漏报。我一般用一个简单脚本统计每个SPEF里的电阻条目数量:
for f in output/spef/*.spef; do r_count=$(grep -c '^\*R' "$f") c_count=$(grep -c '^\*C' "$f") echo "$f R=$r_count C=$c_count" done如果某个模块的R条目数量是0,基本可以确定这个SPEF不能用,回去查命令文件里有没有把电阻提取打开。这一步花不了几分钟,但能在STA之前拦住一堆返工。
这个自动化方案落地之后,我再也没有回到过“手工跑提参再自己盯log”的日子。每次跑完批量任务,对照summary里的成功标记就可以放心进行下一步;即使出现失败项,也有足够的日志和归档让人快速定位。对于90nm这种工艺节点,提参自动化的意义不只是省时间,更重要的是把人为操作带来的不确定性压到最低,让后端的每一步都站在可控的地基上。