/* $Id: d_mos3.cc,v 20.13 2001/10/15 00:57:11 al Exp $ -*- C++ -*-
 * Copyright (C) 2001 Albert Davis
 * Author: Albert Davis <aldavis@ieee.org>
 *
 * This file is part of "GnuCap", the Gnu Circuit Analysis Package
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *------------------------------------------------------------------
 * mos model equations: spice level 3 equivalent
 */
/* This file is automatically generated. DO NOT EDIT */

#include "l_denoise.h"
#include "ap.h"
#include "d_mos3.h"
/*--------------------------------------------------------------------------*/
const double NA(NOT_INPUT);
/*--------------------------------------------------------------------------*/
int MODEL_MOS3::_count = 0;
/*--------------------------------------------------------------------------*/
SDP_MOS3::SDP_MOS3(const COMMON_COMPONENT* cc)
  :SDP_MOS123(cc)
{
}
/*--------------------------------------------------------------------------*/
TDP_MOS3::TDP_MOS3(const DEV_MOS* d)
  :TDP_MOS123(d)
{
  assert(d);
  const COMMON_MOS* c = prechecked_cast<const COMMON_MOS*>(d->common());
  assert(c);
  const SDP_MOS3* b = prechecked_cast<const SDP_MOS3*>(c->sdp());
  assert(b);
  const MODEL_MOS3* m = prechecked_cast<const MODEL_MOS3*>(c->model());
  assert(m);

      double temp = SIM::temp;
      double tempratio  = temp / m->_tnom; // ratio
      double tempratio4 = tempratio * sqrt(tempratio);
      double kt = temp * K;
      double egap = 1.16 - (7.02e-4*temp*temp) / (temp+1108.);
      double arg = (m->egap*tempratio - egap) / (2*kt);

  vt = kt / Q;
  phi = m->phi*tempratio + (-2*vt*(1.5*log(tempratio)+Q*(arg)));
  sqrt_phi = sqrt(phi);
  beta = m->kp * tempratio4 * b->w_eff / b->l_eff;
  uo = m->uo * tempratio4;
  vbi = (fixzero(
	(m->vto - m->polarity * m->gamma * sqrt(m->phi)
	 +.5*(m->egap-egap) + m->polarity* .5 * (phi-m->phi)), m->phi));
}
/*--------------------------------------------------------------------------*/
MODEL_MOS3::MODEL_MOS3()
  :MODEL_MOS123(),
   kp(NA),
   nfs(0.0),
   vmax(NA),
   theta(0.0),
   eta(0.0),
   kappa(0.2),
   delta(0.0),
   calc_kp(false),
   alpha(NA),
   xd(NA),
   cfsox(NA),
   delta3(NA)
{
  ++_count;
  mjsw = .33;
  tox = 1e-7;
  cox = NA;
  vto = NA;
  gamma = NA;
  phi = NA;
  mos_level = LEVEL;
}
/*--------------------------------------------------------------------------*/
bool MODEL_MOS3::parse_front(CS& cmd)
{
  return MODEL_MOS123::parse_front(cmd);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::parse_params(CS& cmd)
{
  get(cmd, "DIODElevel", &mos_level);
  get(cmd, "KP", &kp);
  get(cmd, "NFS", &nfs, mSCALE, ICM2M2);
  get(cmd, "VMAx", &vmax);
  get(cmd, "THEta", &theta);
  get(cmd, "ETA", &eta);
  get(cmd, "KAPpa", &kappa);
  get(cmd, "DELta", &delta);
  MODEL_MOS123::parse_params(cmd);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::parse_finish()
{
  MODEL_MOS123::parse_finish();
      
      cox = E_OX / tox;
      if (kp == NA) {
	kp = uo * cox;
	calc_kp = true;
      }
      if (nsub != NA) {
	if (phi == NA) {
	  phi = (2.*KoQ) * _tnom * log(nsub/NI);
	  if (phi < .1) {
	    untested();
	    error(bWARNING,
		  long_label() + ": calculated phi too small, using .1\n");
	    phi = .1;
	  }
	  calc_phi = true;
	}
	if (gamma == NA) {
	  gamma = sqrt(2. * E_SI * Q * nsub) / cox;
	  calc_gamma = true;
	}
	if (vto == NA) {
	  double phi_ms = (tpg == gtMETAL)
	    ? -.05 - (egap + polarity * phi) / 2.
	    : -polarity * (tpg * egap + phi) / 2.;
	  double vfb = phi_ms - Q * nss / cox;
	  vto = vfb + polarity * (phi + gamma * sqrt(phi));
	  calc_vto = true;
	}
      }

  if (cox == NA) {
    cox = E_OX/tox;
  }
  if (vto == NA) {
    vto = 0.0;
  }
  if (gamma == NA) {
    gamma = 0.0;
  }
  if (phi == NA) {
    phi = 0.6;
  }
  if (kp == NA) {
    kp = 2e-5;
  }
  alpha = ((nsub != NA)
	? (2. * E_SI) / (Q * nsub)
	: 0.);
  xd = sqrt(alpha);
  cfsox = Q * nfs / cox;
  delta3 = delta * kPId2 * E_SI / cox;
}
/*--------------------------------------------------------------------------*/
SDP_CARD* MODEL_MOS3::new_sdp(const COMMON_COMPONENT* c)const
{
  assert(c);
  {if (dynamic_cast<const COMMON_MOS*>(c)) {
    return new SDP_MOS3(c);
  }else{
    return MODEL_MOS123::new_sdp(c);
  }}
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::print_front(OMSTREAM& o)const
{
  MODEL_MOS123::print_front(o);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::print_params(OMSTREAM& o)const
{
  o << "level=3";
  MODEL_MOS123::print_params(o);
  if (mos_level != LEVEL)
    o << "  diodelevel=" << mos_level;
  if (!calc_kp)
    o << "  kp=" << kp;
  o << "  nfs=" << nfs/(ICM2M2);
  if (vmax != NA)
    o << "  vmax=" << vmax;
  o << "  theta=" << theta;
  o << "  eta=" << eta;
  o << "  kappa=" << kappa;
  o << "  delta=" << delta;
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::print_calculated(OMSTREAM& o)const
{
  MODEL_MOS123::print_calculated(o);
  if (calc_kp)
    o << "* kp=" << kp;
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS3::tr_eval(COMPONENT* brh)const
{
  DEV_MOS* d = prechecked_cast<DEV_MOS*>(brh);
  assert(d);
  const COMMON_MOS* c = prechecked_cast<const COMMON_MOS*>(d->common());
  assert(c);
  const SDP_MOS3* b = prechecked_cast<const SDP_MOS3*>(c->sdp());
  assert(b);
  const MODEL_MOS3* m = this;
  TDP_MOS3 t(d);

    #define short_channel	(m->xj != NOT_INPUT  &&  m->xj > 0.)
    #define do_subthreshold	(m->nfs != 0.)
    #define use_vmax		(m->vmax != NOT_INPUT)
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    trace1(d->long_label().c_str(), d->evaliter());
    trace3("", d->vds, d->vgs, d->vbs);
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    d->reverse_if_needed();
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* square root term */
    double sarg, v_phi_s, dsarg_dvbs;
    {
      {if (d->vbs <= 0.) {
	v_phi_s = t.phi - d->vbs;
	sarg = sqrt(v_phi_s);
	dsarg_dvbs = -.5 / sarg;
	d->sbfwd = false;
	trace3("sb-ok", sarg, v_phi_s, dsarg_dvbs);
      }else{
	untested();
	sarg = t.sqrt_phi / (d->vbs / (2 * t.phi) + 1.);
	v_phi_s = sarg * sarg;
	dsarg_dvbs = -v_phi_s / (2 * t.phi*t.sqrt_phi);
	d->sbfwd = true;
	trace3("***sb-reversed***", sarg, v_phi_s, dsarg_dvbs);
      }}
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
    {
      {if (d->vbs - d->vds <= 0.) {
	d->dbfwd = false;
      }else{
	d->dbfwd = true;
      }}
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* short channel effect, body effect */
    double fbody, dfbody_dvbs, qbonco, dqbdvb;
    {
      double fshort, dfs_dvbs;
      {if (short_channel) {
	static const double d[3] = {.0631353, .8013292, -.01110777};
	double wp = m->xd * sarg;
	double wp_xj = wp / m->xj;
	double wc_xj = d[0] + d[1] * wp_xj + d[2] * wp_xj * wp_xj;
	double ld_xj = m->ld / m->xj;
	double xj_le = m->xj / b->l_eff;
	
	double arga = wc_xj + ld_xj;
	double argc = wp_xj / (wp_xj + 1.);
	double argb = sqrt(1. - argc * argc);
	fshort = 1. - xj_le * (arga * argb - ld_xj);
	
	double dwp_dvbs = m->xd * dsarg_dvbs;
	double darga_dvbs = (d[1] + d[2] * (wp_xj + wp_xj)) * dwp_dvbs / m->xj;
	double dargb_dvbs = -argc * argc * (1. - argc) * dwp_dvbs / (argb*wp);
	dfs_dvbs = -xj_le * (darga_dvbs * argb + arga * dargb_dvbs);
	trace2("short-channel", fshort, dfs_dvbs);
      }else{
	fshort = 1.;
	dfs_dvbs = 0.;
	trace2("not-short-channel", fshort, dfs_dvbs);
      }}
      
      double gamma_fs = m->gamma * fshort;
      double fbodys = gamma_fs * .5 / (2 * sarg);
      double fnarrw = m->delta3 / b->w_eff;
      trace3("", gamma_fs, fbodys, fnarrw);
      
      fbody = fbodys + fnarrw;
      dfbody_dvbs = -fbodys * dsarg_dvbs / sarg + fbodys * dfs_dvbs / fshort;
      trace2("", fbody, dfbody_dvbs);
      
      qbonco = gamma_fs * sarg + fnarrw * v_phi_s;
      dqbdvb = gamma_fs * dsarg_dvbs + m->gamma * dfs_dvbs * sarg - fnarrw;
      trace2("", qbonco, dqbdvb);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* threshold voltage */
    double vth, dvth_dvds, dvth_dvbs;
    {
      double sigma = m->eta * 8.15e-22 / (m->cox * b->l_eff*b->l_eff*b->l_eff);
      double vbix = t.vbi - sigma * d->vds;
      vth = vbix + qbonco;
      dvth_dvds = -(sigma);
      dvth_dvbs = dqbdvb;
      trace3("", vth, dvth_dvds, dvth_dvbs);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* joint weak inversion and strong inversion */
    /* von */
    double xn, vtxn, dxn_dvbs, dvon_dvds, dvon_dvbs;
    {
      {if (do_subthreshold) {
	xn = 1. + m->cfsox + qbonco / (2 * v_phi_s);
	vtxn = t.vt * xn;
	dxn_dvbs  = dqbdvb / (2*v_phi_s) - qbonco*dsarg_dvbs / (v_phi_s*sarg);
	trace3("do_sub", xn, vtxn, dxn_dvbs);
	
	d->von  = vth + vtxn;
	dvon_dvds = dvth_dvds;
	dvon_dvbs = dvth_dvbs + t.vt * dxn_dvbs;
	d->vgst = d->vgs - d->von;
	trace4("", d->von, dvon_dvds, dvon_dvbs, d->vgst);
	
	d->subthreshold = (d->vgs < d->von);
	d->cutoff = false;
      }else{
	xn = vtxn = dxn_dvbs = dvon_dvds = dvon_dvbs = 0.;
	d->von = vth;
	d->vgst = d->vgs - d->von;
	trace2("no_sub", vtxn, dxn_dvbs);
	trace4("", d->von, dvon_dvds, dvon_dvbs, d->vgst);
	
	d->subthreshold = false;
	d->cutoff = (d->vgs <= d->von);
	if (d->cutoff) {
	  trace0("***** cut off *****");
	  d->vdsat = 0.;
	  d->ids = 0.;
	  d->gmf = d->gmr = 0.;
	  d->gds = 0.;
	  d->gmbf = d->gmbr = 0.;
	  return;
	}
      }}
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* device is on */
    /* mobility modulation by gate voltage */
    double vc, onfg, us, dfg_dvgs, dfg_dvds, dfg_dvbs, beta;
    {
      double vgsx = (d->subthreshold) ? d->von : d->vgs;
      vc = vgsx - vth;
      onfg = m->theta * vc + 1.;
      double fgate = 1. / onfg;
      trace3("", vc, onfg, fgate);
      
      us = t.uo * fgate;
      beta = t.beta * fgate;
      trace4("", t.beta, beta, t.uo, us);
      
      dfg_dvgs = -(m->theta) * fgate * fgate;
      dfg_dvds = -dfg_dvgs * dvth_dvds;
      dfg_dvbs = -dfg_dvgs * dvth_dvbs;
      trace3("", dfg_dvgs, dfg_dvds, dfg_dvbs);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* saturation voltage */
    /* vdsat, saturated */
    double dvdsat_dvgs, dvdsat_dvds, dvdsat_dvbs;
    double onvdsc, vdsx;
    {
      double onfbdy = 1. / (fbody + 1.);
      double dvsdga = onfbdy;
      d->vdsat = vc * onfbdy;
      trace2("novm", d->vdsat, dvsdga);
      
      {if (use_vmax) {
	double vdsc = b->l_eff * m->vmax / us;
	double argb = sqrt(d->vdsat * d->vdsat + vdsc * vdsc);
	d->vdsat += vdsc - argb;
	dvsdga *= (1. - d->vdsat / argb);
	trace2("vmax", d->vdsat, dvsdga);
	dvdsat_dvgs = dvsdga - (1. - vdsc / argb) * vdsc * dfg_dvgs * onfg;
	onvdsc = 1. / vdsc;
      }else{
	dvdsat_dvgs = dvsdga;
	onvdsc = NOT_VALID;
      }}
      d->saturated = (d->vds > d->vdsat);
      vdsx =  (d->saturated) ? d->vdsat : d->vds;
      trace3("", d->vdsat, vdsx, onvdsc);
      
      dvdsat_dvds = -dvdsat_dvgs * dvth_dvds;
      dvdsat_dvbs = -dvdsat_dvgs * dvth_dvbs - d->vdsat * dfbody_dvbs * dvsdga;
      trace3("", dvdsat_dvgs, dvdsat_dvds, dvdsat_dvbs);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* short cut exit if vds == 0 */
    if (vdsx == 0.) { /*900*/
      trace2("***** vdsx == 0 *****", d->vdsat, d->vds);
      d->ids = 0.;
      d->gmf = d->gmr = 0.;
      d->gds = beta * vc;
      d->gmbf = d->gmbr = 0.;
      if (d->subthreshold) {
	d->gds *= exp(d->vgst / vtxn);
      }
      return;
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* normalized drain current */
    {
      double cdo = vc - (fbody + 1.) * .5 * vdsx;
      double dcodvb = -dvth_dvbs - dfbody_dvbs * .5 * vdsx;
      trace3("", t.beta, cdo, dcodvb);
      
      d->gmf  = vdsx;
      d->gds = vc - (fbody + 1. + dvth_dvds) * vdsx;
      d->gmbf = dcodvb * vdsx;
      d->ids = cdo * vdsx;
      trace4("1", d->ids, d->gmf, d->gds, d->gmbf);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* scale, but without velocity saturation effect */
    {
      double cd1 = t.beta * d->ids;
      d->gmf  *= beta; d->gmf  += dfg_dvgs * cd1;
      d->gds *= beta; d->gds += dfg_dvds * cd1;
      d->gmbf *= beta;
      d->ids *= beta;
      trace4("2", d->ids, d->gmf, d->gds, d->gmbf);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* velocity saturation factor */
    double fdrain, dfd_dvgs, dfd_dvds, dfd_dvbs;
    {if (use_vmax) {
      assert(onvdsc != NOT_VALID);
      fdrain = 1. / (vdsx * onvdsc + 1.);
      double fd2 = fdrain * fdrain;
      double arga = fd2 * vdsx * onvdsc * onfg;
      dfd_dvgs = -dfg_dvgs * arga;
      dfd_dvds = -dfg_dvds * arga - fd2 * onvdsc;
      dfd_dvbs = -dfg_dvbs * arga;
      trace4("", fdrain, dfd_dvgs, dfd_dvds, dfd_dvbs);
      
      d->gmf  *= fdrain; d->gmf  += dfd_dvgs * d->ids;
      d->gds *= fdrain; d->gds += dfd_dvds * d->ids;
      d->gmbf *= fdrain; d->gmbf += dfd_dvbs * d->ids;
      d->ids *= fdrain;
      beta   *= fdrain;
      trace4("3", d->ids, d->gmf, d->gds, d->gmbf);
    }else{
      fdrain = 0.;	/* used only if use_vmax */
      dfd_dvgs = 0.;
      dfd_dvds = 0.;
      dfd_dvbs = 0.;
    }}
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* channel length modulation */
    double gds0;
    {if (d->saturated) {
      double d_l, dl_dvd;
      double ddl_dvgs, ddl_dvds, ddl_dvbs;
      {if (m->alpha == 0.) {
	d_l = dl_dvd = ddl_dvgs = ddl_dvds = ddl_dvbs = 0.;
      }else if (use_vmax) {	/* use_vmax && m->alpha != 0 */
	double gdsat = d->ids * (1. - fdrain) * onvdsc;
	gdsat = std::max(1e-12,gdsat);
	double gdoncd = gdsat / d->ids;
	double gdonfd = gdsat / (1. - fdrain);
	double gdonfg = gdsat * onfg;
	double dgdvg = gdoncd * d->gmf  - gdonfd * dfd_dvgs + gdonfg * dfg_dvgs;
	double dgdvd = gdoncd * d->gds - gdonfd * dfd_dvds + gdonfg * dfg_dvds;
	double dgdvb = gdoncd * d->gmbf - gdonfd * dfd_dvbs + gdonfg * dfg_dvbs;
	
	double emax = d->ids / (b->l_eff * gdsat);
	double emax_o_ids   = emax / d->ids;
	double emax_o_gdsat = emax / gdsat;
	double demax_dvgs = emax_o_ids * d->gmf  - emax_o_gdsat * dgdvg;
	double demax_dvds = emax_o_ids * d->gds - emax_o_gdsat * dgdvd;
	double demax_dvbs = emax_o_ids * d->gmbf - emax_o_gdsat * dgdvb;
	
	double arga = emax * .5 * m->alpha;
	double argc = m->kappa * m->alpha;
	double argb = sqrt(arga * arga + argc * (d->vds - d->vdsat));
	d_l = argb - arga;
	dl_dvd = argc / (argb + argb);
	double dl_demax = (arga / argb - 1.) * .5 * m->alpha;
	ddl_dvgs = dl_demax * demax_dvgs;
	ddl_dvds = dl_demax * demax_dvds - dl_dvd;
	ddl_dvbs = dl_demax * demax_dvbs;
      }else{
	d_l = sqrt(m->kappa * (d->vds - d->vdsat) * m->alpha);
	dl_dvd = d_l * .5 / (d->vds - d->vdsat);
	ddl_dvgs = 0.;
	ddl_dvds = -dl_dvd;
	ddl_dvbs = 0.;
      }}
      
      {if (d_l > b->l_eff * .5) {	/* punch through approximation */
	d->punchthru = true;
	d_l = b->l_eff - b->l_eff*b->l_eff / (d_l*4.);
	double arga = (b->l_eff-d_l)*(b->l_eff-d_l) * 4./(b->l_eff*b->l_eff);
	ddl_dvgs *= arga;
	ddl_dvds *= arga;
	ddl_dvbs *= arga;
	dl_dvd *= arga;
      }else{
	d->punchthru = false;
      }}
      
      {if (m->alpha != 0) {
	double lfact = 1. / (1. - d_l / b->l_eff);
	d->ids *= lfact;
	double diddl = d->ids / (b->l_eff - d_l);
	d->gmf   = d->gmf  * lfact + diddl * ddl_dvgs;
	gds0    = d->gds * lfact + diddl * ddl_dvds;
	d->gmbf  = d->gmbf * lfact + diddl * ddl_dvbs;
	d->gmf  += gds0 * dvdsat_dvgs;
	d->gmbf += gds0 * dvdsat_dvbs;
	d->gds  = gds0 * dvdsat_dvds + diddl * dl_dvd;
      }else{
	gds0 = 0;
      }}
      trace2("", d_l, dl_dvd);
      trace3("", ddl_dvgs, ddl_dvds, ddl_dvbs);
      trace3("4", d->ids, gds0, d_l);
    }else{
      d->punchthru = false;
      gds0 = 0;				/* not saturated */
    }}
    trace4("4", d->ids, d->gmf, d->gds, d->gmbf);
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* weak inversion -- subthreshold region */
    if (d->subthreshold) {
      double wfact = exp(d->vgst / vtxn);
      d->ids *= wfact;
      double gms = d->gmf * wfact;
      double gmw = d->ids / vtxn;
      trace2("subth", gmw, gms);
      
      d->gmf   = gmw;
      d->gmf  += gds0 * dvdsat_dvgs * wfact;
      d->gds *= wfact;
      d->gds += (gms - gmw) * dvon_dvds;
      d->gmbf *= wfact;
      d->gmbf += (gms - gmw) * dvon_dvbs - gmw * d->vgst * dxn_dvbs / xn;
      trace4("5", d->ids, d->gmf, d->gds, d->gmbf);
    }
    {if (d->reversed){
      d->ids *= -1;
      d->gmr = d->gmf;
      d->gmbr = d->gmbf;
      d->gmf = d->gmbf = 0;
    }else{
      d->gmr = d->gmbr = 0.;
    }}
}
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
