文章

TCAD学习笔记——CMI篇

前言

Sentaurus的Sdevice工具中可以使用Mixed-Mode来构建电路来对仿真器件进行相关电学仿真,里面也集成了很多spice模型,其中包括二极管,但是其中二极管模型是不含有反向恢复特性的,为了仿真的准确性,可以有很多解决方法:

  1. 专门跑一个一维的二极管仿真,来拟合目标参数:IV特性、反向恢复时间等等。

  2. 建立一个类似spice模型的二极管,描述其特性的电学方程包含反向恢复特性的模型。

方法一在校准上可能会比较麻烦,因为不能所见即所得地调整二极管的参数,需要调整各种掺杂浓度等参数来拟合需要的目标参数,如反向恢复时间和最大反向恢复电流。

方法二需要用到CMI(Compact Model Interface).

CMI食用方法

CMI的使用和PMI(Physical Model Interface)类似,相关的基本概念可参考该视频:

https://www.bilibili.com/video/BV1ga411B7Au/?p=14

手册cm_ug.pdf也介绍了其使用方法,并且给了一个互感器件的例子,其核心就是把器件物理模型的微分方程表示出来。

对于含反向恢复的spice二极管模型,文献Practical implementation of diode SPICE model with reverse recovery用spice电路来实现了反向恢复二极管模型,并且可以使用datasheet的参数拟合该反向恢复二极管模型。

根据上述文献,描述反向恢复二极管的方程组为:

\begin{aligned} i\left(t\right)&=\frac{\left(q_E-q_M\right)}{T_M}\\ i\left(t\right)&=\frac{dq_M}{dt}+\frac{q_M}{\tau}\\ q_E&=I_s\tau\left(e^{\frac{u\left(t\right)}{nV_T}}-1\right) \end{aligned}

约去q_Eq_M可得微分方程:

\frac{d}{dt}\cdot\left[I_s\tau e^\frac{u_1-u_2}{nV_T}-T_Mi_1\right]+\left[I_s\left(e^\frac{u_1-u_2}{nV_T}-1\right)-\left(\frac{T_M}{\tau}+1\right)i_1\right]=0

这里用u_1-u_2代替u(t)i_1代替i(t),用于匹配CMI例子中电压节点和电流的相关定义。

根据上式就可得到其中的未知量z(t),transient right-hand side: q\left(t,z\left(t\right)\right),DC right-hand side: f\left(t,z\left(t\right)\right)以及各自的雅可比矩阵:

\begin{aligned} z(t)&=\left[\begin{matrix}u_1\\u_2\\i_1\\\end{matrix}\right] \\ q\left(t,z\left(t\right)\right)&=\left[\begin{matrix}0\\0\\I_s\tau e^\frac{u_1-u_2}{nV_T}-T_Mi_1\\\end{matrix}\right] \\ f\left(t,z\left(t\right)\right)&=\left[\begin{matrix}i_1\\-i_1\\I_s\left(e^\frac{u_1-u_2}{nV_T}-1\right)-\left(\frac{T_M}{\tau}+1\right)i_1\\\end{matrix}\right] \\ J_q=\frac{d}{dz}q\left(t,z\left(t\right)\right)&=\left[\begin{matrix}0&0&0\\0&0&0\\\frac{I_s\tau}{nV_T}e^\frac{u_1-u_2}{nV_T}&-\frac{I_s\tau}{nV_T}e^\frac{u_1-u_2}{nV_T}&-T_M\\\end{matrix}\right]\\ J_f=\frac{d}{dz}f\left(t,z\left(t\right)\right)&=\left[\begin{matrix}0&0&1\\0&0&-1\\\frac{I_s}{nV_T}e^\frac{u_1-u_2}{nV_T}&-\frac{I_s}{nV_T}e^\frac{u_1-u_2}{nV_T}&-\frac{T_M+\tau}{\tau}\\\end{matrix}\right] \end{aligned}

根据方程修改相应的C文件后编译即可,不懂C也没问题,这里关键的只是相关公式的键入。这里我把这个模型命名为RRDiode,C文件代码如下:

TL;DR
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "CMIModels.h"
#include "CMISupport.h"

class Parameters {
public:
  double is;
  double tau;
  double tm;
  double n;
  double vt;
  Parameters ();
  void Set (const CCMBaseParam* param);
  void Get (CCMBaseParam* param);
};

Parameters::Parameters () :
  is (0.0004614982),
  tau (5.909138e-8),
  tm (3.181049e-8),
  n (2.460456),
  vt (0.03430948)
{
}

void Parameters::Set (const CCMBaseParam* param)
{ if (param->Defined ()) {
    if (strcmp (param->Name (), "is") == 0) {
      is = *param;
    } else if (strcmp (param->Name (), "tau") == 0) {
      tau = *param;
    } else if (strcmp (param->Name (), "tm") == 0) {
      tm = *param;
    } else if (strcmp (param->Name (), "n") == 0) {
      n = *param;
    } else if (strcmp (param->Name (), "vt") == 0) {
      vt = *param;
    }
  }
}

void Parameters::Get (CCMBaseParam* param)
{ if (strcmp (param->Name (), "is") == 0) {
    *param = is;
    param->Defined (1);
  } else if (strcmp (param->Name (), "tau") == 0) {
    *param = tau;
    param->Defined (1);
  } else if (strcmp (param->Name (), "tm") == 0) {
    *param = tm;
    param->Defined (1);
  } else if (strcmp (param->Name (), "n") == 0) {
    *param = n;
    param->Defined (1);
  } else if (strcmp (param->Name (), "vt") == 0) {
    *param = vt;
    param->Defined (1);
  } else {
    param->Defined (0);
  }
}

class Device {
public:
  Parameters par;
};

class PSet {
public:
  Parameters par;
};

class Instance {
public:
  Parameters par;
  double m;
};

extern "C" void
cmi_device_create (CCMBaseDevice*const device)
{ device->device_desc = new Device;
}

extern "C" void
cmi_device_set_param (CCMBaseDevice*const device,
                      const CCMBaseParam*const param)

{ Device* dev = (Device*) device->device_desc;
  dev->par.Set (param);
}

extern "C" void
cmi_device_initialize (CCMBaseDevice*const device)
{
}

extern "C" void
cmi_device_get_param (CCMBaseDevice*const device,
                      CCMBaseParam*const param)
{ Device* dev = (Device*) device->device_desc;
  dev->par.Get (param);
}

extern "C" void
cmi_device_delete (CCMBaseDevice*const device)
{ delete (Device*) device->device_desc;
}

extern "C" void
cmi_pset_create (CCMBasePSet*const pset)
{ pset->pset_desc = new PSet;
}

extern "C" void
cmi_pset_set_param (CCMBasePSet*const pset,
                    const CCMBaseParam*const param)
{ PSet* ps = (PSet*) pset->pset_desc;
  ps->par.Set (param);
}

extern "C" void
cmi_pset_initialize (CCMBasePSet*const pset)
{
}

extern "C" void
cmi_pset_get_param (CCMBasePSet*const pset,
                    CCMBaseParam*const param)
{ PSet* ps = (PSet*) pset->pset_desc;
  ps->par.Get (param);
}

extern "C" void
cmi_pset_delete (CCMBasePSet*const pset)
{ delete (PSet*) pset->pset_desc;
}

extern "C" void
cmi_instance_create (CCMBaseInstance*const instance)
{ instance->instance_desc = new Instance;
}

extern "C" void
cmi_instance_set_param (CCMBaseInstance*const instance,
                        const CCMBaseParam*const param)
{ Instance* inst = (Instance*) instance->instance_desc;
  inst->par.Set (param);
}

extern "C" void
cmi_instance_initialize (CCMBaseInstance*const instance)
{ Instance* inst = (Instance*) instance->instance_desc;
  inst->m = inst->par.is * (inst->par.tau * inst->par.tau);
}

extern "C" void
cmi_instance_get_param (CCMBaseInstance*const instance,
                        CCMBaseParam*const param)
{ Instance* inst = (Instance*) instance->instance_desc;
  inst->par.Get (param);
}

#define u1 (variables [0])
#define u2 (variables [1])
#define i1 (variables [2])


extern "C" void
cmi_instance_get_rhs (CCMBaseInstance*const instance,
                      const int dc, const double time,
                      const double*const variables,
                      double*const rhs)
{ Instance* inst = (Instance*) instance->instance_desc;
  if (dc) {
    rhs [0] = i1;
    rhs [1] = -i1;
    rhs [2] = inst->par.is * (exp((u1-u2) / inst->par.n / inst->par.vt) - 1) - (inst->par.tm / inst->par.tau + 1) * i1;
  } else {
    rhs [2] = inst->par.is * inst->par.tau * exp((u1-u2) / inst->par.n / inst->par.vt) - inst->par.tm * i1;
    // check of callback into Sentaurus Device
    double starttime = cmi_starttime ();
  }
}

extern "C" void
cmi_instance_get_jacobian (CCMBaseInstance*const instance,
                           const int dc, const double time,
                           const double*const variables,
                           double*const*const jacobian)
{ Instance* inst = (Instance*) instance->instance_desc;
  if (dc) {
    jacobian[0][2] = 1.0;
    jacobian[1][2] = -1.0;
    jacobian[2][0] = inst->par.is / inst->par.n / inst->par.vt * exp((u1-u2) / inst->par.n / inst->par.vt);
    jacobian[2][1] = -1.0 * inst->par.is / inst->par.n / inst->par.vt * exp((u1-u2) / inst->par.n / inst->par.vt);
    jacobian[2][2] = -1.0 * (inst->par.tm + inst->par.tau) / inst->par.tau;
  } else {
    jacobian[2][0] = inst->par.is * inst->par.tau / inst->par.n / inst->par.vt * exp((u1-u2) / inst->par.n / inst->par.vt);
    jacobian[2][1] = -1.0 * inst->par.is * inst->par.tau / inst->par.n / inst->par.vt * exp((u1-u2) / inst->par.n / inst->par.vt);
    jacobian[2][2] = -1.0 * inst->par.tm;
  }
}

extern "C" int
cmi_instance_is_physical (CCMBaseInstance*const instance,
                          const double time,
                          const double*const variables)
{ return 1;
}

extern "C" void
cmi_instance_delete (CCMBaseInstance*const instance)
{ delete (Instance*) instance->instance_desc;
}

编译该c文件得到一RRDiode.so.linux64文件,除此之外还需要写一个RRDiode.ccf文件,内容参照例子写即可:

DEVICE RRDiode
  ELECTRODES
    u1
    u2
  INTERNALS
    i1
  PARAMETERS
    double is
    double tau
    double tm
    double n
    double vt
END DEVICE

PSET RRDiode_pset
  DEVICE RRDiode
END PSET

将编译完后的RRDiode.so.linux64和RRDiode.ccf放在某目录下,然后在Sdevice的system中就能使用该器件了:

File {
	......
	CMIPATH = "PATHtoCMI"
}
......
system {
	Resistor_pset rs (1 234) {resistance = 0.02805803}
	RRDiode_pset Dut (234 0) {is=1.53e-5 tau=9.966e-8 tm=9.120918e-8 n=2.093419 vt=0.03215437}
}
......

is, tau, tm, n, vt 这五个参数可以从开头所述的文献Practical implementation of diode SPICE model with reverse recovery中拟合得到,这里设置的参数是从IGBT器件IKW20N65ET7内封装的的二极管参数拟合而来。

最后和IGBT器件一起Mix-Mode下跑一下开通瞬态仿真,反向恢复电流这不就出来了:

cmi-pin-diode.png

许可协议:  CC BY-NC-SA 4.0