1. 从一条曲线说起:GM-ID 方法到底在解决什么问题
做模拟电路设计的人,尤其是做放大器、跨导级、偏置电路这一块的,几乎绕不开一个核心问题:我到底该把 MOS 管偏置在哪个工作点?这个问题听起来简单,但实际工程里,它决定了增益、带宽、功耗、线性度、噪声这一整串指标的走向。很多新手习惯的做法是:打开仿真器,扫一遍 Vgs,看 Id 曲线,然后凭感觉挑一个"看起来比较顺眼"的点。这种做法在课程设计里能过,但一旦进入真实项目,尤其是需要跨工艺角、跨温度验证的场合,就会暴露出巨大的问题——你根本说不清楚为什么选这个点,也说不清楚换一个工艺之后这个点还成不成立。
GM-ID 方法(也叫 gm/ID 设计方法学)就是用来把这件事从"凭感觉"变成"有依据"的一套系统化流程。它的核心思想非常朴素:不去直接看 Vgs 或 Vov,而是看跨导 gm 与漏电流 Id 的比值。这个比值有一个非常好的性质——它在很大程度上与晶体管的尺寸无关,主要由工艺和沟道长度决定。换句话说,gm/ID 是一个"归一化"的设计变量,它把"器件多大"和"偏置多深"这两件事解耦了。
为什么这件事重要?因为在传统设计流程里,你一旦改了 W,整个偏置点就跟着变,你得重新扫一遍曲线,重新调一遍电流镜,来回迭代。而用 gm/ID 作为入口,你可以先确定"我要工作在中等反型还是强反型",再根据这个选择去查表得到对应的 Id/W、gm/gds、Cgg 等参数,最后才去算 W。整个流程是单向的、可复现的,而不是来回试凑的。
我个人的体会是,GM-ID 方法最大的价值不在于它"更准",而在于它把设计意图显式化了。当你跟别人讨论一个放大器时,你说"我把它偏在 gm/ID=15",对方立刻就知道你大概在中等反型区,线性度和功耗的折中大概是什么水平。这种沟通效率,是看 Vgs 曲线永远给不了的。
那这套方法落地需要什么?需要一张以 L 为参数、以 gm/ID 为横轴的参数表。这张表从哪来?从工艺库的模型里仿真出来。怎么仿真?在 Cadence 里搭一个简单的测试台,扫一遍,导出数据,然后用 Matlab 做后处理和可视化。这就是这篇博文要完整走一遍的流程。
2. 测试台搭建:为什么是二极管接法,而不是固定 Vgs
2.1 二极管接法测试台的结构与理由
在 Cadence 里做 GM-ID 数据提取,最经典的测试台就是二极管接法(diode-connected):把 MOS 管的栅极和漏极短接,然后在漏极加一个电流源或电压源,扫描这个源,记录 Id 和 gm。
为什么用二极管接法?因为在这种接法下,晶体管自动处于饱和区。你可以这样理解:栅漏短接意味着 Vds = Vgs,而饱和区的条件是 Vds ≥ Vgs - Vth,代入 Vds = Vgs,得到 Vgs ≥ Vgs - Vth,也就是 Vth ≥ 0,这永远成立。所以二极管接法的管子天然饱和,你不需要去操心"它到底在不在饱和区"这个问题。
相比之下,如果你用固定 Vgs、扫 Vds 的方式,你得手动保证 Vds 足够大,而且随着 Vgs 变化,饱和边界也在变,测试台会变得很啰嗦。二极管接法把这个麻烦直接消掉了。
具体搭法是这样的:选一个 NMOS,栅极和漏极接在一起,源极接地。在漏极接一个 DC 电压源 Vd,然后对这个 Vd 做 DC 扫描。仿真器会自己解出对应的 Id。同时,在 ADE 的 output 里设置表达式,把 gm、gds、cgg、vth、vdsat 这些参数都存下来。
注意:这里扫的是 Vd,但因为栅漏短接,Vd 实际上就等于 Vgs。所以扫描范围要覆盖从亚阈值到强反型的整个区间,一般从 0 扫到 VDD 就够了。
2.2 扫描范围与步长的取舍
扫描范围好定,步长是个容易被忽略的细节。我见过有人用 0.5V 的步长扫,结果曲线在亚阈值区几乎是一条直线,完全看不出指数特性。也有人用 1mV 的步长,仿真跑了两个小时,数据文件几百兆,后处理的时候 Matlab 直接卡死。
我的经验是:用分段扫描,或者用对数步长。亚阈值区(大概 0 到 Vth 附近)用细步长,比如 2mV 到 5mV;强反型区(Vth 以上)用粗步长,比如 20mV 到 50mV。如果你嫌分段麻烦,直接用 10mV 均匀步长也能接受,对于大多数 0.18um 到 65nm 的工艺,这个精度足够画出平滑的 gm/ID 曲线了。
还有一个细节:扫描点数不要超过 2000 个。Cadence 的 DC 扫描在点数过多时,收敛会变慢,而且导出的 CSV 文件在 Matlab 里读起来也慢。1000 到 1500 个点是比较舒服的区间。
2.3 需要保存哪些参数
这是整个流程里最关键的一步,因为你后面 Matlab 能画出什么图,完全取决于你现在存了什么。我建议至少存以下几项:
| 参数名 | 含义 | 用途 |
|---|---|---|
| Id | 漏电流 | 计算 gm/ID 的分子 |
| gm | 跨导 | 计算 gm/ID、gm/gds |
| gds | 输出电导 | 计算本征增益 |
| cgg | 栅极总电容 | 计算 fT、电容密度 |
| vth | 阈值电压 | 判断反型程度 |
| vdsat | 饱和电压 | 判断余量 |
| cgd | 栅漏电容 | 评估 Miller 效应 |
在 ADE 里,这些参数可以通过dc分析后的annotate或者直接在 output setup 里用表达式提取。不同工艺库的表达式写法略有差异,但大体上gm对应IS("M0:gm")或OP("M0" "gm")这类写法,具体要看你用的 PDK 文档。
提示:如果你用的是 spectre,可以在 model 里加
save M0:all来保存所有小信号参数,省得一个个写表达式。但这样导出的数据列会很多,Matlab 里要按列名索引,稍微麻烦一点。
3. Cadence 数据导出:CSV 不是终点,格式才是坑
3.1 从 ADE 导出数据的几种方式
Cadence 导出仿真数据,常见的有三条路:
第一条是ADE 自带的 Export,在 results 菜单里选 Export,然后选 CSV 或 PSF。这条路最直接,但导出的 CSV 有时候列名是乱的,尤其是当你用了表达式的时候,列名会变成expr1、expr2这种,后处理时对不上号。
第二条是用 OCEAN 脚本,写一段脚本把仿真跑完,然后用ocnPrint把数据打到文件里。这条路适合批量跑多个 L 值的情况,因为你可以用循环把不同 L 的数据一次性导出来,文件名带上 L 的标记,后面 Matlab 读的时候按文件名区分。
第三条是直接读 PSF 文件,用 Cadence 自带的psf工具或者第三方库(比如 Python 的psf-utils)解析。这条路最灵活,但门槛也最高,适合已经有一套自动化流程的人。
我个人的建议是:如果你只跑一两个 L,用第一条;如果你要跑五六个 L 做完整参数表,用第二条。OCEAN 脚本看起来吓人,但其实模板很固定,改几个变量就能复用。
3.2 CSV 列名混乱的根因与处理
很多人导出 CSV 之后,打开一看,第一行是"Vd" "Id" "gm" "gds" ...,看起来没问题。但用 Matlab 的readmatrix或csvread读进去之后,发现第一列是 Vd,第二列是 Id,第三列开始全是 NaN。为什么?因为 Cadence 导出的 CSV 有时候会在数值列里混入单位,比如1.234e-5A这种,Matlab 默认的数值解析器读不了。
处理办法有两个:一是在导出时勾选"不包含单位",二是在 Matlab 里用readtable配合VariableNamingRule和自定义的ImportOptions来跳过单位。我一般用后者,因为前者有时候在 ADE 里找不到那个选项。
opts = detectImportOptions('gm_id_data.csv'); opts.VariableNamingRule = 'preserve'; opts = setvartype(opts, 'double'); data = readtable('gm_id_data.csv', opts);如果列名里有空格或特殊字符,readtable会自动改成下划线,这时候你要用data.Properties.VariableNames看一下实际列名,别硬编码。
3.3 多 L 数据的组织方式
做 GM-ID 参数表,你肯定要跑多个沟道长度。比如 0.18um 工艺,你可能要跑 L=0.18、0.24、0.36、0.5、0.8、1.2 这几个值。每个 L 跑一次 DC 扫描,导出一个 CSV。
组织方式我推荐文件名带 L 标记,比如nmos_L018.csv、nmos_L024.csv,然后在 Matlab 里用一个循环读进来,存到一个 cell array 或者 struct 里。这样后面画图的时候,你可以用不同的颜色或线型区分不同 L,一张图上看清楚 L 对 gm/ID 曲线的影响。
注意:不同 L 的扫描范围可能不一样。短沟道器件的 Vth 通常更低,亚阈值区更窄,所以扫描起点可以稍微高一点。但为了统一,我一般都用同一个范围,反正多扫一点不费事。
4. Matlab 后处理:从原始数据到可查表的设计曲线
4.1 计算 gm/ID 和 gm/gds 的正确姿势
数据读进来之后,第一件事是算 gm/ID。这个简单,gm_id = gm ./ Id。但这里有个坑:亚阈值区的 Id 非常小,除以它得到的 gm/ID 会非常大,有时候能到 30 以上。这不是错误,亚阈值区的 gm/ID 理论上确实可以很高,因为 gm 和 Id 都随指数变化,比值趋于常数。但如果你直接画出来,曲线在低电流端会翘得很厉害,看起来不美观。
处理办法是限制 gm/ID 的显示范围,比如只画 5 到 25 这一段。这一段覆盖了从强反型到中等反型的绝大部分实用区间。亚阈值区虽然 gm/ID 高,但 fT 低、匹配差,实际设计里很少用那么深的亚阈值。
gm/gds 就是本征增益,这个直接算gm ./ gds。注意 gds 在亚阈值区也很小,所以本征增益在低电流端会很高,这符合直觉——电流越小,输出电阻越大,增益越高。
4.2 用插值构建可复用的查找表
画图只是第一步,真正有用的是把曲线变成可以查的表。比如你想设计一个 gm/ID=15 的放大器,你需要知道在这个点上 Id/W 是多少、fT 是多少、Cgg 是多少。这些都可以通过插值得到。
Matlab 里用interp1就行:
gm_id_target = 15; id_w = interp1(gm_id, Id_over_W, gm_id_target, 'pchip'); ft = interp1(gm_id, fT, gm_id_target, 'pchip');这里我用pchip而不是默认的linear,因为 gm/ID 曲线是单调的,pchip能保证插值结果也是单调的,不会出现奇怪的振荡。
提示:插值之前记得把数据按 gm/ID 排序,因为 Cadence 导出的数据是按 Vd 排序的,而 gm/ID 随 Vd 是单调递减的,所以其实已经有序了。但如果你做了任何筛选或合并,最好还是
sort一下。
4.3 可视化:一张图讲清楚设计折中
GM-ID 方法最经典的可视化,是把 gm/ID 作为横轴,把 Id/W、fT、gm/gds、Cgg/W 等参数画在同一张图上。这样你一眼就能看出:往左走(gm/ID 增大),功耗降低但速度变慢;往右走(gm/ID 减小),速度快但功耗高。
我一般会画四个子图:
- 左上:Id/W vs gm/ID,看电流密度
- 右上:fT vs gm/ID,看速度
- 左下:gm/gds vs gm/ID,看本征增益
- 右下:Cgg/W vs gm/ID,看电容负担
每个子图里用不同颜色的线表示不同的 L。这样一张图就能覆盖大部分设计决策。
figure; subplot(2,2,1); for i = 1:length(L_list) plot(gm_id{i}, id_w{i}, 'LineWidth', 1.5); hold on; end xlabel('gm/ID (1/V)'); ylabel('Id/W (A/m)'); legend(L_labels); grid on;这种图在项目评审的时候特别好用,因为你可以指着图说:"我选这个点,是因为它在 fT 和增益之间取得了平衡,而且 Id/W 还在可接受范围内。"
5. 实操中容易踩的五个坑
5.1 坑一:忘了设置正确的体电位
二极管接法的 NMOS,源极接地,体电位也要接地。如果你忘了接,或者接了一个负电压,Vth 会变,整个曲线会平移。这个坑很隐蔽,因为仿真不会报错,只是结果不对。我建议在测试台里显式地把 B 端接地,并且在 schematic 里标注清楚。
5.2 坑二:gm 的表达式的符号问题
有些工艺库的 gm 表达式带符号,比如gm可能是负的(取决于电流方向定义)。如果你直接拿它算 gm/ID,会得到一个负值,曲线就反了。处理办法是取绝对值:gm_id = abs(gm) ./ Id。但更稳妥的做法是先在 ADE 里看一眼 gm 的波形,确认符号。
5.3 坑三:CSV 里的科学计数法
Cadence 导出的 CSV,有时候会用1.23E-05这种科学计数法,Matlab 的readtable默认能读,但如果你用的是csvread或dlmread,就可能读成字符串。我建议统一用readtable,并且显式指定NumHeaderLines和Delimiter。
5.4 坑四:不同 L 的数据点数量不一致
如果你用 OCEAN 脚本跑多个 L,每个 L 的扫描点数可能因为收敛问题而不一样。这时候你不能直接把数据矩阵拼起来,得用 cell array 或者 struct 分别存。画图的时候也要用循环,不能向量化。
5.5 坑五:Matlab 版本兼容性
readtable在 R2013b 之后才有,pchip更早。如果你用的是很老的 Matlab 版本,可能得用csvread加手动解析。另外,Matlab 的图形句柄系统在 R2014b 之后有大改,如果你在网上抄了一段老代码,set(gca, 'FontSize', 12)可能不生效,得用gca.FontSize = 12。
6. 从 GM-ID 表到实际电路:一个两级运放的设计实例
6.1 设计指标与 gm/ID 的映射
假设你要设计一个两级运放,指标是:DC 增益 60dB,单位增益带宽 10MHz,相位裕度 60 度,负载电容 2pF,功耗不超过 1mW。电源电压 1.8V。
第一步是把系统指标翻译成 gm/ID 层面的约束。单位增益带宽 GBW = gm1 / (2π * Cc),其中 Cc 是补偿电容。为了 60 度相位裕度,Cc 一般取 0.2 到 0.3 倍的 CL,这里取 0.5pF。那么 gm1 = 2π * 10MHz * 0.5pF ≈ 31.4uS。
第二级需要提供足够的增益和摆幅,gm2 一般取 2 到 3 倍的 gm1,这里取 80uS。
总功耗 1mW,1.8V 电源,总电流约 0.56mA。两级各分一半,每级 0.28mA。
现在你有了 gm 和 Id,可以算 gm/ID 了:第一级 gm1/Id1 = 31.4u / 0.28m ≈ 0.112,也就是 gm/ID ≈ 11.2。第二级 gm2/Id2 = 80u / 0.28m ≈ 0.286,也就是 gm/ID ≈ 28.6。
6.2 查表确定 W 和 L
有了 gm/ID,你就可以去查之前生成的表了。第一级 gm/ID=11.2,属于强反型区,速度较快但增益一般。第二级 gm/ID=28.6,属于中等反型甚至接近亚阈值,增益高但速度慢——这正好符合两级运放的分工:第一级管带宽,第二级管增益。
在表里找到对应的 Id/W,假设第一级在 L=0.18um 时 Id/W = 5uA/um,那么 W1 = 0.28m / 5u = 56um。第二级在 L=0.36um 时 Id/W = 1uA/um,那么 W2 = 0.28m / 1u = 280um。
这些数字不是最终值,但已经给了你一个非常合理的起点。接下来你可以在 Cadence 里搭出电路,跑 AC 仿真,看实际增益和带宽,然后微调 W 和 L。因为起点已经很接近目标,微调的幅度通常很小,迭代次数从原来的十几次降到两三次。
6.3 验证与迭代
搭好电路之后,跑 AC 仿真,看增益曲线和相位曲线。如果增益不够,可以把第二级的 L 加大一点,牺牲一点速度换增益。如果带宽不够,可以把第一级的 gm/ID 调小一点,也就是往强反型方向走,增加 gm。
这个过程里,GM-ID 表的作用是给你方向。你知道往哪个方向调会得到什么效果,而不是盲目地试。这是我个人觉得这套方法最值钱的地方。
7. 把这套流程自动化的几个思路
7.1 用 OCEAN 脚本批量跑 L
前面提到过,OCEAN 脚本可以一次性跑多个 L。基本结构是:
foreach(L list(0.18 0.24 0.36 0.5 0.8) desVar("L" L) run() ocnPrint(?output sprintf("nmos_L%03d.csv" L*1000) ?numberNotation 'scientific) )这段脚本会依次把 L 设成不同的值,跑仿真,然后导出 CSV。文件名里的L*1000是为了把 0.18 变成 018,方便排序。
7.2 Matlab 端的自动化读取与绘图
Matlab 端可以用dir列出所有 CSV,然后循环读:
files = dir('nmos_L*.csv'); for i = 1:length(files) data = readtable(files(i).name, opts); L_val = str2double(files(i).name(6:8)) / 1000; % 计算 gm/ID 等 % 存到 struct end这样你加一个新的 L,只需要在 OCEAN 脚本里加一行,Matlab 端不用改。
7.3 版本管理与可复现性
最后说一个容易被忽略的点:把测试台、OCEAN 脚本、Matlab 脚本都放到版本控制里。我用的是 git,每次改工艺库或者改测试台,都提交一次。这样半年后你回头看某个设计,能准确知道当时用的是哪版数据、哪版脚本。这个习惯在团队协作里尤其重要,因为别人可以复现你的结果。
提示:Cadence 的 schematic 和 ADE state 也可以导出成文本格式,虽然可读性一般,但至少能 diff。如果你用 git 管理,建议把 ADE state 也一起提交。
8. 一些个人体会
这套 GM-ID 流程我从几年前开始用,中间踩过不少坑,也走过一些弯路。最大的体会是:它不是一个"更高级"的方法,而是一个"更清晰"的方法。你完全可以用传统的 Vgs 扫描做出一样好的电路,但 GM-ID 让你在做的过程中知道自己在干什么,也让别人能看懂你在干什么。
另一个体会是,数据质量比方法本身更重要。如果你的测试台搭错了,体电位没接对,或者 gm 表达式写错了,那后面所有的曲线都是错的,而且错得很隐蔽。所以我在每次跑完数据之后,都会先画一张 Id vs Vgs 的图,确认它看起来像一条正常的 MOS 曲线——亚阈值区指数上升,强反型区线性上升。这一步花不了两分钟,但能省掉后面几个小时的排查。
最后,Matlab 的可视化不要追求花哨。我见过有人把图画得跟艺术品一样,但关键信息反而看不清。我的原则是:一张图只讲一件事。Id/W 一张,fT 一张,增益一张,不要挤在一起。颜色用默认的就行,线宽 1.5 足够,网格打开,坐标轴标签写清楚单位。这样别人拿到你的图,不用问你就能看懂。