
做射频或模拟版图的人应该都经历过这样一个场景手里拿到一份电感参数需求表要往版图里塞一个“3圈、线宽2.5um、间距1.8um、内径36um”的电感。你打开以前的版图改改拉伸重新布走线把一圈圈边缘对齐再盯着DRC慢慢调改完一个电感下一个参数组合又得全部重来。这种重复劳动极度拖节奏还容易改出断线、短路和违规。用 Cadence SKILL 写一个参数化电感 PCell 之后局面彻底变了双击单元改参数几何自动重建几十种候选电感几秒钟就能全部摆出来。这篇文章适合两类人。一类是做射频、模拟版图天天被电感、变压器、匹配网络折磨的工程师可以把我的思路直接搬到你自己的PDK环境里另一类是刚开始接触 Cadence 二次开发想知道 PCell、SKILL 到底怎么落地的人我会从环境准备、坐标计算到完整代码一步步拆开讲。最后还会把我踩过的一些坑和调试习惯列出来省得你再走弯路。1. 项目概述与整体设计思路1.1 为什么必须把电感参数化电感在射频电路里出现频率高但结构高度雷同。绝大多数情况下匹配网络用的都是平面螺旋电感本质就是一根宽金属线在版图里绕圈。真正变化的无非是内径、匝数、线宽、间距这四个参数加上所在的金属层和出线方向。按理说这种元件最容易标准化但很多团队到今天还在手动画图核心原因不是懒而是没有把“参数化”这件事跑通。手动绘图最大的问题不是慢而是不易维护。项目改一版电感从3圈改成4圈你以为只是加一圈走线结果发现绕线顺序、拐角位置、出线口全部要跟着变改完以后还要重新跑DRC、LVS万一某个拐角坐标敲错整根电感就短路了。此类问题排查起来很隐蔽因为视觉上并不容易发现两根相邻金属线距离不对。用 PCell 之后参数与几何完全由代码驱动改圈数、改线宽、改间距都只是一次重新计算不容易引入低级坐标错误。从工程管理的角度看参数化还能让一名工程师同时维护多个电感版本。你不需要在库里堆几十个“电感_3圈”“电感_5圈”的静态单元而只需要一个带参数的电感 PCell把 width、spacing、inner、turns 做成可编辑属性。需要哪个拓扑就用哪个拓扑数据量小、维护成本低做仿真和做版图的人拿到的是同一条参数链路。1.2 这个 PCell 到底要实现什么能力我给自己定的目标是先跑通一个方形平面螺旋电感。方形的坐标计算最简单逻辑清楚也方便后续扩展到八边形、圆形甚至八边形加切角的常用电感结构。核心功能就四点输入参数线宽 width、间距 spacing、内径 inner、匝数 turns、所在图层 layer。输出几何根据参数实时生成一条完整的螺旋形走线。可触发更新每次改参数版图图形自动重建不需要手工删掉重画。集成到 Cadence 环境可以作为 cellView 存在也能在 Virtuoso 的 layout 编辑环境中直接加载和编辑。这里我没有一开始就上很多花哨功能比如自动加 PAD、自动加 DRC 变量、支持差分电感等。原因很简单如果连最基础的“改参数后几何重建”都没跑通附加功能只会把调试难度翻倍。先把核心骨架打扎实再多花半小时把 PAD 和旋转属性补上才是稳妥的做法。整体设计上代码分两层。第一层是纯算法层负责根据参数计算螺旋的所有拐点坐标第二层是 Cadence 交互层负责把坐标点串成金属图形落地到当前 cellView。把两层分开有个好处算法部分不依赖 Cadence 的图形环境可以在任何文本环境里单独验证坐标逻辑发现数学问题远比在版图里发现问题快得多。2. 原理准备PCell、SKILL 与 Cadence 开发环境2.1 PCell 到底是什么PCell 全称 Parameterized Cell也就是参数化单元。它不像普通版图 cell 那样把图形固定死而是保留一组用户可修改的参数版图在打开或参数变更时根据当前值动态生成图形。最典型的例子是 PDK 里的电阻、电容、MOS 管宽度 W 一变整个器件外形自动跟着拉伸或收缩。对电感这类结构PCell 的优势尤其明显。普通图形改参数要一块块拖动PCell 则是在代码里定义“几何规律”。你告诉它“这是一个方形螺旋线宽 3um、间距 2um、内径 40um、4圈”它就能推出每一段线段终点坐标再把线画出来。这种能力不是画图工具给的而是你写的规则代码给的。往深一步说PCell 的意义在于把版图设计的“案例”升级为“规则”。一个手绘电感只解决一个固定尺寸的问题一个逻辑正确的电感 PCell 解决的是整族结构的问题。后续不管是改工艺、调版图寄生还是在不同电路版本间做参数扫描都是在复用同一条规则而不是重新画图。2.2 SKILL 语言的特点SKILL 是 Cadence Virtuoso 平台内置的脚本语言语法类似 Lisp大量使用括号前缀表达式。它可以直接调用 Virtuoso 的版图数据库对象创建 cellView、绘制图形、编辑属性并且能在 CIWCommand Interpreter Window里交互式运行。对不熟悉 Lisp 的人刚开始看到一堆括号可能会不适应但实际用起来比想象中简单它就是“函数名 参数列表”的写法。举个例子创建一个矩形图形在 SKILL 里大概是这样dbCreateRect(cv list(M5 drawing) list(0:0 10:10))这里的dbCreateRect是创建矩形的函数第一个参数是 cellView第二个参数是图层和 purpose第三个参数是两个角点坐标。这种写法很直白关键在于你得知道有哪些函数可用、每个参数是什么意思。SKILL 还有一个特点非常适合做 PCell 开发它支持key参数和缺省值。你可以定义width3.0、spacing2.0这类默认配置调用时只传需要改动的参数其他沿用默认值。这让 PCell 的使用体验非常自然也方便后续做参数范围校验。2.3 开发环境和调试入口我使用的是 Cadence Virtuoso IC6.1.8 版本老版本 6.1.6 也能跑差异不大。开发前需要确认两件事一是你的 PDK 环境和工艺库已经正确加载二是 CIW 窗口正常工作。CIW 是所有 SKILL 交互的核心入口你可以直接在命令栏输入单行 SKILL 测试也可以用load()函数加载整个脚本文件。我的习惯是把待调试代码放在单独的.il文件里然后用load(xxx.il)加载。这样做到好处是代码可追溯、可版本管理且不用每次修改都重新敲一遍命令行。对于比较复杂的 PCell建议加一排printf或println输出中间计算结果比如当前生成的坐标点数量和起点终点位置。load(/home/user/skill/spiral_inductor.il)如果看到t或没有报错信息说明加载成功。看到error开头的信息就要去查语法或者参数类型。调试的时候还要注意一个细节PCell 分“创建阶段”和“更新阶段”。第一次放置是创建之后改参数是更新。两个阶段都会执行代码所以凡是涉及资源创建的函数比如给 cellView 加图形要写清楚先删除旧图形再生成新图形否则改参数时版图里会出现一堆重叠金属。3. 从 0 到 1 实现参数化电感 PCell附完整代码3.1 螺旋坐标计算先把几何数学搞明白方形螺旋电感的几何看起来复杂其实核心只有一条规律走线不断绕圈每一圈之间的边长差异取决于width spacing。假设我们从内圈左上角开始出发先往右走一段长度再往上走一段然后往左、往下如此循环每次切换方向时边长适当增加就能自然形成螺旋。具体参数定义inner内圈第一条边的长度可以理解为内圈方孔的边长。width走线宽度。spacing相邻两圈走线之间的间距。turns螺旋圈数。step边长每次增加的步长等于width spacing。从内圈出发假设初始点位于(-inner/2, inner/2)也就是内圈左上角。第一段向右长度是inner第二段向上长度也是inner第三段向左长度变成inner step第四段向下长度变成inner step。每走两条边长度再增加一个step。这个规律用循环实现非常简洁用dir记录走了多少条边根据方向决定坐标加减每两条边步进一次。对应 SKILL 代码如下/* 计算方形螺旋电感的拐点坐标 */ procedure(genSpiralPts(inner width spacing turns) let((x y len step dir pts) x -inner / 2.0 y inner / 2.0 len inner step width spacing pts list(list(x y)) dir 0 while(dir turns * 4 case(modulo(dir 4) (0 /* 右 */ x x len ) (1 /* 上 */ y y len ) (2 /* 左 */ x x - (len step) ) (3 /* 下 */ y y - (len step) ) ) /* 每走两条边边长步进一次 */ if(modulo(dir 2) 1 then len len step ) pts cons(list(x y) pts) dir dir 1 ) reverse(pts) ) )这个函数返回一个坐标点列表每个元素都是list(x y)的形式正好可以作为后面绘制走线的输入。写完后建议先单独打印一遍坐标点确认首尾闭合、方向正确。常见的错误是最后一点没有落到正确终点导致走线整体偏移。3.2 生成版图图形调用数据库函数落地坐标计算只是第一步要让坐标真正变成版图里的金属还需要调用 Cadence 的图形生成函数。按照 Virtuoso 的传统接口最常用的是rodCreatePath它可以按照一系列坐标点画出一条指定宽度的 path非常适合螺旋走线。主函数可以设计成这个样子/* 创建方形螺旋电感 PCell */ procedure(createSpiralInductor(cv key (width 3.0) (spacing 2.0) (inner 40.0) (turns 4) (layer M5)) let((pts) /* 删除旧图形避免参数更新时重复叠加 */ mapcar(lambda((shape) dbDeleteObject(shape) ) cv~shapes) /* 计算新坐标 */ pts genSpiralPts(inner width spacing turns) /* 根据坐标生成走线 */ rodCreatePath( ?cvId cv ?layer list(layer drawing) ?width width ?pts pts ) t ) )其中cv是当前 cellView 的句柄。在 Virtuoso 的 layout 窗口里你可以用geGetEditCellView()获取当前正在编辑的 cellView 对象然后传入这个函数。如果cv~shapes返回空列表说明当前 cellView 里没有图形直接生成即可。有人会问为什么这里要先删除cv~shapes再重建因为 PCell 更新参数时如果不清理旧几何版图里会出现多套重叠走线视觉上像幽灵图形。这个问题我在第一次迭代时就踩过删除后整个更新逻辑就干净多了。还需要说明的是rodCreatePath是 Virtuoso 版图环境中比较高层级的接口它会自动处理 path 端点、连接点等属性。如果你所在环境没有这个函数也可以用dbCreatePolygon自己做但 path 更贴合电感的实际形态LVS 提取时也更友好。3.3 完整示例代码与调用方式为了让你能直接上手我把完整脚本整理在下面。这个版本不依赖特定 PDK只要你在自己的 Virtuoso 环境里加载运行最后一行函数就能在当前打开的版图 cell 中生成一个参数化电感。/* * spiral_inductor.il * 参数化方形螺旋电感 PCell 示例 * 运行前提已打开 Virtuoso layout 编辑窗口 **/ procedure(genSpiralPts(inner width spacing turns) let((x y len step dir pts) x -inner / 2.0 y inner / 2.0 len inner step width spacing pts list(list(x y)) dir 0 while(dir turns * 4 case(modulo(dir 4) (0 x x len ) (1 y y len ) (2 x x - (len step) ) (3 y y - (len step) ) ) if(modulo(dir 2) 1 then len len step ) pts cons(list(x y) pts) dir dir 1 ) reverse(pts) ) ) procedure(createSpiralInductor(cv key (width 3.0) (spacing 2.0) (inner 40.0) (turns 4) (layer M5)) let((pts) mapcar(lambda((shape) dbDeleteObject(shape) ) cv~shapes) pts genSpiralPts(inner width spacing turns) rodCreatePath( ?cvId cv ?layer list(layer drawing) ?width width ?pts pts ) t ) ) /* 示例调用 */ createSpiralInductor( geGetEditCellView() ?width 3.0 ?spacing 1.8 ?inner 36.0 ?turns 4 ?layer M5 )在 CIW 里逐行加载这个脚本后最后一个示例调用会直接在当前打开的版图里生成一个 4 圈、线宽 3um、间距 1.8um、内径 36um 的方形螺旋电感。生成后你可以选中走线旋转、移动、打孔行为跟手绘的图形完全一致唯一区别是它是由参数驱动生成的。3.4 参数说明与设计陷阱为了让参数更通用我把几个关键参数列成了一张表方便查阅参数名功能典型值影响width走线宽度3.0 um决定电感的串联电阻和Q值spacing相邻走线间距1.8 um决定互感与寄生电容inner内圈方孔边长36.0 um决定电感整体尺寸与感值turns螺旋圈数4直接影响感值大小layer走线金属层M5决定电磁特性和可用的绕线区域这个表格看起来很常规但实际设计时有个容易忽略的坑内径太小而线宽和间距又很大时第一圈和第二圈可能会重叠。因为代码里step width spacing第二圈边长的增长量就是这个值如果内径连两个 step 都不到螺旋在视觉上就会变成一团乱线。解决方法是加一个边界检查if(inner width 2 * spacing then error(inner too small for current width/spacing) )这类守卫条件看似简单却能避免大量无效 PCell。我以前遇到过参数填得极端导致 SKILL 直接崩溃的情况后来统一加了参数范围校验体验稳定很多。另外要注意的是rodCreatePath生成的 path 宽度是放在线上的属性而不是几何多边形的边界。也就是说后续做 DRC 或者导出 GDS 时真正占用的金属区域是“路径中心线 宽度”共同定义的确认版图时别只看中心线之间是否太近。4. 常见问题与排查技巧实录4.1 SKILL 加载报错的常见原因SKILL 脚本写多了以后你会发现大部分报错不是逻辑错而是语法和类型错。最常见的是procedure函数名前漏了名称或者参数列表里用了中文字符标点。Cadence 的解析器对这类错误比较宽容有时候会给出error提示但定位不准确这时候最有效的办法是把脚本切成小段一段一段加载定位到具体出错行。或者你可以充分利用 CIW 里的回显信息。每次加载脚本后CIW 会显示函数名和执行的最终状态如果某一行返回nil说明那行函数没有成功执行。例如rodCreatePath返回t才是成功返回nil就是参数不合法最常见的原因是 layer 名称写错比如 PDK 里只有M5没有M5之外的别名。还有一个常被忽略的问题同一 session 里重复 load 同一个脚本。如果脚本里有procedure定义Cadence 默认可能不会直接覆盖旧定义而是提示函数已存在。解决办法是在脚本开头加一句同步清理if(boundp(createSpiralInductor) then defun(createSpiralInductor ...) )或者干脆每次启动新 session 后直接加载新脚本。总之躲开这些低级问题能节省很多时间。4.2 坐标点算出来了但图形不对这是 PCell 开发里最常见的第二类问题坐标列表没报错但生成的图形乱套。我遇到过的典型情况包括起点不在预期位置导致整个螺旋偏离版图原点看起来像飘在空中。某个方向边长算错导致相邻两圈间距时大时小视觉上一圈贴着另一圈。最后一个点没有闭合走线末尾多出一截小尾巴。排查这类问题我的习惯是在genSpiralPts里临时加一段打印printf(point %L %L\n x y)每计算一个点就打印一次然后把点列拿到脚本环境下画图或者直接用坐标在脑海里过一遍。别觉得这样土很多坐标偏移的问题一眼就能从打印结果里看出来比盯着陌生版图猜快得多。如果问题出在 path 绘制而不是坐标上就重点检查rodCreatePath的?pts参数格式。它要求传入的是一个“由点组成的列表”也就是list(list(x1 y1) list(x2 y2) ...)。很多时候你传入一个扁平的坐标数组函数不报错但不画线非常隐蔽。4.3 PCell 更新时的图形重叠问题前面主函数里我用mapcar删除了cv~shapes这是参数更新的核心逻辑。但这里有一个潜在问题cv~shapes会返回当前 cellView 里所有图形比如你可能还想保留旁边的 PAD、出线铜块或标记层删除操作会把它们都清掉。对于专门的电感 PCell这个问题影响不大因为整个 cellView 就是电感本身如果是放在复杂版图里使用建议按 layer 过滤再删除只删除自己生成的图形。一个简单方案是在生成时给走线加一个唯一 propertyrodCreatePath( ?cvId cv ?layer list(layer drawing) ?width width ?pts pts ?prop list(list(spiral_inductor 1)) )然后删除时只删带着这个标记的对象。从我的实践看这个“加标记再删除”的习惯非常值得养成尤其当 PCell 要嵌入到更复杂的设计环境里时。另外还要注意 cellView 的 dirty 状态。如果在脚本里删除了大量对象Virtuoso 不会立刻帮你清理数据库需要手动保存或调用相关刷新函数否则后续操作可能还会读到旧图形数据。多数情况下手动保存一次即可解决。5. 工程化扩展让电感 PCell 更贴近真实设计5.1 增加出线端口和 Pin 标记一个只有金属走线的电感在 LVS 里通常很难被识别为完整器件因为缺少输入输出 Pin。真实项目里电感两端要接射频信号Layout 和 Schematic 对照时需要明确的 LVS pin。扩展思路不复杂走线生成完后在起点和终点位置分别创建两个 pin。SKILL 里可以先用rodCreateRect在端点画一个小方块再给方块加 pin 属性最后把 pin 名称设置为PLUS和MINUS或P1、P2。这样在 LVS 提取时就能自动把两端识别出来。这段逻辑可以放在一个独立函数里例如procedure(createInductorPins(cv pts layer) let((startPt endPt) startPt car(pts) endPt last(pts) /* 根据你的设计规则确定 pin 尺寸 */ ... ) )需要注意pin 尺寸不能只依赖单点坐标否则 pin 图形可能脱离金属线。我的做法是读取走线宽度和端点方向以端点为圆心生成一个稍大于线宽的正方形。这样无论从哪个角度看Pin 都稳稳地压住走线端点。5.2 支持八边形和圆形螺旋方形螺旋实现以后扩展到八边形其实不难。思路是把每条边的“直角拐点”替换成两个带 45 度角的点或者直接用一般多边形逼近。你可以在坐标计算函数里增加一个shape参数取值square、octagon、circle再根据取值切换不同的拐点生成逻辑。圆形螺旋的算法也类似只是把离散的直线段数量增多。一般用几十条短线段逼近一个圆采样点越多形状越圆但数据量也越大。对于电感仿真和 LVS 来说32 段左右已经足够不用追求 360 段。这个扩展很实用。很多工艺库里的高频电感其实偏向八边形因为八边形拐角比直角拐角的电流拥挤效应小Q 值更高。有了方形基础改起来很快建议你在实际项目里按需实现。5.3 与 CDF 参数绑定如果要让电感 PCell 进入 PDK 标准库像其他器件一样在放置时弹出参数表单还需要把 SKILL 函数和 CDFComponent Description Format绑定。这是一套更规范的集成方式你在 CDF 里定义参数名字段、默认值、单位Cadence 会自动把表单值和 SKILL 函数的key参数对接。绑定之后用户在版图里调用 Add Instance选到你的电感器件就能直接填写内径、线宽、匝数。双击备好的实例还能二次修改参数、实况再生。到这个阶段电感 PCell 才算是真正能交付给团队使用的标准器件。由于 CDF 配置和具体工艺库强相关这里不给固定菜单路径了。但你只要读懂上面的 SKILL 函数逻辑再对照 PDK 里普通电阻或电容的 CDF 配置基本能照猫画虎地移植过去。5.4 性能优化与大数据量图形平面螺旋电感走线很长尤其圈数多、采样点多时生成的 path 点数可能达到几百甚至上千。Virtuoso 本身处理这种数量级没问题但如果同时有几十个电感实例刷新和 DRC 速度会有明显下降。我的建议是采样点够用就好。方形电感不追求圆弧效果每条边两个点足够八边形每条边按两个点处理总共也才十几个点到几十个点。不要为了提高视觉效果盲目增加点数否则整个版图的性能都会被拖垮。另外一个优化习惯是给常用参数组合做缓存。如果团队里电感参数高度标准化比如固定就是 width3、spacing2、inner36、turns4 这一组合可以把生成好的几何实例直接复制而不是重复调用 SKILL 计算。毕竟拿现成版图实例复制比重新跑一次坐标计算和图形生成快得多。6. 实操心得与避坑清单聊到这儿我把整个过程中最重要的一些经验汇总一下方便你以后直接对照。首先PCell 开发的核心不是学会多少 SKILL 函数而是把参数到几何之间的规律理清楚。电感这种结构参数很少但几何关系的推导既简单又容易漏。建议你在写代码前先在纸上画几圈螺旋标出每一段走线的长度和方向确认无误后再落到代码里。我用这个方法省掉了至少三分之二的调试时间。其次所有跟坐标相关的函数都要保持“可打印、可检查”。SKILL 不是编译型语言加载即执行一旦在复杂逻辑里出错很难一眼找到问题。我养的工程习惯是每个关键函数后加几行printf调试输出测完再注释掉。别嫌丑关键时候能救命。最后别忘了给 PCell 加参数范围校验和默认值。天线效应、DRC 规则和电磁仿真的限制都会对电感尺寸有具体要求。如果不加校验极端参数下生成的图形虽然“合法”但它可能完全违反工艺设计规则后续 DRC 满屏报错反而拖慢项目节奏。从我自己的实践来看用 Cadence SKILL 写参数化电感 PCell真正难的不在语法而在“把设计规则表达成几何规律”。一旦把这一步做好电感相关的工作效率提升非常明显。你现在如果正处于“手动画图画到崩溃”的状态不妨照这篇文章搭一个最小可用的 PCell 原型先拿方形电感验证一遍再逐步扩展八边形、引脚和 CDF 绑定。顺利的话一个下午就能看到专属于自己的参数化电感在版图里流畅运行。