请选择 进入手机版 | 继续访问电脑版

EDABOSS电子论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 936|回复: 4

[转帖] ocean脚本实现corner仿真

[复制链接]

34

主题

3

回帖

111

E币

技术员

Rank: 2

积分
72
发表于 2020-4-13 08:51:25 | 显示全部楼层 |阅读模式
本帖最后由 任四 于 2020-4-13 09:07 编辑

集成电路制造过程中同一wafer不同的芯片和不同的批次的wafer之间,都会出现器件参数的不同,而且在芯片的工作过程中外界的环境温度或者供电电压也随时发生变化,这些不确定性因素往往导致集成电路的实际产品性能与设计指标有一定差别。

为了在电路设计前期充分考虑这些因素,同时也为了减小电路设计者的工作难度,目前有很多仿真手段来尽量避免以上情况的发生,其中corner仿真就是其中很重要的一种。
集成电路中的corner仿真
在集成电路制造工艺的理论中,工艺变量可以有精确的数值,而精确的工艺变量值可用来计算该工艺下的仿真结果。但是在实际的制造过程中,工艺变量只能保证在制造公差之内,随机地在理想值附近波动。因此,很多器件的随机参数的变化结合在一起就会导致电路分析结果的不确定性。

为了在—定程度上减小电路设计难度,设计者要保证器件性能在某个可控的范围之内,通常提供给设计者的范围是以“工艺角(corner)”形式给出的。其中心思想是把影响器件参数的工艺波动限制在一定范围内(更具体的工艺角方面介绍可以自行查阅)。

工艺角分析关注的是当工艺偏差参数达到极限(称为corner),或者环境温度、工作电压等参数达到极限时的电路性能。工艺角分析的信息可以确定在工艺参数有随机偏差甚至是最不希望出现的偏差组合方式下,电路的性能是否满足规格要求,以指导电路设计人员修正设计或者在流片前期评估设计风险。
ADE L 电路仿真
今天的主要内容虽然是讲ocean脚本实现corner仿真,但是对于没有接触过ocean语言的同学来说,直接读代码可能会有一些困难,包括ocean语言的语法、格式、用法等。其实ocean脚本并不神奇,它和我们熟悉的ADE L仿真界面实现的是同样的功能,二者只是表示形式不一样而已。

为了简单,这里以反相器的仿真为例,电路基础不在赘述,仿真过程和方法与复杂电路之间没有很大区别,不过是不同电路可能采用的分析方法、设计变量、工艺文件、输出信息的不同而已,如何替换这些内容会在遇到的时候跟大家一一介绍。

设置ADE L的仿真步骤没有详细给出,相信对于想了解IC设计的同学一定对这些简单仿真了如指掌,下面是小目同学设置仿真的原理图和仿真内容。
3.jpg
2.jpg
1.jpg
4.jpg

ADE L界面设置保存为ocean脚本



有人可能会问,明明是讲用ocean脚本仿真,怎么又讲到ADE L的图形化仿真基础了,这是因为ocean脚本和ADE L的图形化仿真互相包含,上面也已经说过能用图形化界面实现的操作,用ocean脚本也都可以实现,所以cadence公司提供了由图形化界面生成ocean脚本的方法。

为了修改脚本内容简单,所以在写ocean脚本之前小目同学一般都会在图形化界面先跑一遍仿真,确认设置没有问题之后把图形化界面的设置保存为ocean脚本,然后在其基础之上修改脚本,这样可以减小一些出错概率。

设置ADE L界面无误后,选择工具栏:Session->Save Ocean Script,之后在弹出的对话框输入想保存脚本的路径和文件名,比如这里保存在桌面,文件名为:ICSkillSharing.ocn,那么确认之后在桌面上可以看到保存的名为ICSkillSharing.ocn的脚本,虽然在Linux环境下文件后缀不在重要,但是为了方便辩识ocean脚本一般以ocn作为后缀,以上操作过程在上面图片中可以清晰看到。

获得的ocean脚本如下面所示,这个脚本和前面的ADE L设置完全一致,以小目同学上面的操作结果为例,可以在CIW窗口输入:load("~/Desktop/ICSkillSharing.ocn") 然后回车,之后会弹出仿真结果,对比发现与图形界面结果完全一致。

对照生成的脚本,简单介绍一下用于仿真的ocean脚本,参考上面图形化界面,小目同学把生成的脚本分为4部分:

分析方法设置:主要是确定采用的分析方法,比如:dc、tran、ac等仿真方法和相关设置(7-9行)。
设计变量设置:确定仿真中设计变量的值(10、11、15行)。
选择输出设置:选择输出信息(17-20行)。
环境设置部分:除了以上3部分的信息,其余内容都认为是仿真环境设置部分,包括选择仿真器、选择仿真设计、选择仿真结果保存路径以及仿真结果保存相关的设置等(其余所有内容)。
熟悉电路仿真的同学应该很容易理解该脚本前三个设置项的内容。环境设置部分的内容会多一些,该脚本中第1-6行的内容是选择仿真器、选择仿真设计、选择仿真结果保存路径并设置仿真的工艺模型文件和工艺角;第12-14行是仿真进行的顺序,这个由设置分析方法的先后顺序决定,可以根据需要调整顺序;第16行是运行仿真。
  1. simulator( 'spectre ) ;setup simulator
  2. design(     "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist") ;setup netlist
  3. resultsDir( "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic" ) ;dir for results
  4. modelFile(
  5.      '("/home/IC/Documents/pdks/smic18mmrf/models/spectre/ms018_v1p9_spe.lib" "tt")
  6. ) ;setup process models & corner
  7. analysis('dc ?saveOppoint t  ?param "va"  ?start "0"  
  8.          ?stop "3"  ) ;add dc simulation
  9. analysis('tran ?stop "10u"  ) ;add tran simulation
  10. desVar(      "avdd" 3  ) ;design variate
  11. desVar(      "va" 1.5  ) ;design variate
  12. envOption(
  13.     'analysisOrder  list("tran" "dc")
  14. ) ;setup simulation order
  15. temp( 27 ) ;setup temp
  16. run() ;start the simulation
  17. selectResult( 'tran ) ;select result for tran simulation
  18. plot(getData("/A") getData("/Y")  ) ;plot A & Y
  19. selectResult( 'dc ) ;select result for dc simulation
  20. plot(getData("/A") getData("/Y") ) ;plot A & Y
复制代码


为了进一步熟悉ocean脚本,下面简单介绍一些ocean语言的基础内容,只有掌握一定的语法知识才可以在修改脚本的时候减少出错,做到胸有成竹。
ocean脚本基础
ocean是基于字符界面的skill语言脚本,可以运行在终端或Cadence CIW界面,学习过C语言的人可以比较容易的掌握其使用。ocean作为一种语言,语法内容较多,下面介绍的内容只是帮助大家理解后面需要用到的脚本内容,至于更全面的语法介绍可以参考Cadence公司的参考手册,比如:OCEAN Reference.

ocean语言的变量:ocean语言和其他所有编程类语言一样,有各种数据类型,但是又不像c语言那样需要在变量使用前做变量声明,跟python语言很像,变量基本上是即拿即用,如:

  1. avdd = 3 ;integer and assign this variate a value of 3
  2. dvdd = 1.8 ;floating and assign this variate a value of 1.8
  3. design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist" ;string
复制代码


ocean语言中,分号 「;」是注释标志,一行中分号后面的内容在程序执行时忽略,编程时要养成使用注释语句的良好习惯。

虽然ocean语言中变量类型使用起来更加灵活,但是一定要注意它是有变量类型区别的,不同变量类型在进行操作的时候需要稍加留意,否则容易出错。特别是在对数据进行格式化输出或者运算的时候,如果数据类型不正确,程序就很容易出错,如:

  1. avdd = 3
  2. dvdd = 1.8
  3. design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist"
  4. printf("avdd = %d dvdd = %f design_dir = %s" avdd dvdd design_dir) ;format print
复制代码


在格式化输出的时候变量类型没有匹配很可能就会在运行时报错,有时候即使程序没有报错也可能出现输出结果与自己预期不一致的情况,比如下面示意,虽然程序运行时没有报错,但是输出结果却与预期完全不一致,这是因为字符型变量只有一位,所以结果中只输出了字符串常量中的第一位,也就是:「/」.
  1. design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist" ;string
  2. printf("design_dir = %c" avdd dvdd design_dir) ;format print of string
复制代码


ocean语言中的算术运算符:ocean语言中的算术运算符基本和其他编程语言一样,包括赋值运算符、加、减、乘、除等,具体运算符号和用法可以从下面表格中看到。
算术运算符
名称
符号
用法
+
a = 1+2
-
a = 2-1
*
a = 2*3
/
a = 8/4
乘方
**
a = 3**3
赋值
=
a = 9



ocean语言中的关系和逻辑运算符:ocean语言中的关系运算符和其他编程语言一样,包括大于、小于等,具体运算符号和用法可以从下面表格中看到,但是逻辑运算符的返回值会和平时接触的语言有不同,可以从示例中看到。

关系和逻辑运算符
名称
符号
操作元
用法
返回值
小于
<
数字
3 < 5
t
大于
>
数字
5 > 7
nil
等于
==
数字、字符串、列表
"ab" == "cd"
nil
不等于
!=
数字、字符串、列表
"ab" != "cd"
t
小于或等于
<=
数字
3 <= 4
t
大于或等于
>=
数字
2 >= 4
nil
&&
所有数据类型
3 && 5
5
||
所有数据类型
3 || 5
3


ocean语言中的函数:ocean语言中可以直接使用很多skill语言的函数,但是skill语言有各种各样功能的函数,小目同学不懂也不可能一一介绍,这里简单列举几个我们可能会用到的函数,还是那句话,想要了解更多就看官方手册。
函数的使用

函数
功能
返回值
outfile("filename" "mode")
打开文件,返回文件句柄
file handle
close(file handle)
关闭打开的文件
t / nil
strcat("abc" "def")
合并多个字符串
"abcdef"
substring("ICSkillSharing" 3 3)
取字符串的子字符串,返回子字符串
"Ski"
a = "ICSkillSharing" printf("Hi %s!" a)
格式化输出语句到CIW
t / nil
p = outfile("filename" "w") fprintf(p "Hi !")
格式化输出语句到文件
t / nil
abs(-5)
求绝对值
5
plot(vt("/vo") ?expr '("vo"))
以给定的名称画出波形
t / nil


ocean语言中的结构控制语句:ocean语言的条件语句和循环语句与其他语言很相似,用户可以通过使用这些语句完成很多重复性工作,corner仿真在很大程度上都依赖于条件语句和循环语句,下面仅介绍即将使用的两个结构语句。

结构控制语句

名称
用法
if(condition then expression)
if(avdd == 3 then corner = "tt")
for(loopVar initinalValue finalValue expression)
for(corner 1 3 print(corner))


以上是简单的ocean语言用法介绍以及示例,因为篇幅限制所以关于ocean语言的内容就不做更多的展开,如果各位同学有什么疑问可以以任何方式【留言】,小目同学会认真解答。
ocean脚本实现corner仿真
文章最开始的示例告诉大家一个简单有效的方法获取ocean脚本,但是上面的脚本内容过于单调,为了便于用在大多数设计中,可以在脚本中添加一些其它仿真和设置内容,在不同设计仿真的时候根据需要注释掉不需要的部分。

以下是根据ADE L界面生成的内容完善之后的ocean脚本,可以用来完成corner仿真。通过改变控制变量的内容设置不同的循环,实现不同PVT组合下的仿真。
  1. filename = "ICSkillSharing.ocn" ;file name
  2. design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist" ;dir of netlist file
  3. modelfile_dir = "/home/IC/Documents/pdks/smic18mmrf/models/spectre/ms018_v1p9_spe.lib" ;dir of process models
  4. for(corner_sim 1 3 ;;loop for corner, corner_mos、corner_bjt、corner_res、corner_cap are different corners
  5.      if(corner_sim == 1 then corner_dir = "tt" corner_mos = list(modelfile_dir "tt") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  6.      if(corner_sim == 2 then corner_dir = "ss" corner_mos = list(modelfile_dir "ss") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  7.      if(corner_sim == 3 then corner_dir = "ff" corner_mos = list(modelfile_dir "ff") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  8.      ;if(corner_sim == 4 then corner_dir = "sf" corner_mos = list(modelfile_dir "sf") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  9.      ;if(corner_sim == 5 then corner_dir = "fs" corner_mos = list(modelfile_dir "fs") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))

  10.         for(vdd_sim 1 3 ;;loop for power supply
  11.             if(vdd_sim == 1 then vdd = 3.0 vdd_dir = "3p0")
  12.             if(vdd_sim == 2 then vdd = 3.3 vdd_dir = "3p3")
  13.             if(vdd_sim == 3 then vdd = 2.7 vdd_dir = "2p7")

  14.                 for(temp_sim 1 3 ;;loop for temp
  15.                     if(temp_sim == 1 then temp = 27 temp_dir = "27")
  16.                     if(temp_sim == 2 then temp = -40 temp_dir = "m40")
  17.                     if(temp_sim == 3 then temp = 125 temp_dir = "125")

  18.                     results_dir = strcat(substring(design_dir 1 62) substring(filename 1 14) "/" corner_dir "_v" vdd_dir "_t" temp_dir) ;save results dir
  19.                     simulator( 'spectre ) ;setup siulator
  20.                     design( design_dir ) ;netlist file
  21.                     resultsDir( results_dir ) ;save results dir

  22. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  23. ;;;;;;;;;;;;;;;;;;;;;;;;;;     add simulation script blow    ;;;;;;;;;;;;;;;;;;;;;;;;
  24. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  25.                     modelFile( corner_mos corner_bjt corner_res corner_cap ) ;setup process models
  26.                     analysis('dc ?saveOppoint t  ?param "va"  ?start "0"  
  27.                         ?stop "3"  ) ;add dc simulation
  28.                     ;analysis('ac ?start "1"  ?stop "1G"  ) ;add ac simulation
  29.                     ;analysis('stb ?start "1" ?stop "10G" ?probe "/IPRB0" ) ;add stb simulation
  30.                     analysis('tran ?stop "10u" ?errpreset "conservative" ) ;add tran simulation
  31.                     desVar( "avdd" vdd ) ;setup avdd
  32.                     desVar(    "va" 1.5 ) ;setup va
  33.                     envOption(
  34.                         'analysisOrder  list("dc" "tran")
  35.                     ) ;setup simulation order
  36.                     option( ?categ 'turboOpts
  37.                         'apsplus  t
  38.                         'errorLevel  "Moderate"
  39.                         'uniMode  "APS"
  40.                     ) ;use aps
  41.                     saveOption( 'subcktprobelvl "5" ) ;save results option, for 5th subckt blow
  42.                     saveOption( 'currents "all" ) ;save all current
  43.                     temp( temp ) ;setup temp
  44.                     run() ;start the simulation

  45.                     selectResult( 'tran ) ;select result for tran simulation
  46.                     VTEST = average(VT("/Y"))
  47.                     plot(getData("/A") getData("/Y") ?expr list(strcat("A_" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y_" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  48.                     selectResult( 'dc ) ;select result for dc simulation
  49.                     plot(getData("/A") getData("/Y") ?expr list(strcat("A:" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y:" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  50.                     ;selectResult( 'ac ) ;select result for ac simulation
  51.                     ;plot(getData("/A") getData("/Y") ?expr list(strcat("A:" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y:" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  52.                     ;YN = clip((- VT("/Y")) 1e-06 9e-06) ;use calculator
  53.                     ;plot( YN ?expr list( strcat("YN:" corner_dir "_v" vdd_dir "_t" temp_dir) ) ) ;plot result

  54.                     ;results_outfile = outfile(strcat(substring(design_dir 1 62) substring(filename 1 14) ".txt") "a") ;save VTEST to a file
  55.                     ;fprintf(results_outfile "The PVT are:\t\t %s_v%s_t%s\n" corner_dir vdd_dir temp_dir) ;save PVT info
  56.                     ;fprintf(results_outfile "The results are:\t VTEST = %f\n" VTEST) ;save result to file
  57.                     ;close(results_outfile) ;close file opened before

  58. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  59. ;;;;;;;;;;;;;;;;;;;;;;;;;;    add simulation script above    ;;;;;;;;;;;;;;;;;;;;;;;;
  60. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  61.                 ) ;;end loop for temp
  62.         ) ;;end loop for power supply
  63. ) ;;end loop for corner
复制代码


以上实现corner仿真的ocean脚本包含ac、dc、tran分析方法;包含设计变量设置;包含仿真结果保存选项;包含输出波形打印;包含波形计算处理实例;使能高性能仿真器。结合注释可以很简单理解脚本的用法,修改网表路径、工艺模型文件路径、设计变量、分析方法等适合自己的仿真需求。

1.jpg
2.jpg
3.jpg

仿真结果保存文件

仿真结果中每条曲线标示出对应的仿真条件,在设置的结果保存路径中有每个corner对应的仿真文件,用结果查看工具可以看到更详细的仿真结果,对应的个别需要使用calculator处理的特殊的信号结果保存在ICSkillSharing.txt文件中。
积分规则
回复

使用道具 举报

34

主题

3

回帖

111

E币

技术员

Rank: 2

积分
72
 楼主| 发表于 2020-4-13 09:15:48 | 显示全部楼层
在之前一篇文章中已经为大家介绍了使用ocean脚本实现corner仿真的方法,并且在文章末尾附上了完整、实用的ocean脚本,不过对于ocean脚本的稍高级用法以及语法内容并没有做详细的介绍,在这里特别做一些补充,帮助同学们完成一些符合自己实际情况的ocean脚本。
用好软件工具
刚开始接触ocean语言的同学可能会感觉到毫无方向,只是别人脚本里写了什么,自己也在脚本里写进去,偶尔遇到需要实现的一些功能又不知从何写起,只能在各大论坛、技术交流群里大海捞针,试图发现一丝对自己有用的内容。

筛选信息的方法虽然有用但是效率太低,而且不能保证每次都能找出有用信息,对于这一点小目同学深有感触,因为这是很多初学者都会走的路,不过今天小目同学想分享一些经验,希望有心学习IC设计的初学者可以少走一些弯路,早日圆了“芯片梦”。

Cadence SKILL API Finder: 这个是Cadence提供的工具,可以用来查找skill语言和ocean语言相关的函数功能以及用法,对于不熟悉的函数,用这个工具可以快速找到解决方法。

启动方法是:在Virtuoso软件CIW界面:Tools->SKILL API Finder, 启动之后可以在搜索框输入需要查找的函数名,如果正确就会看到函数的使用方法和函数功能简介。
1.jpg


Cadence Help: 小目同学个人认为这个是在使用Cadence系列软件的时候最有利的帮手,如果使用得当基本可以解决平时使用中遇到的所有问题,而且使用起来也很方便。

在Virtuoso软件界面:Help->Virtuoso Documentation Library, 之后按下快捷键:F2, 可以看到如图所示的搜索框,像所有的搜索工具一样,只需要输入自己想要查找的内容就可以看到相关的信息以及对应的文档。

正确使用Cadence Help功能可以为我们解决很多工具上的使用问题,再也不必为简单的软件问题头疼,当然ocean语言的学习也离不开Cadence Help的帮助。
2.jpg

SKILL IDE: 目前市面上有很多针对其他语言比如:python, C等开发的各种各样的IDE, 不仅界面友好,而且辅助功能应有尽有。然而,由于ocean或者skill语言的应用相对较少,小目同学暂时没有发现可以独立使用的skill语言IDE, 但是Cadence 公司在工具里集成了一个类似的工具,帮助用户调试脚本。

打开方式是:在Virtuoso软件CIW界面:Tools->SKILL IDE, 对于复杂的程序在这里调试会事半功倍,不过如果程序相对简单,可以直接在CIW界面进行调试,倒是没有使用IDE的必要。

除了Cadence软件自带的SKILL IDE外,小目同学要给大家推荐一下VS Code, 因为这里有各位大神贡献的插件,其中就有一个针对skill语法的高亮插件,可以让脚本看起来更加清晰,不妨一试,两个工具的显示效果如下面图片所示。
3.jpg
4.jpg

VS Code插件显示ocean语言


Cadence Online Support: 网址:https://support.cadence.com, 这相当于一个封闭的Cadence社区,里面包含各种各样的知识库、课程以及专业的技术支持,解决各种各样的疑难杂症。不过由于限制,只有那些大的公司工作人员才有可能获得权限,小目同学也无法看到里面的精彩内容,希望有账号的同学跟我们分享一些“内部消息”,在此谢过!

以上的“利器”是在学习Cadence软件平台使用方法的时候不可或缺的资源,好好利用必然可以节省很多时间,大大获益。
ocean语言的内容补充
以前面文章中给的ocean脚本作为示例,更详细地介绍ocean初学者可能不理解的地方,同时也巩固一些基础内容,希望各位同学能够熟练掌握、达到举一反三的学习目标,能在工作中灵活使用ocean语言。

  1. filename = "ICSkillSharing.ocn" ;file name
  2. design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist" ;dir of netlist file
  3. modelfile_dir = "/home/IC/Documents/pdks/smic18mmrf/models/spectre/ms018_v1p9_spe.lib" ;dir of process models
  4. for(corner_sim 1 3 ;;loop for corner, corner_mos、corner_bjt、corner_res、corner_cap are different corners
  5.      if(corner_sim == 1 then corner_dir = "tt" corner_mos = list(modelfile_dir "tt") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  6.      if(corner_sim == 2 then corner_dir = "ss" corner_mos = list(modelfile_dir "ss") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  7.      if(corner_sim == 3 then corner_dir = "ff" corner_mos = list(modelfile_dir "ff") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  8.      ;if(corner_sim == 4 then corner_dir = "sf" corner_mos = list(modelfile_dir "sf") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))
  9.      ;if(corner_sim == 5 then corner_dir = "fs" corner_mos = list(modelfile_dir "fs") corner_res = list(modelfile_dir "res_tt") corner_cap = list(modelfile_dir "mim_tt") corner_bjt = list(modelfile_dir "bjt_tt"))

  10.         for(vdd_sim 1 3 ;;loop for power supply
  11.             if(vdd_sim == 1 then vdd = 3.0 vdd_dir = "3p0")
  12.             if(vdd_sim == 2 then vdd = 3.3 vdd_dir = "3p3")
  13.             if(vdd_sim == 3 then vdd = 2.7 vdd_dir = "2p7")

  14.                 for(temp_sim 1 3 ;;loop for temp
  15.                     if(temp_sim == 1 then temp = 27 temp_dir = "27")
  16.                     if(temp_sim == 2 then temp = -40 temp_dir = "m40")
  17.                     if(temp_sim == 3 then temp = 125 temp_dir = "125")

  18.                     results_dir = strcat(substring(design_dir 1 62) substring(filename 1 14) "/" corner_dir "_v" vdd_dir "_t" temp_dir) ;save results dir
  19.                     simulator( 'spectre ) ;setup siulator
  20.                     design( design_dir ) ;netlist file
  21.                     resultsDir( results_dir ) ;save results dir

  22. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  23. ;;;;;;;;;;;;;;;;;;;;;;;;;;     add simulation script blow    ;;;;;;;;;;;;;;;;;;;;;;;;
  24. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  25.                     modelFile( corner_mos corner_bjt corner_res corner_cap ) ;setup process models
  26.                     analysis('dc ?saveOppoint t  ?param "va"  ?start "0"  
  27.                         ?stop "3"  ) ;add dc simulation
  28.                     ;analysis('ac ?start "1"  ?stop "1G"  ) ;add ac simulation
  29.                     ;analysis('stb ?start "1" ?stop "10G" ?probe "/IPRB0" ) ;add stb simulation
  30.                     analysis('tran ?stop "10u" ?errpreset "conservative" ) ;add tran simulation
  31.                     desVar( "avdd" vdd ) ;setup avdd
  32.                     desVar(    "va" 1.5 ) ;setup va
  33.                     envOption(
  34.                         'analysisOrder  list("dc" "tran")
  35.                     ) ;setup simulation order
  36.                     option( ?categ 'turboOpts
  37.                         'apsplus  t
  38.                         'errorLevel  "Moderate"
  39.                         'uniMode  "APS"
  40.                     ) ;use aps
  41.                     saveOption( 'subcktprobelvl "5" ) ;save results option, for 5th subckt blow
  42.                     saveOption( 'currents "all" ) ;save all current
  43.                     temp( temp ) ;setup temp
  44.                     run() ;start the simulation

  45.                     selectResult( 'tran ) ;select result for tran simulation
  46.                     VTEST = average(VT("/Y"))
  47.                     plot(getData("/A") getData("/Y") ?expr list(strcat("A_" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y_" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  48.                     selectResult( 'dc ) ;select result for dc simulation
  49.                     plot(getData("/A") getData("/Y") ?expr list(strcat("A:" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y:" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  50.                     ;selectResult( 'ac ) ;select result for ac simulation
  51.                     ;plot(getData("/A") getData("/Y") ?expr list(strcat("A:" corner_dir "_v" vdd_dir "_t" temp_dir) strcat("Y:" corner_dir "_v" vdd_dir "_t" temp_dir)) ) ;plot A & Y
  52.                     ;YN = clip((- VT("/Y")) 1e-06 9e-06) ;use calculator
  53.                     ;plot( YN ?expr list( strcat("YN:" corner_dir "_v" vdd_dir "_t" temp_dir) ) ) ;plot result

  54.                     ;results_outfile = outfile(strcat(substring(design_dir 1 62) substring(filename 1 14) ".txt") "a") ;save VTEST to a file
  55.                     ;fprintf(results_outfile "The PVT are:\t\t %s_v%s_t%s\n" corner_dir vdd_dir temp_dir) ;save PVT info
  56.                     ;fprintf(results_outfile "The results are:\t VTEST = %f\n" VTEST) ;save result to file
  57.                     ;close(results_outfile) ;close file opened before

  58. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  59. ;;;;;;;;;;;;;;;;;;;;;;;;;;    add simulation script above    ;;;;;;;;;;;;;;;;;;;;;;;;
  60. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  61.                 ) ;;end loop for temp
  62.         ) ;;end loop for power supply
  63. ) ;;end loop for corner
复制代码


以上脚本的功能大家都已经熟悉,但是可能对更细节的语法内容有所疑惑,这里就挑一些会令初学者犯难的地方加以说明。

列表结构:首先解释一下ocean语言中频繁出现的半个单引号:「'」, 比如:以上脚本中第22行:simulator( 'spectre ), 第51行:selectResult( 'tran )等,其中的单引号作用是:单引号后面的内容数据类型为列表,也可以用函数:list()来实现,可以看到脚本中也有很多用list()实现的列表。

ocean语言和skill语言中有很多以列表传递参数的形式,因为skill语言是从Lisp(List Processing)语言发展而来的,顾名思义这是一种对list结构操作的语言,所以skill语言中有很多对list处理的语句。

选择设计:脚本中第23行:design( design_dir ),  作用是设置仿真电路的网表文件,这个文件是在图形化界面生成的,初学者不太注意的话很容易产生疑惑。

有时候用户希望和图形化界面操作一样,选择具体的设计文件,对于这种情况,design()函数也可以像后面这样传递参数:design( "libName" "cellName" "viewName" "mode"), 各个参数分别代表:设计所在的库名称、设计的名称、打开的视图和打开方式,对于小目同学的演示设计,第23行可以这样写:
design( "ICSkillSharing" "inv" "shematic" "r")
波形打印:之前的脚本打印波形用plot()函数,传递需要打印的波形信息即可。认真实验的同学可能发现,直接由ADE L保存的ocean脚本运行之后打印的波形名称和直接在ADE L仿真界面打印的波形名称不一致,至于原因小目同学也搞不清楚,只能采用其它方法实现波形名称修改。

在使用ocean脚本时打印波形可以使用这样的方法:plot(waveform ?expr 'waveName), 指定波形名称,不至于在同时打印多个波形时混淆。

坐标轴变换:plot()函数只能实现波形打印,坐标轴变换也是一个常用功能,比如在gm/Id设计方法中需要画出gm/Id与gm*ro的关系曲线,但是gm/Id本身就是一个因变量,所以需要先画出gm/Id和gm*ro与vgs的关系曲线,然后通过坐标变换输出gm/Id与gm*ro的关系曲线。

函数ocnYvsYplot(?wavex wavex ?wavey wavey ?exprx exprx ?expry expry)实现坐标变换,参数的传递与plot()函数很一致,可以类比设置。

保存文件:ocean脚本不仅可以实现仿真功能,还可以根据需要将结果保存至文件以供后续处理。
实现4bit选择开关的遍历仿真
ocean脚本很适合做一些重复性的仿真操作,实现corner仿真只是这类仿真中的一种,还有很多其它类似仿真比如:trimming电路的仿真,虽然很多时候trimming电路只需要通过手算就可以确定结果,但是有时候也可能需要遍历所有可能,这时候使用ocean脚本的优势就会体现出来。

  1. avdd = 3 ;logic 1
  2. for(bit3_sim 0 1 ;loop for bit 3
  3.      for(bit2_sim 0 1 ;loop for bit 2
  4.          for(bit1_sim 0 1 ;loop for bit 1
  5.              for(bit0_sim 0 1 ;loop for bit 0

  6.                  bit3 = avdd*bit3_sim ;value for control vsource
  7.                  bit2 = avdd*bit2_sim ;value for control vsource
  8.                  bit1 = avdd*bit1_sim ;value for control vsource
  9.                 bit0 = avdd*bit0_sim ;value for control vsource

  10. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  11. ;;;;;;;;;;;;;;;;;    add simulation script blow     ;;;;;;;;;;;;;;;;;
  12. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  13.                 ;desVar( "bit3" bit3 ) ;assign value
  14.                 ;desVar( "bit2" bit2 ) ;assign value
  15.                 ;desVar( "bit1" bit1 ) ;assign value
  16.                 ;desVar( "bit0" bit0 ) ;assign value

  17.                 ;add self ocean here


  18. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  19. ;;;;;;;;;;;;;;;;;    add simulation script abovew   ;;;;;;;;;;;;;;;;
  20. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  21.                printf("The trimming code is:%d %d %d %d. \n" bit3 bit2 bit1 bit0);display results
  22.             ) ;end loop for bit 0
  23.         ) ;end loop for bit 1
  24.     ) ;end loop for bit 2
  25. ) ;end loop for bit 3
复制代码


以上的例子希望能起到抛砖引玉的作用,脚本学习重在实践,在工作和学习中活学活用才能体现脚本的魅力,希望使用脚本能给各位同学带来一定的便利。
回复 支持 反对

使用道具 举报

34

主题

3

回帖

111

E币

技术员

Rank: 2

积分
72
 楼主| 发表于 2020-4-13 09:30:46 | 显示全部楼层
在第二帖中推荐完SKILL IDE和VS Code插件之后小目同学又发现了一个新大陆,这里再次推荐一个编辑器:UltraEdit, 可以配置成支持skill语言语法高亮和关键词自动补全功能,而且Windows、Linux、Mac三个平台都可以使用,各位同学不妨一试。

另外,现在很多corner仿真都是在Cadence 61平台上通过ADE XL来进行,设置方便,并且可以同时进行很多组功能不同的仿真,既然如此,也来介绍一下ADE XL环境下使用ocean脚本实现corner仿真的方法。
配置UltraEdit作为skill语言编辑器
正所谓:“工欲善其事,必先利其器”,写代码也是一样。市面上有各种各样的IDE,都是为了让同学们搬砖的时候可以省一些力气,但是由于ocean和skill语言的使用面比较窄,所以除了Cadence集成在软件里的SKILL IDE,好像没有其它的IDE可以使用,在之前的文章中为大家推荐的VS Code插件也仅仅只能实现代码高亮,其他补全功能暂时不完善。

“山穷水复疑无路,柳暗花明又一村”,通过不断的尝试,小目同学终于发现了一个目前来说还比较实用的skill语言编辑器:UltraEdit,下面以Windows平台为例,介绍一下这个软件。
1.gif
UltraEdit实现skill语言的自动补全
2.jpg
UltraEdit实现skill语言的语法高亮

UltraEdit是一套功能强大的文本编辑器,可以通过适当的配置,满足skill语言或者ocean语言编程的需要,不过仅仅是完成高亮和部分补全功能而已,至于程序调试功能建议还是在CIW界面或者Cadence软件自带的SKILL IDE里完成。

配置UltraEdit适合skill语言或者ocean语言编程需要几个步骤,首先下载并安装一个比较新版本的UltraEdit编辑器(以v26版本为例),在UltraEdit打开菜单栏:高级->设置->编辑器显示->语法高亮,这时候已安装的词语文件一栏是找不到Cadence SKILL/SKILL++选项的,在电脑联网的前提下单击:添加/删除语言,在搜索框内输入:skill并回车,勾选搜索结果中:Cadence SKILL/SKILL++之后,点击应用、确定。回到已安装的词语文件中找到Cadence SKILL/SKILL++一项,并选择,到这里UltraEdit编辑器已经可以实现skill语言的高亮和部分补全功能了。

1.jpg

由于不明原因,发现了一个跟注释语句有关的小bug, 会导致使用单个";"注释的内容高亮不太正确,追求完美的同学可以修改文件:Cadence_SKILL.uew(不同版本软件,这一文件后缀可能不一致,但是不影响后面操作),具体路径在菜单栏:高级->设置->编辑器显示->语法高亮->文档的完整目录名称,复制这个路径到资源管理器并打开,即可看到以上所说的文件,这个文件里面写的是与skill语言有关的关键字等,修改这一文件:"/Line Comment = ..."一行,内容与下图中一致即可。
2.png


另外为了方便地使用补全功能,可以打开菜单栏:高级->设置->编辑器->自动完成,勾选自动完成并按照自己的使用习惯输入自动完成中的数值,这里小目同学根据实际体验,建议自动补全为:2, 当输入两个以上字母时软件自动推荐补全内容。
1.png



使用ocean xl实现脚本仿真
之前和同学们分享过ocean脚本实现corner仿真的方法,后来有反映说Cadence 61环境下,用ADE XL做corner分析很方便。这一点是很明显的,在Cadence 61以前的版本,很多人还是选择用ocean脚本做corner仿真,随着软件的更新,工具越来越先进,用ADE XL做corner仿真确实很便捷,所以这里再分享一下用ocean脚本实现ADE XL工具的corner仿真功能。

ADE XL的设计更加人性化,使用ADE XL做仿真是有一定优势的,比如可以针对不同设计、不同工艺等条件同时设置多个测试,一次仿真可以实现很多结果的分析,节约研发时间。而且ADE XL的设置也可以保存为ocean脚本作为参考,用户可以在工具保存的ocean脚本基础上做适当的修改,自定义更灵活、方便的仿真脚本。

以下是小目同学针对corner仿真修改的一套仿真脚本,为了和之前的脚本照应,可能该脚本中会有一些不合理的变量设置,暂且保留。如果各位同学已经对ocean脚本有一定的熟悉,可以自行修改掉认为冗余的地方,如果有什么疑问也可以在文章评论处一起讨论哦!
  1. ;filename = "ICSkillSharing.ocn"
  2.   /**************************************************************/
  3.   /********************* set to XL mode *************************/
  4.   /**************************************************************/
  5.   ocnSetXLMode()
  6.   ocnxlProjectDir( "./simulation" ) ;run simulation here
  7.   ocnxlTargetCellView( "ICSkillSharing" "inv" "adexl" ) ;view opened by ADE XL
  8.   ocnxlResultsLocation( "" )
  9.   ocnxlSimResultsLocation( "" )
  10. /**************************************************************/
  11. /*********************** tests setup **************************/
  12. /**************************************************************/
  13. ;design_dir = "/home/IC/Documents/analog_ic/simulation/inv/spectre/schematic/netlist/netlist" ;dir of netlist file
  14. modelfile_dir = "/home/IC/Documents/pdks/smic18mmrf/models/spectre/ms018_v1p9_spe.lib" ;dir of process models
  15. /**************************************************************/
  16. /*************** test "ICSkillSharing:inv:1" ******************/
  17. /**************************************************************/
  18. ocnxlBeginTest("ICSkillSharing:inv:1") ;test name
  19. simulator( 'spectre )
  20. design( "ICSkillSharing" "inv" "schematic") ;design file

  21. ;modelFile(
  22. ;    '("/home/IC/Documents/pdks/smic18mmrf/models/spectre/ms018_v1p9_spe.lib" "tt")
  23. ;)

  24. analysis( 'tran ?stop "10u"  )
  25. analysis( 'dc ?saveOppoint t  ?param "va"  ?start "0"  
  26.          ?stop "3"  )
  27. desVar(    "avdd" 3    )
  28. desVar(    "va" 1.5    )
  29. envOption(
  30.      'analysisOrder  list("tran" "dc" "pz" "dcmatch" "stb" "envlp" "ac" "lf" "noise" "xf" "sp" "pss" "pac" "pstb" "pnoise" "pxf" "psp" "qpss" "qpac" "qpnoise" "qpxf" "qpsp" "hb" "hbac" "hbstb" "hbnoise" "sens" "acmatch")
  31. )
  32. temp( 27 )
  33. ocnxlOutputSignal( "/A" ?plot t ?save t) ;output signal
  34. ocnxlOutputSignal( "/Y" ?plot t ?save t) ;output signal
  35. ocnxlEndTest() ; "ICSkillSharing:inv:1"
  36. /**************************************************************/
  37. /*********************** sweeps setup *************************/
  38. /**************************************************************/
  39. ocnxlSweepVar("va" "1.5")
  40. ocnxlSweepVar("avdd" "3")
  41. /**************************************************************/
  42. /******************** model group setup ***********************/
  43. /**************************************************************/


  44. /**************************************************************/
  45. /*********************** corners setup ************************/
  46. /**************************************************************/
  47. for( corner_sim 1 3 ;;loop for corner, corner_moscorner_bjtcorner_rescorner_cap are different corners
  48.    if( corner_sim == 1 then corner_dir = "tt" corner_mos = list( modelfile_dir "tt" ) corner_res = list( modelfile_dir "res_tt" ) corner_cap = list( modelfile_dir "mim_tt" ) corner_bjt = list( modelfile_dir "bjt_tt" ))
  49.    if( corner_sim == 2 then corner_dir = "ss" corner_mos = list( modelfile_dir "ss" ) corner_res = list( modelfile_dir "res_tt" ) corner_cap = list( modelfile_dir "mim_tt" ) corner_bjt = list( modelfile_dir "bjt_tt" ))
  50.    if( corner_sim == 3 then corner_dir = "ff" corner_mos = list( modelfile_dir "ff" ) corner_res = list( modelfile_dir "res_tt" ) corner_cap = list( modelfile_dir "mim_tt" ) corner_bjt = list( modelfile_dir "bjt_tt" ))
  51.    ;if(corner_sim == 4 then corner_dir = "sf" corner_mos = list( modelfile_dir "sf" ) corner_res = list( modelfile_dir "res_tt" ) corner_cap = list( modelfile_dir "mim_tt" ) corner_bjt = list( modelfile_dir "bjt_tt" ))
  52.    ;if(corner_sim == 5 then corner_dir = "fs" corner_mos = list( modelfile_dir "fs" ) corner_res = list( modelfile_dir "res_tt" ) corner_cap = list( modelfile_dir "mim_tt" ) corner_bjt = list( modelfile_dir "bjt_tt" ))
  53.    for(vdd_sim 1 3 ;;loop for power supply
  54.      if( vdd_sim == 1 then vdd = "3.0" vdd_dir = "3p0" )
  55.      if( vdd_sim == 2 then vdd = "3.3" vdd_dir = "3p3" )
  56.      if( vdd_sim == 3 then vdd = "2.7" vdd_dir = "2p7" )

  57.      for( temp_sim 1 3 ;;loop for temp
  58.        if( temp_sim == 1 then temp = "27" temp_dir = "27" )
  59.        if( temp_sim == 2 then temp = "-40" temp_dir = "m40" )
  60.        if( temp_sim == 3 then temp = "125" temp_dir = "125" )

  61.        ocnxlCorner( strcat( corner_dir "_" vdd_dir "_" temp_dir ) ;setup corner for ADE XL
  62.          list(
  63.            list( "variable" "temperature" temp )
  64.            list( "variable" "avdd" vdd )
  65.            list( "model" modelfile_dir ?section corner_dir)
  66.            list( "modelGroup" "" )
  67.          )
  68.        )

  69.      ) ;;end loop for temp
  70.    ) ;;end loop for power supply
  71. ) ;;end loop for corner

  72. ocnxlCorner( "other_corner" ;setup corner for ADE XL
  73.    list(
  74.      list( "variable" "temperature" "27" )
  75.      list( "variable" "avdd" "3.0" )
  76.      list( "model" modelfile_dir ?section "tt" )
  77.      list( "modelGroup" "" )
  78.    )
  79. )
  80. /**************************************************************/
  81. /***************** checks and asserts setup *******************/
  82. /**************************************************************/
  83. ocnxlPutChecksAsserts( ?netlist nil )
  84. /**************************************************************/
  85. /******************* test v/s corners setup *******************/
  86. /**************************************************************/


  87. /**************************************************************/
  88. /************************* job setup **************************/
  89. /**************************************************************/
  90. ocnxlJobSetup(
  91.   '(
  92.     "blockemail" "1"
  93.     "configuretimeout" "300"
  94.     "distributionmethod" "Local"
  95.     "estimatememoryvalue" ""
  96.     "lingertimeout" "300"
  97.     "maxjobs" "1"
  98.     "name" "ADE XL Default"
  99.     "preemptivestart" "1"
  100.     "reconfigureimmediately" "1"
  101.     "runtimeout" "-1"
  102.     "showerrorwhenretrying" "1"
  103.     "showoutputlogerror" "0"
  104.     "startmaxjobsimmed" "1"
  105.     "starttimeout" "300"
  106.     "usesameprocess" "1"
  107.   )
  108. )
  109. /**************************************************************/
  110. /********************** disabled items ************************/
  111. /**************************************************************/
  112. ocnxlDisableSweepVar( "va" )
  113. ocnxlDisableSweepVar( "avdd" )
  114. /**************************************************************/
  115. /********************* run mode optioons **********************/
  116. /**************************************************************/


  117. /**************************************************************/
  118. /******************** starting point info *********************/
  119. /**************************************************************/


  120. /**************************************************************/
  121. /************************* run command ************************/
  122. /**************************************************************/
  123. ocnxlRun( ?mode 'sweepsAndCorners ?nominalCornerEnabled nil ?allCornersEnabled t ?allSweepsEnabled t ) ;select corners to run
  124. ocnxlOutputSummary( ?exprSummary t ?specSummary t ?detailed t ?wave t ) ;display summary in CIW
  125. ;ocnxlOpenResults(?testName "ICSkillSharing:inv:1") ;select result for a test, enable this option for waveform plotting
  126. ;selectResult( 'tran ) ;select result from simulation
  127. ;plot(getData("/A")) ;plot waveform

  128. /**************************************************************/
  129. /********************* end XL mode command ********************/
  130. /**************************************************************/
  131. ocnxlEndXLMode() ;close opened file before, if enable waveform plotting, comment out this command
复制代码


参考脚本中的注释和之前文章里的解释应该不难理解这个脚本内容,对应于ADE XL的ocean脚本也叫做ocean xl脚本,很多函数与ocean脚本不一致,都是适用于ADE XL环境的,同学们可以去Cadence Help查看一下具体用法,也可以参考:OCEAN XL Reference手册,里面有详细的讲解。
1.jpg
打开对应设计的ADE XL view
2.jpg
查看相应仿真的结果
3.jpg
选择查看信号并查看波形
4.jpg
输出波形样式


ocean xl实现corner仿真可以根据需要添加特殊的corner, 相比于ocean要方便很多。

上面脚本仿真结束并没有直接打开仿真结果,因为对于一个大型仿真来说,仿真结果很可能数据量巨大,所以可以在仿真结束之后选择手动打开ADE XL界面,然后逐个地选取自己感兴趣的信号,打印波形。
操作方法如上面图片所示,文字部分就不再详细描述。

当然,如果想要在仿真结束就打开结果,也可以像之前脚本一样使用plot()函数,或其他波形打印函数,具体实例在该脚本中也有注释,大家可以尝试取消注释上面脚本中:run command一栏中被注释掉的内容,获得直接打印波形的效果。
回复 支持 反对

使用道具 举报

0

主题

3

回帖

5

E币

技术员

Rank: 2

积分
3
发表于 2020-6-26 17:57:03 | 显示全部楼层
谢谢分享
回复

使用道具 举报

8

主题

21

回帖

170

E币

技术员

Rank: 2

积分
37
发表于 2023-6-26 14:52:30 | 显示全部楼层
学习一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|EDABOSS电子论坛

GMT+8, 2024-4-18 02:16 , Processed in 0.053449 second(s), 23 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表