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 也能实现局部变量,且支持 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 或 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)

文件读取

使用 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)
        )
    )
)
相关推荐
jimy12 分钟前
虚函数vtable,链接时看到构造函数,linker把符号地址写入vtable(三)
开发语言·c++
ss2735 分钟前
AI全栈实战 | 3.3-02 Python 并发:有 GIL 为什么还用多线程,asyncio 和 JS 事件循环同源不同味
开发语言·javascript·python
菜鸟~noob2339 分钟前
【电子战】第20篇:凸优化与对数障碍法【含matlab代码】
开发语言·matlab
Wang's Blog18 分钟前
Java框架 SpringCloud 快速入门: 服务拆分案例 Demo
java·开发语言·spring cloud
小小龙学IT33 分钟前
Python SQLAlchemy 2.0 深度解析:从 Core 到 ORM 的数据访问双引擎
开发语言·数据库·python
2601_9628857235 分钟前
如何用 Python 计算 ROC 变动率指标做动量分析?
开发语言·python·算法
潼心1412o1 小时前
C++初阶(长期更新)第9讲:string(上)
开发语言·c++
朝朝辞暮i1 小时前
C++ 第 39 章: 阶段性总复习——类、对象、构造、继承、this、指针与智能指针
开发语言·c++·算法·ros2
掉进电商坑三年没爬出来的东叔2 小时前
青龙面板进阶:一个面板聚合多平台签到,所有积分自动领
java·开发语言
iCxhust2 小时前
51单片机电力载波应用开发(七)广播通讯
c语言·开发语言·单片机·嵌入式硬件·51单片机