3. MOSFET亚阈值摆幅提取与亚阈值区模型校准

任务目标

从实测亚阈值区转移特性(Id-Vg)中提取亚阈值摆幅 SS(mV/dec),并调整 SPICE Level 1 模型的亚阈值参数,使仿真亚阈值斜率与测量值一致。

环境与依赖

  • Python 3.8+,依赖 numpy、scipy、matplotlib
  • SPICE 仿真器:ngspice(命令行 ngspice 可用)
  • 实测数据:本案例内置一段合成但物理合理的亚阈值 Id-Vg 数据(含噪声),用于演示提取与校准流程

提取与校准流程

实测 Id-Vg 数据 取亚阈值区 (log10 Id 线性段) 线性拟合斜率 SS = 1000/|slope| SS_meas Level 1 参数 NSUB, VTO ngspice 仿真 Id-Vg 扫描 SS_sim 对比 SS_meas 迭代调整 NSUB 直至 |SS_sim - SS_meas| < 容差

完整实现

import numpy as np
from scipy import stats
import subprocess, re, os

# ---------- 1. 实测亚阈值 Id-Vg 数据(合成,含噪声) ----------
Vg = np.linspace(0.0, 1.2, 61)
SS_true = 85.0          # mV/dec
Vth = 0.45
Id = 1e-7 * 10**((Vg - Vth) / (SS_true / 1000.0))
Id = np.clip(Id, 1e-12, 1e-3)
rng = np.random.default_rng(42)
Id *= 10**(rng.normal(0, 0.03, Id.size))   # 3% 对数噪声

# ---------- 2. 提取 SS ----------
def extract_ss(Vg, Id, lo=1e-10, hi=1e-5):
    mask = (Id > lo) & (Id < hi)
    x, y = Vg[mask], np.log10(Id[mask])
    slope, intercept, r, p, se = stats.linregress(x, y)
    ss = 1000.0 / abs(slope)          # mV/dec
    return ss, slope, intercept, r**2

ss_meas, slope_m, icpt_m, r2_m = extract_ss(Vg, Id)
print(f"[Meas] SS = {ss_meas:.2f} mV/dec, R^2 = {r2_m:.4f}")

# ---------- 3. 生成 ngspice 网表并仿真 ----------
def run_ngspice(nsub, vto, vg_start=0.0, vg_stop=1.2, vg_step=0.02):
    net = f"""* Level1 subthreshold calibration
M1 d g 0 0 nmos W=10u L=1u
Vd d 0 0.05
Vg g 0 0
.model nmos nmos level=1 vto={vto} kp=120e-6 nsub={nsub:.3e} gamma=0.5 phi=0.7
.dc Vg {vg_start} {vg_stop} {vg_step}
.print dc i(vd)
.end
"""
    with open("cal.sp", "w") as f:
        f.write(net)
    out = subprocess.run(["ngspice", "-b", "cal.sp"],
                         capture_output=True, text=True).stdout
    vgs, ids = [], []
    for line in out.splitlines():
        m = re.match(r"\s*(\d+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)", line)
        if m:
            vgs.append(float(m.group(2)))
            ids.append(abs(float(m.group(3))))
    return np.array(vgs), np.array(ids)

# ---------- 4. 迭代校准 NSUB ----------
def calibrate(target_ss, vto=0.45, tol=1.0, max_iter=15):
    nsub = 1e17
    for i in range(max_iter):
        vgs, ids = run_ngspice(nsub, vto)
        if len(vgs) == 0:
            raise RuntimeError("ngspice 未返回数据,请检查安装")
        ss_sim, *_ = extract_ss(vgs, ids)
        err = ss_sim - target_ss
        print(f"iter {i:2d}  NSUB={nsub:.3e}  SS_sim={ss_sim:.2f}  err={err:+.2f}")
        if abs(err) < tol:
            return nsub, ss_sim
        # SS 随 NSUB 增大而增大,用比例步长调整
        nsub *= (1.0 + 0.15 * np.sign(err))
    return nsub, ss_sim

nsub_opt, ss_sim = calibrate(ss_meas)
print(f"\n[Result] NSUB = {nsub_opt:.3e} cm^-3")
print(f"[Result] SS_meas = {ss_meas:.2f} mV/dec, SS_sim = {ss_sim:.2f} mV/dec")
print(f"[Result] 偏差 = {abs(ss_sim - ss_meas):.2f} mV/dec")

运行方式

pip install numpy scipy
python calibrate_ss.py

验证方法与通过条件

  • 脚本打印 [Meas] SS = ...,应接近 85 mV/dec(合成数据真值),R² 应大于 0.99,说明亚阈值区线性拟合有效。
  • 迭代过程中 SS_sim 逐步收敛到 SS_meas,最终 |SS_sim - SS_meas| < 1 mV/dec 即判定校准通过。
  • 若 ngspice 未安装或网表报错,脚本会抛出 ngspice 未返回数据,需先确认 ngspice -b 可正常运行。

关键点说明

  • SS 提取只在 Id 处于 1e-10 ~ 1e-5 A 的亚阈值区间做 log10 线性回归,避免进入强反型区导致斜率失真。
  • Level 1 模型中亚阈值斜率主要由 NSUB 控制(通过体效应系数 gamma 与表面势 phi 影响),VTO 主要平移曲线不改变斜率,因此校准只调 NSUB。
  • 迭代采用符号比例步长(每次 ±15%),简单稳健;若需更快收敛可改用二分法或牛顿法。