/*
 * mosfet.cpp - mosfet class implementation
 *
 * Copyright (C) 2004, 2005, 2006, 2008 Stefan Jahn <stefan@lkcc.org>
 *
 * This 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 software 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 package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * $Id$
 *
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "component.h"
#include "device.h"
#include "mosfet.h"

#define NODE_G 0 /* gate node   */
#define NODE_D 1 /* drain node  */
#define NODE_S 2 /* source node */
#define NODE_B 3 /* bulk node   */

using namespace qucs;
using namespace qucs::device;

mosfet::mosfet () : circuit (4) {
  transientMode = 0;
  rg = rs = rd = NULL;
  type = CIR_MOSFET;
}

void mosfet::calcSP (nr_double_t frequency) {
  setMatrixS (ytos (calcMatrixY (frequency)));
}

matrix mosfet::calcMatrixY (nr_double_t frequency) {

  // fetch computed operating points
  nr_double_t Cgd = getOperatingPoint ("Cgd");
  nr_double_t Cgs = getOperatingPoint ("Cgs");
  nr_double_t Cbd = getOperatingPoint ("Cbd");
  nr_double_t Cbs = getOperatingPoint ("Cbs");
  nr_double_t Cgb = getOperatingPoint ("Cgb");
  nr_double_t gbs = getOperatingPoint ("gbs");
  nr_double_t gbd = getOperatingPoint ("gbd");
  nr_double_t gds = getOperatingPoint ("gds");
  nr_double_t gm  = getOperatingPoint ("gm");
  nr_double_t gmb = getOperatingPoint ("gmb");

  // compute the models admittances
  nr_complex_t Ygd = nr_complex_t (0.0, 2.0 * pi * frequency * Cgd);
  nr_complex_t Ygs = nr_complex_t (0.0, 2.0 * pi * frequency * Cgs);
  nr_complex_t Yds = gds;
  nr_complex_t Ybd = nr_complex_t (gbd, 2.0 * pi * frequency * Cbd);
  nr_complex_t Ybs = nr_complex_t (gbs, 2.0 * pi * frequency * Cbs);
  nr_complex_t Ygb = nr_complex_t (0.0, 2.0 * pi * frequency * Cgb);

  // build admittance matrix and convert it to S-parameter matrix
  matrix y (4);
  y.set (NODE_G, NODE_G, Ygd + Ygs + Ygb);
  y.set (NODE_G, NODE_D, -Ygd);
  y.set (NODE_G, NODE_S, -Ygs);
  y.set (NODE_G, NODE_B, -Ygb);
  y.set (NODE_D, NODE_G, gm - Ygd);
  y.set (NODE_D, NODE_D, Ygd + Yds + Ybd - DrainControl);
  y.set (NODE_D, NODE_S, -Yds - SourceControl);
  y.set (NODE_D, NODE_B, -Ybd + gmb);
  y.set (NODE_S, NODE_G, -Ygs - gm);
  y.set (NODE_S, NODE_D, -Yds + DrainControl);
  y.set (NODE_S, NODE_S, Ygs + Yds + Ybs + SourceControl);
  y.set (NODE_S, NODE_B, -Ybs - gmb);
  y.set (NODE_B, NODE_G, -Ygb);
  y.set (NODE_B, NODE_D, -Ybd);
  y.set (NODE_B, NODE_S, -Ybs);
  y.set (NODE_B, NODE_B, Ybd + Ybs + Ygb);

  return y;
}

void mosfet::calcNoiseSP (nr_double_t frequency) {
  setMatrixN (cytocs (calcMatrixCy (frequency) * z0, getMatrixS ()));
}

matrix mosfet::calcMatrixCy (nr_double_t frequency) {
  /* get operating points and noise properties */
  nr_double_t Kf  = getPropertyDouble ("Kf");
  nr_double_t Af  = getPropertyDouble ("Af");
  nr_double_t Ffe = getPropertyDouble ("Ffe");
  nr_double_t gm  = fabs (getOperatingPoint ("gm"));
  nr_double_t Ids = fabs (getOperatingPoint ("Id"));
  nr_double_t T   = getPropertyDouble ("Temp");

  /* compute channel noise and flicker noise generated by the DC
     transconductance and current flow from drain to source */
  nr_double_t i = 8 * celsius2kelvin (T) / T0 * gm / 3 +
    Kf * qucs::pow (Ids, Af) / qucs::pow (frequency, Ffe) / kB / T0;

  /* build noise current correlation matrix and convert it to
     noise-wave correlation matrix */
  matrix cy = matrix (4);
  cy.set (NODE_D, NODE_D, +i);
  cy.set (NODE_S, NODE_S, +i);
  cy.set (NODE_D, NODE_S, -i);
  cy.set (NODE_S, NODE_D, -i);
  return cy;
}

void mosfet::restartDC (void) {
  // apply starting values to previous iteration values
  UgdPrev = real (getV (NODE_G) - getV (NODE_D));
  UgsPrev = real (getV (NODE_G) - getV (NODE_S));
  UbsPrev = real (getV (NODE_B) - getV (NODE_S));
  UbdPrev = real (getV (NODE_B) - getV (NODE_D));
  UdsPrev = UgsPrev - UgdPrev;
}

void mosfet::initDC (void) {

  // allocate MNA matrices
  allocMatrixMNA ();

  // initialize starting values
  restartDC ();

  // initialize the MOSFET
  initModel ();

  // get device temperature
  nr_double_t T = getPropertyDouble ("Temp");

  // possibly insert series resistance at source
  if (Rs != 0.0) {
    // create additional circuit if necessary and reassign nodes
    rs = splitResistor (this, rs, "Rs", "source", NODE_S);
    rs->setProperty ("Temp", T);
    rs->setProperty ("R", Rs);
    rs->setProperty ("Controlled", getName ());
    rs->initDC ();
  }
  // no series resistance at source
  else {
    disableResistor (this, rs, NODE_S);
  }

  // possibly insert series resistance at gate
  nr_double_t Rg = getPropertyDouble ("Rg");
  if (Rg != 0.0) {
    // create additional circuit if necessary and reassign nodes
    rg = splitResistor (this, rg, "Rg", "gate", NODE_G);
    rg->setProperty ("Temp", T);
    rg->setProperty ("R", Rg);
    rg->setProperty ("Controlled", getName ());
    rg->initDC ();
  }
  // no series resistance at source
  else {
    disableResistor (this, rg, NODE_G);
  }

  // possibly insert series resistance at drain
  if (Rd != 0.0) {
    // create additional circuit if necessary and reassign nodes
    rd = splitResistor (this, rd, "Rd", "drain", NODE_D);
    rd->setProperty ("Temp", T);
    rd->setProperty ("R", Rd);
    rd->setProperty ("Controlled", getName ());
    rd->initDC ();
  }
  // no series resistance at drain
  else {
    disableResistor (this, rd, NODE_D);
  }
}

void mosfet::initModel (void) {

  // get device temperature
  nr_double_t T  = getPropertyDouble ("Temp");
  nr_double_t T2 = celsius2kelvin (getPropertyDouble ("Temp"));
  nr_double_t T1 = celsius2kelvin (getPropertyDouble ("Tnom"));

  // apply polarity of MOSFET
  const char * const type = getPropertyString ("Type");
  pol = !strcmp (type, "pfet") ? -1 : 1;

  // calculate effective channel length
  nr_double_t L  = getPropertyDouble ("L");
  nr_double_t Ld = getPropertyDouble ("Ld");
  if ((Leff = L - 2 * Ld) <= 0) {
    logprint (LOG_STATUS, "WARNING: effective MOSFET channel length %g <= 0, "
	      "set to L = %g\n", Leff, L);
    Leff = L;
  }

  // calculate gate oxide overlap capacitance
  nr_double_t W   = getPropertyDouble ("W");
  nr_double_t Tox = getPropertyDouble ("Tox");
  if (Tox <= 0) {
    logprint (LOG_STATUS, "WARNING: disabling gate oxide capacitance, "
	      "Cox = 0\n");
    Cox = 0;
  } else {
    Cox = (ESiO2 * E0 / Tox);
  }

  // calculate DC transconductance coefficient
  nr_double_t Kp = getPropertyDouble ("Kp");
  nr_double_t Uo = getPropertyDouble ("Uo");
  nr_double_t F1 = qucs::exp (1.5 * qucs::log (T1 / T2));
  Kp = Kp * F1;
  Uo = Uo * F1;
  setScaledProperty ("Kp", Kp);
  setScaledProperty ("Uo", Uo);
  if (Kp > 0) {
    beta = Kp * W / Leff;
  } else {
    if (Cox > 0 && Uo > 0) {
      beta = Uo * 1e-4 * Cox * W / Leff;
    } else {
      logprint (LOG_STATUS, "WARNING: adjust Tox, Uo or Kp to get a valid "
		"transconductance coefficient\n");
      beta = 2e-5 * W / Leff;
    }
  }

  // calculate surface potential
  nr_double_t P    = getPropertyDouble ("Phi");
  nr_double_t Nsub = getPropertyDouble ("Nsub");
  nr_double_t Ut   = T0 * kBoverQ;
  P = pnPotential_T (T1,T2, P);
  setScaledProperty ("Phi", P);
  if ((Phi = P) <= 0) {
    if (Nsub > 0) {
      if (Nsub * 1e6 >= NiSi) {
	Phi = 2 * Ut * qucs::log (Nsub * 1e6 / NiSi);
      } else {
	logprint (LOG_STATUS, "WARNING: substrate doping less than intrinsic "
		  "density, adjust Nsub >= %g\n", NiSi / 1e6);
	Phi = 0.6;
      }
    } else {
      logprint (LOG_STATUS, "WARNING: adjust Nsub or Phi to get a valid "
		"surface potential\n");
      Phi = 0.6;
    }
  }

  // calculate bulk threshold
  nr_double_t G = getPropertyDouble ("Gamma");
  if ((Ga = G) < 0) {
    if (Cox > 0 && Nsub > 0) {
      Ga = qucs::sqrt (2 * Q_e * ESi * E0 * Nsub * 1e6) / Cox;
    } else {
      logprint (LOG_STATUS, "WARNING: adjust Tox, Nsub or Gamma to get a "
		"valid bulk threshold\n");
      Ga = 0.0;
    }
  }

  // calculate threshold voltage
  nr_double_t Vt0 = getPropertyDouble ("Vt0");
  if ((Vto = Vt0) == 0.0) {
    nr_double_t Tpg = getPropertyDouble ("Tpg");
    nr_double_t Nss = getPropertyDouble ("Nss");
    nr_double_t PhiMS, PhiG, Eg;
    // bandgap for silicon
    Eg = Egap (celsius2kelvin (T));
    if (Tpg != 0.0) { // n-poly or p-poly
      PhiG = 4.15 + Eg / 2 - pol * Tpg * Eg / 2;
    } else {          // alumina
      PhiG = 4.1;
    }
    PhiMS = PhiG - (4.15 + Eg / 2 + pol * Phi / 2);
    if (Nss >= 0 && Cox > 0) {
      Vto = PhiMS - Q_e * Nss * 1e4 / Cox + pol * (Phi + Ga * qucs::sqrt (Phi));
    } else {
      logprint (LOG_STATUS, "WARNING: adjust Tox, Nss or Vt0 to get a "
		"valid threshold voltage\n");
      Vto = 0.0;
    }
  }

  Cox = Cox * W * Leff;

  // calculate drain and source resistance if necessary
  nr_double_t Rsh = getPropertyDouble ("Rsh");
  nr_double_t Nrd = getPropertyDouble ("Nrd");
  nr_double_t Nrs = getPropertyDouble ("Nrs");
  Rd = getPropertyDouble ("Rd");
  Rs = getPropertyDouble ("Rs");
  if (Rsh > 0) {
    if (Nrd > 0) Rd += Rsh * Nrd;
    if (Nrs > 0) Rs += Rsh * Nrs;
  }

  // calculate zero-bias junction capacitance
  nr_double_t Cj  = getPropertyDouble ("Cj");
  nr_double_t Mj  = getPropertyDouble ("Mj");
  nr_double_t Mjs = getPropertyDouble ("Mjsw");
  nr_double_t Pb  = getPropertyDouble ("Pb");
  nr_double_t PbT, F2, F3;
  PbT = pnPotential_T (T1,T2, Pb);
  F2  = pnCapacitance_F (T1, T2, Mj, PbT / Pb);
  F3  = pnCapacitance_F (T1, T2, Mjs, PbT / Pb);
  Pb  = PbT;
  setScaledProperty ("Pb", Pb);
  if (Cj <= 0) {
    if (Pb > 0 && Nsub >= 0) {
      Cj = qucs::sqrt (ESi * E0 * Q_e * Nsub * 1e6 / 2 / Pb);
    }
    else {
      logprint (LOG_STATUS, "WARNING: adjust Pb, Nsub or Cj to get a "
		"valid square junction capacitance\n");
      Cj = 0.0;
    }
  }
  Cj = Cj * F2;
  setScaledProperty ("Cj", Cj);

  // calculate junction capacitances
  nr_double_t Cbd0 = getPropertyDouble ("Cbd");
  nr_double_t Cbs0 = getPropertyDouble ("Cbs");
  nr_double_t Ad   = getPropertyDouble ("Ad");
  nr_double_t As   = getPropertyDouble ("As");
  Cbd0 = Cbd0 * F2;
  if (Cbd0 <= 0) {
    Cbd0 = Cj * Ad;
  }
  setScaledProperty ("Cbd", Cbd0);
  Cbs0 = Cbs0 * F2;
  if (Cbs0 <= 0) {
    Cbs0 = Cj * As;
  }
  setScaledProperty ("Cbs", Cbs0);

  // calculate periphery junction capacitances
  nr_double_t Cjs = getPropertyDouble ("Cjsw");
  nr_double_t Pd  = getPropertyDouble ("Pd");
  nr_double_t Ps  = getPropertyDouble ("Ps");
  Cjs = Cjs * F3;
  setProperty ("Cbds", Cjs * Pd);
  setProperty ("Cbss", Cjs * Ps);

  // calculate junction capacitances and saturation currents
  nr_double_t Js  = getPropertyDouble ("Js");
  nr_double_t Is  = getPropertyDouble ("Is");
  nr_double_t F4, E1, E2;
  E1 = Egap (T1);
  E2 = Egap (T2);
  F4 = qucs::exp (- QoverkB / T2 * (T2 / T1 * E1 - E2));
  Is = Is * F4;
  Js = Js * F4;
  nr_double_t Isd = (Ad > 0) ? Js * Ad : Is;
  nr_double_t Iss = (As > 0) ? Js * As : Is;
  setProperty ("Isd", Isd);
  setProperty ("Iss", Iss);

#if DEBUG
  logprint (LOG_STATUS, "NOTIFY: Cox=%g, Beta=%g Ga=%g, Phi=%g, Vto=%g\n",
	    Cox, beta, Ga, Phi, Vto);
#endif /* DEBUG */
}

void mosfet::calcDC (void) {

  // fetch device model parameters
  nr_double_t Isd = getPropertyDouble ("Isd");
  nr_double_t Iss = getPropertyDouble ("Iss");
  nr_double_t n   = getPropertyDouble ("N");
  nr_double_t l   = getPropertyDouble ("Lambda");
  nr_double_t T   = getPropertyDouble ("Temp");

  nr_double_t Ut, IeqBS, IeqBD, IeqDS, UbsCrit, UbdCrit, gtiny;

  T = celsius2kelvin (T);
  Ut = T * kBoverQ;
  Ugd = real (getV (NODE_G) - getV (NODE_D)) * pol;
  Ugs = real (getV (NODE_G) - getV (NODE_S)) * pol;
  Ubs = real (getV (NODE_B) - getV (NODE_S)) * pol;
  Ubd = real (getV (NODE_B) - getV (NODE_D)) * pol;
  Uds = Ugs - Ugd;

  // critical voltage necessary for bad start values
  UbsCrit = pnCriticalVoltage (Iss, Ut * n);
  UbdCrit = pnCriticalVoltage (Isd, Ut * n);

  // for better convergence
  if (Uds >= 0) {
    Ugs = fetVoltage (Ugs, UgsPrev, Vto * pol);
    Uds = Ugs - Ugd;
    Uds = fetVoltageDS (Uds, UdsPrev);
    Ugd = Ugs - Uds;
  }
  else {
    Ugd = fetVoltage (Ugd, UgdPrev, Vto * pol);
    Uds = Ugs - Ugd;
    Uds = -fetVoltageDS (-Uds, -UdsPrev);
    Ugs = Ugd + Uds;
  }
  if (Uds >= 0) {
    Ubs = pnVoltage (Ubs, UbsPrev, Ut * n, UbsCrit);
    Ubd = Ubs - Uds;
  }
  else {
    Ubd = pnVoltage (Ubd, UbdPrev, Ut * n, UbdCrit);
    Ubs = Ubd + Uds;
  }
  UgsPrev = Ugs; UgdPrev = Ugd; UbdPrev = Ubd; UdsPrev = Uds; UbsPrev = Ubs;

  // parasitic bulk-source diode
  gtiny = Iss;
  pnJunctionMOS (Ubs, Iss, Ut * n, Ibs, gbs);
  Ibs += gtiny * Ubs;
  gbs += gtiny;

  // parasitic bulk-drain diode
  gtiny = Isd;
  pnJunctionMOS (Ubd, Isd, Ut * n, Ibd, gbd);
  Ibd += gtiny * Ubd;
  gbd += gtiny;

  // differentiate inverse and forward mode
  MOSdir = (Uds >= 0) ? +1 : -1;

  // first calculate sqrt (Upn - Phi)
  nr_double_t Upn = (MOSdir > 0) ? Ubs : Ubd;
  nr_double_t Sarg, Sphi = qucs::sqrt (Phi);
  if (Upn <= 0) {
    // take equation as is
    Sarg = qucs::sqrt (Phi - Upn);
  }
  else {
    // taylor series of "sqrt (x - 1)" -> continual at Ubs/Ubd = 0
    Sarg = Sphi - Upn / Sphi / 2;
    Sarg = std::max (Sarg, 0.0);
  }

  // calculate bias-dependent threshold voltage
  Uon = Vto * pol + Ga * (Sarg - Sphi);
  nr_double_t Utst = ((MOSdir > 0) ? Ugs : Ugd) - Uon;
  // no infinite backgate transconductance (if non-zero Ga)
  nr_double_t arg = (Sarg != 0.0) ? (Ga / Sarg / 2) : 0;

  // cutoff region
  if (Utst <= 0) {
    Ids = 0;
    gm  = 0;
    gds = 0;
    gmb = 0;
  }
  else {
    nr_double_t Vds = Uds * MOSdir;
    nr_double_t b   = beta * (1 + l * Vds);
    // saturation region
    if (Utst <= Vds) {
      Ids = b * Utst * Utst / 2;
      gm  = b * Utst;
      gds = l * beta * Utst * Utst / 2;
    }
    // linear region
    else {
      Ids = b * Vds * (Utst - Vds / 2);
      gm  = b * Vds;
      gds = b * (Utst - Vds) + l * beta * Vds * (Utst - Vds / 2);
    }
    gmb = gm * arg;
  }
  Udsat = pol * std::max (Utst, 0.0);
  Ids = MOSdir * Ids;
  Uon = pol * Uon;

  // compute autonomic current sources
  IeqBD = Ibd - gbd * Ubd;
  IeqBS = Ibs - gbs * Ubs;

  // exchange controlling nodes if necessary
  SourceControl = (MOSdir > 0) ? (gm + gmb) : 0;
  DrainControl  = (MOSdir < 0) ? (gm + gmb) : 0;
  if (MOSdir > 0) {
    IeqDS = Ids - gm * Ugs - gmb * Ubs - gds * Uds;
  } else {
    IeqDS = Ids - gm * Ugd - gmb * Ubd - gds * Uds;
  }

  setI (NODE_G, 0);
  setI (NODE_D, (+IeqBD - IeqDS) * pol);
  setI (NODE_S, (+IeqBS + IeqDS) * pol);
  setI (NODE_B, (-IeqBD - IeqBS) * pol);

  // apply admittance matrix elements
  setY (NODE_G, NODE_G, 0);
  setY (NODE_G, NODE_D, 0);
  setY (NODE_G, NODE_S, 0);
  setY (NODE_G, NODE_B, 0);
  setY (NODE_D, NODE_G, gm);
  setY (NODE_D, NODE_D, gds + gbd - DrainControl);
  setY (NODE_D, NODE_S, -gds - SourceControl);
  setY (NODE_D, NODE_B, gmb - gbd);
  setY (NODE_S, NODE_G, -gm);
  setY (NODE_S, NODE_D, -gds + DrainControl);
  setY (NODE_S, NODE_S, gbs + gds + SourceControl);
  setY (NODE_S, NODE_B, -gbs - gmb);
  setY (NODE_B, NODE_G, 0);
  setY (NODE_B, NODE_D, -gbd);
  setY (NODE_B, NODE_S, -gbs);
  setY (NODE_B, NODE_B, gbs + gbd);
}

/* Usual and additional state definitions. */

#define qgdState  0 // gate-drain charge state
#define igdState  1 // gate-drain current state
#define vgdState  2 // gate-drain voltage state
#define cgdState  3 // gate-drain capacitance state
#define qgsState  4 // gate-source charge state
#define igsState  5 // gate-source current state
#define vgsState  6 // gate-source voltage state
#define cgsState  7 // gate-source capacitance state
#define qbdState  8 // bulk-drain charge state
#define ibdState  9 // bulk-drain current state
#define qbsState 10 // bulk-source charge state
#define ibsState 11 // bulk-source current state
#define qgbState 12 // gate-bulk charge state
#define igbState 13 // gate-bulk current state
#define vgbState 14 // gate-bulk voltage state
#define cgbState 15 // gate-bulk capacitance state

void mosfet::saveOperatingPoints (void) {
  nr_double_t Vgs, Vgd, Vbs, Vbd;
  Vgd = real (getV (NODE_G) - getV (NODE_D)) * pol;
  Vgs = real (getV (NODE_G) - getV (NODE_S)) * pol;
  Vbs = real (getV (NODE_B) - getV (NODE_S)) * pol;
  Vbd = real (getV (NODE_B) - getV (NODE_D)) * pol;
  setOperatingPoint ("Vgs", Vgs);
  setOperatingPoint ("Vgd", Vgd);
  setOperatingPoint ("Vbs", Vbs);
  setOperatingPoint ("Vbd", Vbd);
  setOperatingPoint ("Vds", Vgs - Vgd);
  setOperatingPoint ("Vgb", Vgs - Vbs);
}

void mosfet::loadOperatingPoints (void) {
  Ugs = getOperatingPoint ("Vgs");
  Ugd = getOperatingPoint ("Vgd");
  Ubs = getOperatingPoint ("Vbs");
  Ubd = getOperatingPoint ("Vbd");
  Uds = getOperatingPoint ("Vds");
  Ugb = getOperatingPoint ("Vgb");
}

void mosfet::calcOperatingPoints (void) {

  // fetch device model parameters
  nr_double_t Cbd0 = getScaledProperty ("Cbd");
  nr_double_t Cbs0 = getScaledProperty ("Cbs");
  nr_double_t Cbds = getPropertyDouble ("Cbds");
  nr_double_t Cbss = getPropertyDouble ("Cbss");
  nr_double_t Cgso = getPropertyDouble ("Cgso");
  nr_double_t Cgdo = getPropertyDouble ("Cgdo");
  nr_double_t Cgbo = getPropertyDouble ("Cgbo");
  nr_double_t Pb   = getScaledProperty ("Pb");
  nr_double_t M    = getPropertyDouble ("Mj");
  nr_double_t Ms   = getPropertyDouble ("Mjsw");
  nr_double_t Fc   = getPropertyDouble ("Fc");
  nr_double_t Tt   = getPropertyDouble ("Tt");
  nr_double_t W    = getPropertyDouble ("W");

  nr_double_t Cbs, Cbd, Cgd, Cgb, Cgs;

  // capacitance of bulk-drain diode
  Cbd = gbd * Tt + pnCapacitance (Ubd, Cbd0, Pb, M, Fc) +
    pnCapacitance (Ubd, Cbds, Pb, Ms, Fc);
  Qbd = Ibd * Tt + pnCharge (Ubd, Cbd0, Pb, M, Fc) +
    pnCharge (Ubd, Cbds, Pb, Ms, Fc);

  // capacitance of bulk-source diode
  Cbs = gbs * Tt + pnCapacitance (Ubs, Cbs0, Pb, M, Fc) +
    pnCapacitance (Ubs, Cbss, Pb, Ms, Fc);
  Qbs = Ibs * Tt + pnCharge (Ubs, Cbs0, Pb, M, Fc) +
    pnCharge (Ubs, Cbss, Pb, Ms, Fc);

  // calculate bias-dependent MOS overlap capacitances
  if (MOSdir > 0) {
    fetCapacitanceMeyer (Ugs, Ugd, Uon, Udsat, Phi, Cox, Cgs, Cgd, Cgb);
  } else {
    fetCapacitanceMeyer (Ugd, Ugs, Uon, Udsat, Phi, Cox, Cgd, Cgs, Cgb);
  }

  // charge approximation
  if (transientMode) {
    if (transientMode == 1) {      // by trapezoidal rule
      // gate-source charge
      Qgs = transientChargeTR (qgsState, Cgs, Ugs, Cgso * W);
      // gate-drain charge
      Qgd = transientChargeTR (qgdState, Cgd, Ugd, Cgdo * W);
      // gate-bulk charge
      Qgb = transientChargeTR (qgbState, Cgb, Ugb, Cgbo * Leff);
    }
    else if (transientMode == 2) { // by simpson's rule
      Qgs = transientChargeSR (qgsState, Cgs, Ugs, Cgso * W);
      Qgd = transientChargeSR (qgdState, Cgd, Ugd, Cgdo * W);
      Qgb = transientChargeSR (qgbState, Cgb, Ugb, Cgbo * Leff);
    }
  }
  // usual operating point
  else {
    Cgs += Cgso * W;
    Cgd += Cgdo * W;
    Cgb += Cgbo * Leff;
  }

  // save operating points
  setOperatingPoint ("Id", Ids);
  setOperatingPoint ("gm", gm);
  setOperatingPoint ("gmb", gmb);
  setOperatingPoint ("gds", gds);
  setOperatingPoint ("Vth", Vto);
  setOperatingPoint ("Vdsat", Udsat);
  setOperatingPoint ("gbs", gbs);
  setOperatingPoint ("gbd", gbd);
  setOperatingPoint ("Cbd", Cbd);
  setOperatingPoint ("Cbs", Cbs);
  setOperatingPoint ("Cgs", Cgs);
  setOperatingPoint ("Cgd", Cgd);
  setOperatingPoint ("Cgb", Cgb);
}

void mosfet::initAC (void) {
  allocMatrixMNA ();
}

void mosfet::calcAC (nr_double_t frequency) {
  setMatrixY (calcMatrixY (frequency));
}

void mosfet::calcNoiseAC (nr_double_t frequency) {
  setMatrixN (calcMatrixCy (frequency));
}

void mosfet::initTR (void) {
  setStates (16);
  initDC ();
}

void mosfet::calcTR (nr_double_t) {
  calcDC ();
  transientMode = getPropertyInteger ("capModel");
  saveOperatingPoints ();
  loadOperatingPoints ();
  calcOperatingPoints ();
  transientMode = 0;

  nr_double_t Cgd = getOperatingPoint ("Cgd");
  nr_double_t Cgs = getOperatingPoint ("Cgs");
  nr_double_t Cbd = getOperatingPoint ("Cbd");
  nr_double_t Cbs = getOperatingPoint ("Cbs");
  nr_double_t Cgb = getOperatingPoint ("Cgb");

  Uds = Ugs - Ugd;
  Ugb = Ugs - Ubs;

  transientCapacitance (qbdState, NODE_B, NODE_D, Cbd, Ubd, Qbd);
  transientCapacitance (qbsState, NODE_B, NODE_S, Cbs, Ubs, Qbs);

  // handle Meyer charges and capacitances
  transientCapacitance (qgdState, NODE_G, NODE_D, Cgd, Ugd, Qgd);
  transientCapacitance (qgsState, NODE_G, NODE_S, Cgs, Ugs, Qgs);
  transientCapacitance (qgbState, NODE_G, NODE_B, Cgb, Ugb, Qgb);
}

/* The function uses the trapezoidal rule to compute the current
   capacitance and charge.  The approximation is necessary because the
   Meyer model is a capacitance model and not a charge model. */
nr_double_t mosfet::transientChargeTR (int qstate, nr_double_t& cap,
				       nr_double_t voltage, nr_double_t ccap) {
  int vstate = qstate + 2, cstate = qstate + 3;
  setState (cstate, cap);
  cap = (cap + getState (cstate, 1)) / 2 + ccap;
  setState (vstate, voltage);
  return cap * (voltage - getState (vstate, 1)) + getState (qstate, 1);
}

/* The function uses Simpson's numerical integration rule to compute
   the current capacitance and charge. */
nr_double_t mosfet::transientChargeSR (int qstate, nr_double_t& cap,
				       nr_double_t voltage, nr_double_t ccap) {
  int vstate = qstate + 2, cstate = qstate + 3;
  setState (cstate, cap);
  cap = (cap + 4 * getState (cstate, 1) + getState (cstate, 2)) / 6 + ccap;
  setState (vstate, voltage);
  return cap * (voltage - getState (vstate, 1)) + getState (qstate, 1);
}

// properties
PROP_REQ [] = {
  { "Is", PROP_REAL, { 1e-14, PROP_NO_STR }, PROP_POS_RANGE },
  { "N", PROP_REAL, { 1, PROP_NO_STR }, PROP_RNGII (0.1, 100) },
  { "Vt0", PROP_REAL, { 0, PROP_NO_STR }, PROP_NO_RANGE },
  { "Lambda", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Kp", PROP_REAL, { 2e-5, PROP_NO_STR }, PROP_POS_RANGE },
  { "Gamma", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Phi", PROP_REAL, { 0.6, PROP_NO_STR }, PROP_POS_RANGE },
  PROP_NO_PROP };
PROP_OPT [] = {
  { "Rd", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Rs", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Rg", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "L", PROP_REAL, { 100e-6, PROP_NO_STR }, PROP_RNG_X01I },
  { "Ld", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "W", PROP_REAL, { 100e-6, PROP_NO_STR }, PROP_POS_RANGEX },
  { "Tox", PROP_REAL, { 1e-7, PROP_NO_STR }, PROP_RNG_X01I },
  { "Cgso", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Cgdo", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Cgbo", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Cbd", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Cbs", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Pb", PROP_REAL, { 0.8, PROP_NO_STR }, PROP_RNGXI (0, 10) },
  { "Mj", PROP_REAL, { 0.5, PROP_NO_STR }, PROP_RNGII (0, 1) },
  { "Fc", PROP_REAL, { 0.5, PROP_NO_STR }, PROP_RNGIX (0, 1) },
  { "Cjsw", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Mjsw", PROP_REAL, { 0.33, PROP_NO_STR }, PROP_RNGII (0, 1) },
  { "Tt", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Kf", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Af", PROP_REAL, { 1, PROP_NO_STR }, PROP_POS_RANGE },
  { "Ffe", PROP_REAL, { 1, PROP_NO_STR }, PROP_POS_RANGE },
  { "Nsub", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Nss", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Tpg", PROP_INT, { 1, PROP_NO_STR }, PROP_RNGII (-1 , 1) },
  { "Uo", PROP_REAL, { 600, PROP_NO_STR }, PROP_POS_RANGE },
  { "Rsh", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Nrd", PROP_REAL, { 1, PROP_NO_STR }, PROP_POS_RANGE },
  { "Nrs", PROP_REAL, { 1, PROP_NO_STR }, PROP_POS_RANGE },
  { "Cj", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Js", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Ad", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "As", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Pd", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Ps", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
  { "Temp", PROP_REAL, { 26.85, PROP_NO_STR }, PROP_MIN_VAL (K) },
  { "Tnom", PROP_REAL, { 26.85, PROP_NO_STR }, PROP_MIN_VAL (K) },
  { "Type", PROP_STR, { PROP_NO_VAL, "nfet" }, PROP_RNG_FET },
  { "capModel", PROP_INT, { 2, PROP_NO_STR }, PROP_RNGII (1 , 2) },
  PROP_NO_PROP };
struct define_t mosfet::cirdef =
  { "MOSFET", 4, PROP_COMPONENT, PROP_NO_SUBSTRATE, PROP_NONLINEAR, PROP_DEF };
