共计 2624 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在集成电路设计流程中,手动提取 SPICE 参数(如晶体管的 W /L、电阻值、电容值等)是一个常见但极其繁琐的任务。传统的手动操作方式存在以下痛点:

- 耗时严重:对于复杂电路,工程师需要逐个查看器件属性并记录,一个中等规模的模块可能就需要数小时。
- 容易出错:人工记录过程中,数值抄写错误、单位混淆、器件遗漏等问题难以避免。
- 难以维护:当设计迭代或工艺库更新时,所有手动记录的数据都需要重新核对和更新。
这些问题严重影响了设计效率和质量,特别是在需要频繁进行参数提取和验证的场景下。
技术方案对比
目前主流的自动化参数提取方案主要有三种:
- SKILL 脚本:
- 直接运行在 Virtuoso 环境内,无需额外接口
- 可以访问完整的数据库对象和属性
-
执行效率高,适合处理复杂电路结构
-
Ocean 脚本:
- 主要用于仿真控制
- 参数提取能力有限
-
适合与仿真流程结合的简单场景
-
Python API:
- 需要配置远程连接或文件接口
- 生态丰富,便于后续数据分析
- 执行效率较低,适合对实时性要求不高的场景
对于大多数参数提取需求,SKILL 脚本因其高效性和深度集成优势成为首选方案。
核心实现
1. 获取电路拓扑结构
使用 virtuoso.ilGetCellView() 函数获取当前设计的单元视图,这是所有操作的起点:
cv = virtuoso.ilGetCellView()
unless(cv error("Cannot get cell view"))
2. 遍历器件实例
通过 dbGetq() 函数遍历设计中的所有器件实例,配合过滤器可以只选择特定类型的器件:
; 只提取 nmos 器件
nmosList = setof(inst cv~>instances
inst~>master~>name == "nmos_1p2")
3. 参数提取与处理
提取具体参数时需要注意单位转换和工艺规则解析:
; 提取 W / L 并转换为米制单位
foreach(inst nmosList
w = inst~>w * 1e-6 ; um -> m
l = inst~>l * 1e-6
printf("Device %s: W=%.3g L=%.3g\n" inst~>name w l)
)
对于复杂 PDK 命名规则(如 nmos_1p2 表示 1.2nm 沟道长度),可以使用正则表达式提取关键参数:
; 从器件名中提取沟道长度
rexCompile("^nmos_(\\d+)p(\\d+)$")
if(rexExecute(inst~>master~>name) then
length = atof(rexReplace("\\1.\\2"))
)
完整代码示例
以下是一个带完整错误处理的 SKILL 脚本示例:
procedure(CCFextractParams(@optional (cellName "") (viewName"schematic"))
prog((cv instList outFile)
; 1. 获取当前设计
cv = virtuoso.ilGetCellView()
unless(cv error("Cannot get cell view"))
; 2. 打开输出文件
outFile = outfile("./params.csv" "w")
fprintf(outFile "Device,Type,W,L\n")
; 3. 提取所有 nmos 和 pmos
instList = setof(inst cv~>instances
member(inst~>master~>name '("nmos_1p2""pmos_1p2")))
; 4. 处理每个器件
foreach(inst instList
w = inst~>w * 1e-6
l = inst~>l * 1e-6
fprintf(outFile "%s,%s,%.4e,%.4e\n"
inst~>name inst~>master~>name w l)
)
close(outFile)
printf("Extracted %d devices\n" length(instList))
)
)
性能优化
1. 使用哈希表缓存
频繁访问工艺库数据时,建立缓存可以显著提升速度:
; 全局缓存变量
CCF_pdkCache = makeTable("PDK cache" 0)
procedure(CCFgetPdkParam(deviceName param)
unless(CCF_pdkCache[deviceName]
; 首次访问时加载所有参数
CCF_pdkCache[deviceName] = loadPdkData(deviceName)
)
CCF_pdkCache[deviceName][param]
)
2. 多线程处理
对于大型模块,可以将设计分区后并行处理:
; 将设计按层次划分
subBlocks = setof(inst cv~>instances inst~>cellView)
; 为每个子块创建独立线程
foreach(blk subBlocks
threadId = thrcreate(strcat("BLK_" blk~>name)
CCFextractBlockParams(blk))
)
避坑指南
- PDK 版本兼容性:
- 不同版本的 PDK 可能修改参数命名规则
-
解决方案:在脚本开头检查 PDK 版本并加载对应解析规则
-
层次化引用问题:
- 子电路中的器件可能被重复统计
-
解决方案:使用
dbGetHierInstances()替代普通遍历 -
单位系统混乱:
- 某些 PDK 混合使用 um 和 m 单位
- 解决方案:强制在脚本中进行单位转换
扩展应用
将提取的数据导入 Python 进行进一步分析:
import pandas as pd
import matplotlib.pyplot as plt
data = pd.read_csv('params.csv')
# 绘制 W / L 分布
plt.figure(figsize=(10,6))
plt.scatter(data['W'], data['L'], alpha=0.5)
plt.xlabel('Width (m)')
plt.ylabel('Length (m)')
plt.title('Device Size Distribution')
plt.savefig('wl_dist.png')
结语
通过 SKILL 脚本实现参数提取自动化后,我们团队将原来需要数小时的手动工作缩短到几分钟内完成,且完全消除了人为错误。这套方案已经稳定运行在多个量产项目中,特别推荐在以下场景使用:
- 工艺移植时的参数对比验证
- 设计规则检查前的快速参数审计
- 大规模蒙特卡洛仿真前的参数统计
下一步我们计划将这套系统与 CI/CD 流程集成,实现设计提交时的自动参数检查,进一步提高设计质量保障能力。
