文章目录
- 一、概览
-
- [1.1 Cadence Skill 简介](#1.1 Cadence Skill 简介)
- [1.2 环境配置与常用工具函数](#1.2 环境配置与常用工具函数)
- 二、基础语法与数据类型
- 三、字符串操作
- 四、列表操作
- 五、控制结构
- 六、函数定义与参数
-
- [6.1 基本函数定义](#6.1 基本函数定义)
- [6.2 可选参数、关键字参数与剩余参数](#6.2 可选参数、关键字参数与剩余参数)
- [6.3 函数查找](#6.3 函数查找)
- 七、输入输出与文件操作
-
- [7.1 输出函数](#7.1 输出函数)
- [7.2 文件操作](#7.2 文件操作)
- 八、skill加密与解密
一、概览
1.1 Cadence Skill 简介
Cadence Skill 是 Cadence Design Systems 公司开发的一种基于 Lisp 的脚本语言,主要用于电子设计自动化(EDA)工具的扩展和自动化。它被广泛应用于 Cadence 的集成电路(IC)设计工具链中,如 Virtuoso、Allegro 等,用于实现设计流程自动化、定制功能开发以及工具交互。
Skill 语言文件一般以.il和.ile结尾,其中.ile结尾的文件为经过加密的文件。
核心特性
Lisp 语法基础 :Skill 继承 Lisp 的函数式编程特性,支持动态类型、递归和高阶函数。
EDA 集成 :可直接调用 Cadence 工具的内置函数和数据库接口,操作设计数据(如版图、网表)。
交互式开发 :支持实时解释执行,便于调试和快速原型开发。
跨平台支持:兼容 Unix/Linux 和 Windows 环境。
典型应用场景
设计自动化 :批量修改版图、生成复杂结构(如匹配器件阵列)。
工具扩展 :为 Virtuoso 添加自定义菜单或快捷键。
数据分析:提取仿真结果或设计参数进行后处理。
CIW窗口
CIW(Command Interpreter Window)是启动 Cadence 图形化界面之后先出现的窗口,在 CIW 中可以通过图形化界面进行很多操作,也可以直接在 CIW 中输入指令操作 Virtuoso,还可以通过 CIW 观察指令运行信息。
加载自定义函数
方法一 :在 home 目录里的
.cdsinit文件里写入load "xxx.il",下回启动时会自动加载。方法二 :在 CIW 窗口输入
load "xxx.il",执行后才会加载,下次启动时不会自动加载,需要手动执行load加载。
.cdsinit 与 .cdsenv 文件
.cdsinit:负责一些加载项的设置(load一些自定义函数)
.cdsenv:负责一些环境变量或者函数的设置(一般会对字体、格点、加载工艺库、数字有效位等一些个性化的设置,且cdsenv每行一般分为四部分:大项,小项,类型,值)
导出.cdsenv:CIW窗口 -> option -> save default里导出当前的cdsenv
注释
行注释:使用
;符号。块注释:使用
/* ... */。
1.2 环境配置与常用工具函数
环境变量设置
| 函数 | 说明 | 示例 |
|---|---|---|
| envSetVal(group var type value) | 修改 Cadence 配置变量(.cdsenv),立即生效 | envSetVal("asimenv" "saveDir" 'string "./sim") |
| envSetVal("ddserv.ciw" "promptOnExit" 'boolean nil) | 设置退出提示 | --- |
注意:envSetVal 的第一个参数是组名(Group),第二个是变量名(Variable),第三个是类型,第四个是值。
窗口操作
| 函数 | 说明 | 示例 |
|---|---|---|
| hiGetCurrentWindow() | 获取当前工作窗口的 ID | winId = hiGetCurrentWindow() |
| hiCloseWindow(winId) | 关闭指定的窗口 | hiCloseWindow(winId) |
| hiGetWindowList() | 获取所有打开的窗口 ID 列表 | hiGetWindowList() |
设计打开函数
| 函数 | 说明 | 示例 |
|---|---|---|
| geOpen(lib cell view) | 在新窗口中打开指定设计 | geOpen("myLib" "myCell" "schematic") |
二、基础语法与数据类型
变量命名与赋值
变量类似 Python,直接使用赋值即可,不需要事先声明类型,可以使用 type 函数查看变量类型。
命名规则:
- 可由字符、数字、_ 和 ? 组成。
- 第一个字符不能是数字和 ?。
- 为避免与 Cadence 内置函数冲突(内置函数通常以小写字母开头),建议自定义函数和变量使用大写字母开头。
数据类型
| 数据类型 | 举例 |
|---|---|
| integer | 5 |
| float | 6.1 |
| string | "abc def" |
| list | '(1 2 "a") |
| boolean | t 或 nil |
运算符
数学运算 :+ - * / ++ **
比较运算 :> < >= <= ==
赋值 :=
逻辑运算:&& || (返回值是最后一个计算的表达式的值;只有 nil 为假,其余值都为真)
类型转换
| 函数 | 说明 | 示例(输入 → 输出) |
|---|---|---|
| atoi(s) | 字符串 → 整数(取整) | atoi("123.9") → 123 |
| atof(s) | 字符串 → 浮点数 | atof("3.1415") → 3.1415 |
| cdfParseFloatString(s) | 字符串 → 浮点数(支持 u/n/p/k/M 等后缀) | cdfParseFloatString("1.2u") → 1.2e-06 |
| aelSuffixNotation(x) | 浮点数 → 工程后缀字符串 | aelSuffixNotation(12000) → "12k" |
| evalstring(s) | 执行字符串形式的 Skill 表达式 | evalstring("1+2*3") → 7 |
选型建议:普通数字用 atof;处理 CDF 参数或用户输入带单位的数值时用 cdfParseFloatString。
运算过程跟踪
使用 tracef(t) 语句可以开启跟踪,之后 CIW 会显示算术逻辑运算的进行过程。使用 untrace() 关闭跟踪。使用小括号可以很好地控制运算顺序。
三、字符串操作
字符串拼接
c
sprintf(a "%d %s" 123 "abc") ; a = "123 abc"
a = sprintf(nil "%d %s" 123 "abc") ; a = "123 abc"
strcat("ab" "cd" "12") ; "abcd12"
buildString('("ab" "cd" "123")) ; "ab cd 123" 默认空格拼接
buildString('("ab" "cd" "123") "-") ; "ab-cd-123" 指定拼接符
字符串拆分
c
parseString("a-b c-d") ; ("a-b" "c-d") 默认空格拆分
parseString("a-b c-d" "-") ; ("a" "b c" "d") 指定拆分符号
字符串截取与查找
c
index("abcdabcd" "bc") ; "bcdabcd" 从第一次匹配位置开始截取到结尾
rindex("abcdabcd" "bc") ; "bcd" 从右侧第一次匹配位置开始截取到结尾
nindex("abcdabcd" "bc") ; 2 返回第一次匹配的索引(从 1 开始),不存在返回 nil
常用字符串函数
| 函数 | 说明 | 示例(输入 → 输出) |
|---|---|---|
| strlen(s) | 返回字符串的字符长度 | strlen("hello") → 5 |
| substring(s start len) | 截取字符串子串(start 从 1 开始) | substring("abcdef" 2 3) → "bcd" |
| parseString(s delim) | 按分隔符拆分字符串为列表(默认按空格拆分) | parseString("a,b,c" ",") → ("a" "b" "c") |
| buildString(list delim) | 将字符串列表合并为一个字符串 | buildString(("a" "b" "c") ",") → "a,b,c" |
| evalstring(s) | 执行字符串形式的 Skill 表达式 | evalstring("1+2*3") → 7 |
注意:substring 的位置索引从 1 开始,与很多编程语言不同。
正则表达式
c
; 使用 rexMatchp 直接匹配
rexMatchp("hel" "hello world") ; t
; 使用 rexCompile + rexExecute 进行匹配
rexCompile("hel")
rexExecute("hello world") ; t
; 使用 rexSubstitute 提取匹配内容
rexCompile("\\([A-Z]+[0-9]+\\)x\\([0-9.-]+\\)y\\([0-9.-]\\)")
rexExecute("C12x982.99y-2342.90")
rexSubstitute("\\1") ; "C12"
四、列表操作
创建列表
方式一:使用单引号创建(不进行变量替换)
c
a = 1
b = 2
c = '(1 2) ; (1 2)
d = '(a b) ; (a b) 不会把 a、b 当变量替换进来
方式二:使用 list 创建(进行变量替换)
c
c = list(a b) ; (1 2)
方式三:使用冒号创建坐标列表,xCoord / yCoord 可分别访问
c
a = 3:4
xCoord(a) ; 3
yCoord(a) ; 4
新增元素与列表合并
| 方法 | 位置 | 是否修改原列表 | 返回值 | 适用场景 |
|---|---|---|---|---|
| cons(item list) | 头部 | 否 | 新列表 | 通用头部添加,保留原列表 |
| push(item var) | 头部 | 是(修改变量) | 新列表(变量值) | 需要更新变量本身,简洁高效 |
| pushnew(item var) | 头部 | 是(修改变量) | 新列表(变量值) | 需要去重时添加 |
| append1(list item) | 尾部 | 否 | 新列表 | 少量尾部追加,保留原列表 |
| append(list1 list2 ...) | 合并 | 否 | 新列表 | 合并多个列表,保留原列表 |
| nconc(list1 list2 ...) | 合并 | 是(修改 list1) | 修改后的 list1 | 确定不再需要 list1 原值,追求效率 |
| tconc / lconc | 尾部 | 是(维护尾部) | 尾部 cons | 大量尾部追加,性能敏感场景 |
cons(item list):在列表最前面添加一个元素,返回新列表,原列表不变。
c
aList = '(2 3 4)
newList = cons(1 aList) ; newList = (1 2 3 4),aList 仍为 (2 3 4)
push(item var):将元素添加到变量 var 所指向的列表头部,并更新变量 var 为新列表。它等价于 var = cons(item var)。
c
myList = '(2 3 4)
push(1 myList) ; myList = (1 2 3 4)
pushnew(item var):与 push 类似,但只有当 item 不在列表中时才添加(使用 equal 比较)。如果已存在,则不修改列表
c
myList = '(1 2 3)
pushnew(2 myList) ; myList 不变,仍为 (1 2 3)
pushnew(4 myList) ; myList = (4 1 2 3)
append1(list item):在列表末尾添加一个元素,返回新列表,原列表不变。
c
aList = '(1 2 3)
newList = append1(aList 4) ; newList = (1 2 3 4),aList 仍为 (1 2 3)
tconc用于在列表末尾高效地追加元素,它维护一个指向列表尾部的指针,避免每次追加都遍历整个列表。通常与 lconc 或直接使用 tconc 返回值配合。
c
; 初始化一个空列表和尾部指针
myList = nil
tail = nil
; 循环追加元素
for(i 1 10
tail = tconc(tail i) ; 第一次 tail 为 nil,之后返回尾部 cons 单元
)
; 此时 tail 是最后一个 cons 单元,需要获取整个列表
myList = car(tail) ; 或使用其他方式
更常见的模式是使用 lconc
c
; 使用 lconc 维护列表和尾部
myList = nil
for(i 1 10
myList = tconc(myList i) ; 错误用法?
)
删除元素
remove:按值删除第一个匹配的元素,返回新列表,原列表不变。
c
aList = '(1 2 3 4)
newList = remove(3 aList) ; newList = (1 2 4),aList 仍为 (1 2 3 4)
; 删除所有匹配的元素需要循环或使用过滤函数
aList2 = '(1 2 3 1 2 3)
newList2 = setof(x aList2 (neq x 3)) ; newList2 = (1 2 1 2)
delete:与 remove 类似,但会修改原列表(实际上修改了列表结构),返回删除后的列表,原列表被破坏
c
aList = '(1 2 3 4)
newList = delete(3 aList) ; newList = (1 2 4),aList 也被修改为 (1 2 4)
按比较方式删除
remq:使用 eq 比较(适用于符号、相同的列表引用等)
remv:使用 eqv 比较(适用于数字、字符等)两者均为非破坏性,返回新列表。
c
remq('a '(a b a c)) ; (b c)
remv(2 '(1 2 3 2)) ; (1 3)
remassoc:删除关联列表(如 '((key1 val1) (key2 val2)))中键匹配的整个子列表,返回新列表
c
aList = '((1 "one") (2 "two") (3 "three"))
remassoc(2 aList) ; ((1 "one") (3 "three"))
按索引删除元素:Skill 没有直接按索引删除的函数,可通过 append 和 nthcdr 构造新列表
c
aList = '(10 20 30 40 50)
idx = 2 ; 删除索引为 2 的元素(即 30)
newList = append(subseq aList 1 idx) (subseq aList (+ idx 2) (length aList))
; 或使用 nthcdr 结合 cons 实现
; newList = append(reverse(cdr(reverse(nthelem(idx+1) aList))) ...)
修改元素
索引修改
setf 结合 nth:使用 setf 可以为列表的任意位置赋值(nth 是从 0 开始索引)
c
aList = '(10 20 30 40)
setf(nth(2 aList) 99) ; 将索引 2(第三个元素)修改为 99
; 现在 aList = (10 20 99 40)
修改列表结构
rplaca(list newCar):将列表的第一个元素替换为 newCar,返回修改后的列表。
rplacd(list newCdr):将列表的尾部(第一个元素之后的部分)替换为 newCdr,返回修改后的列表。
c
aList = list(1 2 3)
rplaca(aList 4) ; 修改第一个元素,返回 (4 2 3),aList 变为 (4 2 3)
aList2 = list(1 2 3)
rplacd(aList2 '(4 5)) ; 修改第一个元素之后的元素,返回 (1 4 5),aList2 变为 (1 4 5)
修改子列表中的元素:若列表包含子列表,可先取出子列表,修改后再放回
c
aList = '((1 "one") (2 "two") (3 "three"))
setf(cadr(assoc(2 aList)) "TWO") ; 修改键为 2 的子列表的第二个元素
; 结果 aList = ((1 "one") (2 "TWO") (3 "three"))
取值
以 list = '(2 3 4 5) 为例
| 函数名 | 功能 | 使用 | 返回值 |
|---|---|---|---|
| car | 取第一个元素 | car(list) | 2 |
| cdr | 取第一个元素外的所有元素 | cdr(list) | (3 4 5) |
| caadr | 等同于 car(car(cdr(list))) | caadr(list) | 4 |
| nth | 按索引获取元素(索引从 0 开始) | nth(2 list) | 4 |
| nthelem | 按索引获取元素(索引从 1 开始) | nthelem(2 list) | 3 |
| last | 取最后一个元素(返回列表) | last(list) | (5) |
| member | 判断元素是否存在,返回从该元素开始到结尾的子列表,不存在返回 nil | member(3 list) | (3 4 5) |
assoc 可用于取关联列表中的值,类似字典
c
aList = '((1 "one") (2 "two"))
assoc(1 aList) ; (1 "one")
排序
c
reverse('(1 (1 2) 3)) ; (3 (1 2) 1)
sort('(4 3 2 1) 'lessp) ; (1 2 3 4) lessp 从小到大,greaterp 从大到小
sort('(d b c a) 'alphalessp) ; (a b c d) 按字母排序
sort('("u5" "u7" "U6") 'axlStrcmpAlpNum) ; ("u5" "u6" "u7") 自然排序
; 对子列表按第一个元素排序
sortcar('((4 "fo") (3 "te") (5 "fi")) 'lessp) ; ((3 "te") (4 "fo") (5 "fi"))
过滤
c
setof(x '(1 4 2 3) (x > 2)) ; (4 3) 返回所有符合条件的元素
exists(x '(1 4 2 3) (x > 2)) ; (4 2 3) 返回从第一个符合条件的元素开始到结尾
forall(x '(1 4 2 3) (x > 2)) ; nil 判断是否所有元素都符合条件,返回 t 或 nil
列表长度
c
length('(1 2 3)) ; 3
五、控制结构
条件语句
if:then 不能省略,除非只有一个 if 且没有 else。
c
if( a > b then
c = a
else
c = b
)
case
c
case(type(a)
("fixnum"
println("整型")
)
("flonum"
println("浮点型")
)
)
循环语句
while
c
while( condition
; 循环体
)
for
c
for(n 1 10
; 循环体,n 从 1 到 10
)
遍历映射函数
foreach
c
foreach(x '(1 2 3)
println(x)
)
foreach((x y) '(1 2 3) '(4 5 6)
println(x + y)
) ; 输出 5 7 9
map 系列
c
; mapc:对多个列表逐元素操作,返回 nil
mapc(lambda((x y) (print(list x y))) '(1 2 3) '(4 5 6))
; 输出 (1 4) (2 5) (3 6)
; map:对多个列表逐元素操作,返回结果列表
map(lambda((x y) (append x y)) '(1 2 3) '(4 5 6))
; 返回 ((1 2 3 4 5 6) (2 3 5 6) (3 6))
; mapcar:对多个列表逐元素应用函数并收集结果
mapcar('plus '(1 2 3) '(4 5 6))
; 返回 (5 7 9)
mapcar('list '(1 2 3) '(4 5 6) '(7 8 9))
; 返回 ((1 4 7) (2 5 8) (3 6 9))
; maplist:对列表的连续尾部应用函数
maplist('list '(1 2 3) '(4 5 6))
; 返回 (((1 2 3) (4 5 6)) ((2 3) (5 6)) ((3) (6)))
; mapcan:对多个列表逐元素应用函数并拼接结果
mapcan('list '(1 2 3) '(4 5 6))
; 返回 (1 4 2 5 3 6)
六、函数定义与参数
6.1 基本函数定义
procedure关键字与其后的括号之间不能有空格;函数名和其后的括号之间不能有空格;函数名后面的括号及参数内容不能省略。
函数执行后,返回值为函数体中最后一个被执行语句的值
c
procedure(add(x y) ; 函数名 add,参数 x y
x + y ; 返回值
)
add(1 2) ; 3
另一种定义方式(不推荐,使用 defun)
c
(defun fibonacci(n)
(cond
((or (equal n 1) (equal n 2)) 1)
(t (plus (fibonacci (difference n 1))
(fibonacci (difference n 2))))
)
)
局部变量与作用域
使用
let函数声明局部变量,let 的返回值为其最后一个表达式的值。函数中未用 let 声明的变量为全局变量,函数外也能访问。
c
procedure(add()
c = let( (a b)
a = 1
b = 2
a + b ; let 返回值
)
c ; 函数返回值
)
prog 也能实现局部变量,且支持
go和return,可进行跳转和提前返回。一般情况下使用 let 即可。
c
result = prog(()
for(n 1 10
when(n > 5
go(SkipToEnd)
)
)
SkipToEnd
return 1
)
6.2 可选参数、关键字参数与剩余参数
可选位置参数 @optional
c
procedure(myFunc(a b @optional c)
let((result)
when(c
; 使用 c
)
unless(c
; 未提供 c
)
; 其他逻辑
)
)
myFunc(1 2)
myFunc(1 2 3)
关键字参数 @key
c
procedure(myFunc(a b @key (c nil) (d nil))
; 使用 c、d
)
myFunc(1 2 ?d 3 ?c 4)
剩余参数 @rest
c
procedure(myFunc(a b @rest args)
printf(nil "%s %s %s" a b args)
)
myFunc(1 2 3 4) ; args = (3 4)
参数类型总结
| 语法 | 含义 | 获取方式 | 调用示例 |
|---|---|---|---|
| @optional | 可选位置参数 | 函数体内判断是否存在 | func(1 2) 或 func(1 2 3) |
| @key | 关键字参数(命名) | get(callArgList 'key) | func(1 2 x:10) |
| @rest | 捕获所有额外参数 | 直接引用变量(列表) | func(1 2 3 4) → extraArgs = (3 4) |
@key和@optional不能出现在同一个函数中。
参数类型检测
Skill 提供函数输入参数类型检测功能,类型说明符写在参数列表末尾:
s:symbol 或 stringn:number(fixnum、flonum)u:functiong:any data type
c
procedure(myFunc(a b "nn") ; 表示 a、b 必须为数字类型
; 函数体
)
6.3 函数查找
Cadence 中的函数主要分为两部分:用户自定义函数 和Cadence 已封装好的函数。
自定义函数查找
开启 debug 模式后,可使用
whereIs查找自定义函数所在文件(仅适用于自定义函数)。
c
sstatus(debugMode t) ; 开启 debug 模式
whereIs(functionName) ; 查找函数定义位置
Cadence 封装函数帮助与查询
Cadence 已经封装好的函数可通过下面方式查看函数的介绍与使用方法:
- 在 CIW 区域输入
startFinder,或者终端输入cdsFinder&,打开函数搜索器,查看函数的作用及使用方法。- 运行
cdsdoc&,点击 Search 打开 Cadence Help 的搜索功能。高版本已改为cdnshelp&命令。
七、输入输出与文件操作
7.1 输出函数
输出函数对比
| 函数名 | 输出目标 | 是否自动换行 | 返回值 | 常用场景 |
|---|---|---|---|---|
| stderr 或终端 | 是(对象后加换行) | nil | 简单调试、打印对象结构 | |
| printf | stderr 或终端 | 否(由格式化字符串控制) | nil | 格式化输出日志、进度信息 |
| println | stderr 或终端 | 是 | nil | 简单打印一行文字或变量值 |
| sprintf | 返回字符串(不输出到屏幕) | 否(作为字符串内容) | 字符串 | 生成文件名、构建显示文本、日志记录 |
c
print("hello") ; "hello"
printf("a+b=%d", 10) ; a+b=10
a = sprintf(nil "%d-%s" 123 "abc") ; a = "123-abc"
sprintf(a "%d-%s" 123 "abc") ; 同上,a 被修改
7.2 文件操作
文件写入
c
f = outfile("file_path")
fprintf(f "hello")
fprintf(f " ")
fprintf(f "world")
close(f)
文件读取
使用
infile和gets逐行读取:
c
f = infile("file_path")
when(f
while( gets(nextline f)
println(nextline)
)
close(f)
)
使用
fscanf按指定格式读取:
c
f = infile("file_path")
when(f
while( fscanf( f "%s" word)
println(word)
)
close(f)
)
八、skill加密与解密
skill脚本一般以
.il或.ile结尾,.ile 结尾的是经过加密的,调用需要密码
脚本加密方式
CIW窗口输入指令:
encrypt("xxx.il" "xxx.ile" "passwordxxx")
解密
对于没有密码的加密文件,可以用lineread函数配合print函数来获取源码,load下面内容,使用下面函数可以恢复skill源码
c
procedure(func(inputFile @optional outputFile)
prog( (inp out line)
if( inputFile then
; 默认输出文件名
unless( outputFile
outputFile = sprintf(nil "%s.dec" inputFile)
)
; 检查输出文件是否已存在
when( isFile(outputFile)
hiGetAttention()
printf("Output file %s already exists.\n" outputFile)
return(nil) ; 提前返回 nil
)
; 打开输入文件
unless( inp = infile(inputFile)
printf("Unable to open input file: %s\n" inputFile)
return(nil)
)
; 打开输出文件
unless( out = outfile(outputFile "w")
printf("Unable to open output file: %s\n" outputFile)
close(inp) ; 关闭已打开的输入文件
return(nil)
)
; 逐行复制
while( line = gets(inp)
fprintf(out "%s\n" line)
)
; 关闭文件
close(inp)
close(out)
return(t) ; 成功返回 t
else
printf("No input file specified.\n")
return(nil)
)
)
)