使用ADS创建MOM电容Pcell

在集成电路设计中,Pcell (Parameterized Cell)指只需要输入参数就可以通过程序代码自动创建的版图。例如,代工厂提供的工艺库中就含有常用的晶体管、电容以至电感的Pcell文件,在版图设计时只需要指定一些参数(在晶体管设计中例如栅长、栅宽、栅指数等)即可自动生成对应的版图。这极大简化了设计流程,缩短了设计周期。

然而,代工厂提供的预设工艺库有时可能不能满足设计要求。比如说,用户可能想设计一系列更高Q值,但精度较低的fringe电容,而工艺库却没有提供。此时,自定义Pcell可以省去重复设计的麻烦。常用的设计软件如Cadence、ADS都支持通过相应的编程语言(SKILL和AEL)自定义创建Pcell。

此外,也可以自行撰写脚本文件(例如Python),生成EGS格式的版图并导入ADS。使用这种方法时,也可以通过ADS的命令行工具进行批量仿真,十分方便。自行撰写脚本文件比较通用,可操控性强,但需要大量的编程工作(编写基础的图形类库)。

由于ADS集成了Momentum电磁场仿真软件,并支持通过自带的AMC (Advanced Model Composer)对Pcell进行自动仿真和建模,在设计中应用较为广泛。本文以电容设计为例,介绍了基于ADS的AEL语言的Pcell设计和批量仿真建模。

预备

这一部分介绍了一些预备知识。

电容

电容是射频集成电路设计必不可少的组件。它可以构成LC谐振网络,提供交流耦合、直流阻断。常见的电容类型有晶体管电容、二极管电容、MIM(Metal-Insulator-Metal)电容以及MOM(Metal-Oxide-Metal)电容。由于栅氧较薄,晶体管电容面积紧凑,但存在电压调制效应。就MIM和MOM而言,虽然Oxide也是一种Insulator,但MOM电容通常指代同层两个金属插指之间和其间氧化物形成的电容,而MIM以平板电容为主,有时特指模拟/射频工艺中提供的额外提供两层金属间的电容。

电容的一个简单模型是RC并联网络,其等效R为:

其等效C为:

品质因数定义为:

但这个简单模型只能在工作频率附近的窄带使用,针对电容更宽带的建模则需要将电感考虑进去。此时,模型为:

real world capacitor model

此时,直流电阻为:

高频电阻为:

低频电抗为

谐振频率(虚部为0)为:

因此,可以将这些参数从仿真中提取出来。

关于MOM电容的版图,其既可以是简单的同层结构

mom capacitor

也可以是交错的立体结构。此时,其电容密度较大:

fringe capacitor

3D透视图如下:

3D mom capacitor

AEL语言

AEL(Application Extension Language)是Keysight公司开发的基于C语言的通用编程语言,可用于配置、扩展ADS设计环境。其与C语言的区别主要有:

  • 没有预处理,不支持#if, #ifdef, #ifndef, #endif, #define, #undef以及#include,但增加了load()函数用于代替#include
  • 没有类型,变量声明使用decl,函数声明使用defun,函数可以在函数内定义
  • 支持复数和列表,但没有结构
  • 拥有变量回收机制

其列表的语法为:

1
2
decl a = list(1,2,3);
decl b = list("hello", a[0]); //可以混合类型

其数组语法为:

1
2
3
4
decl x = {1+2i, 3+4i, 5+6i};
decl y = [1+2i, 3+4i, 5+6i]; //{}和[]可以互换
decl z = {1::10); //生成1-10的数组
decl u = {1::0.5::10); //生成1-10的数组,间隔0.5

对于矩阵:

1
2
3
4
5
decl m = {{1},{2},{3}};
decl n = [1; 2; 3]; //两者相同,生成二维数组
decl o = {{1,2}, {3,4}};
decl p = [1, 2; 3, 4]; //两者相同,生成二维数组(矩阵)
fprintf(stdout, "%s\n", identify_value(m)); //打印矩阵

AMC

AMC (Advanced Model Composer)可以用于无源器件的模型生成,它可以根据预设参数和频率范围对Pcell进行自适应EM仿真,并根据结果建立模型。对于没有仿到的点,也可以根据其算法进行插值,得到较为精确的结果。

实现

实现绘制电容的Pcell代码主要为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
load("vias.ael");

// create a parallel lines
//
// context: the design context
// layer: the layer number
// fingers: number of the lines
// length: length of the lines
// width: width of the lines
// space: space of the lines
// x: lower-left x coordinate of the shape
// y: lower-left y coordinate of the shape
// extension: extension of lines on vertical direction
// dummy_finger: number of unconnected lines at each side
// polar: direction of the extension (0 at bottom, 1 at top)

defun create_plines(context, layer, fingers, length, width, space, extension, x, y, dummy_finger, polar)
{
x = float(x);
y = float(y);
decl i;
decl xpos = 0;
polar = int(polar);
fingers = int(fingers);
dummy_finger = int(dummy_finger);
for(i=0; i<dummy_finger; i++)
{
db_add_rectangle(context, layer, x+xpos, y+extension, x+xpos+width, y+extension+length);
xpos += (width + space);
}
for(i=0; i<fingers; i++)
{
if(polar == 0)
db_add_rectangle(context, layer, x+xpos, y+0, x+xpos+width, y+extension+length);
if(polar == 1)
db_add_rectangle(context, layer, x+xpos, y+extension, x+xpos+width, y+2*extension+length);
xpos += (width + space);
polar = 1 - polar;
}
for(i=0; i<dummy_finger; i++)
{
db_add_rectangle(context, layer, x+xpos, y+extension, x+xpos+width, y+extension+length);
xpos += (width + space);
}
}

// create a mom capacitor
//
// start_layer: the lowest layer number
// end_layer: the highest layer number
// fingers: number of the fingers
// length: length of the fingers
// width: width of the fingers
// space: space of the fingers
// x_extension: extension of via array
// y_extension: space of figner to via array
// via_height: height of the via, width is calculated
// dummy_finger: number of unconnected fingers at each side
// fringe: whether to create a fringe capacitor
// combine: whether to combine vias for simulation simplification

defun mom_cap(start_layer, end_layer, fingers, length, width, space, x_extension, y_extension, via_height, dummy_finger, fringe, combine)
{
decl designContext = de_get_current_design_context();
decl polar = 0;
decl i = 0;
fringe = int(fringe);

// get layer IDs
decl M7 = db_get_layerid(designContext, "M7", "drawing");
decl M6 = db_get_layerid(designContext, "M6", "drawing");
decl M5 = db_get_layerid(designContext, "M5", "drawing");
decl M4 = db_get_layerid(designContext, "M4", "drawing");
decl M3 = db_get_layerid(designContext, "M3", "drawing");
decl M2 = db_get_layerid(designContext, "M2", "drawing");
decl M1 = db_get_layerid(designContext, "M1", "drawing");
decl Metals = list(M1, M1, M2, M3, M4, M5, M6, M7);

// calculate the width of the via array
decl t_width = 2 * x_extension + space * (2*dummy_finger + fingers - 1) + width * (2*dummy_finger + fingers);

// create pins
db_create_pin(designContext, t_width/2, 0, -90, Metals[end_layer], 1);
db_create_pin(designContext, t_width/2, 2*via_height+2*y_extension+length, 90, Metals[end_layer], 2);

// create vias at two terminals
via_fillto7(start_layer, end_layer, t_width, via_height, 0.04, 0.04, 0.04, 0.04, 0, 0, combine);
via_fillto7(start_layer, end_layer, t_width, via_height, 0.04, 0.04, 0.04, 0.04, 0, via_height+2*y_extension+length, combine);

// create lines for M1 to M7
for(i=1; i<8; i++)
{
if(start_layer <= i && end_layer >= i)
{
create_plines(designContext, Metals[i], fingers, length, width, space, y_extension, x_extension, via_height, dummy_finger, polar);
polar = fringe - polar;
}
}
}

// A function wrapper
defun mom_cap_wrapper(start_layer, end_layer, fingers, length, width, space, x_extension, y_extension, via_height, dummy_finger, fringe, combine)
{
decl designContext = de_get_current_design_context();

decl mks2uu = db_get_mks_to_uu_factor(designContext);

// convert the units
length = length * mks2uu;
width = width * mks2uu;
space = space * mks2uu;
x_extension = x_extension * mks2uu;
y_extension = y_extension * mks2uu;
via_height = via_height * mks2uu;

mom_cap(start_layer, end_layer, fingers, length, width, space, x_extension, y_extension, via_height, dummy_finger, fringe, combine);
}

测试

测试流程

为了运行代码,需要在ADS中进行一系列设置:

  1. 在ADS库目录下新建mom.ael文件,加入上述代码;

  2. 在ADS库目录下新建eesof_lib.cfg文件,加入

    1
    BOOT_AEL=./mom.ael

    此时,当ADS载入库时,代码会自动加载。为了强制重载,可以点击 File > Recent Workspaces中这个库的名字;

  3. 新建一个空白layout,命名为momcap,点击File > Design parameters,在其中设置各个参数的名称、类型、默认值、单位和描述。设置完成后点击确认。

    set design parameters

    点击File > Customize PCell,选择artwork类型为”AEL Macro“,将函数名写为主函数名。设置完成后点击确认。

    customize pcell

    保存layout;

  4. 另建一个名为momcap_sweep的layout,拖入刚才创建的momcap。此时应该出现默认值下的电容图形。在两端加上pin并设置好EM仿真。在EM > Component > Parameters中设置要扫描的变量名和范围(只能设置两个连续型变量),并将变量名填入Pcell的参数中。在EM > Component > Advanced Model Composer > Create Model中开始仿真;

  5. 仿真结束后,点击EM > Component > Create EM Model and Symbol,在新建的原理图中加入symbol,设置好参数即可进行电路仿真。

测试结果

首先对插指长度进行扫描,绘制了等效电容和等效Q值的曲线:

sweep_length_Q

事实上,AMC并没有对8-25 um区间范围内的每个点进行仿真,而是在之前仿真结果的基础上进行插值,因为仿真速度非常快,适用于快速迭代和自动优化。仿真结果较为精确。

当观察28 GHz处的等效电容时随插指长变化时,有:

sweep_length

当观察28 GHz处的等效电容时随插指数变化时,有:

sweep_fingers

可以发现,在28 GHz处电容值基本随插指长、插指数而线性变化,因而实际设计时可以通过长度、插指数进行估算,进一步节省时间。

参考

  1. Keysight, Advanced Design System Documentation
  2. Behzard Razavi, RF Microelectronics
0%