简介:这份 Cadence SKILL Language User Guide 中文翻译版面向英语阅读较吃力、但具备一定 C 语言基础的技术人员,服务于集成电路设计自动化中的 SKILL 脚本学习、查阅与入门培训需求。内容依据产品版本 6.1.6 整理,涵盖语言定位、与 Lisp 的关系、基础语法、数据类型标识符、示例代码约定、安装与环境配置、许可说明及技术支持资源等,并保留原指南章节结构与检索线索,便于按主题快速定位。压缩包内共 1 个 PDF 文件,约 68.23MB,可作为案头手册、机器翻译参考和模板化查阅素材。已有 527 人学习,读者可借助中文译文降低阅读门槛,对照示例理解脚本编写、环境搭建与文档查阅路径,逐步建立 SKILL 编程基础,并为后续定制设计工具和验证流程提供参考。
1. 打开一份 Lisp 方言的中文底牌:这份翻译文档到底帮谁省时间
如果你在模拟版图环境里写过hiSetPoint或dbCreateRect,多半绕不开 SKILL。可原厂手册动辄上千页,全是英文,术语密度高,查一个回调函数的参数顺序就得来回翻五六页。这份《Cadence SKILL Language User Guide》中文翻译版,对应的原文是 6.1.6 版本、2014 年 11 月发布的产品文档,整份内容覆盖语言基础、函数定义、控制流、列表操作和文件读写,属于能把 SKILL 从「靠抄同事代码」拉到「自己能写补丁」的那类材料。它适合两类人:一是刚进版图或模拟设计组、有点 C 语言底子但被 Lisp 括号劝退的工程师;二是想系统性把常用脚本整理成团队模板、却不想每次硬啃英文 PDF 的老手。需要明确一点:这是翻译件,不是官方中文手册,它的价值在「降低第一遍入门的阅读摩擦」,而不是替代原厂文档的权威性。先把定位说清楚,后面用起来才不会被译文里的个别拗口句子带偏。
2. SKILL 是 Lisp 的工业变种:先把语法骨架和数据类型立住
2.1 SKILL 与 Lisp 的关系,以及为什么不能拿「C 思维」直接写脚本
文档开篇一章专门讲 SKILL 与 Lisp 的关系,这不是凑篇幅。SKILL 的求值模型、符号(symbol)表示、前缀表达式、列表结构都继承了 Lisp 家族,但 Cadence 在工程接口上做了大量扩展。如果你带着 C 的思路写,第一周大概会踩两个坑:一是习惯写a = b + c再期待函数返回,结果 SKILL 里赋值表达式本身也有返回值,容易把中间结果当最终结果用;二是把操作符当语法结构看,而在 SKILL 里,加减乘除、比较、逻辑运算本质上都是函数调用。
原文里的例子是(plus 1 2)与(1 + 2)的关系说明,翻译文档也保留了「操作符是 SKILL 函数」这一节。落地理解就是:你在括号里写+、-、==,底层走的还是函数分派,参数顺序、求值顺序都遵循函数规则。这意味着两件事:第一,不要指望像 C 那样靠运算符优先级来「猜」结果,括号才是硬边界;第二,你可以把+当普通符号传给别的函数,这在写通用累加器的时候是能用的。
提示:机器翻译出来的文档,在「操作符」和「函数」混用的段落容易让人迷惑。遇到一个表达式的求值顺序说不清时,回到括号的最外层往里一层层剥,基本不会错。
常见的写法对照如下,建议一开始就用免费的小段代码形成肌肉记忆:
; 前缀写法,SKILL 的默认求值形式 (plus 1 2) ; 返回 3 ; 操作符位置看起来像中缀,本质仍是函数调用 1 + 2 ; 返回 3 ; 嵌套时保持前缀可读性更好 (plus (times 2 3) 4) ; 返回 10这里没有花哨技巧,核心是让读代码的人不依赖「中缀直觉」。在版图脚本里,坐标计算经常嵌套三层以上,前缀写法虽然一开始别扭,但省去了记优先级的负担。
2.2 数据类型六件套:数字、字符串、符号、字符、原子、列表
翻译文档第 2 章铺开了数据类型,但译文里「原子」和「符号」容易看混。按工程用法区分:数字和字符串是你会大量直接写进代码的;符号(symbol)通常用作变量名、函数名、属性名,很多 API 的入参就是符号而不是字符串,比如层名、对象属性键;字符是单引号包起来的单个字符,用得少但解析字符串时会碰;原子(atom)在文档里是作为「非列表」的统称,理解成 Lisp 里的最小数据单元即可;列表是 SKILL 的骨架,几乎所有参数表和返回值列表都靠它。
有一处译文我建议对照英文原文核一下:翻译版把t和nil的说明放在「术语和定义」附近,表述上不如后文控制流章节直白。实际写条件判断时你就记:nil为假,除nil以外的一切为真,t是习惯用的真值。别在if的条件里写(equal x t)这种比较,直接拿x当条件,非nil就过,这是 Lisp 系脚本最常见的误用之一。
; 符号和字符串不要混:API 多认符号 layerName = "M1" ; 字符串 layerSym = 'M1 ; 符号,前面有单引号 ; 数字运算直接嵌套 width = 0.12 halfW = width / 2.0 ; 返回 0.06 ; 列表是最常用的复合结构 point = list(0.0 1.5) ; 坐标点参数说明:list接收任意数量参数,返回列表;上面point的结构在调用dbCreateRect之类的版图接口时很常见,区别只是有的 API 认两个独立坐标参数,有的认一个列表,查手册时重点看形参类型,别凭习惯改。nil和空列表在多数判断里等价,但涉及car取值的接口,空列表直接传给car会返回nil,后续再对nil取属性就会报错,这是新手脚本第一次跑就崩的典型原因。
2.3 用最小闭环验证环境:加载、执行、看返回值
只看不跑是学不会的。这份文档的「开始」一章给了调用函数和看返回值的基本流程,落到工程环境里,最常见的是在命令行解释器里做三段验证:定义变量、调用一个内置函数、观察回显。原文提到的「函数的返回值」一节值得反复看,因为 SKILL 里很多函数都静默返回nil表示副作用执行成功(比如设置属性),如果你习惯 Java 那种「没有返回值就是失败」的思路,会把正常返回当异常。
; 交互式验证三段式 x = 10 ; 赋值,回显 10 (y = plus(x 5)) ; 回显 15,括号包住便于观察 printf("x is %d\n" x) ; 打印调试,返回 t 或 nil 视实现逻辑说明:第二行用额外括号把整个表达式包住是交互调试习惯,能让解释器把求值结果作为整体回显,翻译文档里没有强调这一点,但在排查嵌套表达式时很省事。参数上唯一需要注意的是printf的格式串沿用类 C 风格,%d对应整数,%s对应字符串,传错类型不会崩,但输出会乱,别在关键日志里依赖它做类型校验。
3. 列表与控制流:把手册里查得最勤的两块吃透
3.1 列表的构造、访问与就地修改,以及一个容易翻车的返回值约定
翻译文档中间几章关于「SKILL 清单」的部分,覆盖了构建列表、访问列表和修改列表。这一块是整份材料里最值得反复翻的,因为 SKILL 的工程数据几乎都以列表形式流转:版图对象列表、属性键值对、参数表、坐标序列。构建用list、cons,访问用car、cdr及其组合,修改分两类——返回新列表的函数和就地修改的函数,混用这两类是最常见的逻辑错误来源。
我的经验是:先记「取头」和「取余」两个动作,再记组合链条。car取第一个元素,cdr取除第一个之外的剩余列表,嵌套列表用cadr、cddr这类简写时不要跳着用,先用展开写法确认层级,再换简写。原文里提到「超右括号」(extra right parenthesis)这类排版层面的问题,翻译版保留了说明,实操中它对应的报错往往是「括号不匹配但位置飘了几行」,排查时不要盯着报错行看,往上回溯到上一个完整的顶层表达式。
; 构造与访问 p = list(1 2 3 4) first = car(p) ; 1 rest = cdr(p) ; (2 3 4) second = cadr(p) ; 2,等价于 car(cdr(p)) ; 就地修改:先确认函数是否改变原列表 q = cons(0 p) ; p 不变,q 为 (0 1 2 3 4) r = append(p list(5)) ; 返回新列表,p 不变参数和返回值的说明:cons的第一个参数是元素、第二个是列表,返回新列表;append接收两个列表并拼接,返回新列表。这两个函数都不会改动原列表,是函数式风格的写法。真正要留意的是就地修改类函数,翻译文档在「修改列表」一节提到它们会改变原对象并返回该对象,一旦你把同一个列表同时传给两条逻辑,前一条的修改会静默影响后一条,这种 bug 在数据量不大时极难复现。我一般会在函数的入口处对传入列表做一次copy,牺牲一点性能换取确定性,这在跑批处理脚本时尤其值得。
3.2 条件分支:if、when、unless、case 各自守什么边界
控制流章节把if、when、unless、case、for、foreach放在一起讲,翻译版没遗漏,但有些例句顺序被打乱了。按使用频次和语义边界重新梳理:if是二选一,有条件为真和条件为假的执行体;when只在条件为真时执行,没有其它分支;unless是when的反面,条件为假才执行;case适合等值多分支,但它的分派键必须是字面量或常量,不要拿运行时计算的字符串硬套,否则匹配不上还找不到原因。
一个在原文里没有大声强调、但实际会遇到的边界:if的第二个分支不写并不等于出错,它会返回nil,但这在你期望返回值参与后续计算时就是个隐式子。我在脚本里会强制自己给if的两条路径都写明确的返回,避免把「副作用函数返回nil」当成「表达式计算失败」来判。
; 分支覆盖的写法 if(x > 0 then printf("positive\n") else printf("non-positive\n") ) when(x > 0 printf("positive path\n") ) case(mode ("fast" printf("fast mode\n")) ("safe" printf("safe mode\n")) (t printf("unknown mode, fallback\n")) )逻辑说明:case的最后一个分支用t兜底,这是 Lisp 系脚本的惯例,不给默认分支会返回nil,后续取返回值做运算就崩。参数上注意case的分派键是求值后的等值比较,你写"fast"字符串没问题,但如果模式值本身是符号,两边类型要一致。翻译文档在这一段的例子混用了字符串和符号,照抄时先确认你手里的值是哪种。
3.3 for 与 foreach:循环变量作用域和列表遍历的坑
循环语法在译文里占了不小篇幅,for和foreach的名字接近,用途不同。for是计数循环,适合已知次数或递增索引;foreach遍历列表,变量每次取列表中的一个元素。最容易出错的地方是循环变量的作用域泄漏——在循环结束后继续引用循环变量,某些语境下值还留着,被当成有效结果使用。
; for:计数 for(i 0 3 printf("i = %d\n" i) ) ; foreach:遍历列表 shapes = list("rect" "polygon" "path") foreach(shape shapes printf("shape = %s\n" shape) )参数说明:for的写法是for(变量 初值 终值 循环体),这里终值是否包含取决于具体版本的实现,翻译文档在这个边界上表述不算利索,稳妥做法是在调试输出里验证一次,别凭记忆假设。foreach的列表参数不要传nil,空列表下循环体一次都不进,逻辑上不报错,但如果你在循环外依赖循环变量取结果,拿到的就是未定义或旧值。我在批量处理版图对象时,会先把对象列表做一次空判断,再进foreach,省得后面取属性时对nil操作。
4. 从调用别人到自己定义:函数、参数与加载机制的完整链路
4.1 声明一个函数之前,先选定正确的定义形式
文档「在 SKILL 中创建函数」一章系统讲了procedure、lambda、nprocedure、defmacro、defglofun等定义形式。对绝大多数工程脚本而言,procedure是默认选择,它声明一个具名函数、可以带参数列表、有返回值。lambda适合匿名函数场景,但 SKILL 里函数式接口没有像现代语言那么多,实际写版图脚本时lambda出现频率不高。defmacro是宏,展开发生在求值前,调试成本和阅读成本都高,除非在做 DSL 或性能敏感的内联,否则普通补丁脚本不要碰。
翻译版在「语法函数总结」一节用表格汇总了各定义形式,这张表建议打印出来贴在显示器旁边。它的实用价值在于:当你从同事手里接过来一个用defun还是nprocedure写的旧脚本,先看定义形式,大致能判断作者想干什么——nprocedure对参数个数和类型的宽容度与procedure不同,老代码兼容性依赖很可能就藏在那里。
; 具名函数的标准写法 procedure( MyRectWidth(w h) let((area) area = w * h area ) ) ; 调用 MyRectWidth(2.0 3.0) ; 返回 6.0逻辑说明:procedure后的第一项是函数名,第二项是形参列表,之后是函数体。函数体的最后一个表达式值自动作为返回值,不需要显式return,这是 SKILL 与 C 的另一个认知差异点。参数上,形参名称尽量带前缀或团队统一缩写,原文在一章里专门讲了「为你的函数选择前缀」,目的是避免和内置函数或第三方工具包重名;不加前缀的函数名在一个共享环境里被覆盖,表现出来是「昨天还能跑的脚本今天静默返回错结果」,排查成本极高。
4.2 @rest、@optional、@key:三类可变参数标记
参数定义一节的核心价值在执行@rest、@optional、@key三个选项上。@rest收集剩余参数为列表,适合写不定参数的包装函数;@optional声明可省略参数,省略时取nil;@key支持关键字参数,调用时按关键字名传值,顺序可以打乱。翻译文档在这里给的例子质量尚可,但译文把「关键字参数」和「可选参数」在行文里靠得太近,第一次读容易混。
; @optional:后两个参数可省略 procedure( DrawRect(w h @optional (l 0.0) (b 0.0)) printf("rect %f %f %f %f\n" l b w h) ) DrawRect(2.0 3.0) ; l、b 取默认值 DrawRect(2.0 3.0 1.0) ; b 取默认值 ; @key:开关式调用 procedure( ExportFile(@key name format verbose) printf("export %s as %s\n" name format) ) ExportFile(?name "cell" ?format "gds" ?verbose t)参数说明:@optional后的默认值用括号(l 0.0)形式给出,只写变量名不写默认值则省略时为nil;@key的调用语法是?关键字名 值,关键字名前面带问号,传参顺序随意,但拼写必须和定义一致,拼错不会报错,只会让对应形参取nil,这是个隐蔽的翻车点。我的做法是:自定义函数如果参数超过四个,一律用@key,可读性收益远大于多敲几个字符的成本。
4.3 let 与 prog:局部变量的声明差异和全局状态的管控
局部变量章节点名讲了let和prog。简单说,let是块作用域局部变量绑定,函数返回时绑定撤销;prog在旧式 Lisp 里用于带跳转的块结构,配套return使用。翻译文档在「初始化局部变量为非空值」一节的表述值得留意,译文容易让人以为let只能绑定nil,其实带初值的绑定写法是标准用法,不初始化并不推荐——未初始化的局部变量在后续分支里被引用,很容易拿到上一次调用残留的值。
; let 带初值绑定 procedure( Accumulate(lst) let((sum 0) foreach(item lst sum = sum + item ) sum ) )逻辑说明:let的绑定列表是(变量 初值)成对出现,示例中sum初始化为0,避免foreach空列表情况下返回未定义值。关于全局变量,原文在「全局变量」一节提醒了命名和覆盖风险,真实项目里更隐蔽的问题是:在一段脚本里定义了全局变量,被另一段脚本重名覆盖,前后跑批时结果不一致。我一般会在团队约定里要求全局变量集中在一个文件里声明,脚本入口显式加载,别在各处临时定义。
4.4 加载与重新定义:源码维护的工程化细节
「分组 SKILL 陈述」「维护 SKILL 源代码」「加载你的 SKILL 源代码」「重新定义 SKILL 函数」这几小节是这份文档实用度最高的部分之一,因为它解决的是「写完放哪、怎么加载、改完怎么生效」的工程问题。load加载源码文件,loadContext之类的上下文加载视环境而定。重新定义函数那一节提醒了一个关键事实:同名函数后加载的覆盖先加载的,不报错。这意味着调试时你以为在跑新版本,实际加载路径里旧的同名定义先被找到,表现出来就是「代码明明改了结果没变」。
; 加载脚本,注意路径和返回状态 status = load("/path/to/myScript.il") when(status == nil printf("load failed, check syntax\n") ) ; 重新定义测试:在交互环境里改完函数体后重新 load load("/path/to/myScript.il")参数说明:load的传入路径用字符串,注意不同运行环境下工作目录不一致,能用绝对路径就不要用相对路径。返回值为nil时通常表示加载失败或文件不存在,这个返回值要判,不要假定加载一定成功。翻译文档在「加载」一节的示例偏简单,工程上更常见的做法是在脚本入口加一个版本打印,保证你看到运行日志时能确认加载的是哪一版。
5. 翻译版避坑清单:从血泪踩坑到可复现的排查路径
5.1 现象:代码块里的函数名被翻成了中文
原因:机器翻译把代码区域也当成正文处理,出现了「函数」「列表」之类词直接替换原符号名的情况。 解决:凡是代码块,一律以英文原文或你自己环境里验证过的写法为准,译文中的代码块只作为句式参考。加载前用肉眼扫一遍符号是否含非 ASCII 字符,脚本报「未定义函数」时优先怀疑符号被污染。
5.2 现象:同一个术语在不同章节译法不一致,比如列表和清单混用
原因:译文由机器翻译产出,术语没有统一替换表,文档自身的前后一致性弱于原厂手册。 解决:建立自己的术语对照表,只认结构不认译名。看到「清单」按上下文判断是列表还是清单文件,涉及函数名时翻回英文手册核对形参表。不要背译文术语,背函数名。
5.3 现象:按译文的参数顺序写调用,运行后结果不对但不报错
原因:译文在个别函数说明处把形参顺序的叙述打乱了,常见于参数较多的构造函数。 解决:调用前先做一次类型自检,用printf打出每个入参的实际值;参数超过三个的函数,切换到@key写法或先按英文原文核对。凡是静默返回错误值的接口,优先怀疑参数位置错位。
5.4 现象:函数改了但运行结果没变,load 也不报错
原因:同名函数被先加载的旧定义覆盖,或加载路径指向了另一份文件。 解决:在脚本入口打印加载文件的绝对路径和版本标记;用交互环境查函数定义是否生效,确认执行的是最新代码。把「改完必须显式重新加载并验证」写进团队流程里,别依赖记忆。
5.5 现象:括号报错定位飘忽,指向空行或无关行
原因:Lisp 系语法的括号错误往往在解析到后续表达式时才暴露,译文里关于「超右括号」的说明偏术语化。 解决:用编辑器的括号匹配功能从报错位置往上一层层回退到最近的顶层表达式,重点查let、procedure、when这类多行结构的收尾括号。写完一段函数就先加载验证再继续写,把矛盾集中在一个小范围内解决。
6. 把翻译文档用成查表工具:进阶技巧与验证方法
进入实际使用阶段,我建议把这份文档当「第一遍入门材料 + 日常速查」,而不是逐页精读的书。具体做法是两张表加一个习惯。第一张表是「函数定义形式对照表」,把procedure、lambda、nprocedure、defmacro的定义语法和典型场景各占一行,从翻译文档的总结表里抄出来,再补上你自己项目里用到的写法,写错一次补一次,两三个月就能覆盖八成常用场景。
第二张表是「报错-章节」映射,把实际遇到的加载失败、括号不匹配、参数错位、同名覆盖,逐条对应到文档章节。这个映射的价值在于缩短二次排查时间——同一个坑第二次踩时,你不需要重新读整章,直接翻到对应小节。
| 场景 | 优先翻的章节 | 验证动作 |
|---|---|---|
| 函数报未定义 | 加载与重新定义 | 打印实际加载路径,核对符号拼写 |
| 参数传了但结果为空 | 定义参数 | 逐个打出入参值,核对@optional、@key写法 |
| 循环结果异常 | for 与 foreach | 先单独验证空列表和单元素列表 |
| 嵌套判断走错分支 | 控制流程 | 每个分支入口加一行日志,确认求值路径 |
一个具体技巧:把文档里关于返回值语义的小节单独摘出来做成注释模板,贴在你写的每个公共函数头部,注明「返回什么、失败时返回什么」。听起来像多余动作,但我见过太多脚本崩在「副作用函数返回nil被当成功」上,尤其批量处理版图对象时,前一百个对象处理成功、第一百零一个静默跳过,日志里根本看不出来。加一行返回值判断,比事后翻原始文档省太多时间。
从那以后我每次把新脚本交给别人之前,都会强制自己走一遍「加载路径打印、关键函数入参打印、返回值和容错判断」三件事,跑通再交。这份翻译文档的最大意义,是让你在第一次写 SKILL 时少翻几次英文手册,但它的译文边界也必须心里有数:代码块符号、参数顺序、术语一致性这三处,动手前先核对,别直接照抄。希望帮到你。
本文还有配套的精品资源,点击获取