Cadence 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 也能实现局部变量,且支持 goreturn,可进行跳转和提前返回。一般情况下使用 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 或 string
  • n:number(fixnum、flonum)
  • u:function
  • g: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 输出函数

输出函数对比

函数名 输出目标 是否自动换行 返回值 常用场景
print 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)

文件读取

使用 infilegets 逐行读取:

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)
        )
    )
)
相关推荐
10mAh1 小时前
【Java】HashMap 的 put 到底做了什么?——冲突、扩容与树化实测
java·开发语言·hash
九九落2 小时前
JavaScript 实现北京时间精确到毫秒显示:UTC+8、Asia/Shanghai 与在线时间校准详解
开发语言·javascript·ecmascript
东华万里3 小时前
第41篇 C++类与对象核心知识梳理:从底层原理到面试实战
开发语言·c++·大学生专区
微小冷3 小时前
Python图论库NetworkX初步
开发语言·python·图论·graph·networkx·digraph
Data_Journal4 小时前
如何使用 Python 抓取 Google Flights:分步指南
大数据·开发语言·数据库·python·scrapy
一直C4 小时前
【数据结构】哈希表+算法复杂度与经典排序查找(C语言)
java·linux·开发语言·数据结构·算法·ubuntu·散列表
Kismet_nvi4 小时前
Python 基础核心知识点总结
开发语言·windows·python
爱学习的小邓同学4 小时前
Golang语言入门
开发语言·后端·golang
编码浪子5 小时前
Rust 智能指针深度实战:Box/Rc/Arc/RefCell 选型决策与内存管理避坑
开发语言·后端·rust