资讯动态

用SKILL脚本给Virtuoso原理图自动标注Net名,彻底告别逐根查线

发布时间:2026/10/2 7:20:18 来源:尧图企业网站定制
很多做模拟版图或者顶集成的兄弟应该都有这种经历从别人手里接过来一份Virtuoso Schematic打开一看pin的name倒是都在但net名完全没有显示出来甚至pin的name和net name根本对不上。你想确认某条信号到底连到哪只能拿鼠标悬停在线上一点点看遇到几百个pin的block整个人都是崩溃的。更麻烦的是做LVS或者生成网表之前你往往需要把这些net信息清楚地标注出来方便评审、核对和反标。这个项目就是解决这个问题的用SKILL写一个脚本在Virtuoso Schematic里跑一下自动遍历所有pin把每个pin所在net的名字打成一个pin label整齐地标在pin旁边。几十个pin秒完几百个pin也就是喝口水的时间而且不会漏、不会错、风格统一。IC设计工程师、模拟版图工程师、数字顶层集成、以及所有日常和Cadence Virtuoso schematic打交道的人都能直接用上。1. 项目背景原理图pin标注的痛点与解决思路1.1 我们到底要自动做什么先明确一个概念这里说的“pin label”不是指pin自己的名字而是要在原理图上给每个pin补一个net label让读图的人一眼就知道这个pin连的是哪个net。在Virtuoso的Schematic Editor里net label通常是打在wire上的那种文字标签比如VDD、GND、clk、data3:0。当你把label打到某条net上之后这个label就和net绑定在一起net的名字由label决定导出网表、做LVS时都以这个label为准。问题在于很多schematic里pin本身和net是同名的——pin叫VDD连接它的net也叫VDD——但net label并没有被画出来。本来这也不影响电气连接但人眼看起来非常痛苦。尤其是有层次化设计的时候top level看到一堆子模块的pin每个pin旁边只有一个pin name你想知道这个pin到底是不是接着你要的那条电源线就不得不用「鼠标悬浮高亮」的方式一条条去查。自动化要做的就是把这些net name全部变成可见的label批量、快速、无遗漏。1.2 为什么选SKILL而不是GUI手工操作这类事情在Cadence环境里第一反应应该是用SKILL而不是手动去点。原因有几个批量操作效率差距巨大。一个block里面几十上百个pinGUI手工一个个“Add Label”再输入net名少说几分钟多则十几分钟而且人工输入容易出错。逻辑完全可以程序化。Pin和net的关系在设计数据库里是明确的遍历、判断、写入都是标准数据库操作不存在需要“人眼判断”的复杂情况。可复用性。脚本写一次以后任何设计、任何工艺库都能用还能挂快捷键、集成进初始化脚本变成工具链的一部分。这不是说GUI不能做而是GUI适合“偶尔标一个”SKILL适合“批量处理”。日常工作中凡是重复次数超过5次的操作我都会条件反射地想写成脚本。这次这个需求就是一个非常典型的“适合脚本化”的场景。1.3 预期效果与适用范围脚本跑完之后每个pin的旁边会出现一个和此pin相连net的label。如果这个pin连接的是VDDlabel就是VDD连接的是data3:0label就是data3:0。label与net是真正关联的不是画上去好看的装饰品所以它和手工加的label完全等价可以被网表工具正确识别。适用范围是Virtuoso的schematic editor无论是纯模拟设计、混合信号、数字顶层集成还是从网表反向恢复出来的schematic都能用。工艺库不限制只要是Cadence的电路图环境脚本的核心逻辑都成立。2. 方案拆解读懂Virtuoso中的pin、net、label2.1 关键对象模型梳理要写这个脚本先得搞清楚Virtuoso数据库里几个对象的关系。在schematic这样一个cellView里主要对象包括cellView整个原理图视图。可以理解为“这一张图纸”。instinstance原理图里放下的子模块单元比如一个运放符号、一个电阻符号。pin严格说schematic里的pin其实叫“terminal”。它是这个cellView对外连接的端口。从底层数据库来看顶层的pin和cell自身的terminal是同一个概念。比如一个反相器的schematic输入和输出就是两个pin。net网络。在schematic里所有通过wire连在一起的东西构成一个net。net连接了pin、inst的pin以及label。label网络标签。打到wire或pin上的文字用于给net命名。如果用关系型数据库来类比cellView是表pin和inst是记录net是连接关系label是net的一个属性标签。我们要做的事情就是遍历所有pin找到pin所属的net然后在pin的几何位置上创建一个label。有一点容易被新手搞混在schematic里pin本身的名字和它连接的net名不一定是同一个。比如一个pin在当前层叫IN但外面接的net可能叫clk_in。我们脚本要打的label是net名不是pin名因为最终网表里认的是net名。2.2 label与net的关联原理如果只是想在pin旁边画几个字用dbCreateText之类的东西也能画但那样画出来的字是“死”的它不会和net建立关联netlist的时候也不认等于白画。正确的做法是用schCreateLabel这个函数。它的作用是创建一个真正的net label并且自动把label和它所处的net绑定起来。这里有个极其关键的细节schCreateLabel创建的时候label会尝试关联到它的origin放置点所落在的那个net figure上。可以这样理解label落点就像是在net这张“网”上挂了一个名牌。如果你的落点不在这张网上名牌就是悬空的不生效。所以我们取label位置的时候一定要取pin body的几何中心或者至少确保落点落在pin body这个图形内部。pin body本身是和net有连接关系的这样label才能挂靠成功。2.3 获取pin的net名称——核心路径在SKILL里从pin对象拿到net名字是沿着对象的属性链走的。一个pin对象有一个net属性指向它所属的net对象net对象有一个name属性就是net的名字。简单来说就是pin~net~name如果pin没有连接到任何net这个表达式会返回nil说明这个pin是悬空的。这里要注意一个bus的情况。如果一个pin是bus pin比如名字叫A3:0在schematic里它作为一个整体和一条bus net相连那么这个pin~net~name返回的通常还是一个带尖括号的名字比如A3:0。这没问题label打出来就是了。如果你的设计里bus被拆成了单bit比如A0、A1各连各的net那么每个pin对应的net就是单独的名字脚本同样能正确处理。3. 完整实现autoPinLabel脚本开发实录3.1 整体流程与版本设计在动手写之前我先把整个流程在脑子里过了一遍分成了几个步骤获取当前打开的schematic cellView如果没打开就直接报错退出。从cellView里取出所有pin列表。遍历每个pin获取它的net名。如果net名存在计算该pin body的中心坐标。以该坐标作为label落点调用schCreateLabel创建与net关联的label。给label打上一个自定义标记属性方便以后清理和去重。统计成功数量和跳过数量打印报告刷新窗口。版本上我做了一个权衡默认版本处理当前cellView里所有pin一次性搞定再额外提供一个“只处理选中的pin”的交互版本适合只修改局部的情况。开发时先保证全图版本逻辑正确选中版本只是把“所有pin”改成“用户选中的pin”其他逻辑完全复用这样代码结构最清晰维护成本也最低。3.2 核心代码逐段解析下面是全图自动标注的核心代码。先看完整版本再逐段解释。procedure(autoPinLabelCreate optional (lblLayer wireName) (lblMag 0.0625) let((cv pins pin netName bodyBBox x y newLabel cnt skipCnt) cv geGetWindowCellView(hiGetCurrentWindow()) unless(cv error(autoPinLabel: 请先打开一个schematic cellview。\n) ) pins dbGet(cv~pins) cnt 0 skipCnt 0 foreach(pin pins netName pin~net~name if(netName then bodyBBox autoPinLabelGetBodyBBox(pin) when(bodyBBox x (caar(bodyBBox) caadr(bodyBBox)) / 2.0 y (cadar(bodyBBox) cadadr(bodyBBox)) / 2.0 unless(autoPinLabelExistAt(cv netName x y) newLabel schCreateLabel( ?cellView cv ?layer lblLayer ?name netName ?origin list(x y) ?just centerCenter ?magnitude lblMag ) when(newLabel dbReplaceProp(newLabel autoPinLabel boolean t) cnt cnt 1 ) ) ) else skipCnt skipCnt 1 ) ) printf(autoPinLabel: 已创建 %d 个label跳过 %d 个悬空pin。\n cnt skipCnt) hiRedraw() ) ) procedure(autoPinLabelGetBodyBBox(pin) let((figs bodyFig) figs pin~figures foreach(fig figs when(!fig~isText bodyFig fig ) ) when(bodyFig bodyFig~bBox ) ) )第一段中geGetWindowCellView(hiGetCurrentWindow())的作用是拿到当前窗口正在编辑的cellView。hiGetCurrentWindow()获取当前激活的窗口句柄geGetWindowCellView用来取得该窗口对应的cellView对象。如果你在CIW里直接跑这个函数它会自动找到当前schematic窗口。接下来dbGet(cv~pins)从cellView中取出全部pin对象的列表。这是最核心的一个数据获取操作。dbGet是Cadence SKILL里非常常用的一组数据库访问接口它可以沿着对象的属性链直接抓取数据。如果你用cv~pins?得到的是pin objects的列表而用dbGet(cv~pins~name)则直接得到所有pin名字的列表。然后开始遍历。pin~net~name拿net名如果pin是悬空的pin~net本身可能是nil直接取name会报错。所以在外面先判断一下netName是否为真。每一处重点我要单独说label的创建参数。schCreateLabel里必带的有?cellView、?name和?origin。?layer是label要放到的层schematic里的net label一般放在wireName层如果你的设计库或公司规范里用了别的label层手动改这个参数就行。?just是文字的对齐方式用“centerCenter”表示文字中心正好对准origin坐标这样label会均匀地出现在pin body上。?magnitude是文字大小单位是格子snap spacing。!fig~isText这一段是为了筛出pin body而不是pin的name text。一个schematic pin对象通常包含两个figure一个几何图形body和一个text字符串pin名字。label要挂在body上所以我们从pin的所有figure里挑出那个非text的作为几何定位参考。有的库pin body可能不止一个figure但绝大多数情况下非text的figure就是我们要的body。3.3 计算label放哪里更合理label的位置直接决定了最终效果好不好看、关联对不对。先说最核心的原则label落在pin body上才能保证和net正确关联。所以用pin body的bBox中心点作为origin是稳妥的。但实际使用中我发现纯中心点有时会被pin自己的名字遮挡。pin body旁边往往已经有一个pin name text如果再在中心放一个net label两个字符串会叠在一起看起来很乱。为了解决这个问题我后来在脚本里加了一个落点微调逻辑如果pin body的宽度大于高度说明这是一个水平走向的pinlabel放到body下方如果高度大于宽度说明pin是竖直走向的label放到body左侧或右侧。这样能把net label和pin name错开。这个逻辑的代码片段如下procedure(autoPinLabelGetPlacePos(bodyBBox) let((w h xC yC) xC (caar(bodyBBox) caadr(bodyBBox)) / 2.0 yC (cadar(bodyBBox) cadadr(bodyBBox)) / 2.0 w caadr(bodyBBox) - caar(bodyBBox) h cadadr(bodyBBox) - cadar(bodyBBox) if(w h then list(xC yC - 0.0625) else list(xC - 0.0625 yC) ) ) )这里的0.0625是一个比较小的偏移量实际使用时可以根据格点设置调整。如果你发现label还是压着pin name适当把这个值调到大一点。还有一点label的orient方向我没有刻意设置默认R0就可以。pin朝上的label和pin朝下的label在可读性上差别不大保持默认最简单。3.4 给label加标记运行几次都不重复脚本写到这里其实已经能跑了。但有一个很实际的问题运行两次label会重复打两遍。Virtuoso不会自动帮你判断“这里已经有一个同名label了”第二次跑还会再创建一个原理图上就出现两个一模一样的net label很丑而且可能导致网表工具警告重复label。解决办法是给label加上自定义属性作为标记。我在创建label之后立即用dbReplaceProp给它塞了一个名为autoPinLabel的boolean属性值为t。这样每次运行前可以先遍历检查如果某个pin附近已经存在带这个标记的label就跳过不建。检查函数如下procedure(autoPinLabelExist(cv netName x y) let((nets lbls found) nets dbGet(cv~nets) foreach(net nets when(net~name netName lbls net~labels foreach(lbl lbls when(lbl~prop~autoPinLabel found t ) ) ) ) found ) )这个函数的思路是遍历当前cellView的所有net找到名字和目标netName相同的net其实netName本身就是从net取出来的所以一定找得到然后检查这个net下的所有label里有没有带autoPinLabel标记的。有就说明之前已经打过了跳过。如果希望彻底重打可以再写一个清理函数把所有带标记的label先删掉再重新执行创建逻辑procedure(autoPinLabelClean() let((cv nets lbls rmCnt) cv geGetWindowCellView(hiGetCurrentWindow()) nets dbGet(cv~nets) rmCnt 0 foreach(net nets lbls net~labels foreach(lbl lbls when(lbl~prop~autoPinLabel dbDeleteObject(lbl) rmCnt rmCnt 1 ) ) ) printf(autoPinLabel: 已清理 %d 个旧label。\n rmCnt) hiRedraw() ) )实际开发中我的建议是把“先清理、再创建”作为一个整体开关放在脚本里。默认情况下每次运行先清理旧标记再全量重建这样原理图始终是干净的。如果你只想增量补充缺失的label就把清理步骤注释掉。3.5 选中版本只处理你需要改的pin有时候你不想动整个schematic只想给某一个子模块的pin加label。这时候可以选中一组pin然后调用只处理选中对象的版本。procedure(autoPinLabelOnSelected() let((sel cnt skipCnt pin netName bodyBBox x y newLabel) sel geGetSelSet() cnt 0 skipCnt 0 foreach(pin sel when(pin~objType pin netName pin~net~name if(netName then bodyBBox autoPinLabelGetBodyBBox(pin) when(bodyBBox x (caar(bodyBBox) caadr(bodyBBox)) / 2.0 y (cadar(bodyBBox) cadadr(bodyBBox)) / 2.0 newLabel schCreateLabel( ?cellView geGetWindowCellView(hiGetCurrentWindow()) ?layer wireName ?name netName ?origin list(x y) ?just centerCenter ?magnitude 0.0625 ) when(newLabel dbReplaceProp(newLabel autoPinLabel boolean t) cnt cnt 1 ) ) else skipCnt skipCnt 1 ) ) ) printf(autoPinLabel: 选中pin中已创建 %d 个label跳过 %d 个悬空pin。\n cnt skipCnt) hiRedraw() ) )这里用geGetSelSet()获取当前选中的对象列表。geGetSelSet()返回的是当前schematic window里所有选中的对象类型可能包括inst、wire、pin等所以用pin~objType pin进行过滤。注意不同版本中pin的objType字符串可能略有差异有的是pin有的是term。如果发现过滤不到可以先用print(sel~objType)看一下实际类型。这个选中版本非常实用。比如在top level只选了某个子模块的几个IO pin一跑就只给这几个pin加label不会动其他任何东西。对于层级多、pin多的大图这种局部操作比全图操作更安全。3.6 加载脚本与快捷键绑定脚本写完保存为autoPinLabel.il文件后在CIW窗口里执行load(autoPinLabel.il)然后运行全图版本autoPinLabelCreate()或者选中pin后运行选中版本autoPinLabelOnSelected()我习惯把它挂到快捷键上这样效率更高。在CIW里执行hiSetBindKey(Layout KeyF9 autoPinLabelOnSelected())这里有一个容易踩的坑不同版本的Cadenceschematic editor的binding table名称可能不一样。有的版本是Layout有的版本是Schematics还有的通用的是General。我在一个比较老的版本上绑定Layout没问题换到新版本之后发现F9按了没反应查了好久才意识到是table名变了。如果你也遇到绑定不生效用hiGetBindTableList()列出当前所有可用的binding table名称换成实际生效的那个就行。4. 实际运行结果与效果对比4.1 全图自动标注效果我在一个中等规模的模拟顶层上测试过这个脚本。这个顶层大概有80多个IO pin混合了电源、地、模拟信号、数字控制信号和各种bus其中还有几个pin是悬空没接线的。跑之前整个schematic看起来比较空pin的name倒是都在但net名一个都看不见。要查信号只能一个一个hover。跑完autoPinLabelCreate()之后所有有连接关系的pin旁边都多了net label整张图的信息密度一下子高了很多评审的时候别人也能一眼看清每根pin的信号流向。脚本输出的统计信息很直观autoPinLabel: 已创建 78 个label跳过 3 个悬空pin。3个悬空的pin提示我上层可能有连线漏了。这其实是一个额外的好处脚本相当于做了一次简单的“pin连接性检查”哪些pin没接net一目了然。旁边有pin name旁边没有net label的就是悬空的。4.2 视觉效果的调整经验第一次跑出来的效果其实不太理想。原因是label文字大小和pin name文字大小不一致放在一起显得很突兀。pin name通常是0.0625这个级别但net label如果也用0.0625又偏小打印的时候看不清。后来我把?magnitude做成参数默认取0.0625实际项目里我会根据schematic的显示比例手动调一下。经验上模拟设计库的pin name一般在0.0625到0.125之间net label取0.0625基本合适如果是比较紧凑的数字标准单元库可能0.03125就够了。多试两次找到当前设计看着最舒服的那个值就行。还有对齐方式。默认的centerCenter把label文字中心和pin body中心重合在pin body比较小的情况下会盖住导线阅读体验不好。如果遇到这种情况可以改成centerBottomJust或centerTopJust让label挂在pin body的下方或上方。just参数的可选值有很多包括centerCenter、centerTopJust、centerBottomJust、leftCenterJust、rightCenterJust等实际效果在schematic里试一下就知道。4.3 层次化过程中的几个特殊场景实际使用中有些特殊场景值得单独说一下。第一是顶层cell的IO pin。cellView里的pin分两种情况一种是在当前cell里创建并连线的内部pin一种是作为子模块被上层引用时暴露出来的port。在我们脚本里cv~pins取到的就是当前cellView自身的所有pin对象不管它的类型是input、output还是inout。这意味着top level的IO pad和某个子block的port都能被覆盖到。第二是bus pin。如果pin是A3:0这种bus pin它对应的net往往也是A3:0。label打出来会带尖括号这在schematic里是完全合法的。但如果你的schematic里bus被bit-blast了每个bit是一个独立pin对应net名可能就是A3、A2这种单bit名字。脚本会自动适配不需要额外处理。第三是重复net名的情况。如果同一张schematic里存在两个不同net使用了同一个名字——这本来是设计不规范但现实中偶尔会有——脚本会在两个net上都打同名的label。这个属于数据自身的问题脚本无需特殊处理但你应该在检查设计时主动排除这种命名冲突。5. 常见问题、坑点与排查实录5.1 问题速查表现象可能原因解决方法label打上去了但点击label不高亮netlabel没有和net关联成功确认origin是否落在pin body内不要取pin text的中心脚本报*Error* get: no such parameter - labels版本差异导致net对象没有labels属性检查一下当前版本的文档换成遍历cv~shapes过滤label的方式bus pin的net名显示为A3:0但想显示A3bus被整体连接net确实是bus名这不是bug如果非要单bit名需要先bit-blast bus运行两次label重复没有检查已有label使用autoPinLabelExist去重或先执行autoPinLabelClean清理重建F9快捷键没反应binding table名称不对用hiGetBindTableList()查看当前有效的table名换一个绑定表部分pin没有生成labelpin悬空或pin~net返回nil检查上游连线确认pin是否真的连接了net脚本会统计并打印skip数量label文字方向和pin name不一样没设置?orient保持默认即可也可以统一显式设置?orient R05.2 我踩过的几个坑第一个坑是label关联不生效。早期版本我取的是pin整个对象的bBox中心结果发现有一部分label打出来是“悬浮”的点击label不会高亮net。排查发现pin的bBox是包含text在内的如果pin name text比较长bBox中心就会偏向text那一边脱离了pin body区域。后来改成遍历pin figures、过滤掉text、取body的bBox问题就彻底解决了。第二个坑是清旧label的时候误删了手工label。一开始我判断“旧label”的方式比较粗暴只要net名下已经存在label就删。结果把设计者自己手工加的一些额外注释label也删了差点出事故。后来改成只删除带autoPinLabel标记的label手工label完全不受影响。这也是为什么我坚持要给每一条脚本创建的label打上自定义属性这是批量自动化的基本素养。第三个坑是脚本在层次化schematic中跑了之后子模块的pin也被打了重复label。这倒不是脚本逻辑问题而是使用场景问题。如果你在一个顶层cellView中选择了“descend edit”模式窗口中显示的可能是子模块的内容此时geGetWindowCellView()返回的是你正在编辑的那个子模块cellView。脚本会在正确的位置运行但如果你忘了自己还在子模块里面可能会误以为脚本跑错了。5.3 扩展玩法从“标注”到“检查”这个脚本最直接的用途是自动打label但稍微改一下它还能干更多事情。比如你可以加一个“反标”功能用脚本读取出pin对应的net名之后把net名反向写回pin的name里。这在某些网表恢复场景下非常好用可以快速修正pin name和net name不一致的问题。只要在遍历循环里加一句dbReplaceProp(pin name string netName)就行或者用pin~name netName直接赋值。做之前一定要确认设计是否需要这种修改因为它会改变pin的物理名字影响连接关系。再比如你可以把脚本的输出从“创建label”改成“生成报告”。遍历所有pin检查哪些pin没有net哪些net只有单一pin哪些net明明有名字但schematic里看不到label。这样它就从一个加label的小工具变成一个schematic连接性健康检查工具。尤其对于从别人手里接过来的、没有经过规范化整理的设计这种自动化检查能省掉大量人工核对时间。如果你需要把它集成进团队流程还可以放到~/.cdsinit里每次打开Virtuoso自动加载。或者和项目的Makefile、仿真前check脚本串起来在跑仿真或者LVS之前自动执行一遍保证schematic里的net标注始终是最新的。这些扩展都不需要改核心逻辑只是把脚本的输出或触发方式做一些调整。回到最本质的用法当你在schematic里运行一次自动标注之后整张图的信号走向变得一目了然评审、检查、写文档都会顺畅很多。我自己在用了这个脚本之后再看那些没有net label的schematic总觉得少了点什么。如果你也经常被这种重复标注困扰建议直接在现有脚本基础上改一套适合自己的版本把label层、文字大小、偏移方向这些参数按照你们的设计规范固定下来以后就是一行load加一个快捷键的事情。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑