/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles
Modified: 2000 AlansFixes
Modified by Paolo Nenzi 2003, Dietmar Warning 2012 and Arpad Buermen 2025
**********/

/* load the diode structure with those pointers needed later
 * for fast matrix loading
 */

#include "ngspice/ngspice.h"
#include "ngspice/smpdefs.h"
#include "ngspice/cktdefs.h"
#include "diodefs.h"
#include "ngspice/sperror.h"
#include "ngspice/suffix.h"
#include "ngspice/fteext.h"
#include "ngspice/compatmode.h"

int
DIOsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt, int *states)
{
    DIOmodel *model = (DIOmodel*)inModel;
    DIOinstance *here;
    int error;
    CKTnode *tmp;
    double scale;

    if (!cp_getvar("scale", CP_REAL, &scale, 0))
        scale = 1;

    /*  loop through all the diode models */
    for( ; model != NULL; model = DIOnextModel(model)) {

        if(!model->DIOlevelGiven) {
            model->DIOlevel = 1;
        } else if (model->DIOlevel == 2) {
            SPfrontEnd->IFerrorf(ERR_FATAL,
                "%s: Diode model level 2 is not supported.", model->DIOmodName);
            return(E_BADPARM);
        }
        if(!model->DIOemissionCoeffGiven) {
            model->DIOemissionCoeff = 1;
        }
        if(!model->DIOsatCurGiven) {
            model->DIOsatCur = 1e-14;
        }
        if(!model->DIOsatSWCurGiven) {
            model->DIOsatSWCur = 0.0;
        }
        if(!model->DIOswEmissionCoeffGiven) {
            model->DIOswEmissionCoeff = 1;
        }
        if(!model->DIObreakdownCurrentGiven) {
            model->DIObreakdownCurrent = 1e-3;
        }
        if(!model->DIOjunctionPotGiven){
            model->DIOjunctionPot = 1;
        }
        if(!model->DIOgradingCoeffGiven) {
            model->DIOgradingCoeff = .5;
        }
        if(!model->DIOgradCoeffTemp1Given) {
            model->DIOgradCoeffTemp1 = 0.0;
        }
        if(!model->DIOgradCoeffTemp2Given) {
            model->DIOgradCoeffTemp2 = 0.0;
        }
        if(!model->DIOdepletionCapCoeffGiven) {
            model->DIOdepletionCapCoeff = .5;
        }
        if(!model->DIOdepletionSWcapCoeffGiven) {
            model->DIOdepletionSWcapCoeff = .5;
        }
        if(!model->DIOtransitTimeGiven) {
            model->DIOtransitTime = 0;
        }
        if(!model->DIOtranTimeTemp1Given) {
            model->DIOtranTimeTemp1 = 0.0;
        }
        if(!model->DIOtranTimeTemp2Given) {
            model->DIOtranTimeTemp2 = 0.0;
        }
        if(!model->DIOjunctionCapGiven) {
            if (newcompat.ps || newcompat.lt) {
                double cdiode = 0.;
                /* to improve convergence (sometimes) */
                if (cp_getvar("diode_cj0", CP_REAL, &cdiode, 0) && cdiode > 0) {
                    model->DIOjunctionCap = cdiode;
                    if (ft_ngdebug)
                        fprintf(stderr, "Diode junction capacitance in model %s set to %e F\n", model->gen.GENmodName, cdiode);
                }
                else
                    model->DIOjunctionCap = 0.0;
            }
            else {
                model->DIOjunctionCap = 0.0;
            }
        }
        if(!model->DIOjunctionSWCapGiven) {
            model->DIOjunctionSWCap = 0;
        }
        if(!model->DIOjunctionSWPotGiven){
            model->DIOjunctionSWPot = 1;
        }
        if(!model->DIOgradingSWCoeffGiven) {
            model->DIOgradingSWCoeff = .33;
        }
        if(model->DIOforwardKneeCurrentGiven) {
            if (model->DIOforwardKneeCurrent < ckt->CKTepsmin) {
                model->DIOforwardKneeCurrentGiven = FALSE;
                fprintf(stderr, "Warning: %s: IKF too small - model effect disabled!\n",
                model->DIOmodName);
            }
        }
        if(model->DIOreverseKneeCurrentGiven) {
            if (model->DIOreverseKneeCurrent < ckt->CKTepsmin) {
                model->DIOreverseKneeCurrentGiven = FALSE;
                fprintf(stderr, "Warning: %s: IKR too small - model effect disabled!\n",
                model->DIOmodName);
            }
        }
        if(model->DIOforwardSWKneeCurrentGiven) {
            if (model->DIOforwardSWKneeCurrent < ckt->CKTepsmin) {
                model->DIOforwardSWKneeCurrentGiven = FALSE;
                fprintf(stderr, "Warning: %s: IKP too small - model effect disabled!\n",
                model->DIOmodName);
            }
        }
        if(!model->DIObrkdEmissionCoeffGiven) {
            model->DIObrkdEmissionCoeff = model->DIOemissionCoeff;
        }
        if(!model->DIOtlevGiven) {
            model->DIOtlev = 0;
        }
        if(!model->DIOtlevcGiven) {
            model->DIOtlevc = 0;
        }
        if(!model->DIOactivationEnergyGiven) {
            if(model->DIOtlev == 2) {
                model->DIOactivationEnergy = 1.16;
            } else {
                model->DIOactivationEnergy = 1.11;
            }
        }
        if(!model->DIOfirstBGcorrFactorGiven) {
            model->DIOfirstBGcorrFactor = 7.02e-4;
        }
        if(!model->DIOsecndBGcorrFactorGiven) {
            model->DIOsecndBGcorrFactor = 1108.0;
        }
        if(!model->DIOsaturationCurrentExpGiven) {
            model->DIOsaturationCurrentExp = 3;
        }
        if(!model->DIOctaGiven) {
            model->DIOcta = 0.0;
        }
        if(!model->DIOctpGiven) {
            model->DIOctp = 0.0;
        }
        if(!model->DIOtpbGiven) {
            model->DIOtpb = 0.0;
        }
        if(!model->DIOtphpGiven) {
            model->DIOtphp = 0.0;
        }
        if(!model->DIOfNcoefGiven) {
            model->DIOfNcoef = 0.0;
        }
        if(!model->DIOfNexpGiven) {
            model->DIOfNexp = 1.0;
        }
        if(!model->DIOresistTemp1Given) {
            model->DIOresistTemp1 = 0.0;
        }
        if(!model->DIOresistTemp2Given) {
            model->DIOresistTemp2 = 0.0;
        }
        if(!model->DIOtcvGiven) {
            model->DIOtcv = 0.0;
        }
        if(!model->DIOareaGiven) {
            model->DIOarea = 1.0;
        }
        if(!model->DIOpjGiven) {
            model->DIOpj = 0.0;
        }
        if(!model->DIOtunSatCurGiven) {
            model->DIOtunSatCur = 0.0;
        }
        if(!model->DIOtunSatSWCurGiven) {
            model->DIOtunSatSWCur = 0.0;
        }
        if(!model->DIOtunEmissionCoeffGiven) {
            model->DIOtunEmissionCoeff = 30.0;
        }
        if(!model->DIOtunSaturationCurrentExpGiven) {
            model->DIOtunSaturationCurrentExp = 3.0;
        }
        if(!model->DIOtunEGcorrectionFactorGiven) {
            model->DIOtunEGcorrectionFactor = 1.0;
        }
        if(!model->DIOfv_maxGiven) {
            model->DIOfv_max = 1e99;
        }
        if(!model->DIObv_maxGiven) {
            model->DIObv_max = 1e99;
        }
        if(!model->DIOid_maxGiven) {
            model->DIOid_max = 1e99;
        }
        if(!model->DIOpd_maxGiven) {
            model->DIOpd_max = 1e99;
        }
        if(!model->DIOte_maxGiven) {
            model->DIOte_max = 1e99;
        }
        if(!model->DIOrecEmissionCoeffGiven) {
            model->DIOrecEmissionCoeff = 2;
        }
        if(!model->DIOrecSatCurGiven) {
            model->DIOrecSatCur = 1e-14;
        }
        if (!model->DIOsoftRevRecParamGiven) {
            model->DIOsoftRevRecParam = 0.0;
        }

        /* set lower limit of saturation current */
        if (model->DIOsatCur < ckt->CKTepsmin)
            model->DIOsatCur = ckt->CKTepsmin;

        if(!model->DIOnomTempGiven) {
            model->DIOnomTemp = ckt->CKTnomTemp;
        }

        if((!model->DIOresistGiven) || (model->DIOresist==0)) {
            if (newcompat.ps || newcompat.lt) {
                double rsdiode = 0.;
                /* to improve convergence (sometimes) */
                if (cp_getvar("diode_rser", CP_REAL, &rsdiode, 0) && rsdiode > 0) {
                    model->DIOconductance = 1./rsdiode;
                    model->DIOresist = rsdiode;
                    if (ft_ngdebug)
                        fprintf(stderr, "Diode series resistance in model %s set to %e Ohm\n", model->gen.GENmodName, rsdiode);
                }
                else
                    model->DIOconductance = 0.0;
            }
            else
                model->DIOconductance = 0.0;
        } else {
            model->DIOconductance = 1/model->DIOresist;
        }
        if((!model->DIOresistSWGiven) || (model->DIOresistSW==0)) {
            if (newcompat.ps || newcompat.lt) {
                double rsdiode = 0.;
                /* to improve convergence (sometimes) */
                if (cp_getvar("diode_rser", CP_REAL, &rsdiode, 0) && rsdiode > 0) {
                    model->DIOconductanceSW = 1./rsdiode;
                    model->DIOresistSW = rsdiode;
                    if (ft_ngdebug)
                        fprintf(stderr, "Diode sidewall series resistance in model %s set to %e Ohm\n", model->gen.GENmodName, rsdiode);
                }
                else
                    model->DIOconductanceSW = 0.0;
            }
            else
                model->DIOconductanceSW = 0.0;
        } else {
            model->DIOconductanceSW = 1/model->DIOresistSW;
        }

        if (!model->DIOrth0Given) {
            model->DIOrth0 = 0;
        }
        if (!model->DIOcth0Given) {
            model->DIOcth0 = 1e-5;
        }

        if(!model->DIOlengthMetalGiven) {
            model->DIOlengthMetal = 0.0;
        }
        if(!model->DIOlengthPolyGiven) {
            model->DIOlengthPoly = 0.0;
        }
        if(!model->DIOwidthMetalGiven) {
            model->DIOwidthMetal = 0.0;
        }
        if(!model->DIOwidthPolyGiven) {
            model->DIOwidthPoly = 0.0;
        }
        if(!model->DIOmetalOxideThickGiven) {
            model->DIOmetalOxideThick = 1e4; /* 10k Angstrom */
        }
        if(!model->DIOpolyOxideThickGiven) {
            model->DIOpolyOxideThick = 1e4; /* 10k Angstrom */
        }
        if(!model->DIOmetalMaskOffsetGiven) {
            model->DIOmetalMaskOffset = 0.0;
        }
        if(!model->DIOpolyMaskOffsetGiven) {
            model->DIOpolyMaskOffset = 0.0;
        }
        if(!model->DIOmaskOffsetGiven) {
            model->DIOmaskOffset = 0.0;
        }

        /* loop through all the instances of the model */
        for (here = DIOinstances(model); here != NULL ;
                here=DIOnextInstance(here)) {

            if(!here->DIOareaGiven) {
                if((!here->DIOwGiven) && (!here->DIOlGiven))  {
                    here->DIOarea = model->DIOarea;
                } else {
                    here->DIOarea = 1;
                }
            }
            if(!here->DIOpjGiven) {
                if((!here->DIOwGiven) && (!here->DIOlGiven))  {
                    here->DIOpj = model->DIOpj;
                } else {
                    here->DIOpj = 0;
                }
            }
            if(!here->DIOmGiven) {
                here->DIOm = 1;
            }

            here->DIOcmetal = 0.0;
            here->DIOcpoly = 0.0;
            if (model->DIOlevel == 3) {
                double wm, lm, wp, lp;
                if((here->DIOwGiven) && (here->DIOlGiven))  {
                    here->DIOarea = (here->DIOw+model->DIOmaskOffset) * (here->DIOl+model->DIOmaskOffset) * here->DIOm * scale * scale;
                    here->DIOpj = (2 * (here->DIOw+model->DIOmaskOffset) + 2 * (here->DIOl+model->DIOmaskOffset)) * here->DIOm * scale;
                }
                if (here->DIOwidthMetalGiven)
                    wm = here->DIOwidthMetal;
                else
                    wm = model->DIOwidthMetal;
                if (here->DIOlengthMetalGiven)
                    lm = here->DIOlengthMetal;
                else
                    lm = model->DIOlengthMetal;
                if (here->DIOwidthPolyGiven)
                    wp = here->DIOwidthPoly;
                else
                    wp = model->DIOwidthPoly;
                if (here->DIOlengthPolyGiven)
                    lp = here->DIOlengthPoly;
                else
                    lp = model->DIOlengthPoly;
                here->DIOcmetal = CONSTepsSiO2 / model->DIOmetalOxideThick  * here->DIOm
                                  * (wm * scale + model->DIOmetalMaskOffset)
                                  * (lm * scale + model->DIOmetalMaskOffset);
                here->DIOcpoly = CONSTepsSiO2 / model->DIOpolyOxideThick  * here->DIOm
                                  * (wp * scale + model->DIOpolyMaskOffset)
                                  * (lp * scale + model->DIOpolyMaskOffset);
            }
            here->DIOforwardKneeCurrent = model->DIOforwardKneeCurrent * here->DIOarea * here->DIOm;
            here->DIOreverseKneeCurrent = model->DIOreverseKneeCurrent * here->DIOarea * here->DIOm;
            here->DIOforwardSWKneeCurrent = model->DIOforwardSWKneeCurrent * here->DIOpj * here->DIOm;
            here->DIOjunctionCap = model->DIOjunctionCap * here->DIOarea * here->DIOm;
            here->DIOjunctionSWCap = model->DIOjunctionSWCap * here->DIOpj * here->DIOm;

            here->DIOstate = *states;
            *states += DIOnumStates;
            if(ckt->CKTsenInfo && (ckt->CKTsenInfo->SENmode & TRANSEN) ){
                *states += DIOnumSenStates * (ckt->CKTsenInfo->SENparms);
            }

            if(model->DIOresist == 0) {

                here->DIOposPrimeNode = here->DIOposNode;

            } else if(here->DIOposPrimeNode == 0) {

                CKTnode *tmpNode;
                IFuid tmpName;

                error = CKTmkVolt(ckt,&tmp,here->DIOname,"internal");
                if(error) return(error);
                here->DIOposPrimeNode = tmp->number;
                if (ckt->CKTcopyNodesets) {
                    if (CKTinst2Node(ckt,here,1,&tmpNode,&tmpName)==OK) {
                        if (tmpNode->nsGiven) {
                            tmp->nodeset=tmpNode->nodeset;
                            tmp->nsGiven=tmpNode->nsGiven;
                        }
                    }
                }
            }

            if(!model->DIOresistSWGiven) {

                here->DIOposSwPrimeNode = here->DIOposPrimeNode;

            } else if(here->DIOposSwPrimeNode == 0) {

                CKTnode *tmpNode;
                IFuid tmpName;

                error = CKTmkVolt(ckt,&tmp,here->DIOname,"internal_sw");
                if(error) return(error);
                here->DIOposSwPrimeNode = tmp->number;
                if (ckt->CKTcopyNodesets) {
                    if (CKTinst2Node(ckt,here,1,&tmpNode,&tmpName)==OK) {
                        if (tmpNode->nsGiven) {
                            tmp->nodeset=tmpNode->nodeset;
                            tmp->nsGiven=tmpNode->nsGiven;
                        }
                    }
                }
            }

            /* rev-rec */
            if (model->DIOsoftRevRecParamGiven && model->DIOsoftRevRecParam!=0 && model->DIOtransitTime!=0) {
                if(here->DIOqpNode == 0) {
                    error = CKTmkVolt(ckt, &tmp, here->DIOname, "qp");
                    if(error) return(error);
                    here->DIOqpNode = tmp->number;
                }
            } else {
                here->DIOqpNode = 0;
            }


            int selfheat = ((here->DIOtempNode > 0) && (here->DIOthermal) && (model->DIOrth0Given));

/* macro to make elements with built in test for out of memory */
#define TSTALLOC(ptr,first,second) \
do { if((here->ptr = SMPmakeElt(matrix, here->first, here->second)) == NULL){\
    return(E_NOMEM);\
} } while(0)

            TSTALLOC(DIOposPosPrimePtr,DIOposNode,DIOposPrimeNode);
            TSTALLOC(DIOnegPosPrimePtr,DIOnegNode,DIOposPrimeNode);
            TSTALLOC(DIOposPrimePosPtr,DIOposPrimeNode,DIOposNode);
            TSTALLOC(DIOposPrimeNegPtr,DIOposPrimeNode,DIOnegNode);
            TSTALLOC(DIOposPosPtr,DIOposNode,DIOposNode);
            TSTALLOC(DIOnegNegPtr,DIOnegNode,DIOnegNode);
            TSTALLOC(DIOposPrimePosPrimePtr,DIOposPrimeNode,DIOposPrimeNode);
            if(model->DIOresistSWGiven) {
                /* separate sidewall */
                TSTALLOC(DIOposPosSwPrimePtr,DIOposNode,DIOposSwPrimeNode);
                TSTALLOC(DIOnegPosSwPrimePtr,DIOnegNode,DIOposSwPrimeNode);
                TSTALLOC(DIOposSwPrimePosPtr,DIOposSwPrimeNode,DIOposNode);
                TSTALLOC(DIOposSwPrimeNegPtr,DIOposSwPrimeNode,DIOnegNode);
                TSTALLOC(DIOposSwPrimePosSwPrimePtr,DIOposSwPrimeNode,DIOposSwPrimeNode);
            }

            if (selfheat) {
                TSTALLOC(DIOtempPosPtr,      DIOtempNode,     DIOposNode);
                TSTALLOC(DIOtempPosPrimePtr, DIOtempNode,     DIOposPrimeNode);
                TSTALLOC(DIOtempNegPtr,      DIOtempNode,     DIOnegNode);
                TSTALLOC(DIOtempTempPtr,     DIOtempNode,     DIOtempNode);
                TSTALLOC(DIOposTempPtr,      DIOposNode,      DIOtempNode);
                TSTALLOC(DIOposPrimeTempPtr, DIOposPrimeNode, DIOtempNode);
                TSTALLOC(DIOnegTempPtr,      DIOnegNode,      DIOtempNode);
                if(model->DIOresistSWGiven) {
                    /* separate sidewall */
                    TSTALLOC(DIOtempPosSwPrimePtr, DIOtempNode, DIOposSwPrimeNode);
                    TSTALLOC(DIOposSwPrimeTempPtr, DIOposSwPrimeNode, DIOtempNode);
                }
            }
            
            /* rev-rec */
            if (model->DIOsoftRevRecParamGiven && model->DIOsoftRevRecParam!=0 && model->DIOtransitTime!=0) {
                TSTALLOC(DIOqpQpPtr      , DIOqpNode, DIOqpNode);
                TSTALLOC(DIOqpPosPrimePtr, DIOqpNode, DIOposPrimeNode);
                TSTALLOC(DIOqpNegPtr     , DIOqpNode, DIOnegNode);
                TSTALLOC(DIOposPrimeQpPtr, DIOposPrimeNode, DIOqpNode);
                TSTALLOC(DIOnegQpPtr,      DIOnegNode, DIOqpNode);
            }
        }
    }
    return(OK);
}

int
DIOunsetup(
    GENmodel *inModel,
    CKTcircuit *ckt)
{
    DIOmodel *model;
    DIOinstance *here;

    for (model = (DIOmodel *)inModel; model != NULL;
        model = DIOnextModel(model))
    {
        for (here = DIOinstances(model); here != NULL;
                here=DIOnextInstance(here))
        {

            if (here->DIOposPrimeNode > 0
              && here->DIOposPrimeNode != here->DIOposNode)
                CKTdltNNum(ckt, here->DIOposPrimeNode);
                here->DIOposPrimeNode = 0;

            if(model->DIOresistSWGiven) {
                /* separate sidewall */
                if (here->DIOposSwPrimeNode > 0
                  && here->DIOposSwPrimeNode != here->DIOposNode)
                    CKTdltNNum(ckt, here->DIOposSwPrimeNode);
                here->DIOposSwPrimeNode = 0;
            }

            /* rev-rec */
            if (here->DIOqpNode > 0)
                CKTdltNNum(ckt, here->DIOqpNode);
                here->DIOqpNode = 0;

        }
    }
    return OK;
}
