/**********
License              : 3-clause BSD
Spice3 Implementation: 2019-2020 Dietmar Warning, Markus Müller, Mario Krattenmacher
Model Author         : (Copyright 1993-2024) Michael Schroter
**********/

#include "ngspice/ngspice.h"
#include "hicum2defs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/iferrmsg.h"
#include "ngspice/noisedef.h"
#include "ngspice/suffix.h"

/*
 * HICUMnoise (mode, operation, firstModel, ckt, data, OnDens)
 *
 *    This routine names and evaluates all of the noise sources
 *    associated with HICUM's.  It starts with the model *firstModel and
 *    traverses all of its insts.  It then proceeds to any other models
 *    on the linked list.  The total output noise density generated by
 *    all of the HICUM's is summed with the variable "OnDens".
 */


int
HICUMnoise (int mode, int operation, GENmodel *genmodel, CKTcircuit *ckt, Ndata *data, double *OnDens)
{
    NOISEAN *job = (NOISEAN *) ckt->CKTcurJob;

    HICUMmodel *firstModel = (HICUMmodel *) genmodel;
    HICUMmodel *model;
    HICUMinstance *here;
    double tempOnoise;
    double tempInoise;
    double noizDens[HICUMNSRCS];
    double lnNdens[HICUMNSRCS];
    int i;
    double dtemp;

    double Ibbp_Vbbp;
    double Icic_Vcic;
    double Ibpbi_Vbpbi;
    double Ieie_Veie;
    double Isis_Vsis;

    /* define the names of the noise sources */

    static char *HICUMnNames[HICUMNSRCS] = {
        /* Note that we have to keep the order consistent with the
          strchr definitions in HICUMdefs.h */
        "_rcx",             /* thermal noise due to rcx */
        "_rbx",             /* thermal noise due to rbx */
        "_rbi",             /* thermal noise due to rbi */
        "_re",              /* thermal noise due to re */
        "_rsu",             /* thermal noise due to rsu */
        "_iavl",            /* shot noise due to iavl */
        "_ibci",            /* shot noise due to ibci */
        "_ibep",            /* shot noise due to ibep */
        "_ijbcx",           /* shot noise due to ijbcx */
        "_ijsc",            /* shot noise due to ijsc */
        "_it",              /* shot noise due to iciei */
        "_ibei",            /* shot noise due to ibiei */
        "_1overfbe",        /* flicker (1/f) noise ibe */
        "_1overfre",        /* flicker (1/f) noise re */
        ""                  /* total transistor noise */
    };

    for (model=firstModel; model != NULL; model=HICUMnextModel(model)) {
        for (here=HICUMinstances(model); here != NULL;
                here=HICUMnextInstance(here)) {

            // get all derivatives of branch DC currents
            if(model->HICUMrcxGiven && model->HICUMrcx != 0) {
                Icic_Vcic    = 1/here->HICUMrcx_t.rpart;
            } else {
                Icic_Vcic    = 0.0;
            }
            if(model->HICUMrbxGiven && model->HICUMrbx != 0) {
                Ibbp_Vbbp    = 1/here->HICUMrbx_t.rpart;
            } else {
                Ibbp_Vbbp    = 0.0;
            }
            if(model->HICUMreGiven && model->HICUMre != 0) {
                Ieie_Veie    = 1/here->HICUMre_t.rpart;
            } else {
                Ieie_Veie    = 0.0;
            }
            if(model->HICUMrsuGiven && model->HICUMrsu != 0) {
                Isis_Vsis    = 1/model->HICUMrsu*here->HICUMm;
            } else {
                Isis_Vsis    = 0.0;
            }
            if(here->HICUMrbi > 0) {
                Ibpbi_Vbpbi  = 1/here->HICUMrbi;
            } else {
                Ibpbi_Vbpbi  = 0.0;
            }

            switch (operation) {

            case N_OPEN:

                /* see if we have to to produce a summary report */
                /* if so, name all the noise generators */

                if (job->NStpsSm != 0) {
                    switch (mode) {

                    case N_DENS:
                        for (i=0; i < HICUMNSRCS; i++) {
                            NOISE_ADD_OUTVAR(ckt, data, "onoise_%s%s", here->HICUMname, HICUMnNames[i]);
                        }
                        break;

                    case INT_NOIZ:
                        for (i=0; i < HICUMNSRCS; i++) {
                            NOISE_ADD_OUTVAR(ckt, data, "onoise_total_%s%s", here->HICUMname, HICUMnNames[i]);
                            NOISE_ADD_OUTVAR(ckt, data, "inoise_total_%s%s", here->HICUMname, HICUMnNames[i]);
                        }
                        break;
                    }
                }
                break;

            case N_CALC:
                switch (mode) {

                case N_DENS:

                    if (here->HICUMtempGiven)
                        dtemp = here->HICUMtemp - ckt->CKTtemp + (model->HICUMtnom-CONSTCtoK);
                    else
                        dtemp = here->HICUMdtemp;

                    NevalSrcInstanceTemp(&noizDens[HICUMRCNOIZ],&lnNdens[HICUMRCNOIZ],
                                 ckt,THERMNOISE,here->HICUMcollCINode,here->HICUMcollNode,
                                 Icic_Vcic, dtemp);

                    NevalSrcInstanceTemp(&noizDens[HICUMRBNOIZ],&lnNdens[HICUMRBNOIZ],
                                 ckt,THERMNOISE,here->HICUMbaseNode,here->HICUMbaseBPNode,
                                 Ibbp_Vbbp, dtemp);

                    NevalSrcInstanceTemp(&noizDens[HICUMRBINOIZ],&lnNdens[HICUMRBINOIZ],
                                 ckt,THERMNOISE,here->HICUMbaseBPNode,here->HICUMbaseBINode,
                                 Ibpbi_Vbpbi, dtemp);

                    NevalSrcInstanceTemp(&noizDens[HICUMRENOIZ],&lnNdens[HICUMRENOIZ],
                                 ckt,THERMNOISE,here->HICUMemitEINode,here->HICUMemitNode,
                                 Ieie_Veie, dtemp);

                    NevalSrcInstanceTemp(&noizDens[HICUMRSNOIZ],&lnNdens[HICUMRSNOIZ],
                                 ckt,THERMNOISE,here->HICUMsubsSINode,here->HICUMsubsNode,
                                 Isis_Vsis, dtemp);


                    NevalSrc(&noizDens[HICUMIAVLNOIZ],&lnNdens[HICUMIAVLNOIZ],
                                 ckt,SHOTNOISE,here->HICUMcollCINode,here->HICUMbaseBINode,
                                 here->HICUMiavl);

                    NevalSrc(&noizDens[HICUMIBCINOIZ],&lnNdens[HICUMIBCINOIZ],
                                 ckt,SHOTNOISE,here->HICUMbaseBINode,here->HICUMcollCINode,
                                 *(ckt->CKTstate0 + here->HICUMibici)+here->HICUMiavl);

                    NevalSrc(&noizDens[HICUMIBEPNOIZ],&lnNdens[HICUMIBEPNOIZ],
                                 ckt,SHOTNOISE,here->HICUMbaseBPNode,here->HICUMemitEINode,
                                 *(ckt->CKTstate0 + here->HICUMibpei));

                    NevalSrc(&noizDens[HICUMIBCXNOIZ],&lnNdens[HICUMIBCXNOIZ],
                                 ckt,SHOTNOISE,here->HICUMbaseBPNode,here->HICUMcollCINode,
                                 *(ckt->CKTstate0 + here->HICUMibpci));

                    NevalSrc(&noizDens[HICUMIJSCNOIZ],&lnNdens[HICUMIJSCNOIZ],
                                 ckt,SHOTNOISE,here->HICUMsubsSINode,here->HICUMcollCINode,
                                 *(ckt->CKTstate0 + here->HICUMisici));

                    NevalSrc(&noizDens[HICUMITNOIZ],&lnNdens[HICUMITNOIZ],
                                 ckt,SHOTNOISE,here->HICUMcollCINode,here->HICUMemitEINode,
                                 *(ckt->CKTstate0 + here->HICUMit));

                    NevalSrc(&noizDens[HICUMIBEINOIZ],&lnNdens[HICUMIBEINOIZ],
                                 ckt,SHOTNOISE,here->HICUMbaseBINode,here->HICUMemitEINode,
                                 *(ckt->CKTstate0 + here->HICUMibiei));


                    if (model->HICUMcfbe == -1) {
                        NevalSrc(&noizDens[HICUMFLBENOIZ], NULL, ckt,
                                     N_GAIN,here->HICUMbaseBINode, here->HICUMemitEINode,
                                     (double)0.0);
                    } else {
                        NevalSrc(&noizDens[HICUMFLBENOIZ], NULL, ckt,
                                     N_GAIN,here->HICUMbaseBPNode, here->HICUMemitEINode,
                                     (double)0.0);
                    }
                    noizDens[HICUMFLBENOIZ] *= here->HICUMkf_scaled *
                                 exp(model->HICUMaf *
                                 log(MAX(fabs((*(ckt->CKTstate0 + here->HICUMibiei)+*(ckt->CKTstate0 + here->HICUMibpei))),N_MINLOG))) /
                                 data->freq;
                    lnNdens[HICUMFLBENOIZ] =
                                 log(MAX(noizDens[HICUMFLBENOIZ],N_MINLOG));

                    NevalSrc(&noizDens[HICUMFLRENOIZ], NULL, ckt,
                                 N_GAIN,here->HICUMemitEINode, here->HICUMemitNode,
                                 (double)0.0);
                    noizDens[HICUMFLRENOIZ] *= here->HICUMkfre_scaled *
                                 exp(model->HICUMafre *
                                 log(MAX(fabs(*(ckt->CKTstate0 + here->HICUMieie)),N_MINLOG))) /
                                 data->freq;
                    lnNdens[HICUMFLRENOIZ] =
                                 log(MAX(noizDens[HICUMFLRENOIZ],N_MINLOG));


                    noizDens[HICUMTOTNOIZ] = noizDens[HICUMRCNOIZ]   +
                                             noizDens[HICUMRBNOIZ]   +
                                             noizDens[HICUMRBINOIZ]  +
                                             noizDens[HICUMRENOIZ]   +
                                             noizDens[HICUMRSNOIZ]   +
                                             noizDens[HICUMIAVLNOIZ] +
                                             noizDens[HICUMIBCINOIZ] +
                                             noizDens[HICUMIBEPNOIZ] +
                                             noizDens[HICUMIBCXNOIZ] +
                                             noizDens[HICUMIJSCNOIZ] +
                                             noizDens[HICUMITNOIZ]   +
                                             noizDens[HICUMIBEINOIZ] +
                                             noizDens[HICUMFLBENOIZ] +
                                             noizDens[HICUMFLRENOIZ];


                    lnNdens[HICUMTOTNOIZ] =
                                 log(noizDens[HICUMTOTNOIZ]);

                    *OnDens += noizDens[HICUMTOTNOIZ];

                    if (data->delFreq == 0.0) {

                        /* if we haven't done any previous integration, we need to */
                        /* initialize our "history" variables                      */

                        for (i=0; i < HICUMNSRCS; i++) {
                            here->HICUMnVar[LNLSTDENS][i] = lnNdens[i];
                        }

                        /* clear out our integration variables if it's the first pass */

                        if (data->freq == job->NstartFreq) {
                            for (i=0; i < HICUMNSRCS; i++) {
                                here->HICUMnVar[OUTNOIZ][i] = 0.0;
                                here->HICUMnVar[INNOIZ][i] = 0.0;
                            }
                        }
                    } else {   /* data->delFreq != 0.0 (we have to integrate) */

/* In order to get the best curve fit, we have to integrate each component separately */

                        for (i=0; i < HICUMNSRCS; i++) {
                            if (i != HICUMTOTNOIZ) {
                                tempOnoise = Nintegrate(noizDens[i], lnNdens[i],
                                      here->HICUMnVar[LNLSTDENS][i], data);
                                tempInoise = Nintegrate(noizDens[i] * data->GainSqInv ,
                                      lnNdens[i] + data->lnGainInv,
                                      here->HICUMnVar[LNLSTDENS][i] + data->lnGainInv,
                                      data);
                                here->HICUMnVar[LNLSTDENS][i] = lnNdens[i];
                                data->outNoiz += tempOnoise;
                                data->inNoise += tempInoise;
                                if (job->NStpsSm != 0) {
                                    here->HICUMnVar[OUTNOIZ][i] += tempOnoise;
                                    here->HICUMnVar[OUTNOIZ][HICUMTOTNOIZ] += tempOnoise;
                                    here->HICUMnVar[INNOIZ][i] += tempInoise;
                                    here->HICUMnVar[INNOIZ][HICUMTOTNOIZ] += tempInoise;
                                }
                            }
                        }
                    }
                    if (data->prtSummary) {
                        for (i=0; i < HICUMNSRCS; i++) {     /* print a summary report */
                            data->outpVector[data->outNumber++] = noizDens[i];
                        }
                    }
                    break;

                case INT_NOIZ:        /* already calculated, just output */
                    if (job->NStpsSm != 0) {
                        for (i=0; i < HICUMNSRCS; i++) {
                            data->outpVector[data->outNumber++] = here->HICUMnVar[OUTNOIZ][i];
                            data->outpVector[data->outNumber++] = here->HICUMnVar[INNOIZ][i];
                        }
                    }    /* if */
                    break;
                }    /* switch (mode) */
                break;

            case N_CLOSE:
                return (OK);         /* do nothing, the main calling routine will close */
                break;               /* the plots */
            }    /* switch (operation) */
        }    /* for here */
    }    /* for model */

return(OK);
}
