https://www.cnblogs.com/yeungchie/
写个大笔记,低速更新中 ...
Cadence Skill
Cadence 提供二次开发的 SKILL 语言,它是一种基于通用人工智能语言— Lisp 的交互式高级编程语言 ①。
SKILL 语言支持一套类似 C 语言的语法,大大降低了初学者学习的难度,同时高水平的编程者可以选择使用类似 Lisp 语言的全部功能。所以 SKILL 语言既可以用作最简单的工具语言,也可以作为开发任何应用的、强大的编程语言。
SKILL 可以与底层系统交互,也提供了访问 Cadence 各个工具的丰富接口。用户可以通过 Skill 语言来访问,并且可以开发自己的基于 Cadence 平台的工具。
① LISP 即 List Processing-表处理,是最早和最重要的符号处理编程语言之一,它于1958年由美国的 J. McCarthy 提出,LISP 在人工智能AI方面获得广泛应用。
我的环境是 Virtuoso IC618 平台,可能存在某些特性不适用于旧版本。
如何查看官方资料
cdsFinder
- 用于模糊查找 Skill 函数,查看简单介绍。
cdsFinder &
# $CDSHOME/tools/bin/cdsFinder
cdnshelp
- 用于查看更加详细的内容,软件的使用手册。
cdnshelp &
# $CDSHOME/tools/bin/cdnshelp
代码风格
语法风格
由于 Skill 语言是基于 Lisp ,因此它支持 函数表示法
和 前缀表示法
来书写代码。
-
函数表示法
func( arg1 arg2 )
-
前缀表示法
( func arg1 arg2 )
以上的两种表示法可以同时存在,因此在编写代码时候最好注意格式的统一。
推荐统一使用 函数表示法
,并需要注意函数名和括号之间不能有空格。
错误写法 :
func ( arg1 arg2 )
func
后面多了一个空格,这会将arg1
作为函数名
命名风格
其次函数和变量的命名风格,Skill 中一般是使用 驼峰命名法 ( Camel-Case ),函数名中的每一个逻辑断点(单词)首字母大写,开头第一个单词全部小写,一般代表 函数/变量 的类别、作者,像我写的函数和全局变量一般都习惯用 yc
开头。
这是官方使用的命名方式,也是现在流行的一种命名方式。
当然如何选择取决于你自己。
下面是一个对比
-
驼峰命名法
geGetEditCellView() dbOpenCellViewByType(libName cellName viewName nil mode)
-
蛇形命名法(下划线)
不推荐。
ge_get_edit_cell_view() db_open_cell_view_by_type(lib_name cell_name view_name nil mode)
最后再注意命名不能过于简化,例如 a
,b
,c
。命名的目标是至少让人能一眼看出来这个变量是做什么用的(一眼不行看两眼),例如上面的 libName
,cellName
,viewName
。
下面介绍语法部分我会使用的一些非常简化变量命名,因为只是用于演示,命名就会比较随意了。
基础语法
hello world
举例一种 hello world 的写法。
println( "hello world" )
; "hello world"
注释
- 单行注释
; 这是单行注释
- 多行注释
/*
这是
多行注释
*/
真 / 假
Skill 中 真用 t
来表示,假用 nil
来表示。
其实参与判断中的值,除了 nil
和空链表以外都可以认为是真,例如:
-
判断
t
when( t println( "True" ) ) ; t 为真,打印 "True"
-
判断
10
when( 10 println( "True" ) ) ; 10 为真,打印 "True"
-
判断
"YEUNGCHIE"
when( "YEUNGCHIE" println( "True" ) ) ; "YEUNGCHIE" 为真,打印 "True"
-
判断
nil
when( nil println( "True" ) ) ; nil 为假,不运行 println 语句
when 语句中第一个参数为判断条件,条件为真才会运行。
数据类型
可以用函数 type
查看一个数据的类型标识。
type( 'YEUNGCHIE ) ; symbol
type( "YEUNGCHIE" ) ; string
type( list( "YEUNGCHIE" )) ; list
用 println
打印一个数据的内容,同时也能够从打印结果观察到数据类型。
println( 'YEUNGCHIE ) ; YEUNGCHIE
println( "YEUNGCHIE" ) ; "YEUNGCHIE"
println( list( "YEUNGCHIE" )) ; ( YEUNGCHIE )
数据类型标识
官方的教程中一般会用来表明函数需要的输入数据是什么类型的( 标在前缀 )。也用于限制子函数输入数据的类型。
前缀 | 内部命名 | 数据类型 |
---|---|---|
d | dbobject | id , Cadence 数据对象 |
x | integer | 整数 |
f | flonum | 浮点数 |
n | number | 整数 或者 浮点数 |
g | general | 通用的 , 任何数据类型 |
l | list | 链表 |
p | port | I / O 句柄 |
t | string | 字符串 |
s | symbol | " 符号 " |
S | stringSymbol | " 符号 " 或者 字符串 |
u | function | 函数对象 , 函数名 或者 lambda对象 |
... | ... | ... |
字符串
- 定义方式
字符串用双引号括起来。
"YEUNGCHIE"
; "YEUNGCHIE"
-
相关函数
-
strcat 字符串连接
a = "YEUNG" b = "CHIE" c = strcat( a b ) println( c ) ; "YEUNGCHIE"
-
strlen 字符串长度
下面的 c 沿用上面的变量 c,为了演示不写得过于繁杂、重复。
println(strlen( c )) ; 9
-
数字
数字分为 整数 和 浮点数。
-
整数
18
也可以直接编写成 二进制 (
0b
前缀 )、八进制 (0
前缀 )、十六进制 (0x
前缀 ),但默认会输出成 十进制0b10010 ; 18 024 ; 20 0xFE ; 254
-
浮点数
3.14
浮点数也可以使用 科学计数法 和 单位后缀 来表示
1e-06 ; 0.000001 1u ; 0.000001
symbol(符号)
symbol 的写法是用一个单引号开头,例如:'a
、'b
、'c
,这个类型比较抽象,知道有这么个东西就行,重点是需要用到的时候知道如何去使用。
symbol 实际上是一种指针。
每个 symbol 都存在以下几个组成部分(slot):
- Print name ( name )
- Value
- Function binding
- Property list
只有 name 是必要的,其他的部分都可以为空,当一个 文本引用 产生时,系统会创建一个 symbol ,且 Value 会设置为 'unbound
。
这段话不知道怎么讲更好,贴一下原文
The system creates a symbol whenever it encounters a text reference to the symbol for the first time. When the system creates a new symbol, the value of the symbol is set to unbound.
意思就是创建一个变量会自动生成与之对应的一个 symbol ,默认值为 'unbound
,这个过程是非显式的。
这样,如果需要将一个变量设置为 未定义/未绑定 的状态,则可以将它赋值为 'unbound
。
举个例子:
-
给 arg 赋值为 12,然后打印出来。
arg = 12 println( arg ) ; 终端会打印 12
-
给 arg 再赋值为
'unbound
,然后尝试 print 一下。arg = 'unbound println( arg ) ; 终端会提示 *Error* eval: unbound variable - arg
当一个变量没有被定义过的时候,引用它但不赋值系统会报 Error ,但如果只是想判断某个变量是否被定义了,可以使用函数 boundp
去检测目标变量的 symbol name。
例如,检测一下变量 arg 是否被定义:
-
arg 已经被赋值为
'unbound
现在是未定义的状态。boundp( 'arg ) ; 结果是 nil
-
再给它赋值一个值。
arg = 1 boundp( 'arg ) ; 结果是 t
判断一个函数是否存在的时候也需要利用到 symbol。
例如,判断一下函数 dbCreateRect
是否存在:
dbCreateRect 是 Virtuoso 自带函数。
fboundp( 'dbCreateRect )
; 结果是 lambda:dbCreateRect
判断一下,函数 QWE123
是否存在:
fboundp( 'QWE123 )
; 不存在,结果是 nil
链表 list
链表其实不是一种数据类型,而是一种数据的存储结构。
-
定义方式
-
list
list( arg1 arg2 list( arg3 arg4 ) ... )
listA = list( 1 2 ) ; ( 1 2 )
-
'
'( "value1" sym1 (1 2) ... )
这种表达方式需要注意,它不适用于变量元素,例如上面
sym1
并不会带入变量值,而是 symbol 类型'sym1
listB = '( 3 4 ) ; ( 3 4 )
-
:
arg1 : arg2
仅在只有两个元素时使用,通常用来表达一个坐标点
point = 1 : 2 ; ( 1 2 )
-
-
相关函数
-
append 连接两个 list
listC = append( listA listB ) println( listC ) ; ( 1 2 3 4 )
-
append1 往末尾追加元素
listD = append1( listC 5 ) println( listD ) ; ( 1 2 3 4 5 )
-
cons 往开头追加
listE = cons( 0 listD ) println( listE ) ; ( 0 1 2 3 4 5 )
-
reverse 翻转一个 list
listF = reverse( listE ) println( listF ) ; ( 5 4 3 2 1 0 )
-
length 获取一个 list 元素个数
length( listF ) ; 6
-
car 提取第一个元素
car( listF ) ; 5
-
cdr 提取除了第一个元素之后的 list
cdr( listF ) ; ( 4 3 2 1 0 )
-
cadr 及其更多组合
cadr( listF )
其实就是car(cdr( listF ))
的简写cadr( listF ) ; 4 caddr( listF ) ; 3
-
nth 根据索引提取
nth( 0 listF ) ; 5 nth( 1 listF ) ; 4 nth( length(listF)-1 listF ) ; 0
-
数组 / 向量
数组 和 向量 不常用,了解一下长什么样就行。
数组(不常用)
-
定义
declare
declare( ARRAY[10] )
-
赋值
ARRAY[2] = 4 ARRAY[3] = 5
-
引用
println( ARRAY[2] * ARRAY[3] ) ; 20 println( ARRAY[0] ) ; unbound println( ARRAY[11] ) ; *Error* arrayref: array index out of bounds - ARRAY[11]
向量(不常用)
-
定义
makeVector
VECTOR = makeVector( 10 )
-
赋值
VECTOR[2] = 4 VECTOR[3] = 5
-
引用
println( VECTOR[2] * VECTOR[3] ) ; 20 println( VECTOR[0] ) ; unbound println( VECTOR[11] ) ; *Error* arrayref: array index out of bounds - VECTOR[11]
对照表 / 哈希
某些情况下可以使用哈希来优化代码逻辑、运行速度等等。
-
定义
makeTable
HASH = makeTable( 'HASH )
-
赋值
HASH[1] = "ONE" HASH["2"] = 2 HASH[cvId] = "cellName"
-
引用
-
查看一个哈希的所有 key / value
HASH~>? ; ( "2" 1 db:0x21cfda1a ) HASH~>?? ; ( "2" 2 1 "ONE" db:0x21cfda1a "cellName" )
-
遍历一个哈希
foreach( key HASH printf( "key: %A , value: %A\n" key HASH[key] ) ) ; key: db:0x21cfda1a , value: "cellName" ; key: 1 , value: "ONE" ; key: "2" , value: 2
-
条件判断
if
-
真/假 情况运行的都是单一的语句
if( 条件 当条件成立时运行 当条件不成立时运行 )
例如:当 a > b 成立时,打印 "Yes";不成立时,打印时 "No"。
if( a > b println( "Yes" ) println( "No" ) )
-
真/假 情况需要运行多条语句
if( 条件 then 当条件成立时运行 1 当条件成立时运行 2 else 当条件不成立时运行 1 当条件不成立时运行 2 )
例如:当 a > b 成立时,c 赋值 1 然后打印 "Yes";不成立时,c 赋值为 0 然后打印时 "No"。
if( a > b then c = 1 println( "Yes" ) else c = 0 println( "No" ) )
-
多层嵌套
Skill 不支持
if-elsif-else
这种简化的写法,但是逻辑是一样的,后级的if
需要在上一级的else
中。例如:
- 当 a > b 成立时,打印 "Yes";
- 否则当 a > c 成立时,打印 "Yes";
- 都不成立时,打印时 "No"。
if( a > b println( "Yes" ) if( a > c println( "Yes" ) println( "No" ) ) )
第二个
if
虽然不是 单一语句,但可以将整个if( a>c ... )
看做一个整体,所以也可以忽略then
/else
。这就存在一个问题,当需要判断多个条件的时候
if
的写法就不太好看了,这时就可以使用case
或者cond
,后面会讲。
when / unless
when / unless 就非常简单了,只当给定的条件为 真/假 的时候才运行给定的语句
条件为真才运行。
when( a > b
println( "Yes" )
)
条件为假才运行。
unless( a > b
println( "No" )
)
case / cond
前面说了 case
/cond
可以用来优化多条件下的 if
,因此逻辑是一样的。
-
case
当所有的条件都是对一个变量做是否相等的判断的时候,可以使用
case
。例如,现在有一个变量 arg:
- 当 arg 等于 "a" 时,打印 "Is a";
- 否则当 arg 等于 "b" 或 "c" 时,打印 "Is b or c";
- 否则当 arg 等于 "d" 时,打印 "Is d";
- 全都不成立时,打印 "Unmatch"。
case( arg ( "a" println( "Is a" ) ) (( "b" "c" ) println( "Is b or c" ) ) ( "d" println( "Is d" ) ) ( t println( "Unmatch" ) ) )
上面的语句换成
if
需要这样写:if( arg == "a" println( "Is a" ) if( arg == "b" || arg == "c" println( "Is b or c" ) if( arg == "d" println( "Is d" ) println( "Unmatch" ) ) ) )
很明显
case
的写法更加清晰,观感上也更加舒服。 -
cond
case
的使用情景比较单一,但条件多且判断的对象或者逻辑不唯一的时候可以cond
。例如,现在需要对变量 a 做几个判断:
- 当 a > b 时,打印 "Bigger than b";
- 否则当 a > c 时,打印 "Bigger than c";
- 都不成立时,打印 "Smallest"。
cond( ( a > b println( "Bigger than b" ) ) ( a > c println( "Bigger than c" ) ) ( t println( "Smallest" ) ) )
循环控制
for
指定一个起始整数(initialValue)和终止整数(finalValue),依次遍历从 initial 到 final 组成的 list 的每一个元素,间隔为 1。
下面用 for
打印从 0 到 2:
for( x 0 2
println( x )
)
; 0
; 1
; 2
foreach
指定一个 list ,依此遍历每一个元素。
下面用 foreach
打印从 0 到 2:
foreach( x list( 0 1 2 )
println( x )
)
; 0
; 1
; 2
while
指定一个条件,当条件为真时才会运行,当条件为假时跳出 while 循环。
下面用 while
打印从 0 到 2:
a = 0
while( a < 3
println( a )
a++
)
; 0
; 1
; 2
运算
算数运算
操作符 | 函数 | |||
---|---|---|---|---|
加 | + | a + b | plus | plus( a b ) |
减 | - | a - b | difference | difference( a b ) |
乘 | * | a * b | times | times( a b ) |
商 | / | a / b | quotient | quotient( a b ) |
余 | remainder | remainder( a b ) | ||
乘方 | ** | a ** b | expt | expt( a b ) |
开方 | sqrt | sqrt( a b ) |
赋值运算
操作符 | 函数 | |||
---|---|---|---|---|
直接赋值 | = | a = 1 | setq | setq( a 1 ) |
自增 | += | a += 1 | ||
自增(+1) | ++ | a ++ | add1 | add1( a ) |
自减 | -= | a -= 1 | ||
自减(+1) | -- | a -- | sub1 | sub1( a ) |
比较运算
操作符 | 函数 | |||
---|---|---|---|---|
相等 | == | a == b | equal | equal( a b ) |
不等 | != | a != b | nequal | nequal( a b ) |
小于 | < | a < b | lessp | lessp( a b ) |
小于等于 | <= | a <= b | leqp | equal( a b ) |
大于 | > | a > b | greaterp | greaterp( a b ) |
大于等于 | >= | a >= b | geqp | equal( a b ) |
几乎相等 | nearlyEqual | nearlyEqual( a b ) |
逻辑运算
操作符 | 函数 | |||
---|---|---|---|---|
与 | && | a && b | and | and( a b ) |
或 | || | a || b | or | or( a b ) |
非 | ! | ! a | not | not( a ) |