
简介这份资源是《Cadence SKILL Language User Guide》的中文翻译版基于产品版本6.1.6整理面向英语阅读能力有限但具备一定C语言基础的技术人员帮助其快速掌握SKILL脚本语言在集成电路设计自动化中的应用。内容涵盖SKILL语言简介、与Lisp的关系、安装与环境配置、语法示例、数据类型标识符、编程指南及技术支持资源等模块并附有前言、许可条款、节名含义等章节说明便于按主题检索学习。资源包为1个PDF文件大小约68.23MB适合作为工具书随时查阅。目前已有526人学习下载读者可借此理解SKILL基本语法结构与核心概念结合示例代码练习定制化设计工具用于电路验证、布局编辑等实际任务也可作为官方文档之外的辅助参考资料。1. 从一份翻译文档说起SKILL 语言到底解决什么问题如果你在版图、原理图或者验证环境里折腾过自动化脚本大概率听过 SKILL 这个名字。它不像 Python 那样能随手 pip 一个包也不像 Tcl 那样语法轻到几乎透明但在定制化 IC 设计流程里它是一把绕不开的钥匙。这份《sklanguserCadence SKILL Language User Guide(翻译)》就是针对产品版本 6.1.6、2014 年 11 月发布的那份官方手册的中文翻译版面向的是英语阅读吃力、但有 C 语言底子的工程师。它不讲花哨的框架只把 SKILL 的语法、函数定义、列表操作、流程控制这些基础但关键的东西掰开揉碎。适合谁刚接手版图自动化脚本维护的新人、想从零写一个自定义检查工具的验证工程师以及需要快速查阅语法细节的老手。翻译版最大的价值在于降低阅读门槛但机器翻译的痕迹也意味着有些术语需要对照英文原版确认这一点后面会细说。2. SKILL 语言核心机制从 Lisp 血统到列表操作2.1 为什么 SKILL 长得像 Lisp以及这对写脚本意味着什么SKILL 的语法根基来自 Lisp这一点在手册第一章“SKILL 与 Lisp 的关系”里就点明了。所有东西都是列表函数调用也是列表操作符本质上就是函数。这意味着你写a b和写(plus a b)在 SKILL 里是一回事只是前者是语法糖。对 C 语言背景的人来说最反直觉的是括号的位置函数名在括号内参数跟在后面而不是func(a, b)那种形式。手册里给了个最简单的例子调用plus函数做加法。你可以在 CIWCommand Interpreter Window里直接敲(plus 3 4)返回7。这行代码的逻辑是plus是函数名3和4是参数整个表达式被括号包裹。参数说明上plus接受任意数量的数值型参数返回它们的和。如果你写(plus 3 4 5)返回12说明它是变参函数。为什么强调这个因为很多从 C 转过来的人会习惯性写plus(3, 4)SKILL 会直接报错。手册里“调用 SKILL 函数”那一节专门提醒了这一点。另外SKILL 的变量不需要声明类型但区分全局和局部let和prog用来定义局部变量setq用来赋值。手册第 3 章“在 SKILL 中创建函数”里详细讲了procedure、lambda、nprocedure这些定义函数的语法函数其中procedure是最常用的。我一般会建议新手先在 CIW 里把手册第 1 章的示例逐个敲一遍尤其是列表构建和访问的部分。因为 SKILL 处理版图数据时几乎全是列表的嵌套和遍历不熟悉car、cdr、cons这些操作后面写稍微复杂点的脚本就会卡住。2.2 列表构建、访问与修改三个必须刻进肌肉记忆的函数手册第 1 章“SKILL 清单”一节把列表操作单独拎出来讲可见其分量。构建列表用list函数(myList list(1 2 3 4))这行把1 2 3 4四个元素组成一个列表赋值给myList。逻辑上list接受任意数量的参数返回一个以这些参数为元素的列表。参数说明参数可以是任意 SKILL 对象包括数字、字符串、符号甚至嵌套列表。访问列表用car和cdr。car返回第一个元素cdr返回除第一个元素外的剩余部分。手册里有个容易翻车的地方cdr返回的仍然是列表不是单个元素。比如(cdr (1 2 3))返回(2 3)而不是2。如果你想取第二个元素得写(car (cdr (1 2 3)))。这个嵌套写法在版图遍历里非常常见比如遍历一个 shape 的 points 列表。修改列表用cons、append、setq组合。cons把元素加到列表头部(newList cons(0 myList))newList变成(0 1 2 3 4)。注意cons不修改原列表而是返回新列表。如果你要追加到尾部用append但append会复制整个列表大列表上频繁用会有性能问题。手册里“修改列表”一节提到了这一点但没有展开性能分析。实际项目中我见过有人在循环里反复append一个几千个元素的列表结果脚本跑了几分钟才结束。后来改成先cons再reverse时间降到几秒。提示SKILL 的列表是单向链表结构头部插入是 O(1)尾部追加是 O(n)。处理大规模版图数据时优先用cons构建最后reverse一次。2.3 文件 I/O 与流程控制让脚本能读数据、能写报告手册第 1 章“文件输入/输出”一节讲了print、println、fprintf、fscanf这些函数。显示数据到 CIW 用println写文件用fprintf配合outfile。读文件用infile和fscanf。这里有个参数细节fprintf的第一个参数是文件句柄第二个是格式字符串后面跟变量。格式字符串里%s对应字符串%d对应整数%f对应浮点数。手册里给的示例是写一个简单的报告文件但没提错误处理。实际用的时候如果文件路径不存在outfile会返回nil后面fprintf就会报错。我一般会加一层判断(outPort outfile(/tmp/report.txt w)) (if outPort then (fprintf outPort Count: %d\n count) (close outPort) else (println Failed to open file) )逻辑说明outfile第二个参数w表示写模式返回文件句柄或nil。if判断句柄非空才写入最后close关闭。参数上close必须调用否则缓冲区数据可能不落盘。流程控制方面手册第 1 章“控制流程”一节覆盖了if、when、unless、case、for、foreach。foreach是遍历列表最常用的语法是(foreach item myList ...)。for循环的语法比较特殊(for i 0 10 ...)表示i从 0 到 10。注意 SKILL 的for不包含结束值还是包含手册里写的是“从初始值到最终值”实际测试是包含最终值的。这一点和 C 语言的for不同容易多跑一次或少跑一次。我踩过的坑是用for循环遍历一个列表的索引结果多访问了一个越界位置因为误以为不包含结束值。后来统一改用foreach省心。3. 函数定义与参数传递从 procedure 到 key 选项3.1 procedure、lambda 和 defmacro 的选择逻辑手册第 3 章“在 SKILL 中创建函数”列出了procedure、lambda、nprocedure、defmacro、mprocedures、defglofun这些定义函数的语法函数。对大多数脚本来说procedure是默认选择。它的语法是(procedure (myFunc arg1 arg2) 文档字符串 (let (localVar) (localVar arg1 arg2) localVar ) )逻辑说明procedure第一个参数是函数名和形参列表后面是函数体。函数体最后一个表达式的值作为返回值。参数说明形参按位置接收实参调用时用(myFunc 3 4)。let用来声明局部变量避免污染全局命名空间。lambda用于定义匿名函数通常作为参数传给mapcar或sort这类高阶函数。defmacro定义宏在编译期展开适合写语法糖但调试困难新手慎用。nprocedure是“新式 procedure”手册里提到它和procedure的区别在于参数处理方式但实际项目中我很少见到有人用大多数遗留代码都是procedure。选择逻辑很简单需要复用、需要文档字符串、需要明确参数列表用procedure临时传给mapcar的小函数用lambda需要控制求值时机才考虑defmacro。手册里没有明确给出选择建议但“各种功能”一节列出了每个语法函数的适用场景可以对照查阅。3.2 rest、optional、key三种参数选项的实战用法手册第 3 章“定义参数”一节详细讲了rest、optional、key三个选项。这是 SKILL 函数定义里最灵活也最容易出错的部分。rest让函数接收任意数量的额外参数打包成一个列表。语法(procedure (sumAll rest nums) (let ((total 0)) (foreach n nums (total total n) ) total ) )调用(sumAll 1 2 3 4)返回10。逻辑说明rest后面的nums是一个列表包含所有额外参数。参数说明rest必须放在参数列表最后前面可以有固定参数。optional让参数变成可选的调用时不传就用默认值nil。语法(procedure (greet name optional (greeting Hello)) (println greeting name) )调用(greet A)输出Hello A调用(greet A Hi)输出Hi A。注意默认值要用括号包起来写成(greeting Hello)否则greeting会被当成必选参数。key让参数通过关键字传递调用时用?key value的形式。语法(procedure (createRect key (width 10) (height 20) (layer M1)) ... )调用(createRect ?width 50 ?layer M2)height用默认值20。逻辑说明key后面的参数列表里每个参数可以指定默认值。参数说明调用时关键字必须和定义时一致顺序无关。手册里“结合参数”一节讲了这三种选项可以混用但顺序必须是固定参数 →optional→rest→key。我见过有人把key写在rest前面结果rest把关键字也吞了函数行为完全错乱。这个顺序规则手册里写了但翻译版可能表述不够醒目建议对照英文原版确认。3.3 局部变量与全局变量let、prog 和 setq 的边界手册第 3 章“局部变量”一节讲了let和prog两种定义局部变量的方式。let的语法是(let (var1 var2) body)变量初始值为nil。prog的语法类似但允许在变量列表里直接赋初值(prog (var1 10) body)。手册里还提到“初始化局部变量为非空值”可以用let配合setq实现。全局变量用setq直接赋值不需要声明。但手册“全局变量”一节提醒了命名冲突的风险。SKILL 没有命名空间隔离所有全局变量共享一个顶层环境。如果你定义的全局变量名和 Cadence 内部函数名冲突可能会覆盖掉系统函数导致后续操作异常。我踩过的坑是定义了一个叫db的全局变量结果和 Cadence 的数据库访问函数db冲突后面调用dbCreateRect之类的函数全部报错。排查了半天才发现是变量名的问题。从那以后我定义全局变量一律加项目前缀比如myproj_count。注意SKILL 的全局变量没有作用域限制任何加载的脚本都能读写。多人协作的项目里变量命名规范比函数命名规范更重要。4. 避坑与排查翻译版手册里没写清楚的五个细节4.1 括号匹配报错但找不到位置现象加载脚本时提示“括号不匹配”但肉眼检查所有括号都是成对的。原因SKILL 的注释符是;和/* */但字符串里的括号也会被计入匹配。如果字符串里有一个未转义的(解析器会认为括号不平衡。解决用checkSyntax函数或者逐段注释法定位。我一般会把脚本分成几段逐段加载直到找到报错的那一段。另外手册“评论”一节提到;是行注释/* */是块注释但块注释不能嵌套嵌套会导致解析错误。4.2 函数重定义后行为没变现象修改了函数定义重新加载脚本但调用时还是旧行为。原因SKILL 的函数重定义需要先卸载旧定义或者用defun重新定义。手册“重新定义 SKILL 函数”一节提到了这一点但翻译版表述比较模糊。解决在函数定义前加(undefun myFunc)或者直接用procedure重新定义SKILL 会自动覆盖。但如果旧函数已经被其他函数引用可能需要重新加载依赖的脚本。4.3 foreach 遍历时修改列表导致跳过元素现象在foreach循环里用setq修改正在遍历的列表结果有些元素被跳过。原因foreach内部用cdr推进修改列表结构会破坏遍历状态。解决不要在foreach里修改原列表用mapcar生成新列表或者先复制一份再遍历。手册“foreach 函数”一节没有明确警告这一点但这是 Lisp 系语言的通病。4.4 文件路径中的空格导致 outfile 失败现象outfile返回nil文件没创建。原因路径里有空格SKILL 把空格当成了参数分隔符。解决用双引号包裹路径或者用strcat拼接路径时确保没有多余空格。手册“将数据写入文件”一节的示例路径没有空格所以没暴露这个问题。实际项目中项目路径带空格很常见这个坑几乎每个人都会踩一次。4.5 翻译版术语与英文原版不一致现象按翻译版手册写的函数名加载时报“未定义”。原因机器翻译把某些函数名也翻译了比如procedure被译成“过程”但代码里必须用英文。解决代码一律以英文原版为准翻译版只看解释性文字。手册第 3 章“各种功能”一节里procedure、lambda这些词在翻译版里有时保留英文有时译成中文阅读时需要自行判断。我一般会同时打开英文原版和翻译版代码部分对照英文概念解释看中文。5. 进阶技巧用 SKILL 写一个可复用的版图检查工具5.1 从零搭建一个检查工具的函数骨架假设你要写一个检查版图中指定层上所有矩形宽度的工具。核心逻辑是遍历当前 cellview 的所有矩形筛选出目标层计算宽度输出报告。手册里没有完整的示例但把各章节的函数组合起来就能实现。下面是我常用的骨架(procedure (checkRectWidth layerName minWidth) (let ((cellView (geGetEditCellView)) (rects nil) (count 0) (reportPort nil)) ;; 获取所有矩形 (rects geGetSelSet()) ;; 打开报告文件 (reportPort outfile(/tmp/width_report.txt w)) (if reportPort then (fprintf reportPort Layer: %s, MinWidth: %f\n layerName minWidth) (foreach rect rects (when (equal (dbGetObjType rect) rect) (let ((lpp (dbGet rect lpp)) (width (dbGet rect width))) (when (equal (car lpp) layerName) (count count 1) (if (lessp width minWidth) then (fprintf reportPort Violation: width %f %f\n width minWidth) ) ) ) ) ) (fprintf reportPort Total checked: %d\n count) (close reportPort) else (println Cannot open report file) ) ) )逻辑说明geGetEditCellView获取当前编辑的 cellviewgeGetSelSet获取选中的对象列表。dbGetObjType判断对象类型dbGet获取属性。lpp是层-目的对car取层名。lessp是小于比较函数。参数说明layerName是字符串比如M1minWidth是浮点数单位与数据库一致。报告文件路径硬编码为/tmp/width_report.txt实际用的时候可以改成参数。这个骨架覆盖了手册里列表遍历、文件 I/O、条件判断、函数定义几个核心知识点。写完之后在 CIW 里加载(load /path/to/checkRectWidth.il) (checkRectWidth M1 0.1)如果报错先检查load的路径是否正确再检查函数名是否和已有函数冲突。5.2 调试技巧用 printf 和断点定位问题SKILL 没有现代 IDE 的断点调试最实用的调试手段是printf打日志。在关键分支插入println输出变量值比如在foreach里打印每个矩形的宽度确认遍历是否正常。手册“显示数据”一节讲了print和println的区别print不换行println换行。我一般用println输出带标签的变量值比如(println width: width)。另一个技巧是用errset捕获错误。errset的语法是(errset expr)如果expr执行出错返回nil否则返回结果列表。手册里没有重点讲errset但在实际脚本里非常有用。比如文件操作可能失败用errset包一层避免整个脚本崩溃(errset (close reportPort))如果reportPort已经是nilclose会报错但errset会吞掉错误继续执行。5.3 性能优化避免在循环里做重复计算版图数据量大的时候脚本性能很关键。手册没有专门讲性能优化但根据经验最常见的性能杀手是在循环里重复调用dbGet或者重复构建列表。比如上面的检查工具dbGet rect lpp和dbGet rect width在每次循环里都调用一次如果矩形数量上万累积开销不小。优化方法是在循环外先把需要的属性批量取出来或者用dbGet的批量查询模式。另一个优化点是报告文件写入fprintf每次调用都有 I/O 开销可以先把结果存到列表里循环结束后一次性写入。我一般会在脚本里加一个计时器用getCurrentTime记录开始和结束时间输出耗时。这样能快速判断优化是否有效。手册里没有getCurrentTime的说明但它是 SKILL 内置函数可以直接用。从那以后我每次写稍微复杂点的 SKILL 脚本都强制走一遍“先跑通、再打日志、最后优化”的流程不跳过任何一步。希望帮到你。本文还有配套的精品资源点击获取