/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles
Modified: 2000 AlansFixes
**********/

/* support routines for device models */

#include "ngspice/ngspice.h"
#include "ngspice/devdefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/suffix.h"

#include <stdarg.h>


/* 
 * Limit the per-iteration change of VDS 
 */
double
DEVlimvds(double vnew, double vold)
{

    if(vold >= 3.5) {
        if(vnew > vold) {
            vnew = MIN(vnew,(3 * vold) +2);
        } else {
            if (vnew < 3.5) {
                vnew = MAX(vnew,2);
            }
        }
    } else {
        if(vnew > vold) {
            vnew = MIN(vnew,4);
        } else {
            vnew = MAX(vnew,-.5);
        }
    }
    return(vnew);
}


/*  
 * Limit the per-iteration change of PN junction voltages 
 *
 * This code has been fixed by Alan Gillespie adding limiting
 * for negative voltages.
 */
double
DEVpnjlim(double vnew, double vold, double vt, double vcrit, int *icheck)
{
    double arg;

    if((vnew > vcrit) && (fabs(vnew - vold) > (vt + vt))) {
        if(vold > 0) {
            arg = (vnew - vold) / vt;
            if(arg > 0) {
                vnew = vold + vt * (2+log(arg-2));
            } else {
                vnew = vold - vt * (2+log(2-arg));
            }
        } else {
            vnew = vt *log(vnew/vt);
        }
        *icheck = 1;
    } else {
       if (vnew < 0) {
           if (vold > 0) {
               arg = -1*vold-1;
           } else {
               arg = 2*vold-1;
           }
           if (vnew < arg) {
              vnew = arg;
              *icheck = 1;
           } else {
              *icheck = 0;
           }
        } else {
           *icheck = 0;
        }
    }
    return(vnew);
}

/* 
 * Limit the per-iteration change of FET voltages 
 *
 * This code has been fixed by Alan Gillespie: a new
 * definition for vtstlo. 
 */
double
DEVfetlim(double vnew, double vold, double vto)
{
    double vtsthi;
    double vtstlo;
    double vtox;
    double delv;
    double vtemp;

    vtsthi = fabs(2*(vold-vto))+2;
    vtstlo = fabs(vold-vto)+1;
    vtox = vto + 3.5;
    delv = vnew-vold;

    if (vold >= vto) {
        if(vold >= vtox) {
            if(delv <= 0) {
                /* going off */
                if(vnew >= vtox) {
                    if(-delv >vtstlo) {
                        vnew =  vold - vtstlo;
                    }
                } else {
                    vnew = MAX(vnew,vto+2);
                }
            } else {
                /* staying on */
                if(delv >= vtsthi) {
                    vnew = vold + vtsthi;
                }
            }
        } else {
            /* middle region */
            if(delv <= 0) {
                /* decreasing */
                vnew = MAX(vnew,vto-.5);
            } else {
                /* increasing */
                vnew = MIN(vnew,vto+4);
            }
        }
    } else {
        /* off */
        if(delv <= 0) {
            if(-delv >vtsthi) {
                vnew = vold - vtsthi;
            } 
        } else {
            vtemp = vto + .5;
            if(vnew <= vtemp) {
                if(delv >vtstlo) {
                    vnew = vold + vtstlo;
                }
            } else {
                vnew = vtemp;
            }
        }
    }
    return(vnew);
}

/* DEVlimitlog(deltemp, deltemp_old, LIM_TOL, check)
 * Logarithmic damping the per-iteration change of deltemp beyond LIM_TOL.
 */
double
DEVlimitlog(
    double deltemp,
    double deltemp_old,
    double LIM_TOL,
    int *check)
{
    static bool shown = FALSE;
    *check = 0;
    if (!shown && (isnan (deltemp) || isnan (deltemp_old)))
    {
        fprintf(stderr, "\n\nThe temperature limiting function received NaN.\n");
        fprintf(stderr, "Please check your power dissipation and improve your heat sink Rth!\n");
        fprintf(stderr, "    This message will be shown only once.\n\n");
        deltemp = 0.0;
        *check = 1;
        shown = TRUE;
    }
    /* Logarithmic damping of deltemp beyond LIM_TOL */
    if (deltemp > deltemp_old + LIM_TOL) {
        deltemp = deltemp_old + LIM_TOL + log10((deltemp-deltemp_old)/LIM_TOL);
        *check = 1;
    }
    else if (deltemp < deltemp_old - LIM_TOL) {
        deltemp = deltemp_old - LIM_TOL - log10((deltemp_old-deltemp)/LIM_TOL);
        *check = 1;
    }
    return deltemp;
}

int
ACM_SourceDrainResistances(
int ACM,
double LD,
double LDIF,
double HDIF,
double WMLT,
double w,
double XW,
double RSH,
int drainSquaresGiven,
double RD,
double RDC,
double drainSquares,
int sourceSquaresGiven,
double RS,
double RSC,
double sourceSquares,
double *drainResistance,
double *sourceResistance
)
{
    switch (ACM)
    {
    case 1:
    case 11:
        *drainResistance = (LD + LDIF)/(w * WMLT + XW)*RD + RSH*drainSquares + RDC;
        *sourceResistance = (LD + LDIF)/(w * WMLT + XW)*RS + RSH*sourceSquares + RSC;

        break;

    case 2:
    case 12:
    case 3:
    case 13:
        if (drainSquaresGiven)
          *drainResistance = (LD + LDIF)/(w * WMLT + XW)*RD + RSH*drainSquares + RDC;
        else
          *drainResistance = ((LD + LDIF)*RD + (HDIF * WMLT)*RSH)/(w * WMLT + XW) + RDC;
        if (sourceSquaresGiven)
          *sourceResistance = (LD + LDIF)/(w * WMLT + XW)*RS + RSH*sourceSquares + RSC;
        else
          *sourceResistance = ((LD + LDIF)*RS + (HDIF * WMLT)*RSH)/(w * WMLT + XW) + RSC;

        break;

    default:
        break;
    }

    return 0;
}

/* Area Calculation Method (ACM) for MOS models */
int
ACM_saturationCurrents(
int ACM,
int CALCACM,
int GEO,
double HDIF,
double WMLT,
double w,
double XW,
double jctTempSatCurDensity,
double jctSidewallTempSatCurDensity,
int drainAreaGiven,
double drainArea,
int drainPerimeterGiven,
double drainPerimeter,
int sourceAreaGiven,
double sourceArea,
int sourcePerimeterGiven,
double sourcePerimeter,
double *DrainSatCurrent,
double *SourceSatCurrent
)
{
    switch (ACM)
    {
    case 1:
    case 11:
        drainArea = (w * WMLT + XW) * WMLT;
        drainPerimeter = (w * WMLT + XW);
        *DrainSatCurrent = drainArea * jctTempSatCurDensity + drainPerimeter * jctSidewallTempSatCurDensity;
        if (*DrainSatCurrent <= 0.0) *DrainSatCurrent = 1.0e-14;

        sourceArea = (w * WMLT + XW) * WMLT;
        sourcePerimeter = (w * WMLT + XW);
        *SourceSatCurrent = sourceArea * jctTempSatCurDensity + sourcePerimeter * jctSidewallTempSatCurDensity;
        if (*SourceSatCurrent <= 0.0) *SourceSatCurrent = 1.0e-14;

        break;

    case 2:
    case 12:
        if ((ACM == 2) || ((ACM == 12) && (CALCACM == 1))) {
          if (!drainAreaGiven)
            drainArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            drainArea = drainArea * WMLT * WMLT;
          if (!drainPerimeterGiven)
            drainPerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
          else
            drainPerimeter = drainPerimeter * WMLT;
        }
        *DrainSatCurrent = drainArea * jctTempSatCurDensity + drainPerimeter * jctSidewallTempSatCurDensity;
        if (*DrainSatCurrent <= 0.0) *DrainSatCurrent = 1.0e-14;

        if ((ACM == 2) || ((ACM == 12) && (CALCACM == 1))) {
          if (!sourceAreaGiven)
            sourceArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            sourceArea = sourceArea * WMLT * WMLT;
          if (!sourcePerimeterGiven)
            sourcePerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
          else
            sourcePerimeter = sourcePerimeter * WMLT;
        }
        *SourceSatCurrent = sourceArea * jctTempSatCurDensity + sourcePerimeter * jctSidewallTempSatCurDensity;
        if (*SourceSatCurrent <= 0.0) *SourceSatCurrent = 1.0e-14;

        break;

    case 3:
    case 13:
        if (!drainAreaGiven)
          if ((GEO == 0) || (GEO == 2))
            drainArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            drainArea = (HDIF * WMLT) * (w * WMLT + XW);
        else
          drainArea = drainArea * WMLT * WMLT;
        if (!drainPerimeterGiven)
          if ((GEO == 0) || (GEO == 2))
            drainPerimeter = 4.0 * (HDIF * WMLT) + (w * WMLT + XW);
          else
            drainPerimeter = 2.0 * (HDIF * WMLT);
        else
          drainPerimeter = drainPerimeter * WMLT;
        *DrainSatCurrent = drainArea * jctTempSatCurDensity + drainPerimeter * jctSidewallTempSatCurDensity;
        if (*DrainSatCurrent <= 0.0) *DrainSatCurrent = 1.0e-14;

        if (!sourceAreaGiven)
          if ((GEO == 0) || (GEO == 1))
            sourceArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            sourceArea = (HDIF * WMLT) * (w * WMLT + XW);
        else
          sourceArea = sourceArea * WMLT * WMLT;
        if (!sourcePerimeterGiven)
          if ((GEO == 0) || (GEO == 1))
            sourcePerimeter = 4.0 * (HDIF * WMLT) + (w * WMLT + XW);
          else
            sourcePerimeter = 2.0 * (HDIF * WMLT);
        else
          sourcePerimeter = sourcePerimeter * WMLT;
        *SourceSatCurrent = sourceArea * jctTempSatCurDensity + sourcePerimeter * jctSidewallTempSatCurDensity;
        if (*SourceSatCurrent <= 0.0) *SourceSatCurrent = 1.0e-14;

        break;

    default:
        break;
    }

    return 0;
}

int
ACM_junctionCapacitances(
int ACM,
int CALCACM,
int GEO,
double HDIF,
double WMLT,
double w,
double XW,
int drainAreaGiven,
double drainArea,
int drainPerimeterGiven,
double drainPerimeter,
int sourceAreaGiven,
double sourceArea,
int sourcePerimeterGiven,
double sourcePerimeter,
double CJ,
double CJSW,
double CJGATE,
double *areaDrainBulkCapacitance,
double *periDrainBulkCapacitance,
double *gateDrainBulkCapacitance,
double *areaSourceBulkCapacitance,
double *periSourceBulkCapacitance,
double *gateSourceBulkCapacitance
)
{
    switch (ACM)
    {
    case 1:
        drainArea = (w * WMLT + XW) * WMLT;
        drainPerimeter = (w * WMLT + XW);
        *areaDrainBulkCapacitance = drainArea * CJ;
        *periDrainBulkCapacitance = drainPerimeter * CJSW;
        *gateDrainBulkCapacitance = 0.0;

        sourceArea = (w * WMLT + XW) * WMLT;
        sourcePerimeter = (w * WMLT + XW);
        *areaSourceBulkCapacitance = sourceArea * CJ;
        *periSourceBulkCapacitance = sourcePerimeter * CJSW;
        *gateSourceBulkCapacitance = 0.0;

        break;

    case 2:
        if (!drainAreaGiven)
          drainArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
        else
          drainArea = drainArea * WMLT * WMLT;
        if (!drainPerimeterGiven)
          drainPerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
        else
          drainPerimeter = drainPerimeter * WMLT;
        *areaDrainBulkCapacitance = drainArea * CJ;
        if (drainPerimeter > (w * WMLT + XW)) {
          *periDrainBulkCapacitance = (drainPerimeter - (w * WMLT + XW)) * CJSW;
          *gateDrainBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periDrainBulkCapacitance = drainPerimeter * CJGATE;
          *gateDrainBulkCapacitance = 0.0;
        }

        if (!sourceAreaGiven)
          sourceArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
        else
          sourceArea = sourceArea * WMLT * WMLT;
        if (!sourcePerimeterGiven)
          sourcePerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
        else
          sourcePerimeter = sourcePerimeter * WMLT;
        *areaSourceBulkCapacitance = sourceArea * CJ;
        if (sourcePerimeter > (w * WMLT + XW)) {
          *periSourceBulkCapacitance = (sourcePerimeter - (w * WMLT + XW)) * CJSW;
          *gateSourceBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periSourceBulkCapacitance = sourcePerimeter * CJGATE;
          *gateSourceBulkCapacitance = 0.0;
        }

        break;

    case 3:
        if (!drainAreaGiven)
          if ((GEO == 0) || (GEO == 2))
            drainArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            drainArea = (HDIF * WMLT) * (w * WMLT + XW);
        else
          drainArea = drainArea * WMLT * WMLT;
        if (!drainPerimeterGiven)
          if ((GEO == 0) || (GEO == 2))
            drainPerimeter = 4.0 * (HDIF * WMLT) + (w * WMLT + XW);
          else
            drainPerimeter = 2.0 * (HDIF * WMLT);
        else
          drainPerimeter = drainPerimeter * WMLT;
        *areaDrainBulkCapacitance = drainArea * CJ;
        *periDrainBulkCapacitance = drainPerimeter * CJSW ;
        *gateDrainBulkCapacitance = (w * WMLT + XW) * CJGATE;

        if (!sourceAreaGiven)
          if ((GEO == 0) || (GEO == 1))
            sourceArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            sourceArea = (HDIF * WMLT) * (w * WMLT + XW);
        else
          sourceArea = sourceArea * WMLT * WMLT;
        if (!sourcePerimeterGiven)
          if ((GEO == 0) || (GEO == 1))
            sourcePerimeter = 4.0 * (HDIF * WMLT) + (w * WMLT + XW);
          else
            sourcePerimeter = 2.0 * (HDIF * WMLT);
        else
          sourcePerimeter = sourcePerimeter * WMLT;
        *areaSourceBulkCapacitance = sourceArea * CJ;
        *periSourceBulkCapacitance = sourcePerimeter * CJSW;
        *gateSourceBulkCapacitance = (w * WMLT + XW) * CJGATE;

        break;

    case 11:
        drainArea = (w * WMLT + XW) * WMLT;
        drainPerimeter = (w * WMLT + XW);
        *areaDrainBulkCapacitance = drainArea * CJ;
        *periDrainBulkCapacitance = drainPerimeter * CJSW;
        *gateDrainBulkCapacitance = 0.0;

        sourceArea = (w * WMLT + XW) * WMLT;
        sourcePerimeter = (w * WMLT + XW);
        *areaSourceBulkCapacitance = sourceArea * CJ;
        *periSourceBulkCapacitance = sourcePerimeter * CJSW;
        *gateSourceBulkCapacitance = 0.0;

        break;

    case 12:
        if (CALCACM == 1) {
          if (!drainAreaGiven)
            drainArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            drainArea = drainArea * WMLT * WMLT;
          if (!drainPerimeterGiven)
            drainPerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
          else
            drainPerimeter = drainPerimeter * WMLT;
        }
        *areaDrainBulkCapacitance = drainArea * CJ;
        if (drainPerimeter > (w * WMLT + XW)) {
          *periDrainBulkCapacitance = (drainPerimeter - (w * WMLT + XW)) * CJSW;
          *gateDrainBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periDrainBulkCapacitance = 0.0;
          *gateDrainBulkCapacitance = drainPerimeter * CJGATE;
        }

        if (CALCACM == 1) {
          if (!sourceAreaGiven)
            sourceArea = 2.0 * (HDIF * WMLT) * (w * WMLT + XW);
          else
            sourceArea = sourceArea * WMLT * WMLT;
          if (!sourcePerimeterGiven)
            sourcePerimeter = 4.0 * (HDIF * WMLT) + 2.0 * (w * WMLT + XW);
          else
            sourcePerimeter = sourcePerimeter * WMLT;
        }
        *areaSourceBulkCapacitance = sourceArea * CJ;
        if (sourcePerimeter > (w * WMLT + XW)) {
          *periSourceBulkCapacitance = (sourcePerimeter - (w * WMLT + XW)) * CJSW;
          *gateSourceBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periSourceBulkCapacitance = 0.0;
          *gateSourceBulkCapacitance = sourcePerimeter * CJGATE;
        }

        break;

    case 13:
        drainArea = drainArea * WMLT * WMLT;
        drainPerimeter = drainPerimeter * WMLT;
        *areaDrainBulkCapacitance = drainArea * CJ;
        if (drainPerimeter > (w * WMLT + XW)) {
          *periDrainBulkCapacitance = (drainPerimeter - (w * WMLT + XW)) * CJSW;
          *gateDrainBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periDrainBulkCapacitance = 0.0;
          *gateDrainBulkCapacitance = drainPerimeter * CJGATE;
        }

        sourceArea = sourceArea * WMLT * WMLT;
        sourcePerimeter = sourcePerimeter * WMLT;
        *areaSourceBulkCapacitance = sourceArea * CJ;
        if (sourcePerimeter > (w * WMLT + XW)) {
          *periSourceBulkCapacitance = (sourcePerimeter - (w * WMLT + XW)) * CJSW;
          *gateSourceBulkCapacitance = (w * WMLT + XW) * CJGATE;
        } else {
          *periSourceBulkCapacitance = 0.0;
          *gateSourceBulkCapacitance = sourcePerimeter * CJGATE;
        }

        break;

    default:
        break;
    }

    return 0;
}

/* Compute the MOS overlap capacitances as functions of the device
 * terminal voltages
 *
 * PN 2002: As of ngspice this code is not used by any device. 
 */
void
DEVcmeyer(double vgs0,		/* initial voltage gate-source */
	  double vgd0,		/* initial voltage gate-drain */
	  double vgb0,		/* initial voltage gate-bulk */
	  double von0,
	  double vdsat0,
	  double vgs1,		/* final voltage gate-source */
	  double vgd1,		/* final voltage gate-drain */
	  double vgb1,		/* final voltage gate-bulk */
	  double covlgs,	/* overlap capacitance gate-source */
	  double covlgd,	/* overlap capacitance gate-drain */
	  double covlgb,	/* overlap capacitance gate-bulk */
	  double *cgs,
	  double *cgd,
	  double *cgb,
	  double phi,
	  double cox,
	  double von,
	  double vdsat)
{


    double vdb;
    double vdbsat;
    double vddif;
    double vddif1;
    double vddif2;
    double vgbt;

    *cgs = 0;
    *cgd = 0;
    *cgb = 0;

    vgbt = vgs1-von;
    if (vgbt <= -phi) {
        *cgb = cox;
    } else if (vgbt <= -phi/2) {
        *cgb = -vgbt*cox/phi;
    } else if (vgbt <= 0) {
        *cgb = -vgbt*cox/phi;
        *cgs = cox/(7.5e-1*phi)*vgbt+cox/1.5;
    } else {
        vdbsat = vdsat-(vgs1-vgb1);
        vdb = vgb1-vgd1;
        if (vdbsat <= vdb) {
            *cgs = cox/1.5;
        } else {
            vddif = 2.0*vdbsat-vdb;
            vddif1 = vdbsat-vdb-1.0e-12;
            vddif2 = vddif*vddif;
            *cgd = cox*(1.0-vdbsat*vdbsat/vddif2)/1.5;
            *cgs = cox*(1.0-vddif1*vddif1/vddif2)/1.5;
        }
    }

    vgbt = vgs0-von0;
    if (vgbt <= -phi) {
        *cgb += cox;
    } else if (vgbt <= -phi/2) {
        *cgb += -vgbt*cox/phi;
    } else if (vgbt <= 0) {
        *cgb += -vgbt*cox/phi;
        *cgs += cox/(7.5e-1*phi)*vgbt+cox/1.5;
    } else  {
        vdbsat = vdsat0-(vgs0-vgb0);
        vdb = vgb0-vgd0;
        if (vdbsat <= vdb) {
            *cgs += cox/1.5;
        } else {
            vddif = 2.0*vdbsat-vdb;
            vddif1 = vdbsat-vdb-1.0e-12;
            vddif2 = vddif*vddif;
            *cgd += cox*(1.0-vdbsat*vdbsat/vddif2)/1.5;
            *cgs += cox*(1.0-vddif1*vddif1/vddif2)/1.5;
        }
    }

    *cgs = *cgs *.5 + covlgs;
    *cgd = *cgd *.5 + covlgd;
    *cgb = *cgb *.5 + covlgb;
}

/* model according to
http://ltwiki.org/index.php5?title=Undocumented_LTspice#VDMOS:_Breakdown_and_Sub-threshold_Enhancements
*/
void
DevCapVDMOS(double vgd, double cgdmin,
            double cgdmax, double a, double cgs,
            double *capgs, double *capgd)
{
    double s = (cgdmax - cgdmin) / (1 + M_PI / 2);
    double y = cgdmax - s;
    if (vgd > 0)
        *capgd = 0.5 * (s * tanh(a * vgd) + y);
    else
        *capgd = 0.5 * (s * atan(a * vgd) + y);
    *capgs = 0.5 * cgs;
}

/* Compute the MOS overlap capacitances as functions of the device
 * terminal voltages 
 *
 * PN 2002: This is the Meyer model used by  MOS1 MOS2 MOS3 MOS6 and MOS9
 *          device models.
 */
void
DEVqmeyer(double vgs,		/* initial voltage gate-source */
	  double vgd,		/* initial voltage gate-drain */
	  double vgb,		/* initial voltage gate-bulk */
	  double von,
	  double vdsat,
	  double *capgs,	/* non-constant portion of g-s overlap
                                   capacitance */
	  double *capgd,	/* non-constant portion of g-d overlap
                                   capacitance */
	  double *capgb,	/* non-constant portion of g-b overlap
                                   capacitance */
	  double phi,
	  double cox)		/* oxide capactiance */
{
    double vds;
    double vddif;
    double vddif1;
    double vddif2;
    double vgst;

    NG_IGNORE(vgb);

#define MAGIC_VDS 0.025

    vgst = vgs-von;
    vdsat = MAX(vdsat, MAGIC_VDS);
    if (vgst <= -phi) {
        *capgb = cox/2;
        *capgs = 0;
        *capgd = 0;
    } else if (vgst <= -phi/2) {
        *capgb = -vgst*cox/(2*phi);
        *capgs = 0;
        *capgd = 0;
    } else if (vgst <= 0) {
        *capgb = -vgst*cox/(2*phi);
        *capgs = vgst*cox/(1.5*phi)+cox/3;
        vds = vgs-vgd;
        if (vds>=vdsat) {
           *capgd = 0;
        } else {
            vddif  = 2.0*vdsat-vds;
            vddif1 = vdsat-vds/*-1.0e-12*/;
            vddif2 = vddif*vddif;
            *capgd = *capgs*(1.0-vdsat*vdsat/vddif2);
            *capgs = *capgs*(1.0-vddif1*vddif1/vddif2);
        }
    } else  {
        vds = vgs-vgd;
        vdsat = MAX(vdsat, MAGIC_VDS);
        if (vdsat <= vds) {
            *capgs = cox/3;
            *capgd = 0;
            *capgb = 0;
        } else {
            vddif = 2.0*vdsat-vds;
            vddif1 = vdsat-vds/*-1.0e-12*/;
            vddif2 = vddif*vddif;
            *capgd = cox*(1.0-vdsat*vdsat/vddif2)/3;
            *capgs = cox*(1.0-vddif1*vddif1/vddif2)/3;
            *capgb = 0;
        }
    }

}


#ifdef notdef
/* XXX This is no longer used, apparently 
 * PN 2002: This is industrial archaelology
 */
void
DEVcap(CKTcircuit *ckt, double vgd, double vgs, double vgb, double covlgd,
       double covlgs, double covlgb, double capbd, double capbs, double cggb,
       double cgdb, double cgsb, double cbgb, double cbdb, double cbsb,
       double *gcggb, double *gcgdb, double *gcgsb, double *gcbgb, 
       double *gcbdb, double *gcbsb, double *gcdgb, double *gcddb, 
       double *gcdsb, double *gcsgb, double *gcsdb, double *gcssb,
       double qgate, double qchan, double qbulk, double *qdrn, double *qsrc,
       double xqc)

    /*
     *     compute equivalent conductances
     *     divide up the channel charge (1-xqc)/xqc to source and drain
     */
{

    double gcd;
    double gcdxd;
    double gcdxs;
    double gcg;
    double gcgxd;
    double gcgxs;
    double gcs;
    double gcsxd;
    double gcsxs;
    double qgb;
    double qgd;
    double qgs;

    gcg = (cggb+cbgb)*ckt->CKTag[1];
    gcd = (cgdb+cbdb)*ckt->CKTag[1];
    gcs = (cgsb+cbsb)*ckt->CKTag[1];
    gcgxd = -xqc*gcg;
    gcgxs = -(1-xqc)*gcg;
    gcdxd = -xqc*gcd;
    gcdxs = -(1-xqc)*gcd;
    gcsxd = -xqc*gcs;
    gcsxs = -(1-xqc)*gcs;
    *gcdgb = gcgxd-covlgd*ckt->CKTag[1];
    *gcddb = gcdxd+(capbd+covlgd)*ckt->CKTag[1];
    *gcdsb = gcsxd;
    *gcsgb = gcgxs-covlgs*ckt->CKTag[1];
    *gcsdb = gcdxs;
    *gcssb = gcsxs+(capbs+covlgs)*ckt->CKTag[1];
    *gcggb = (cggb+covlgd+covlgs+covlgb)*ckt->CKTag[1];
    *gcgdb = (cgdb-covlgd)*ckt->CKTag[1];
    *gcgsb = (cgsb-covlgs)*ckt->CKTag[1];
    *gcbgb = (cbgb-covlgb)*ckt->CKTag[1];
    *gcbdb = (cbdb-capbd)*ckt->CKTag[1];
    *gcbsb = (cbsb-capbs)*ckt->CKTag[1];
    /*
     *     compute total terminal charges
     */
    qgd = covlgd*vgd;
    qgs = covlgs*vgs;
    qgb = covlgb*vgb;
    qgate = qgate+qgd+qgs+qgb;
    qbulk = qbulk-qgb;
    *qdrn = xqc*qchan-qgd;
    *qsrc = (1-xqc)*qchan-qgs;
    /*
     *     finished
     */
}
#endif


/* Predict a value for the capacitor at loct by extrapolating from
 * previous values */
double
DEVpred(CKTcircuit *ckt, int loct)
{
    if (!ckt->CKTnewtrunc) {
        double xfact;

        xfact = ckt->CKTdelta / ckt->CKTdeltaOld[1];
        return(((1 + xfact) * *(ckt->CKTstate1 + loct)) -
            (xfact * *(ckt->CKTstate2 + loct)));
    }
    else {
        return 1.;
    }
}


/* SOA check printout used in DEVsoaCheck functions */
extern FILE *slogp;  /* soa log file ('--soa-log file' command line option) */

void
soa_printf(CKTcircuit *ckt, GENinstance *instance, const char *fmt, ...)
{
    FILE *fp = slogp ? slogp : stdout;

    va_list ap;

    va_start(ap, fmt);

    if (ckt->CKTmode & MODETRAN)
        fprintf(fp, "Instance: %s Model: %s Time: %g ",
                instance->GENname, instance->GENmodPtr->GENmodName, ckt->CKTtime);
    else
        fprintf(fp, "Instance: %s Model: %s ",
                instance->GENname, instance->GENmodPtr->GENmodName);

    vfprintf(fp, fmt, ap);

    va_end(ap);
}
