From f5f8602d3a59bde5a20545a50bd718e7e2011b97 Mon Sep 17 00:00:00 2001 From: pnenzi Date: Mon, 11 Aug 2003 19:25:28 +0000 Subject: [PATCH] Cider simulator (simulator routines) Import. --- src/ciderlib/Makefile.am | 7 + src/ciderlib/input/Makefile.am | 33 + src/ciderlib/input/bdryset.c | 208 +++++ src/ciderlib/input/boundary.c | 157 ++++ src/ciderlib/input/cards.c | 46 + src/ciderlib/input/contact.c | 130 +++ src/ciderlib/input/contset.c | 87 ++ src/ciderlib/input/domain.c | 133 +++ src/ciderlib/input/domnset.c | 207 +++++ src/ciderlib/input/doping.c | 286 ++++++ src/ciderlib/input/dopset.c | 393 +++++++++ src/ciderlib/input/elctset.c | 177 ++++ src/ciderlib/input/electrod.c | 118 +++ src/ciderlib/input/material.c | 305 +++++++ src/ciderlib/input/matlset.c | 180 ++++ src/ciderlib/input/mesh.c | 148 ++++ src/ciderlib/input/meshset.c | 1302 +++++++++++++++++++++++++++ src/ciderlib/input/method.c | 114 +++ src/ciderlib/input/mobility.c | 210 +++++ src/ciderlib/input/mobset.c | 157 ++++ src/ciderlib/input/models.c | 133 +++ src/ciderlib/input/modlset.c | 92 ++ src/ciderlib/input/options.c | 163 ++++ src/ciderlib/input/outpset.c | 148 ++++ src/ciderlib/input/output.c | 241 +++++ src/ciderlib/input/readme | 6 + src/ciderlib/notes | 9 + src/ciderlib/oned/Makefile.am | 23 + src/ciderlib/oned/notes | 36 + src/ciderlib/oned/oneadmit.c | 742 ++++++++++++++++ src/ciderlib/oned/oneaval.c | 172 ++++ src/ciderlib/oned/onecond.c | 245 ++++++ src/ciderlib/oned/onecont.c | 657 ++++++++++++++ src/ciderlib/oned/oneddefs.h | 29 + src/ciderlib/oned/onedest.c | 74 ++ src/ciderlib/oned/onedext.h | 121 +++ src/ciderlib/oned/onedopng.c | 166 ++++ src/ciderlib/oned/onefreez.c | 121 +++ src/ciderlib/oned/onemesh.c | 371 ++++++++ src/ciderlib/oned/onepoiss.c | 192 ++++ src/ciderlib/oned/oneprint.c | 534 ++++++++++++ src/ciderlib/oned/oneproj.c | 348 ++++++++ src/ciderlib/oned/oneread.c | 94 ++ src/ciderlib/oned/onesetup.c | 233 +++++ src/ciderlib/oned/onesolve.c | 1084 +++++++++++++++++++++++ src/ciderlib/oned/readme | 13 + src/ciderlib/support/Makefile.am | 22 + src/ciderlib/support/database.c | 72 ++ src/ciderlib/support/devprint.c | 74 ++ src/ciderlib/support/geominfo.c | 130 +++ src/ciderlib/support/globals.c | 161 ++++ src/ciderlib/support/integset.c | 155 ++++ src/ciderlib/support/integuse.c | 302 +++++++ src/ciderlib/support/logfile.c | 41 + src/ciderlib/support/mater.c | 404 +++++++++ src/ciderlib/support/misc.c | 161 ++++ src/ciderlib/support/mobil.c | 421 +++++++++ src/ciderlib/support/readme | 9 + src/ciderlib/support/recomb.c | 50 ++ src/ciderlib/support/suprem.c | 210 +++++ src/ciderlib/support/suprmitf.c | 357 ++++++++ src/ciderlib/twod/Makefile.am | 31 + src/ciderlib/twod/readme | 15 + src/ciderlib/twod/twoadmit.c | 1316 ++++++++++++++++++++++++++++ src/ciderlib/twod/twoaval.c | 208 +++++ src/ciderlib/twod/twocond.c | 612 +++++++++++++ src/ciderlib/twod/twocont.c | 1052 ++++++++++++++++++++++ src/ciderlib/twod/twocurr.c | 183 ++++ src/ciderlib/twod/twoddefs.h | 57 ++ src/ciderlib/twod/twodest.c | 76 ++ src/ciderlib/twod/twodext.h | 169 ++++ src/ciderlib/twod/twodopng.c | 234 +++++ src/ciderlib/twod/twoelect.c | 180 ++++ src/ciderlib/twod/twofield.c | 125 +++ src/ciderlib/twod/twomesh.c | 626 +++++++++++++ src/ciderlib/twod/twomobdv.c | 1399 ++++++++++++++++++++++++++++++ src/ciderlib/twod/twomobfn.c | 411 +++++++++ src/ciderlib/twod/twomobil.c | 131 +++ src/ciderlib/twod/twoncont.c | 889 +++++++++++++++++++ src/ciderlib/twod/twopcont.c | 888 +++++++++++++++++++ src/ciderlib/twod/twopoiss.c | 283 ++++++ src/ciderlib/twod/twoprint.c | 561 ++++++++++++ src/ciderlib/twod/twoproj.c | 679 +++++++++++++++ src/ciderlib/twod/tworead.c | 116 +++ src/ciderlib/twod/twosetbc.c | 94 ++ src/ciderlib/twod/twosetup.c | 318 +++++++ src/ciderlib/twod/twosolve.c | 1197 +++++++++++++++++++++++++ 87 files changed, 24864 insertions(+) create mode 100644 src/ciderlib/Makefile.am create mode 100644 src/ciderlib/input/Makefile.am create mode 100644 src/ciderlib/input/bdryset.c create mode 100644 src/ciderlib/input/boundary.c create mode 100644 src/ciderlib/input/cards.c create mode 100644 src/ciderlib/input/contact.c create mode 100644 src/ciderlib/input/contset.c create mode 100644 src/ciderlib/input/domain.c create mode 100644 src/ciderlib/input/domnset.c create mode 100644 src/ciderlib/input/doping.c create mode 100644 src/ciderlib/input/dopset.c create mode 100644 src/ciderlib/input/elctset.c create mode 100644 src/ciderlib/input/electrod.c create mode 100644 src/ciderlib/input/material.c create mode 100644 src/ciderlib/input/matlset.c create mode 100644 src/ciderlib/input/mesh.c create mode 100644 src/ciderlib/input/meshset.c create mode 100644 src/ciderlib/input/method.c create mode 100644 src/ciderlib/input/mobility.c create mode 100644 src/ciderlib/input/mobset.c create mode 100644 src/ciderlib/input/models.c create mode 100644 src/ciderlib/input/modlset.c create mode 100644 src/ciderlib/input/options.c create mode 100644 src/ciderlib/input/outpset.c create mode 100644 src/ciderlib/input/output.c create mode 100644 src/ciderlib/input/readme create mode 100644 src/ciderlib/notes create mode 100644 src/ciderlib/oned/Makefile.am create mode 100644 src/ciderlib/oned/notes create mode 100644 src/ciderlib/oned/oneadmit.c create mode 100644 src/ciderlib/oned/oneaval.c create mode 100644 src/ciderlib/oned/onecond.c create mode 100644 src/ciderlib/oned/onecont.c create mode 100644 src/ciderlib/oned/oneddefs.h create mode 100644 src/ciderlib/oned/onedest.c create mode 100644 src/ciderlib/oned/onedext.h create mode 100644 src/ciderlib/oned/onedopng.c create mode 100644 src/ciderlib/oned/onefreez.c create mode 100644 src/ciderlib/oned/onemesh.c create mode 100644 src/ciderlib/oned/onepoiss.c create mode 100644 src/ciderlib/oned/oneprint.c create mode 100644 src/ciderlib/oned/oneproj.c create mode 100644 src/ciderlib/oned/oneread.c create mode 100644 src/ciderlib/oned/onesetup.c create mode 100644 src/ciderlib/oned/onesolve.c create mode 100644 src/ciderlib/oned/readme create mode 100644 src/ciderlib/support/Makefile.am create mode 100644 src/ciderlib/support/database.c create mode 100644 src/ciderlib/support/devprint.c create mode 100644 src/ciderlib/support/geominfo.c create mode 100644 src/ciderlib/support/globals.c create mode 100644 src/ciderlib/support/integset.c create mode 100644 src/ciderlib/support/integuse.c create mode 100644 src/ciderlib/support/logfile.c create mode 100644 src/ciderlib/support/mater.c create mode 100644 src/ciderlib/support/misc.c create mode 100644 src/ciderlib/support/mobil.c create mode 100644 src/ciderlib/support/readme create mode 100644 src/ciderlib/support/recomb.c create mode 100644 src/ciderlib/support/suprem.c create mode 100644 src/ciderlib/support/suprmitf.c create mode 100644 src/ciderlib/twod/Makefile.am create mode 100644 src/ciderlib/twod/readme create mode 100644 src/ciderlib/twod/twoadmit.c create mode 100644 src/ciderlib/twod/twoaval.c create mode 100644 src/ciderlib/twod/twocond.c create mode 100644 src/ciderlib/twod/twocont.c create mode 100644 src/ciderlib/twod/twocurr.c create mode 100644 src/ciderlib/twod/twoddefs.h create mode 100644 src/ciderlib/twod/twodest.c create mode 100644 src/ciderlib/twod/twodext.h create mode 100644 src/ciderlib/twod/twodopng.c create mode 100644 src/ciderlib/twod/twoelect.c create mode 100644 src/ciderlib/twod/twofield.c create mode 100644 src/ciderlib/twod/twomesh.c create mode 100644 src/ciderlib/twod/twomobdv.c create mode 100644 src/ciderlib/twod/twomobfn.c create mode 100644 src/ciderlib/twod/twomobil.c create mode 100644 src/ciderlib/twod/twoncont.c create mode 100644 src/ciderlib/twod/twopcont.c create mode 100644 src/ciderlib/twod/twopoiss.c create mode 100644 src/ciderlib/twod/twoprint.c create mode 100644 src/ciderlib/twod/twoproj.c create mode 100644 src/ciderlib/twod/tworead.c create mode 100644 src/ciderlib/twod/twosetbc.c create mode 100644 src/ciderlib/twod/twosetup.c create mode 100644 src/ciderlib/twod/twosolve.c diff --git a/src/ciderlib/Makefile.am b/src/ciderlib/Makefile.am new file mode 100644 index 000000000..b82862052 --- /dev/null +++ b/src/ciderlib/Makefile.am @@ -0,0 +1,7 @@ +## Process this file with automake + +EXTRA_DIST = notes + +SUBDIRS = input support oned twod + +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/ciderlib/input/Makefile.am b/src/ciderlib/input/Makefile.am new file mode 100644 index 000000000..4afc12296 --- /dev/null +++ b/src/ciderlib/input/Makefile.am @@ -0,0 +1,33 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libciderinput.a + +libciderinput_a_SOURCES = \ + bdryset.c \ + boundary.c \ + cards.c \ + contact.c \ + contset.c \ + domain.c \ + domnset.c \ + doping.c \ + dopset.c \ + elctset.c \ + electrod.c \ + material.c \ + matlset.c \ + mesh.c \ + meshset.c \ + method.c \ + mobility.c \ + mobset.c \ + models.c \ + modlset.c \ + options.c \ + outpset.c \ + output.c + + +EXTRA_DIST = readme +INCLUDES = -I$(top_srcdir)/src/include +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/ciderlib/input/bdryset.c b/src/ciderlib/input/bdryset.c new file mode 100644 index 000000000..7c3b86d1c --- /dev/null +++ b/src/ciderlib/input/bdryset.c @@ -0,0 +1,208 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "bdrydefs.h" +#include "meshext.h" +#include "gendev.h" +#include "sperror.h" + +extern int BDRYcheck( BDRYcard *, DOMNdomain * ); +extern int BDRYsetup( BDRYcard *, MESHcoord *, MESHcoord *, DOMNdomain * ); + + +/* + * Name: BDRYcheck + * Purpose: checks a list of BDRYcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +BDRYcheck(BDRYcard *cardList, DOMNdomain *domnList) +{ + BDRYcard *card; + DOMNdomain *domn; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(BDRYcard); card = card->BDRYnextCard ) { + cardNum++; + if (card->BDRYxLowGiven && card->BDRYixLowGiven) { + sprintf( ebuf, + "boundary card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->BDRYxLowGiven = FALSE; + } + if (card->BDRYxHighGiven && card->BDRYixHighGiven) { + sprintf( ebuf, + "boundary card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->BDRYxHighGiven = FALSE; + } + if (card->BDRYyLowGiven && card->BDRYiyLowGiven) { + sprintf( ebuf, + "boundary card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->BDRYyLowGiven = FALSE; + } + if (card->BDRYyHighGiven && card->BDRYiyHighGiven) { + sprintf( ebuf, + "boundary card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->BDRYyHighGiven = FALSE; + } + if (!card->BDRYdomainGiven) { + sprintf( ebuf, + "boundary card %d is missing a domain index", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } else { + /* Make sure the domain exists */ + for ( domn = domnList; domn != NIL(DOMNdomain); domn = domn->next ) { + if ( card->BDRYdomain == domn->id ) { + break; + } + } + if (domn == NIL(DOMNdomain)) { + sprintf( ebuf, + "boundary card %d specifies a non-existent domain", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + + if (!card->BDRYneighborGiven) { + card->BDRYneighbor = card->BDRYdomain; + } else { + /* Make sure the neighbor exists */ + for ( domn = domnList; domn != NIL(DOMNdomain); domn = domn->next ) { + if ( card->BDRYneighbor == domn->id ) { + break; + } + } + if (domn == NIL(DOMNdomain)) { + sprintf( ebuf, + "interface card %d specifies a non-existent domain", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + + if (!card->BDRYqfGiven) { + card->BDRYqf = 0.0; + } + if (!card->BDRYsnGiven) { + card->BDRYsn = 0.0; + } + if (!card->BDRYspGiven) { + card->BDRYsp = 0.0; + } + if (!card->BDRYlayerGiven) { + card->BDRYlayer = 0.0; + } + +/* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + + +/* + * Name: BDRYsetup + * Purpose: Checks BDRY cards and then sets the indices + * Formals: cardList: list of cards to setup, returns with indices set + * xMeshList: list of coordinates in the x mesh + * yMeshList: list of coordinates in the y mesh + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: BDRYcheck + */ +int +BDRYsetup(BDRYcard *cardList, MESHcoord *xMeshList, MESHcoord *yMeshList,DOMNdomain *domnList) +{ + BDRYcard *card; + int ixMin, ixMax, iyMin, iyMax; + int cardNum = 0; + int error; + char ebuf[512]; /* error message buffer */ + +/* Check the card list */ + if ((error = BDRYcheck( cardList, domnList ))) return( error ); + +/* Find the limits on the indices */ + MESHiBounds( xMeshList, &ixMin, &ixMax ); + MESHiBounds( yMeshList, &iyMin, &iyMax ); + + error = OK; + for ( card = cardList; card != NIL(BDRYcard); card = card->BDRYnextCard ) { + cardNum++; + + if (card->BDRYixLowGiven) { + card->BDRYixLow = MAX(card->BDRYixLow, ixMin); + } + else if (card->BDRYxLowGiven) { + card->BDRYixLow = MESHlocate( xMeshList, card->BDRYxLow ); + } + else { + card->BDRYixLow = ixMin; + } + if (card->BDRYixHighGiven) { + card->BDRYixHigh = MIN(card->BDRYixHigh, ixMax); + } + else if (card->BDRYxHighGiven) { + card->BDRYixHigh = MESHlocate( xMeshList, card->BDRYxHigh ); + } + else { + card->BDRYixHigh = ixMax; + } + if (card->BDRYixLow > card->BDRYixHigh) { + sprintf( ebuf, + "boundary card %d has low x index (%d) > high x index (%d)", + cardNum, card->BDRYixHigh, card->BDRYixLow ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + if (card->BDRYiyLowGiven) { + card->BDRYiyLow = MAX(card->BDRYiyLow, iyMin); + } + else if (card->BDRYyLowGiven) { + card->BDRYiyLow = MESHlocate( yMeshList, card->BDRYyLow ); + } + else { + card->BDRYiyLow = iyMin; + } + if (card->BDRYiyHighGiven) { + card->BDRYiyHigh = MIN(card->BDRYiyHigh, iyMax); + } + else if (card->BDRYyHighGiven) { + card->BDRYiyHigh = MESHlocate( yMeshList, card->BDRYyHigh ); + } + else { + card->BDRYiyHigh = iyMax; + } + if (card->BDRYiyLow > card->BDRYiyHigh) { + sprintf( ebuf, + "boundary card %d has low y index (%d) > high y index (%d)", + cardNum, card->BDRYiyHigh, card->BDRYiyLow ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + return( error ); +} diff --git a/src/ciderlib/input/boundary.c b/src/ciderlib/input/boundary.c new file mode 100644 index 000000000..1ef054b6d --- /dev/null +++ b/src/ciderlib/input/boundary.c @@ -0,0 +1,157 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "bdrydefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +#define UM_TO_CM 1.0e-4 + +extern int BDRYnewCard(void**,void*); +extern int BDRYparam(int,IFvalue*,void*); + +IFparm BDRYpTable[] = { + IP("domain", BDRY_DOMAIN, IF_INTEGER, "Primary domain"), + IP("neighbor",BDRY_NEIGHBOR, IF_INTEGER, "Neighboring domain"), + IP("x.low", BDRY_X_LOW, IF_REAL, "Location of left edge"), + IP("x.high", BDRY_X_HIGH, IF_REAL, "Location of right edge"), + IP("y.low", BDRY_Y_LOW, IF_REAL, "Location of top edge"), + IP("y.high", BDRY_Y_HIGH, IF_REAL, "Location of bottom edge"), + IP("ix.low", BDRY_IX_LOW, IF_INTEGER, "Index of left edge"), + IP("ix.high", BDRY_IX_HIGH, IF_INTEGER, "Index of right edge"), + IP("iy.low", BDRY_IY_LOW, IF_INTEGER, "Index of top edge"), + IP("iy.high", BDRY_IY_HIGH, IF_INTEGER, "Index of bottom edge"), + IP("nss", BDRY_QF, IF_REAL, "Fixed charge"), + IP("qss", BDRY_QF, IF_REAL, "Fixed charge"), + IP("qf", BDRY_QF, IF_REAL, "Fixed charge"), + IP("sn", BDRY_SN, IF_REAL, "Electron recomb velocity"), + IP("srvn", BDRY_SN, IF_REAL, "Electron recomb velocity"), + IP("vsrfn", BDRY_SN, IF_REAL, "Electron recomb velocity"), + IP("sp", BDRY_SP, IF_REAL, "Hole recomb velocity"), + IP("srvp", BDRY_SP, IF_REAL, "Hole recomb velocity"), + IP("vsrfp", BDRY_SP, IF_REAL, "Hole recomb velocity"), + IP("layer.width",BDRY_LAYER, IF_REAL, "Width of surface charge layer") +}; + +IFcardInfo BDRYinfo = { + "boundary", + "Specify properties of a domain boundary", + NUMELEMS(BDRYpTable), + BDRYpTable, + + BDRYnewCard, + BDRYparam, + NULL +}; +IFcardInfo INTFinfo = { + "interface", + "Specify properties of an interface between two domains", + NUMELEMS(BDRYpTable), + BDRYpTable, + + BDRYnewCard, + BDRYparam, + NULL +}; + +int +BDRYnewCard(void **inCard, void *inModel) +{ + BDRYcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( BDRYcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->BDRYnextCard = (BDRYcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENboundaries; + if (!tmpCard) { /* First in list */ + model->GENboundaries = newCard; + } else { + /* Go to end of list */ + while (tmpCard->BDRYnextCard) tmpCard = tmpCard->BDRYnextCard; + /* And add new card */ + tmpCard->BDRYnextCard = newCard; + } + return(OK); +} + +int +BDRYparam(int param, IFvalue *value, void *inCard) +{ + BDRYcard *card = (BDRYcard *)inCard; + + switch (param) { + case BDRY_DOMAIN: + card->BDRYdomain = value->iValue; + card->BDRYdomainGiven = TRUE; + break; + case BDRY_NEIGHBOR: + card->BDRYneighbor = value->iValue; + card->BDRYneighborGiven = TRUE; + break; + case BDRY_X_LOW: + card->BDRYxLow = value->rValue * UM_TO_CM; + card->BDRYxLowGiven = TRUE; + break; + case BDRY_X_HIGH: + card->BDRYxHigh = value->rValue * UM_TO_CM; + card->BDRYxHighGiven = TRUE; + break; + case BDRY_Y_LOW: + card->BDRYyLow = value->rValue * UM_TO_CM; + card->BDRYyLowGiven = TRUE; + break; + case BDRY_Y_HIGH: + card->BDRYyHigh = value->rValue * UM_TO_CM; + card->BDRYyHighGiven = TRUE; + break; + case BDRY_IX_LOW: + card->BDRYixLow = value->iValue; + card->BDRYixLowGiven = TRUE; + break; + case BDRY_IX_HIGH: + card->BDRYixHigh = value->iValue; + card->BDRYixHighGiven = TRUE; + break; + case BDRY_IY_LOW: + card->BDRYiyLow = value->iValue; + card->BDRYiyLowGiven = TRUE; + break; + case BDRY_IY_HIGH: + card->BDRYiyHigh = value->iValue; + card->BDRYiyHighGiven = TRUE; + break; + case BDRY_QF: + card->BDRYqf = value->rValue; + card->BDRYqfGiven = TRUE; + break; + case BDRY_SN: + card->BDRYsn = value->rValue; + card->BDRYsnGiven = TRUE; + break; + case BDRY_SP: + card->BDRYsp = value->rValue; + card->BDRYspGiven = TRUE; + break; + case BDRY_LAYER: + card->BDRYlayer = value->rValue; + card->BDRYlayerGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/cards.c b/src/ciderlib/input/cards.c new file mode 100644 index 000000000..8eafdc5d0 --- /dev/null +++ b/src/ciderlib/input/cards.c @@ -0,0 +1,46 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" + +extern IFcardInfo CONTinfo; +extern IFcardInfo DOPinfo; +extern IFcardInfo ELCTinfo; +extern IFcardInfo BDRYinfo; +extern IFcardInfo INTFinfo; +extern IFcardInfo XMSHinfo; +extern IFcardInfo YMSHinfo; +extern IFcardInfo METHinfo; +extern IFcardInfo MOBinfo; +extern IFcardInfo MODLinfo; +extern IFcardInfo PHYSinfo; +extern IFcardInfo MATLinfo; +extern IFcardInfo DOMNinfo; +extern IFcardInfo REGNinfo; +extern IFcardInfo OPTNinfo; +extern IFcardInfo OUTPinfo; + +IFcardInfo *INPcardTab[] = { + &CONTinfo, + &DOPinfo, + &ELCTinfo, + &BDRYinfo, + &INTFinfo, + &XMSHinfo, + &YMSHinfo, + &METHinfo, + &MOBinfo, + &MODLinfo, + &PHYSinfo, + &MATLinfo, + &DOMNinfo, + ®Ninfo, + &OPTNinfo, + &OUTPinfo +}; + +int INPnumCards = sizeof(INPcardTab)/sizeof(IFcardInfo*); diff --git a/src/ciderlib/input/contact.c b/src/ciderlib/input/contact.c new file mode 100644 index 000000000..a4a37be31 --- /dev/null +++ b/src/ciderlib/input/contact.c @@ -0,0 +1,130 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "contdefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int CONTnewCard(void**,void*); +extern int CONTparam(int,IFvalue*,void*); + + +IFparm CONTpTable[] = { + IP("neutral", CONT_NEUTRAL, IF_FLAG, "Charge neutral"), + IP("aluminum", CONT_ALUMINUM, IF_FLAG, "Aluminum contact"), + IP("p.polysilicon", CONT_P_POLY, IF_FLAG, "P poly contact"), + IP("n.polysilicon", CONT_N_POLY, IF_FLAG, "N poly contact"), + IP("workfunction", CONT_WORKFUN, IF_REAL, "Metal work function"), + IP("number", CONT_NUMBER, IF_INTEGER, "Electrode ID number") +}; + +IFcardInfo CONTinfo = { + "contact", + "Properties of a contact to the device", + NUMELEMS(CONTpTable), + CONTpTable, + + CONTnewCard, + CONTparam, + NULL +}; + +int +CONTnewCard(void **inCard, void *inModel) +{ + CONTcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( CONTcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->CONTnextCard = (CONTcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENcontacts; + if (!tmpCard) { /* First in list */ + model->GENcontacts = newCard; + } else { + /* Go to end of list */ + while (tmpCard->CONTnextCard) tmpCard = tmpCard->CONTnextCard; + /* And add new card */ + tmpCard->CONTnextCard = newCard; + } + return(OK); +} + +int +CONTparam(int param, IFvalue *value, void *inCard) +{ + CONTcard *card = (CONTcard *)inCard; + + switch (param) { + case CONT_NEUTRAL: + if ( value->iValue ) { + card->CONTtype = CONT_NEUTRAL; + card->CONTtypeGiven = TRUE; + } else { + if ( card->CONTtype == CONT_NEUTRAL ) { + card->CONTtype = -1; + card->CONTtypeGiven = FALSE; + } + } + break; + case CONT_ALUMINUM: + if ( value->iValue ) { + card->CONTtype = CONT_ALUMINUM; + card->CONTtypeGiven = TRUE; + } else { + if ( card->CONTtype == CONT_ALUMINUM ) { + card->CONTtype = -1; + card->CONTtypeGiven = FALSE; + } + } + break; + case CONT_P_POLY: + if ( value->iValue ) { + card->CONTtype = CONT_P_POLY; + card->CONTtypeGiven = TRUE; + } else { + if ( card->CONTtype == CONT_P_POLY ) { + card->CONTtype = -1; + card->CONTtypeGiven = FALSE; + } + } + break; + case CONT_N_POLY: + if ( value->iValue ) { + card->CONTtype = CONT_N_POLY; + card->CONTtypeGiven = TRUE; + } else { + if ( card->CONTtype == CONT_N_POLY ) { + card->CONTtype = -1; + card->CONTtypeGiven = FALSE; + } + } + break; + case CONT_WORKFUN: + card->CONTtype = CONT_WORKFUN; + card->CONTtypeGiven = TRUE; + card->CONTworkfun = value->rValue; + card->CONTworkfunGiven = TRUE; + break; + case CONT_NUMBER: + card->CONTnumber = value->iValue; + card->CONTnumberGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/contset.c b/src/ciderlib/input/contset.c new file mode 100644 index 000000000..e8b10cedd --- /dev/null +++ b/src/ciderlib/input/contset.c @@ -0,0 +1,87 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "contdefs.h" +#include "meshext.h" +#include "gendev.h" +#include "sperror.h" +#include "suffix.h" + +extern int CONTcheck( CONTcard * ); +extern int CONTsetup( CONTcard *, ELCTelectrode * ); + + +/* + * Name: CONTcheck + * Purpose: checks a list of CONTcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +CONTcheck(CONTcard *cardList) +{ + CONTcard *card; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(CONTcard); card = card->CONTnextCard ) { + cardNum++; + if (!card->CONTnumberGiven) { + sprintf( ebuf, + "contact card %d is missing an electrode index", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + +/* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + + +/* + * Name: CONTsetup + * Purpose: copies information from list of CONTcard's to ELCTelectrode's + * Formals: cardList: list of cards to setup + * electrodeList: previously built list of ELCTelectrode's + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: CONTcheck + */ +int +CONTsetup(CONTcard *cardList, ELCTelectrode *electrodeList) +{ + CONTcard *card; + ELCTelectrode *electrode; + int error; + +/* Check the card list */ + if ((error = CONTcheck( cardList ))) return( error ); + + for ( card = cardList; card != NIL(CONTcard); card = card->CONTnextCard ) { + + /* Copy workfunction to all matching electrodes */ + for ( electrode = electrodeList; electrode != NIL(ELCTelectrode); + electrode = electrode->next ) { + if ( card->CONTnumber == electrode->id ) { + if ( card->CONTworkfunGiven ) { + electrode->workf = card->CONTworkfun; + } else { + electrode->workf = 4.10 /* electron volts */; + } + } + } + } + return( OK ); +} diff --git a/src/ciderlib/input/domain.c b/src/ciderlib/input/domain.c new file mode 100644 index 000000000..c77bc890a --- /dev/null +++ b/src/ciderlib/input/domain.c @@ -0,0 +1,133 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group\ +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "numenum.h" +#include "domndefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +#define UM_TO_CM 1.0e-4 + +extern int DOMNnewCard(void**,void*); +extern int DOMNparam(int,IFvalue*,void*); + + +IFparm DOMNpTable[] = { + IP("x.low", DOMN_X_LOW, IF_REAL, "Location of left edge"), + IP("x.high", DOMN_X_HIGH, IF_REAL, "Location of right edge"), + IP("y.low", DOMN_Y_LOW, IF_REAL, "Location of top edge"), + IP("y.high", DOMN_Y_HIGH, IF_REAL, "Location of bottom edge"), + IP("ix.low", DOMN_IX_LOW, IF_INTEGER, "Index of left edge"), + IP("ix.high", DOMN_IX_HIGH, IF_INTEGER, "Index of right edge"), + IP("iy.low", DOMN_IY_LOW, IF_INTEGER, "Index of top edge"), + IP("iy.high", DOMN_IY_HIGH, IF_INTEGER, "Index of bottom edge"), + IP("number", DOMN_NUMBER, IF_INTEGER, "Domain ID number"), + IP("material",DOMN_MATERIAL, IF_INTEGER, "Material ID number") +}; + +IFcardInfo DOMNinfo = { + "domain", + "Identify material-type for portion of a device", + NUMELEMS(DOMNpTable), + DOMNpTable, + + DOMNnewCard, + DOMNparam, + NULL +}; +IFcardInfo REGNinfo = { + "region", + "Identify material-type for portion of a device", + NUMELEMS(DOMNpTable), + DOMNpTable, + + DOMNnewCard, + DOMNparam, + NULL +}; + +int +DOMNnewCard(void **inCard, void *inModel) +{ + DOMNcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( DOMNcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->DOMNnextCard = (DOMNcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENdomains; + if (!tmpCard) { /* First in list */ + model->GENdomains = newCard; + } else { + /* Go to end of list */ + while (tmpCard->DOMNnextCard) tmpCard = tmpCard->DOMNnextCard; + /* And add new card */ + tmpCard->DOMNnextCard = newCard; + } + return(OK); +} + +int +DOMNparam(int param, IFvalue *value, void *inCard) +{ + DOMNcard *card = (DOMNcard *)inCard; + + switch (param) { + case DOMN_X_LOW: + card->DOMNxLow = value->rValue * UM_TO_CM; + card->DOMNxLowGiven = TRUE; + break; + case DOMN_X_HIGH: + card->DOMNxHigh = value->rValue * UM_TO_CM; + card->DOMNxHighGiven = TRUE; + break; + case DOMN_Y_LOW: + card->DOMNyLow = value->rValue * UM_TO_CM; + card->DOMNyLowGiven = TRUE; + break; + case DOMN_Y_HIGH: + card->DOMNyHigh = value->rValue * UM_TO_CM; + card->DOMNyHighGiven = TRUE; + break; + case DOMN_IX_LOW: + card->DOMNixLow = value->iValue; + card->DOMNixLowGiven = TRUE; + break; + case DOMN_IX_HIGH: + card->DOMNixHigh = value->iValue; + card->DOMNixHighGiven = TRUE; + break; + case DOMN_IY_LOW: + card->DOMNiyLow = value->iValue; + card->DOMNiyLowGiven = TRUE; + break; + case DOMN_IY_HIGH: + card->DOMNiyHigh = value->iValue; + card->DOMNiyHighGiven = TRUE; + break; + case DOMN_NUMBER: + card->DOMNnumber = value->iValue; + card->DOMNnumberGiven = TRUE; + break; + case DOMN_MATERIAL: + card->DOMNmaterial = value->iValue; + card->DOMNmaterialGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/domnset.c b/src/ciderlib/input/domnset.c new file mode 100644 index 000000000..1a564ecb9 --- /dev/null +++ b/src/ciderlib/input/domnset.c @@ -0,0 +1,207 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modifed: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "numenum.h" +#include "domndefs.h" +#include "material.h" +#include "meshext.h" +#include "gendev.h" +#include "sperror.h" +#include "suffix.h" + + +extern int DOMNcheck( DOMNcard *, MATLmaterial * ); +extern int DOMNsetup( DOMNcard *, DOMNdomain **, MESHcoord *, MESHcoord *, + MATLmaterial * ); + + +/* + * Name: DOMNcheck + * Purpose: checks a list of DOMNcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +DOMNcheck(DOMNcard *cardList, MaterialInfo *matlList) +{ + DOMNcard *card; + MATLmaterial *matl; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(DOMNcard); card = card->DOMNnextCard ) { + cardNum++; + if (card->DOMNxLowGiven && card->DOMNixLowGiven) { + sprintf( ebuf, + "domain card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->DOMNxLowGiven = FALSE; + } + if (card->DOMNxHighGiven && card->DOMNixHighGiven) { + sprintf( ebuf, + "domain card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->DOMNxHighGiven = FALSE; + } + if (card->DOMNyLowGiven && card->DOMNiyLowGiven) { + sprintf( ebuf, + "domain card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->DOMNyLowGiven = FALSE; + } + if (card->DOMNyHighGiven && card->DOMNiyHighGiven) { + sprintf( ebuf, + "domain card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->DOMNyHighGiven = FALSE; + } + if (!card->DOMNmaterialGiven) { + sprintf( ebuf, + "domain card %d is missing a material index", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } else { + /* Make sure the material exists */ + for ( matl = matlList; matl != NIL(MATLmaterial); matl = matl->next ) { + if ( card->DOMNmaterial == matl->id ) { + break; + } + } + if (matl == NIL(MATLmaterial)) { + sprintf( ebuf, + "domain card %d specifies a non-existent material", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + if (!card->DOMNnumberGiven) { + sprintf( ebuf, + "domain card %d is missing an ID number", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + +/* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + + +/* + * Name: DOMNsetup + * Purpose: convert a list of DOMNcard's to DOMNdomain's + * Formals: cardList: list of cards to setup + * domainList: returns the list of DOMNdomain's + * xMeshList: list of coordinates in the x mesh + * yMeshList: list of coordinates in the y mesh + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: DOMNcheck + */ +int +DOMNsetup(DOMNcard *cardList, DOMNdomain **domainList, MESHcoord *xMeshList, + MESHcoord *yMeshList, MaterialInfo *materialList) +{ + DOMNcard *card; + DOMNdomain *newDomain = NULL; + int ixMin, ixMax, iyMin, iyMax; + int cardNum = 0; + int error; + char ebuf[512]; /* error message buffer */ + +/* Initialize list of domains */ + *domainList = NIL(DOMNdomain); + +/* Check the card list */ + if ((error = DOMNcheck( cardList, materialList ))) return( error ); + +/* Find the limits on the indices */ + MESHiBounds( xMeshList, &ixMin, &ixMax ); + MESHiBounds( yMeshList, &iyMin, &iyMax ); + + error = OK; + for ( card = cardList; card != NIL(DOMNcard); card = card->DOMNnextCard ) { + cardNum++; + + if (*domainList == NIL(DOMNdomain)) { + RALLOC( newDomain, DOMNdomain, 1 ); + *domainList = newDomain; + } else { + RALLOC( newDomain->next, DOMNdomain, 1 ); + newDomain = newDomain->next; + } + + newDomain->id = card->DOMNnumber; + newDomain->material = card->DOMNmaterial; + newDomain->next = NIL(DOMNdomain); + + if (card->DOMNixLowGiven) { + newDomain->ixLo = MAX(card->DOMNixLow, ixMin); + } + else if (card->DOMNxLowGiven) { + newDomain->ixLo = MESHlocate( xMeshList, card->DOMNxLow ); + } + else { + newDomain->ixLo = ixMin; + } + if (card->DOMNixHighGiven) { + newDomain->ixHi = MIN(card->DOMNixHigh, ixMax); + } + else if (card->DOMNxHighGiven) { + newDomain->ixHi = MESHlocate( xMeshList, card->DOMNxHigh ); + } + else { + newDomain->ixHi = ixMax; + } + if (newDomain->ixLo > newDomain->ixHi) { + sprintf( ebuf, + "domain card %d has low x index (%d) > high x index (%d)", + cardNum, newDomain->ixLo, newDomain->ixHi ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + if (card->DOMNiyLowGiven) { + newDomain->iyLo = MAX(card->DOMNiyLow, iyMin); + } + else if (card->DOMNyLowGiven) { + newDomain->iyLo = MESHlocate( yMeshList, card->DOMNyLow ); + } + else { + newDomain->iyLo = iyMin; + } + if (card->DOMNiyHighGiven) { + newDomain->iyHi = MIN(card->DOMNiyHigh, iyMax); + } + else if (card->DOMNyHighGiven) { + newDomain->iyHi = MESHlocate( yMeshList, card->DOMNyHigh ); + } + else { + newDomain->iyHi = iyMax; + } + if (newDomain->iyLo > newDomain->iyHi) { + sprintf( ebuf, + "domain card %d has low y index (%d) > high y index (%d)", + cardNum, newDomain->iyLo, newDomain->iyHi ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + return( error ); +} diff --git a/src/ciderlib/input/doping.c b/src/ciderlib/input/doping.c new file mode 100644 index 000000000..0e9723ae2 --- /dev/null +++ b/src/ciderlib/input/doping.c @@ -0,0 +1,286 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "dopdefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +#define UM_TO_CM 1.0e-4 + +extern int DOPnewCard(void**,void*); +extern int DOPparam(int,IFvalue*,void*); + + +IFparm DOPpTable[] = { + IP("domains", DOP_DOMAIN, IF_INTVEC, "Domain(s) to dope"), + IP("uniform", DOP_UNIF, IF_FLAG, "Uniform doping"), + IP("linear", DOP_LINEAR, IF_FLAG, "Linear graded doping"), + IP("gaussian",DOP_GAUSS, IF_FLAG, "Gaussian doping"), + IP("gdiff", DOP_GAUSS, IF_FLAG, "Gaussian diffusion"), + IP("gimp", DOP_GAUSS, IF_FLAG, "Gaussian implantation"), + IP("erfc", DOP_ERFC, IF_FLAG, "Error-function doping"), + IP("errfc", DOP_ERFC, IF_FLAG, "Error-function doping"), + IP("exponential",DOP_EXP, IF_FLAG, "Exponential doping"), + IP("suprem3", DOP_SUPREM3, IF_FLAG, "Suprem3 doping"), + IP("ascii", DOP_ASCII, IF_FLAG, "Ascii-input doping"), + IP("infile", DOP_INFILE, IF_STRING, "Input-file name"), + IP("lat.rotate",DOP_ROTATE_LAT,IF_FLAG, "Rotate principal profile"), + IP("lat.unif",DOP_UNIF_LAT, IF_FLAG, "Uniform lateral falloff"), + IP("lat.linf",DOP_LINEAR_LAT, IF_FLAG, "Linear lateral falloff"), + IP("lat.gauss",DOP_GAUSS_LAT, IF_FLAG, "Gaussian lateral falloff"), + IP("lat.erfc",DOP_ERFC_LAT, IF_FLAG, "Erfc lateral falloff"), + IP("lat.exp", DOP_EXP_LAT, IF_FLAG, "Exponential lateral falloff"), + IP("boron", DOP_BORON, IF_FLAG, "Boron impurities"), + IP("phosphorus",DOP_PHOSP, IF_FLAG, "Phosphorus impurities"), + IP("arsenic", DOP_ARSEN, IF_FLAG, "Arsenic impurities"), + IP("antimony",DOP_ANTIM, IF_FLAG, "Antimony impurities"), + IP("p.type", DOP_P_TYPE, IF_FLAG, "P-type impurities"), + IP("acceptor",DOP_P_TYPE, IF_FLAG, "Acceptor impurities"), + IP("n.type", DOP_N_TYPE, IF_FLAG, "N-type impurities"), + IP("donor", DOP_N_TYPE, IF_FLAG, "Donor impurities"), + IP("x.axis", DOP_X_AXIS, IF_FLAG, "X principal axis"), + IP("y.axis", DOP_Y_AXIS, IF_FLAG, "Y principal axis"), + IP("x.low", DOP_X_LOW, IF_REAL, "Low X edge"), + IP("x.high", DOP_X_HIGH, IF_REAL, "High X edge"), + IP("y.low", DOP_Y_LOW, IF_REAL, "Low Y edge"), + IP("y.high", DOP_Y_HIGH, IF_REAL, "High Y edge"), + IP("conc", DOP_CONC, IF_REAL, "Concentration"), + IP("peak.conc",DOP_CONC, IF_REAL, "Peak concentration"), + IP("location",DOP_LOCATION, IF_REAL, "Peak location"), + IP("range", DOP_LOCATION, IF_REAL, "Peak location"), + IP("char.length",DOP_CHAR_LEN,IF_REAL, "Characteristic length"), + IP("ratio.lat",DOP_RATIO_LAT, IF_REAL, "Lateral ratio") +}; + +IFcardInfo DOPinfo = { + "doping", + "Add dopant to domains of a device", + NUMELEMS(DOPpTable), + DOPpTable, + + DOPnewCard, + DOPparam, + NULL +}; + +int +DOPnewCard(void **inCard, void *inModel) +{ + DOPcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( DOPcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->DOPnextCard = (DOPcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENdopings; + if (!tmpCard) { /* First in list */ + model->GENdopings = newCard; + } else { + /* Go to end of list */ + while (tmpCard->DOPnextCard) tmpCard = tmpCard->DOPnextCard; + /* And add new card */ + tmpCard->DOPnextCard = newCard; + } + return(OK); +} + + +int +DOPparam(int param, IFvalue *value, void *inCard) +{ + int i; + DOPcard *card = (DOPcard *)inCard; + + switch (param) { + case DOP_DOMAIN: + if ( !card->DOPdomainsGiven ) { + card->DOPnumDomains = value->v.numValue; + card->DOPdomains = (int *)xmalloc(value->v.numValue * sizeof(int)); + for ( i=0; i < card->DOPnumDomains; i++ ) { + card->DOPdomains[i] = value->v.vec.iVec[i]; + } + card->DOPdomainsGiven = TRUE; + } /* else: do nothing, can only define domains once */ + break; + case DOP_ROTATE_LAT: + card->DOProtateLat = TRUE; + card->DOProtateLatGiven = TRUE; + break; + case DOP_UNIF: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_UNIF; + card->DOPprofileTypeGiven = TRUE; + } + break; + case DOP_UNIF_LAT: + if ( !card->DOPlatProfileTypeGiven ) { + card->DOPlatProfileType = DOP_UNIF; + card->DOPlatProfileTypeGiven = TRUE; + } + break; + case DOP_LINEAR: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_LINEAR; + card->DOPprofileTypeGiven = TRUE; + } + break; + case DOP_LINEAR_LAT: + if ( !card->DOPlatProfileTypeGiven ) { + card->DOPlatProfileType = DOP_LINEAR_LAT; + card->DOPlatProfileTypeGiven = TRUE; + } + break; + case DOP_GAUSS: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_GAUSS; + card->DOPprofileTypeGiven = TRUE; + } + break; + case DOP_GAUSS_LAT: + if ( !card->DOPlatProfileTypeGiven ) { + card->DOPlatProfileType = DOP_GAUSS; + card->DOPlatProfileTypeGiven = TRUE; + } + break; + case DOP_ERFC: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_ERFC; + card->DOPprofileTypeGiven = TRUE; + } + break; + case DOP_ERFC_LAT: + if ( !card->DOPlatProfileTypeGiven ) { + card->DOPlatProfileType = DOP_ERFC; + card->DOPlatProfileTypeGiven = TRUE; + } + break; + case DOP_EXP: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_EXP; + card->DOPprofileTypeGiven = TRUE; + } + break; + case DOP_EXP_LAT: + if ( !card->DOPlatProfileTypeGiven ) { + card->DOPlatProfileType = DOP_EXP; + card->DOPlatProfileTypeGiven = TRUE; + } + break; + case DOP_SUPREM3: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_SUPREM3; + card->DOPprofileTypeGiven = TRUE; + } else if ( card->DOPprofileType == DOP_ASCII ) { + card->DOPprofileType = DOP_SUPASCII; + } + break; + case DOP_ASCII: + if ( !card->DOPprofileTypeGiven ) { + card->DOPprofileType = DOP_ASCII; + card->DOPprofileTypeGiven = TRUE; + } else if ( card->DOPprofileType == DOP_SUPREM3 ) { + card->DOPprofileType = DOP_SUPASCII; + } + break; + case DOP_BORON: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_BORON; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_PHOSP: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_PHOSP; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_ARSEN: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_ARSEN; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_ANTIM: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_ANTIM; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_P_TYPE: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_P_TYPE; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_N_TYPE: + if ( !card->DOPimpurityTypeGiven ) { + card->DOPimpurityType = DOP_N_TYPE; + card->DOPimpurityTypeGiven = TRUE; + } + break; + case DOP_X_AXIS: + if ( !card->DOPaxisTypeGiven ) { + card->DOPaxisType = DOP_X_AXIS; + card->DOPaxisTypeGiven = TRUE; + } + break; + case DOP_Y_AXIS: + if ( !card->DOPaxisTypeGiven ) { + card->DOPaxisType = DOP_Y_AXIS; + card->DOPaxisTypeGiven = TRUE; + } + break; + case DOP_INFILE: + card->DOPinFile = value->sValue; + card->DOPinFileGiven = TRUE; + break; + case DOP_X_LOW: + card->DOPxLow = value->rValue * UM_TO_CM; + card->DOPxLowGiven = TRUE; + break; + case DOP_X_HIGH: + card->DOPxHigh = value->rValue * UM_TO_CM; + card->DOPxHighGiven = TRUE; + break; + case DOP_Y_LOW: + card->DOPyLow = value->rValue * UM_TO_CM; + card->DOPyLowGiven = TRUE; + break; + case DOP_Y_HIGH: + card->DOPyHigh = value->rValue * UM_TO_CM; + card->DOPyHighGiven = TRUE; + break; + case DOP_CONC: + card->DOPconc = fabs(value->rValue); + card->DOPconcGiven = TRUE; + break; + case DOP_LOCATION: + card->DOPlocation = value->rValue * UM_TO_CM; + card->DOPlocationGiven = TRUE; + break; + case DOP_CHAR_LEN: + card->DOPcharLen = value->rValue * UM_TO_CM; + card->DOPcharLenGiven = TRUE; + break; + case DOP_RATIO_LAT: + card->DOPratioLat = value->rValue; + card->DOPratioLatGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/dopset.c b/src/ciderlib/input/dopset.c new file mode 100644 index 000000000..79d2cc289 --- /dev/null +++ b/src/ciderlib/input/dopset.c @@ -0,0 +1,393 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modifed: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "numenum.h" +#include "dopdefs.h" +#include "meshext.h" +#include "profile.h" +#include "gendev.h" +#include "sperror.h" +#include "suffix.h" + +extern int DOPnewCard(void**,void*); +extern int DOPparam(int,IFvalue*,void*); + + +/* + * Name: DOPcheck + * Purpose: checks a list of DOPcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +DOPcheck(DOPcard *cardList, MESHcoord *xMeshList, MESHcoord *yMeshList) +{ + DOPcard *card; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(DOPcard); card = card->DOPnextCard ) { + cardNum++; + if (!card->DOPdomainsGiven) { + card->DOPnumDomains = 0; + card->DOPdomains = NIL(int); + } + if (!card->DOPprofileTypeGiven) { + sprintf( ebuf, + "doping card %d does not specify profile type", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } else switch (card->DOPprofileType) { + case DOP_UNIF: + if (!card->DOPconcGiven) { + sprintf( ebuf, + "doping card %d needs conc of uniform distribution", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_LINEAR: + if (!card->DOPconcGiven) { + sprintf( ebuf, + "doping card %d needs peak conc of linear distribution", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_GAUSS: + if (!card->DOPconcGiven) { + sprintf( ebuf, + "doping card %d needs peak conc of gaussian distribution", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_ERFC: + if (!card->DOPconcGiven) { + sprintf( ebuf, + "doping card %d needs peak conc of error-function distribution", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_EXP: + if (!card->DOPconcGiven) { + sprintf( ebuf, + "doping card %d needs peak conc of exponential distribution", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_SUPREM3: + case DOP_SUPASCII: + if (!card->DOPinFileGiven) { + sprintf( ebuf, + "doping card %d needs input-file name of suprem3 data", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + case DOP_ASCII: + if (!card->DOPinFileGiven) { + sprintf( ebuf, + "doping card %d needs input-file name of ascii data", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + break; + default: + sprintf( ebuf, + "doping card %d has unrecognized profile type", + cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, ebuf, NIL(IFuid) ); + error = E_NOTFOUND; + break; + } + if (!card->DOProtateLatGiven) { + card->DOProtateLat = FALSE; + } + if (!card->DOPlatProfileTypeGiven || card->DOProtateLat) { + card->DOPlatProfileType = card->DOPprofileType; + } + if (!card->DOPratioLatGiven) { + card->DOPratioLat = 1.0; + } + if (!card->DOPcharLenGiven) { + card->DOPcharLen = 1.0e-4; /* 1um in centimeters */ + } + if (!card->DOPlocationGiven) { + card->DOPlocation = 0.0; + } + if (!card->DOPimpurityTypeGiven) { + card->DOPimpurityType = IMP_N_TYPE; + } else switch (card->DOPimpurityType) { + case DOP_BORON: + card->DOPimpurityType = IMP_BORON; + break; + case DOP_PHOSP: + card->DOPimpurityType = IMP_PHOSPHORUS; + break; + case DOP_ARSEN: + card->DOPimpurityType = IMP_ARSENIC; + break; + case DOP_ANTIM: + card->DOPimpurityType = IMP_ANTIMONY; + break; + case DOP_N_TYPE: + card->DOPimpurityType = IMP_N_TYPE; + break; + case DOP_P_TYPE: + card->DOPimpurityType = IMP_P_TYPE; + break; + default: + break; + } + + if (!card->DOPaxisTypeGiven) { + if ( xMeshList && yMeshList ) { /* both lists are non-empty */ + card->DOPaxisType = DOP_Y_AXIS; + } else if ( xMeshList ) { /* x-mesh list is non-empty */ + card->DOPaxisType = DOP_X_AXIS; + } else if ( yMeshList ) { /* y-mesh list is non-empty */ + card->DOPaxisType = DOP_Y_AXIS; + } + } + +/* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + + +/* + * Name: DOPsetup + * Purpose: convert a list of DOPcard's to DOPprofile's + * Formals: cardList: list of cards to setup + * profileList: returns the list of DOPprofile's + * xMeshList: list of coordinates in the x mesh + * yMeshList: list of coordinates in the y mesh + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: DOPcheck + */ +int +DOPsetup(DOPcard *cardList, DOPprofile **profileList, DOPtable **tableList, + MESHcoord *xMeshList, MESHcoord *yMeshList) +{ + DOPcard *card; + DOPprofile *newProfile = NULL, *endProfile; + int impurityId = 0; + double xMin, xMax, yMin, yMax; + double sign; + int error, xProfUnif, yProfUnif; + +/* Initialize list of profiles */ + *profileList = endProfile = NIL(DOPprofile); + +/* Check the card list */ + if ((error = DOPcheck( cardList, xMeshList, yMeshList ))) return( error ); + +/* Find the limits on locations */ + MESHlBounds( xMeshList, &xMin, &xMax ); + MESHlBounds( yMeshList, &yMin, &yMax ); + + for ( card = cardList; card != NIL(DOPcard); card = card->DOPnextCard ) { + + if (*profileList == NIL(DOPprofile)) { + RALLOC( newProfile, DOPprofile, 1 ); + *profileList = newProfile; + } + else { + RALLOC( newProfile->next, DOPprofile, 1 ); + newProfile = newProfile->next; + } + newProfile->next = NIL(DOPprofile); + + newProfile->numDomains = card->DOPnumDomains; + if ( newProfile->numDomains > 0 ) { + int i; + RALLOC( newProfile->domains, int, newProfile->numDomains ); + for ( i=0; i < newProfile->numDomains; i++ ) { + newProfile->domains[i] = card->DOPdomains[i]; + } + } + else { + newProfile->domains = NIL(int); + } + + if ( card->DOPimpurityType == IMP_P_TYPE ) { + sign = -1.0; + } + else { + sign = 1.0; + } + switch( card->DOPprofileType ) { + case DOP_UNIF: + newProfile->type = UNIF; + newProfile->CONC = sign * card->DOPconc; + break; + case DOP_LINEAR: + newProfile->type = LIN; + newProfile->CONC = sign * card->DOPconc; + break; + case DOP_GAUSS: + newProfile->type = GAUSS; + newProfile->CONC = sign * card->DOPconc; + break; + case DOP_ERFC: + newProfile->type = ERRFC; + newProfile->CONC = sign * card->DOPconc; + break; + case DOP_EXP: + newProfile->type = EXP; + newProfile->CONC = sign * card->DOPconc; + break; + case DOP_SUPREM3: + newProfile->type = LOOKUP; + readSupremData( card->DOPinFile, 0, card->DOPimpurityType, tableList ); + newProfile->IMPID = ++impurityId; + break; + case DOP_SUPASCII: + newProfile->type = LOOKUP; + readSupremData( card->DOPinFile, 1, card->DOPimpurityType, tableList ); + newProfile->IMPID = ++impurityId; + break; + case DOP_ASCII: + newProfile->type = LOOKUP; + readAsciiData( card->DOPinFile, card->DOPimpurityType, tableList ); + newProfile->IMPID = ++impurityId; + break; + default: + break; + } + switch( card->DOPlatProfileType ) { + case DOP_UNIF: + newProfile->latType = UNIF; + break; + case DOP_LINEAR: + newProfile->latType = LIN; + break; + case DOP_GAUSS: + newProfile->latType = GAUSS; + break; + case DOP_ERFC: + newProfile->latType = ERRFC; + break; + case DOP_EXP: + newProfile->latType = EXP; + break; + case DOP_SUPREM3: + case DOP_SUPASCII: + newProfile->latType = LOOKUP; + break; + case DOP_ASCII: + newProfile->latType = LOOKUP; + break; + default: + break; + } + newProfile->rotate = card->DOProtateLat; + newProfile->LOCATION = card->DOPlocation; + newProfile->CHAR_LENGTH = card->DOPcharLen; + newProfile->LAT_RATIO = card->DOPratioLat; + + xProfUnif = yProfUnif = FALSE; + if (card->DOPaxisType == DOP_X_AXIS) { + newProfile->DIRECTION = X; + if (newProfile->type == UNIF) xProfUnif = TRUE; + if (newProfile->latType == UNIF) yProfUnif = TRUE; + } + else { + newProfile->DIRECTION = Y; + if (newProfile->type == UNIF) yProfUnif = TRUE; + if (newProfile->latType == UNIF) xProfUnif = TRUE; + } + +/* Fill in x coordinates. Use defaults if necessary */ + if (card->DOPxLowGiven && card->DOPxHighGiven) { + newProfile->X_LOW = card->DOPxLow; + newProfile->X_HIGH = card->DOPxHigh; + } + else if (card->DOPxLowGiven) { + newProfile->X_LOW = card->DOPxLow; + if (xProfUnif) { + newProfile->X_HIGH = xMax; + } + else { + newProfile->X_HIGH = newProfile->X_LOW; + } + } + else if (card->DOPxHighGiven) { + newProfile->X_HIGH = card->DOPxHigh; + if (xProfUnif) { + newProfile->X_LOW = xMin; + } + else { + newProfile->X_LOW = newProfile->X_HIGH; + } + } + else { + if (xProfUnif) { + newProfile->X_LOW = xMin; + newProfile->X_HIGH = xMax; + } + else { + newProfile->X_LOW = 0.5 * (xMin + xMax); + newProfile->X_HIGH = 0.5 * (xMin + xMax); + } + } + +/* Fill in y coordinates. Use defaults if necessary */ + if (card->DOPyLowGiven && card->DOPyHighGiven) { + newProfile->Y_LOW = card->DOPyLow; + newProfile->Y_HIGH = card->DOPyHigh; + } + else if (card->DOPyLowGiven) { + newProfile->Y_LOW = card->DOPyLow; + if (yProfUnif) { + newProfile->Y_HIGH = yMax; + } + else { + newProfile->Y_HIGH = newProfile->Y_LOW; + } + } + else if (card->DOPyHighGiven) { + newProfile->Y_HIGH = card->DOPyHigh; + if (xProfUnif) { + newProfile->Y_LOW = yMin; + } + else { + newProfile->Y_LOW = newProfile->Y_HIGH; + } + } + else { + if (yProfUnif) { + newProfile->Y_LOW = yMin; + newProfile->Y_HIGH = yMax; + } + else { + newProfile->Y_LOW = 0.5 * (yMin + yMax); + newProfile->Y_HIGH = 0.5 * (yMin + yMax); + } + } + } + return( OK ); +} diff --git a/src/ciderlib/input/elctset.c b/src/ciderlib/input/elctset.c new file mode 100644 index 000000000..d6e1babc7 --- /dev/null +++ b/src/ciderlib/input/elctset.c @@ -0,0 +1,177 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "elctdefs.h" +#include "meshext.h" +#include "twomesh.h" +#include "gendev.h" +#include "sperror.h" +#include "suffix.h" + + +extern int ELCTcheck( ELCTcard * ); +extern int ELCTsetup( ELCTcard *, ELCTelectrode **, MESHcoord *, MESHcoord * ); + + +/* + * Name: ELCTcheck + * Purpose: checks a list of ELCTcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +ELCTcheck(ELCTcard *cardList) +{ + ELCTcard *card; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(ELCTcard); card = card->ELCTnextCard ) { + cardNum++; + if (card->ELCTxLowGiven && card->ELCTixLowGiven) { + sprintf( ebuf, + "electrode card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->ELCTxLowGiven = FALSE; + } + if (card->ELCTxHighGiven && card->ELCTixHighGiven) { + sprintf( ebuf, + "electrode card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->ELCTxHighGiven = FALSE; + } + if (card->ELCTyLowGiven && card->ELCTiyLowGiven) { + sprintf( ebuf, + "electrode card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->ELCTyLowGiven = FALSE; + } + if (card->ELCTyHighGiven && card->ELCTiyHighGiven) { + sprintf( ebuf, + "electrode card %d uses both location and index - location ignored", + cardNum ); + SPfrontEnd->IFerror( ERR_INFO, ebuf, NIL(IFuid) ); + card->ELCTyHighGiven = FALSE; + } + if (!card->ELCTnumberGiven) { + card->ELCTnumber = -1; + } + +/* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + +/* + * Name: ELCTsetup + * Purpose: convert a list of ELCTcard's to ELCTelectrode's + * Formals: cardList: list of cards to setup + * electrodeList: returns the list of ELCTelectrode's + * xMeshList: list of coordinates in the x mesh + * yMeshList: list of coordinates in the y mesh + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: ELCTcheck + */ +int +ELCTsetup(ELCTcard *cardList, ELCTelectrode **electrodeList, + MESHcoord *xMeshList, MESHcoord *yMeshList) +{ + ELCTcard *card; + ELCTelectrode *newElectrode = NULL; + int ixMin, ixMax, iyMin, iyMax; + int cardNum = 0; + int error; + char ebuf[512]; /* error message buffer */ + +/* Initialize list of electrodes */ + *electrodeList = NIL(ELCTelectrode); + +/* Check the card list */ + if ((error = ELCTcheck( cardList ))) return( error ); + +/* Find the limits on the indices */ + MESHiBounds( xMeshList, &ixMin, &ixMax ); + MESHiBounds( yMeshList, &iyMin, &iyMax ); + + error = OK; + for ( card = cardList; card != NIL(ELCTcard); card = card->ELCTnextCard ) { + cardNum++; + + if (*electrodeList == NIL(ELCTelectrode)) { + RALLOC( newElectrode, ELCTelectrode, 1 ); + *electrodeList = newElectrode; + } else { + RALLOC( newElectrode->next, ELCTelectrode, 1 ); + newElectrode = newElectrode->next; + } + newElectrode->next = NIL(ELCTelectrode); + newElectrode->id = card->ELCTnumber; + newElectrode->workf = 4.10 /* electron volts */; + + if (card->ELCTixLowGiven) { + newElectrode->ixLo = MAX(card->ELCTixLow, ixMin); + } + else if (card->ELCTxLowGiven) { + newElectrode->ixLo = MESHlocate( xMeshList, card->ELCTxLow ); + } + else { + newElectrode->ixLo = ixMin; + } + if (card->ELCTixHighGiven) { + newElectrode->ixHi = MIN(card->ELCTixHigh, ixMax); + } + else if (card->ELCTxHighGiven) { + newElectrode->ixHi = MESHlocate( xMeshList, card->ELCTxHigh ); + } + else { + newElectrode->ixHi = ixMax; + } + if (newElectrode->ixLo > newElectrode->ixHi) { + sprintf( ebuf, + "electrode card %d has low x index (%d) > high x index (%d)", + cardNum, newElectrode->ixLo, newElectrode->ixHi ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + if (card->ELCTiyLowGiven) { + newElectrode->iyLo = MAX(card->ELCTiyLow, iyMin); + } + else if (card->ELCTyLowGiven) { + newElectrode->iyLo = MESHlocate( yMeshList, card->ELCTyLow ); + } + else { + newElectrode->iyLo = iyMin; + } + if (card->ELCTiyHighGiven) { + newElectrode->iyHi = MIN(card->ELCTiyHigh, iyMax); + } + else if (card->ELCTyHighGiven) { + newElectrode->iyHi = MESHlocate( yMeshList, card->ELCTyHigh ); + } + else { + newElectrode->iyHi = iyMax; + } + if (newElectrode->iyLo > newElectrode->iyHi) { + sprintf( ebuf, + "electrode card %d has low y index (%d) > high y index (%d)", + cardNum, newElectrode->iyLo, newElectrode->iyHi ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + return( error ); +} diff --git a/src/ciderlib/input/electrod.c b/src/ciderlib/input/electrod.c new file mode 100644 index 000000000..150da10b5 --- /dev/null +++ b/src/ciderlib/input/electrod.c @@ -0,0 +1,118 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "elctdefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +#define UM_TO_CM 1.0e-4 + +extern int ELCTnewCard(void**,void*); +extern int ELCTparam(int,IFvalue*,void*); + + +IFparm ELCTpTable[] = { + IP("x.low", ELCT_X_LOW, IF_REAL, "Location of left edge"), + IP("x.high", ELCT_X_HIGH, IF_REAL, "Location of right edge"), + IP("y.low", ELCT_Y_LOW, IF_REAL, "Location of top edge"), + IP("y.high", ELCT_Y_HIGH, IF_REAL, "Location of bottom edge"), + IP("ix.low", ELCT_IX_LOW, IF_INTEGER, "Index of left edge"), + IP("ix.high", ELCT_IX_HIGH, IF_INTEGER, "Index of right edge"), + IP("iy.low", ELCT_IY_LOW, IF_INTEGER, "Index of top edge"), + IP("iy.high", ELCT_IY_HIGH, IF_INTEGER, "Index of bottom edge"), + IP("number", ELCT_NUMBER, IF_INTEGER, "Electrode ID number") +}; + +IFcardInfo ELCTinfo = { + "electrode", + "Location of a contact to the device", + NUMELEMS(ELCTpTable), + ELCTpTable, + + ELCTnewCard, + ELCTparam, + NULL +}; + +int +ELCTnewCard(void **inCard, void *inModel) +{ + ELCTcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( ELCTcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->ELCTnextCard = (ELCTcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENelectrodes; + if (!tmpCard) { /* First in list */ + model->GENelectrodes = newCard; + } else { + /* Go to end of list */ + while (tmpCard->ELCTnextCard) tmpCard = tmpCard->ELCTnextCard; + /* And add new card */ + tmpCard->ELCTnextCard = newCard; + } + return(OK); +} + + +int +ELCTparam(int param, IFvalue *value, void *inCard) +{ + ELCTcard *card = (ELCTcard *)inCard; + + switch (param) { + case ELCT_X_LOW: + card->ELCTxLow = value->rValue * UM_TO_CM; + card->ELCTxLowGiven = TRUE; + break; + case ELCT_X_HIGH: + card->ELCTxHigh = value->rValue * UM_TO_CM; + card->ELCTxHighGiven = TRUE; + break; + case ELCT_Y_LOW: + card->ELCTyLow = value->rValue * UM_TO_CM; + card->ELCTyLowGiven = TRUE; + break; + case ELCT_Y_HIGH: + card->ELCTyHigh = value->rValue * UM_TO_CM; + card->ELCTyHighGiven = TRUE; + break; + case ELCT_IX_LOW: + card->ELCTixLow = value->iValue; + card->ELCTixLowGiven = TRUE; + break; + case ELCT_IX_HIGH: + card->ELCTixHigh = value->iValue; + card->ELCTixHighGiven = TRUE; + break; + case ELCT_IY_LOW: + card->ELCTiyLow = value->iValue; + card->ELCTiyLowGiven = TRUE; + break; + case ELCT_IY_HIGH: + card->ELCTiyHigh = value->iValue; + card->ELCTiyHighGiven = TRUE; + break; + case ELCT_NUMBER: + card->ELCTnumber = value->iValue; + card->ELCTnumberGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/material.c b/src/ciderlib/input/material.c new file mode 100644 index 000000000..151ee18e6 --- /dev/null +++ b/src/ciderlib/input/material.c @@ -0,0 +1,305 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "numenum.h" +#include "matldefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + + +extern int MATLnewCard(void**,void *); +extern int MATLparam(int,IFvalue*,void *); + + +IFparm MATLpTable[] = { + IP("number", MATL_NUMBER, IF_INTEGER, "Material ID number"), + IP("insulator",MATL_INSULATOR,IF_FLAG, "Insulator"), + IP("oxide", MATL_OXIDE, IF_FLAG, "Oxide"), + IP("sio2", MATL_OXIDE, IF_FLAG, "Oxide"), + IP("nitride", MATL_NITRIDE, IF_FLAG, "Nitride"), + IP("si3n4", MATL_NITRIDE, IF_FLAG, "Nitride"), + IP("semiconductor",MATL_SEMICON,IF_FLAG, "Semiconductor"), + IP("silicon", MATL_SILICON, IF_FLAG, "Silicon"), + IP("polysilicon",MATL_POLYSIL,IF_FLAG, "Polysilicon"), + IP("gaas", MATL_GAAS, IF_FLAG, "Gallium-Arsenide"), + IP("nc", MATL_NC0, IF_REAL, "Conduction band density"), + IP("nc0", MATL_NC0, IF_REAL, "Conduction band density"), + IP("nc300", MATL_NC0, IF_REAL, "Conduction band density"), + IP("nv", MATL_NV0, IF_REAL, "Valence band density"), + IP("nv0", MATL_NV0, IF_REAL, "Valence band density"), + IP("nv300", MATL_NV0, IF_REAL, "Valence band density"), + IP("eg", MATL_EG0, IF_REAL, "Energy gap"), + IP("eg0", MATL_EG0, IF_REAL, "Energy gap"), + IP("eg300", MATL_EG0, IF_REAL, "Energy gap"), + IP("deg.dt", MATL_DEGDT, IF_REAL, "Bandgap narrowing w/ temp"), + IP("egalpha", MATL_DEGDT, IF_REAL, "Bandgap narrowing w/ temp"), + IP("eg.tref", MATL_TREF_EG, IF_REAL, "E-gap reference temperature"), + IP("egbeta", MATL_TREF_EG, IF_REAL, "E-gap reference temperature"), + IP("deg.dc", MATL_DEGDC, IF_REAL, "Bandgap narrowing w/ N&P doping"), + IP("eg.cref", MATL_CREF_EG, IF_REAL, "E-gap reference conc (N&P type)"), + IP("nbgn", MATL_CREF_EG, IF_REAL, "E-gap reference conc (N&P type)"), + IP("deg.dn", MATL_DEGDN, IF_REAL, "Bandgap narrowing w/ N doping"), + IP("eg.nref", MATL_NREF_EG, IF_REAL, "E-gap reference conc (N type)"), + IP("nbgnn", MATL_NREF_EG, IF_REAL, "E-gap reference conc (N type)"), + IP("deg.dp", MATL_DEGDP, IF_REAL, "Bandgap narrowing w/ P doping"), + IP("eg.pref", MATL_PREF_EG, IF_REAL, "E-gap reference conc (P type)"), + IP("nbgnp", MATL_PREF_EG, IF_REAL, "E-gap reference conc (P type)"), + IP("affinity",MATL_AFFIN, IF_REAL, "Electron affinity"), + IP("permittivity",MATL_PERMIT,IF_REAL, "Dielectric permittivity"), + IP("epsilon", MATL_PERMIT, IF_REAL, "Dielectric permittivity"), + IP("tn", MATL_TAUN0, IF_REAL, "SRH electron lifetime"), + IP("tn0", MATL_TAUN0, IF_REAL, "SRH electron lifetime"), + IP("taun0", MATL_TAUN0, IF_REAL, "SRH electron lifetime"), + IP("tp", MATL_TAUP0, IF_REAL, "SRH hole lifetime"), + IP("tp0", MATL_TAUP0, IF_REAL, "SRH hole lifetime"), + IP("taup0", MATL_TAUP0, IF_REAL, "SRH hole lifetime"), + IP("nsrhn", MATL_NSRHN, IF_REAL, "SRH reference conc (electrons)"), + IP("srh.nref",MATL_NSRHN, IF_REAL, "SRH reference conc (electrons)"), + IP("nsrhp", MATL_NSRHP, IF_REAL, "SRH reference conc (holes)"), + IP("srh.pref",MATL_NSRHP, IF_REAL, "SRH reference conc (holes)"), + IP("cn", MATL_CNAUG, IF_REAL, "Auger coefficient (electrons)"), + IP("cnaug", MATL_CNAUG, IF_REAL, "Auger coefficient (electrons)"), + IP("augn", MATL_CNAUG, IF_REAL, "Auger coefficient (electrons)"), + IP("cp", MATL_CPAUG, IF_REAL, "Auger coefficient (holes)"), + IP("cpaug", MATL_CPAUG, IF_REAL, "Auger coefficient (holes)"), + IP("augp", MATL_CPAUG, IF_REAL, "Auger coefficient (holes)"), + IP("arichn", MATL_ARICHN, IF_REAL, "Richardson constant (electrons)"), + IP("arichp", MATL_ARICHP, IF_REAL, "Richardson constant (holes)") +}; + +IFcardInfo MATLinfo = { + "material", + "Specify physical properties of a material", + NUMELEMS(MATLpTable), + MATLpTable, + + MATLnewCard, + MATLparam, + NULL +}; + +IFcardInfo PHYSinfo = { + "physics", + "Specify physical properties of a material", + NUMELEMS(MATLpTable), + MATLpTable, + + MATLnewCard, + MATLparam, + NULL +}; + +int +MATLnewCard(void **inCard, void *inModel) +{ + MATLcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( MATLcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->MATLnextCard = (MATLcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENmaterials; + if (!tmpCard) { /* First in list */ + model->GENmaterials = newCard; + } else { + /* Go to end of list */ + while (tmpCard->MATLnextCard) tmpCard = tmpCard->MATLnextCard; + /* And add new card */ + tmpCard->MATLnextCard = newCard; + } + return(OK); +} + +int +MATLparam(int param, IFvalue *value, void *inCard) +{ + MATLcard *card = (MATLcard *)inCard; + + switch (param) { + case MATL_NUMBER: + card->MATLnumber = value->iValue; + card->MATLnumberGiven = TRUE; + break; + case MATL_NC0: + card->MATLnc0 = value->rValue; + card->MATLnc0Given = TRUE; + break; + case MATL_NV0: + card->MATLnv0 = value->rValue; + card->MATLnv0Given = TRUE; + break; + case MATL_EG0: + card->MATLeg0 = value->rValue; + card->MATLeg0Given = TRUE; + break; + case MATL_DEGDT: + card->MATLdEgdT = value->rValue; + card->MATLdEgdTGiven = TRUE; + break; + case MATL_TREF_EG: + card->MATLtrefEg = value->rValue; + card->MATLtrefEgGiven = TRUE; + break; + case MATL_DEGDC: + card->MATLdEgdN = value->rValue; + card->MATLdEgdNGiven = TRUE; + card->MATLdEgdP = value->rValue; + card->MATLdEgdPGiven = TRUE; + break; + case MATL_CREF_EG: + card->MATLnrefEg = value->rValue; + card->MATLnrefEgGiven = TRUE; + card->MATLprefEg = value->rValue; + card->MATLprefEgGiven = TRUE; + break; + case MATL_DEGDN: + card->MATLdEgdN = value->rValue; + card->MATLdEgdNGiven = TRUE; + break; + case MATL_NREF_EG: + card->MATLnrefEg = value->rValue; + card->MATLnrefEgGiven = TRUE; + break; + case MATL_DEGDP: + card->MATLdEgdP = value->rValue; + card->MATLdEgdPGiven = TRUE; + break; + case MATL_PREF_EG: + card->MATLprefEg = value->rValue; + card->MATLprefEgGiven = TRUE; + break; + case MATL_AFFIN: + card->MATLaffinity = value->rValue; + card->MATLaffinityGiven = TRUE; + break; + case MATL_PERMIT: + card->MATLpermittivity = value->rValue; + card->MATLpermittivityGiven = TRUE; + break; + case MATL_TAUN0: + card->MATLtaun0 = value->rValue; + card->MATLtaun0Given = TRUE; + break; + case MATL_TAUP0: + card->MATLtaup0 = value->rValue; + card->MATLtaup0Given = TRUE; + break; + case MATL_NSRHN: + card->MATLnrefSRHn = value->rValue; + card->MATLnrefSRHnGiven = TRUE; + break; + case MATL_NSRHP: + card->MATLnrefSRHp = value->rValue; + card->MATLnrefSRHpGiven = TRUE; + break; + case MATL_CNAUG: + card->MATLcnAug = value->rValue; + card->MATLcnAugGiven = TRUE; + break; + case MATL_CPAUG: + card->MATLcpAug = value->rValue; + card->MATLcpAugGiven = TRUE; + break; + case MATL_ARICHN: + card->MATLaRichN = value->rValue; + card->MATLaRichNGiven = TRUE; + break; + case MATL_ARICHP: + card->MATLaRichP = value->rValue; + card->MATLaRichPGiven = TRUE; + break; + case MATL_INSULATOR: + if ( value->iValue ) { + card->MATLmaterial = INSULATOR; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == INSULATOR ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_OXIDE: + if ( value->iValue ) { + card->MATLmaterial = OXIDE; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == OXIDE ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_NITRIDE: + if ( value->iValue ) { + card->MATLmaterial = NITRIDE; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == NITRIDE ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_SEMICON: + if ( value->iValue ) { + card->MATLmaterial = SEMICON; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == SEMICON ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_SILICON: + if ( value->iValue ) { + card->MATLmaterial = SILICON; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == SILICON ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_POLYSIL: + if ( value->iValue ) { + card->MATLmaterial = POLYSILICON; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == POLYSILICON ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + case MATL_GAAS: + if ( value->iValue ) { + card->MATLmaterial = GAAS; + card->MATLmaterialGiven = TRUE; + } else { + if ( card->MATLmaterial == GAAS ) { + card->MATLmaterial = -1; + card->MATLmaterialGiven = FALSE; + } + } + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/matlset.c b/src/ciderlib/input/matlset.c new file mode 100644 index 000000000..9b54529af --- /dev/null +++ b/src/ciderlib/input/matlset.c @@ -0,0 +1,180 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "numconst.h" +#include "numenum.h" +#include "matldefs.h" +#include "material.h" +#include "sperror.h" +#include "suffix.h" + + +extern int MATLcheck( MATLcard * ); +extern int MATLsetup( MATLcard *, MaterialInfo ** ); + +/* + * Name: MATLcheck + * Purpose: checks a list of MATLcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int + MATLcheck(MATLcard *cardList) +{ + MATLcard *card, *card2; + int cardNum = 0, cardNum2; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(MATLcard); card = card->MATLnextCard ) { + cardNum++; + + if( !card->MATLmaterialGiven ) { + card->MATLmaterial = SILICON; + } + if (!card->MATLnumberGiven) { + sprintf( ebuf, + "material card %d is missing an id number", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + + /* Return now if anything has failed */ + if (error) return(error); + + /* Make sure this id is different from all previous ones. */ + cardNum2 = 0; + for ( card2 = cardList; card2 != card; card2 = card2->MATLnextCard ) { + cardNum2++; + if (card2->MATLnumber == card->MATLnumber) { + sprintf( ebuf, + "material cards %d and %d use same id %d", + cardNum2, cardNum, card->MATLnumber ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + + /* Return now if anything has failed */ + if (error) return(error); + } + return(OK); +} + + + +/* + * Name: MATLsetup + * Purpose: setup the physical model parameters + * Formals: cardList: list of cards to setup + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: MATLcheck + */ +int + MATLsetup(MATLcard *cardList, MaterialInfo **materialList) +{ + MATLcard *card; + MATLmaterial *newMaterial = NULL; + int error; + +/* Initialize list of electrodes */ + *materialList = NIL(MATLmaterial); + + /* Check the card list */ + if ((error = MATLcheck( cardList ))) return( error ); + + for ( card = cardList; card != NIL(MATLcard); card = card->MATLnextCard ) { + + if (*materialList == NIL(MATLmaterial)) { + RALLOC( newMaterial, MATLmaterial, 1 ); + *materialList = newMaterial; + } + else { + RALLOC( newMaterial->next, MATLmaterial, 1 ); + newMaterial = newMaterial->next; + } + newMaterial->next = NIL(MATLmaterial); + newMaterial->id = card->MATLnumber; + newMaterial->material = card->MATLmaterial; + + /* Fill in default values */ + MATLdefaults( newMaterial ); + + /* Now override with parameters set on the card */ + if ( card->MATLpermittivityGiven ) { + newMaterial->eps = card->MATLpermittivity; + /* Multiply by permittivity of free space if relative epsilon given. */ + if (newMaterial->eps > 0.1) { + newMaterial->eps *= EPS0; + } + } + if ( card->MATLaffinityGiven ) { + newMaterial->affin = card->MATLaffinity; + } + if ( card->MATLnc0Given ) { + newMaterial->nc0 = card->MATLnc0; + } + if ( card->MATLnv0Given ) { + newMaterial->nv0 = card->MATLnv0; + } + if ( card->MATLeg0Given ) { + newMaterial->eg0 = card->MATLeg0; + } + if ( card->MATLdEgdTGiven ) { + newMaterial->dEgDt = card->MATLdEgdT; + } + if ( card->MATLtrefEgGiven ) { + newMaterial->trefBGN = card->MATLtrefEg; + } + if ( card->MATLdEgdNGiven ) { + newMaterial->dEgDn[ELEC] = card->MATLdEgdN; + } + if ( card->MATLnrefEgGiven ) { + newMaterial->nrefBGN[ELEC] = card->MATLnrefEg; + } + if ( card->MATLdEgdPGiven ) { + newMaterial->dEgDn[HOLE] = card->MATLdEgdP; + } + if ( card->MATLprefEgGiven ) { + newMaterial->nrefBGN[HOLE] = card->MATLprefEg; + } + if ( card->MATLtaup0Given ) { + newMaterial->tau0[HOLE] = card->MATLtaup0; + } + if ( card->MATLtaun0Given ) { + newMaterial->tau0[ELEC] = card->MATLtaun0; + } + if ( card->MATLtaup0Given ) { + newMaterial->tau0[HOLE] = card->MATLtaup0; + } + if ( card->MATLnrefSRHnGiven ) { + newMaterial->nrefSRH[ELEC] = card->MATLnrefSRHn; + } + if ( card->MATLnrefSRHpGiven ) { + newMaterial->nrefSRH[HOLE] = card->MATLnrefSRHp; + } + if ( card->MATLcnAugGiven ) { + newMaterial->cAug[ELEC] = card->MATLcnAug; + } + if ( card->MATLcpAugGiven ) { + newMaterial->cAug[HOLE] = card->MATLcpAug; + } + if ( card->MATLaRichNGiven ) { + newMaterial->aRich[ELEC] = card->MATLaRichN; + } + if ( card->MATLaRichPGiven ) { + newMaterial->aRich[HOLE] = card->MATLaRichP; + } + + } + return( OK ); +} diff --git a/src/ciderlib/input/mesh.c b/src/ciderlib/input/mesh.c new file mode 100644 index 000000000..da898bbe2 --- /dev/null +++ b/src/ciderlib/input/mesh.c @@ -0,0 +1,148 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "meshdefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int XMSHnewCard(void**,void*); +extern int YMSHnewCard(void**,void*); +extern int MESHparam(int,IFvalue*,void*); + + + +IFparm MESHpTable[] = { + IP("location",MESH_LOCATION, IF_REAL, "Meshline location"), + IP("width", MESH_WIDTH, IF_REAL, "Distance to next line"), + IP("number", MESH_NUMBER, IF_INTEGER, "Meshline number"), + IP("node", MESH_NUMBER, IF_INTEGER, "Meshline number"), + IP("ratio", MESH_RATIO, IF_REAL, "Suggested spacing ratio"), + IP("h.start", MESH_H_START, IF_REAL, "Spacing at start of interval"), + IP("h1", MESH_H_START, IF_REAL, "Spacing at start of interVal"), + IP("h.end", MESH_H_END, IF_REAL, "Spacing at end of interval"), + IP("h2", MESH_H_END, IF_REAL, "Spacing at end of interval"), + IP("h.max", MESH_H_MAX, IF_REAL, "Max spacing during interval"), + IP("h3", MESH_H_MAX, IF_REAL, "Max spacing during interval") +}; + +IFcardInfo XMSHinfo = { + "x.mesh", + "Location of mesh lines", + NUMELEMS(MESHpTable), + MESHpTable, + + XMSHnewCard, + MESHparam, + NULL +}; + +IFcardInfo YMSHinfo = { + "y.mesh", + "Location of mesh lines", + NUMELEMS(MESHpTable), + MESHpTable, + + YMSHnewCard, + MESHparam, + NULL +}; + +int +XMSHnewCard(void **inCard, void *inModel) +{ + MESHcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( MESHcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->MESHnextCard = (MESHcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENxMeshes; + if (!tmpCard) { /* First in list */ + model->GENxMeshes = newCard; + } else { + /* Go to end of list */ + while (tmpCard->MESHnextCard) tmpCard = tmpCard->MESHnextCard; + /* And add new card */ + tmpCard->MESHnextCard = newCard; + } + return(OK); +} + +int +YMSHnewCard(void **inCard, void *inModel) +{ + MESHcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( MESHcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->MESHnextCard = (MESHcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENyMeshes; + if (!tmpCard) { /* First in list */ + model->GENyMeshes = newCard; + } else { + /* Go to end of list */ + while (tmpCard->MESHnextCard) tmpCard = tmpCard->MESHnextCard; + /* And add new card */ + tmpCard->MESHnextCard = newCard; + } + return(OK); +} + +int +MESHparam(int param, IFvalue *value, void *inCard) +{ + MESHcard *card = (MESHcard *)inCard; + + switch (param) { + case MESH_LOCATION: + card->MESHlocation = value->rValue; + card->MESHlocationGiven = TRUE; + break; + case MESH_WIDTH: + card->MESHwidth = value->rValue; + card->MESHwidthGiven = TRUE; + break; + case MESH_H_START: + card->MESHhStart = value->rValue; + card->MESHhStartGiven = TRUE; + break; + case MESH_H_END: + card->MESHhEnd = value->rValue; + card->MESHhEndGiven = TRUE; + break; + case MESH_H_MAX: + card->MESHhMax = value->rValue; + card->MESHhMaxGiven = TRUE; + break; + case MESH_RATIO: + card->MESHratio = value->rValue; + card->MESHratioGiven = TRUE; + break; + case MESH_NUMBER: + card->MESHnumber = value->iValue; + card->MESHnumberGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/meshset.c b/src/ciderlib/input/meshset.c new file mode 100644 index 000000000..5fbccefe2 --- /dev/null +++ b/src/ciderlib/input/meshset.c @@ -0,0 +1,1302 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ +/********** +Mesh Setup & Query Routines. +**********/ + +/* Imports */ +#include "ngspice.h" +#include "cktdefs.h" +#include "meshdefs.h" +#include "meshext.h" +#include "gendev.h" +#include "sperror.h" +#include "suffix.h" + +/* Local Constants */ +#define CMP_TOL 1.0e-9 /* Tolerance on (double) comparisons */ +#define RAT_TOL 1.0e-6 /* Error allowed in ratio calc's */ +#define RAT_LIM 50 /* Maximum number of ratio iterations */ +#define UM_TO_CM 1.0e-4 /* Micron to centimeter conversion */ + +/* Forward Declarations */ +static int oneSideSpacing( double, double, double, + double *, int * ); +static int twoSideSpacing( double, double, double, double, + double *, double *, int *, int * ); +static int maxLimSpacing( double, double, double, double, + double *, int *, int * ); +static int oneSideRatio( double, double, double *, int ); +static int twoSideRatio( double, double, double, double *, int, int ); +static int MESHspacing( MESHcard *, double *, double *, int *, int *, int * ); + +/* END OF HEADER */ + + +/* + * Name: MESHmkArray + * Purpose: Turn a coordinate list into a coordinate array. + * Formals: < I > coordList: a sorted list of all the coordinates + * < I > numCoords: the length of the listi, if 0 find it + * Returns: a (double) array of those coordinates, with length in a[0] + * Users: routines used to create the final mesh + * Calls: (none) + */ +double * +MESHmkArray(MESHcoord *coordList, int numCoords) +{ + double *array = NULL; + MESHcoord *coord; + + if ( numCoords <= 0 ) { + numCoords = 0; + for ( coord = coordList; coord != NIL(MESHcoord); coord = coord->next ) { + numCoords++; + } + } + if ( numCoords != 0 ) { + XALLOC( array, double, 1 + numCoords ); + numCoords = 0; + array[ 0 ] = (double) numCoords; + numCoords = 1; + for ( coord = coordList; coord != NIL(MESHcoord); coord = coord->next ) { + array[ numCoords++ ] = coord->location; + } + return array; + } + else { + return NIL(double); + } + /* NOTREACHED */ +} + + +/* + * Name: MESHiBounds + * Purpose: Find the minimum and maximum indices in a mesh list. + * Formals: < I > coordList: a sorted list of all the coordinates + * < O > ixMin: the minimum index + * < O > ixMax: the maximum index + * Returns: (none) + * Users: routines wanting to fine the ends of a mesh + * Calls: (none) + */ +void +MESHiBounds(MESHcoord *coordList, int *ixMin, int *ixMax) +{ + MESHcoord *last; + + if (coordList) { + *ixMin = coordList->number; + for ( last = coordList; last->next != NIL(MESHcoord); last = last->next ); + *ixMax = last->number; + } + else { + *ixMin = *ixMax = -1; + } +} + + +/* + * Name: MESHlBounds + * Purpose: Find the minimum and maximum locations in a mesh list. + * Formals: < I > coordList: a sorted list of all the coordinates + * < O > lcMin: the minimum location + * < O > lcMax: the maximum location + * Returns: (none) + * Users: routines wanting to find the ends of a mesh + * Calls: (none) + */ +void +MESHlBounds(MESHcoord *coordList, double *lcMin, double *lcMax) +{ + MESHcoord *last; + + if (coordList) { + *lcMin = coordList->location; + for ( last = coordList; last->next != NIL(MESHcoord); last = last->next ); + *lcMax = last->location; + } + else { + *lcMin = *lcMax = 0.0; + } +} + + +/* + * Name: MESHlocate + * Purpose: Finds the index of the MESHcoord nearest to a location. + * Formals: < I > coordList: a sorted list of all available coordinates + * < I > location: the location to find + * Returns: index / -1 (list empty) + * Users: routines that convert distances to indices + * Calls: (none) + */ +int +MESHlocate(MESHcoord *coordList, double location) +{ + MESHcoord *coord, *prevCoord = NIL(MESHcoord); + int index; + +/* Find the coordinates which flank the location. */ + for ( coord = coordList; coord != NIL(MESHcoord); coord = coord->next ) { + if ( coord->location > location ) break; + prevCoord = coord; + } + +/* Get the index. */ + if (prevCoord && coord) { + if ( location <= (prevCoord->location + coord->location) / 2.0 ) { + index = prevCoord->number; + } + else { + index = coord->number; + } + } + else if ( coord ) { + index = coord->number; + } + else if ( prevCoord ) { + index = prevCoord->number; + } + else { + index = -1; + } + return( index ); +} + + + +/* + * Name: MESHcheck + * Purpose: Checks a list of mesh cards for input errors. + * Formals: cardList: pointer to head of linked list of MESHcard's + * < I > dim: 'x', 'y' or 'z' dimension + * Returns: OK / E_PRIVATE + * Users: setup routines + * Calls: error-message handler + */ +int +MESHcheck(char dim, MESHcard *cardList) +{ + MESHcard *card; + int cardNum = 0; + double locStart = 0.0, locEnd; + double ratio; + int error = OK; + char errBuf[512]; + + if ( cardList == NIL(MESHcard) ) { + sprintf( errBuf, + "%c.mesh card list is empty", + dim ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + locEnd = locStart; + return( E_PRIVATE ); + } + + for ( card = cardList; card != NIL(MESHcard); card = card->MESHnextCard ) { + cardNum++; + +/* Am I trying to find number of nodes directly & indirectly? */ + if (card->MESHnumberGiven && card->MESHratioGiven) { + sprintf( errBuf, + "%c.mesh card %d uses both number and ratio - number ignored", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_INFO, errBuf, NIL(IFuid) ); + + card->MESHnumberGiven = FALSE; + } + +/* Will I be able to locate endpoints? */ + if (!card->MESHlocationGiven && !card->MESHwidthGiven) { + sprintf( errBuf, + "%c.mesh card %d has no distances", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + locEnd = locStart; + error = E_PRIVATE; + } + else if (card->MESHlocationGiven && card->MESHwidthGiven) { + sprintf( errBuf, + "%c.mesh card %d uses both location and width - location ignored", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_INFO, errBuf, NIL(IFuid) ); + + card->MESHlocationGiven = FALSE; + locEnd = locStart + card->MESHwidth; + } + else if (card->MESHlocationGiven) { + locEnd = card->MESHlocation; + if (cardNum == 1) locStart = locEnd; + } + else { + locEnd = locStart + card->MESHwidth; + } + +/* Are the endpoints in the wrong order? */ + if ( locEnd - locStart < - CMP_TOL ) { + sprintf( errBuf, + "%c.mesh card %d uses negative width", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + +/* Are the endpoints too close together? */ + else if ( (locEnd - locStart <= CMP_TOL) ) { + if ( !(cardNum == 1 && locStart == locEnd) ) { + sprintf( errBuf, + "%c.mesh card %d has negligible width - ignored", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_INFO, errBuf, NIL(IFuid) ); + locStart = locEnd; + } + } + +/* Is the ratio out of the acceptable range? */ + if (card->MESHratioGiven) { + ratio = card->MESHratio; + } + else { + ratio = 1.0; + } + if ((ratio < 1.0) || (ratio > 10.0)) { + sprintf( errBuf, + "%c.mesh card %d has ratio out of range - reset to 1.0", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_INFO, errBuf, NIL(IFuid) ); + ratio = 1.0; + } + +/* Check sizes of h.start, h.end and h.max. */ + if ((card->MESHhStartGiven && (card->MESHhStart <= 0.0)) || + (card->MESHhEndGiven && (card->MESHhEnd <= 0.0)) || + (card->MESHhMaxGiven && (card->MESHhMax <= 0.0))) { + sprintf( errBuf, + "%c.mesh card %d wants to use a non-positive spacing", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + +/* Is the max spacing being used improperly? */ + if (card->MESHhMaxGiven && ( + ( card->MESHhStartGiven && card->MESHhEndGiven) || + (!card->MESHhStartGiven && !card->MESHhEndGiven))) { + sprintf( errBuf, + "%c.mesh card %d needs to use one of h.start or h.end with h.max", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + else if (card->MESHhMaxGiven && card->MESHhStartGiven) { + if (card->MESHhStart > card->MESHhMax) { + sprintf( errBuf, + "%c.mesh card %d wants h.start > h.max", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + else { + card->MESHhEnd = card->MESHhMax; + } + } + else if (card->MESHhMaxGiven && card->MESHhEndGiven) { + if (card->MESHhEnd > card->MESHhMax) { + sprintf( errBuf, + "%c.mesh card %d wants h.end > h.max", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + else { + card->MESHhStart = card->MESHhMax; + } + } + +/* Return now if anything has failed. */ + if (error) return(error); + +/* Note: at this point we still aren't sure whether node numbers are OK. */ + +/* Fill-in newly computed information. */ + card->MESHlocStart = locStart; + card->MESHlocEnd = locEnd; + card->MESHratio = ratio; + +/* Advance current location. */ + locStart = locEnd; + } + return(OK); +} + + +/* + * Name: geomSum + * Purpose: Computes the sum of n terms of a geometric series. + * Formals: < I > r: ratio of one term to the next + * < I > n: number of terms to sum + * Returns: sum / 0.0 + * Users: spacing routines + * Calls: pow + */ +static double +geomSum(double r, double n) +{ + double sum; + + if ((r < 0.0) || (n <= 0.0)) { + sum = 0.0; + } + else if (r == 0.0) { + sum = 1.0; + } + else { + if (ABS(r - 1.0) < 1.0e-4) { + sum = n * (1.0 + (n - 1.0)*(r - 1.0)/2.0); + } + else { + sum = (1.0 - pow(r,n))/(1.0 - r); + } + } + return( sum ); +} + + +/* + * Name: addCoord + * Purpose: add a new coordinate to the tail of a linked list + * Formals: head: head of linked list + * tail: tail of linked list + * < I > number: node number of coordinate + * < I > location: location of coordinate + * Returns: OK / E_NOMEM + * Users: MESHsetup + * Calls: memory allocator + */ +static int +addCoord(MESHcoord **head, MESHcoord **tail, int number, double location) +{ + MESHcoord *newCoord; + + if (*head == NIL(MESHcoord)) { + RALLOC( *tail, MESHcoord, 1 ); + newCoord = *head = *tail; + } + else { + RALLOC( (*tail)->next, MESHcoord, 1 ); + newCoord = *tail = (*tail)->next; + } + newCoord->next = NIL(MESHcoord); + newCoord->number = number; + newCoord->location = location * UM_TO_CM; + return(OK); +} + + +/* + * Name: MESHsetup + * Purpose: Converts a list of input MESHcard's to a list of MESHcoord's. + * Expansion is performed so that node numbers in the final list + * increase by one from coordinate to coordinate. The list + * will grow until the input ends or a bad card is found. + * Formals: < I > dim: 'x', 'y', or 'z' dimension + * cardList: the list of input cards + * < O > coordList: the final list of coordinates + * < O > numCoords: the number of coords in coordList + * Returns: OK / E_PRIVATE + * Users: numerical device setup routines + * Calls: MESHcheck, MESHspacing, error-message handler + */ +int +MESHsetup(char dim, MESHcard *cardList, MESHcoord **coordList, int *numCoords) +{ + MESHcard *card; + MESHcoord *endCoord; + int cardNum = 0; + int i, totCoords, numStart=1, numEnd = 0, nspStart, nspEnd, nspMax, nspLeft; + double locStart, locEnd = 0.0, location, space; + double hStart, hEnd, hMax, hBig; + double ratStart, ratEnd; + int error = OK; + char errBuf[512]; + +/* Initialize list of coordinates. */ + *coordList = endCoord = NIL(MESHcoord); + *numCoords = totCoords = 0; + +/* Check the card list. */ + if ((error = MESHcheck( dim, cardList ))) return( error ); + +/* Print info header. */ +#ifdef NOTDEF + fprintf( stdout, " %c.Mesh Card Information\n", toupper(dim) ); + fprintf( stdout, "-------------------------\n" ); + fprintf( stdout, " %3s %3s %3s %9s %9s %9s %9s %9s %9s\n", + "n.s", "n.m", "n.e", "l.e", "h.s", "h.e", "h.m", "r.s", "r.e" ); +#endif + + for ( card = cardList; card != NIL(MESHcard); card = card->MESHnextCard ) { + cardNum++; + locStart = card->MESHlocStart; + locEnd = card->MESHlocEnd; + + if (locEnd == locStart) { /* This card has no width. */ +/* First update the node number. */ + if (card->MESHnumberGiven) { + if (card->MESHlocationGiven) { /* Absolute node number given */ + numEnd = card->MESHnumber; + if (cardNum == 1) numStart = numEnd; + } + else { /* Number of spaces instead */ + numEnd = numStart + card->MESHnumber; + } + } +/* Are node numbers in the wrong order? */ + if ( numEnd < numStart ) { + sprintf( errBuf, + "%c.mesh card %d has out-of-order node numbers ( %d > %d )", + dim, cardNum, numStart, numEnd ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + else { /* This card has some width. */ +/* First update the node number. */ + if (card->MESHnumberGiven) { /* Uniform mesh */ + if (card->MESHlocationGiven) { /* Absolute node number given */ + numEnd = card->MESHnumber; + if (cardNum == 1) numStart = numEnd; + nspStart = numEnd - numStart; + } + else { /* Number of spaces instead */ + nspStart = card->MESHnumber; + numEnd = numStart + nspStart; + } + ratStart = 1.0; + ratEnd = 0.0; + nspEnd = 0; + nspMax = 0; + if ( nspStart > 0 ) { + card->MESHhStart = (locEnd - locStart) / (double)nspStart; + card->MESHhEnd = 0.0; + } + } + else { /* Nonuniform mesh */ + error = MESHspacing( card, &ratStart, &ratEnd, + &nspStart, &nspMax, &nspEnd ); + if (error) { + sprintf( errBuf, + "%c.mesh card %d can't be spaced automatically", + dim, cardNum ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + return( error ); + } + else { + numEnd = numStart + nspStart + nspMax + nspEnd; + } + } +/* Are the node numbers properly ordered? */ + if ( numEnd <= numStart ) { + sprintf( errBuf, + "%c.mesh card %d results in out-of-order node numbers ( %d > %d )", + dim, cardNum, numStart, numEnd ); + SPfrontEnd->IFerror( ERR_FATAL, errBuf, NIL(IFuid) ); + error = E_PRIVATE; + } + else { +/* Create the new MESHcoord's. */ + hStart = card->MESHhStart; + hEnd = card->MESHhEnd; + hMax = card->MESHhMax; + hBig = 0.0; + +/* Generate the first coord in this section */ + location = locStart; + error = addCoord( coordList, &endCoord, ++totCoords, location ); + if (error) return(error); + +/* Generate new coords for the starting section. */ + nspLeft = nspStart + nspMax + nspEnd; + if ( nspStart != 0 ) { + hBig = MAX( hBig, hStart*pow( ratStart, (double) (nspStart - 1) ) ); + space = hStart; + for ( i = 0; (i < nspStart)&&(nspLeft > 1); i++, nspLeft-- ) { + location += space; + space *= ratStart; + error = addCoord( coordList, &endCoord, ++totCoords, location ); + if (error) return(error); + } + } + +/* Generate new coords for the maximum section. */ + if ( nspMax != 0 ) { + hBig = MAX( hBig, hMax ); + space = hMax; + for ( i = 0; (i < nspMax)&&(nspLeft > 1); i++, nspLeft-- ) { + location += space; + error = addCoord( coordList, &endCoord, ++totCoords, location ); + if (error) return(error); + } + } + +/* Generate new coords for the ending section. */ + if ( nspEnd != 0 ) { + hBig = MAX( hBig, hEnd*pow( ratEnd, (double) (nspEnd - 1) ) ); + space = hEnd * pow( ratEnd, (double) (nspEnd - 1) ); + for ( i = 0; (i < nspEnd)&&(nspLeft > 1); i++, nspLeft-- ) { + location += space; + space /= ratEnd; + error = addCoord( coordList, &endCoord, ++totCoords, location ); + if (error) return(error); + } + } +#ifdef NOTDEF + fprintf( stdout, " %9.5f\n", locStart ); + fprintf( stdout, + " %3d %3d %3d %9.5f %9.5f %9.5f %9.5f %9.5f\n", + nspStart, nspMax, nspEnd, + hStart, hEnd, hBig, ratStart, ratEnd ); +#endif + } + } + +/* Return now if anything has failed. */ + if (error) return(error); + +/* Advance the node number. */ + numStart = numEnd; + } + +/* + * If the mesh is not empty, then the loop above has exited before + * adding the final coord to the list. So we need to do that now. + */ + if (*coordList != NIL(MESHcoord)) { + error = addCoord( coordList, &endCoord, ++totCoords, locEnd ); + if (error) return(error); +#ifdef NOTDEF + fprintf( stdout, " %9.5f\n", locEnd ); +#endif + } +#ifdef NOTDEF + fprintf( stdout, "\n" ); +#endif + *numCoords = totCoords; + return(OK); +} + + + +/* + * Name: MESHspacing + * Purpose: Find ratios, spacings, and node numbers for a mesh span. + * Formals: < I > card: the input card for this span + * < O > rS: ratio found for spacings at start of span + * < O > rE: ratio found for spacings at end of span + * < O > nS: number of start spaces + * < O > nM: number of max spaces + * < O > nE: number of end spaces + * Returns: OK / E_PRIVATE + * Users: MESHsetup + * Calls: oneSideSpacing, twoSideSpacing, maxLimSpacing + */ +static int +MESHspacing(MESHcard *card, double *rS, double *rE, int *nS, int *nM, int *nE) +{ + int error = OK; + int hStartGiven = card->MESHhStartGiven; + int hEndGiven = card->MESHhEndGiven; + int hMaxGiven = card->MESHhMaxGiven; + double hS = card->MESHhStart; + double hE = card->MESHhEnd; + double hM = card->MESHhMax; + double rW = card->MESHratio; /* The ratio wanted */ + double width; + + width = card->MESHlocEnd - card->MESHlocStart; + +/* Call subsidiary routine depending on how flags are set. */ + if (!hStartGiven && hEndGiven && !hMaxGiven ) { +/* End section only */ + error = oneSideSpacing( width, hE, rW, rE, nE ); + *nM = *nS = 0; + *rS = 0.0; + } + else if ( hStartGiven && !hEndGiven && !hMaxGiven ) { +/* Start section only */ + error = oneSideSpacing( width, hS, rW, rS, nS ); + *nM = *nE = 0; + *rE = 0.0; + } + else if ( hStartGiven && hEndGiven && !hMaxGiven ) { +/* Both a start and an end section */ + error = twoSideSpacing( width, hS, hE, rW, rS, rE, nS, nE ); + *nM = 0; + } + else if ( hStartGiven && !hEndGiven && hMaxGiven ) { +/* Limited size in end section */ + error = maxLimSpacing( width, hS, hM, rW, rS, nS, nM ); + *nE = 0; + *rE = 1.0; + } + else if (!hStartGiven && hEndGiven && hMaxGiven ) { +/* Limited size in start section */ + error = maxLimSpacing( width, hE, hM, rW, rE, nE, nM ); + *nS = 0; + *rS = 1.0; + } + else if ( hStartGiven && hEndGiven && hMaxGiven ) { +/* Limited size somewhere in the middle */ + /* NOT IMPLEMENTED */ + /* + error = midLimSpacing( width, hS, hE, hM, rW, rS, rE, nS, nE, nM ); + */ + error = E_PRIVATE; + } + else { +/* Illegal situations */ + error = E_PRIVATE; + } + + return( error ); +} + + + +/* + * Name: stepsInSpan + * Purpose: Finds the number of steps needed to go a given distance + * while increasing each step by a given ratio. + * Formals: < I > width: size of total distance + * < I > spacing: size of initial step + * < O > ratio: increase with each step + * Returns: number of steps + * Users: spacing routines + * Calls: log + */ +static double +stepsInSpan(double width, double spacing, double ratio) +{ + double nSpaces; + +/* Handle ratios near 1.0 specially. */ + if ( ABS(ratio - 1.0) < 1.0e-4 ) { + nSpaces = (width/spacing); + } + else { + nSpaces = (log(1.0-width*(1.0-ratio)/spacing)/log(ratio)); + } + return(nSpaces); +} + + +/* + * Name: oneSideSpacing + * Purpose: Find compatible number of spaces and ratio when the spacing + * is constrained at one end of a span. + * Formals: < I > width: width of the span + * < I > spacing: spacing constraint + * < I > rWanted: ideal ratio of one spacing to the next + * < O > rFound: actual ratio discovered + * < O > nFound: number of spaces found + * Returns: OK / E_PRIVATE + * Users: MESHspacing + * Calls: oneSideRatio, stepsInSpan + */ +static int +oneSideSpacing(double width, double spacing, double rWanted, double *rFound, + int *nFound) +{ + int nSpaces; /* Number of spaces */ + double rTemp1, rTemp2; /* For temporarily calc'ed ratios */ + char errBuf[80]; + +/* Make sure we can take at least one step. */ + if ( width < spacing ) { + sprintf( errBuf, + "one-sided spacing can't find an acceptable solution\n"); + SPfrontEnd->IFerror( ERR_WARNING, errBuf, NIL(IFuid) ); + *rFound = 0.0; + *nFound = 0; + return(E_PRIVATE); + } + + nSpaces = (int)stepsInSpan( width, spacing, rWanted ); + +/* Check to see whether a flat span is acceptable. */ + if ( ABS(nSpaces*spacing - width) < 1.0e-3*spacing ) { + *rFound = 1.0; + *nFound = nSpaces; + return( OK ); + } + else if ( ABS((nSpaces+1)*spacing - width) < 1.0e-3*spacing ) { + *rFound = 1.0; + *nFound = nSpaces + 1; + return( OK ); + } + +/* Too much error involved in flat span means we have to ramp up. */ + rTemp1 = rTemp2 = rWanted; + oneSideRatio( width, spacing, &rTemp1, nSpaces ); + oneSideRatio( width, spacing, &rTemp2, nSpaces+1 ); + if ( (rTemp1 == 0.0) && (rTemp2 == 0.0) ) { + sprintf( errBuf, + "one-sided spacing can't find an acceptable solution\n"); + SPfrontEnd->IFerror( ERR_WARNING, errBuf, NIL(IFuid) ); + *rFound = 0.0; + *nFound = 0; + return(E_PRIVATE); + } + else if (rTemp1 == 0.0) { + *rFound = rTemp2; + *nFound = nSpaces + 1; + } + else if (rTemp2 == 0.0) { + *rFound = rTemp1; + *nFound = nSpaces; + } + else if (ABS(rWanted-rTemp2) < 4.0*ABS(rWanted-rTemp1)) { + *rFound = rTemp2; + *nFound = nSpaces + 1; + } + else { + *rFound = rTemp1; + *nFound = nSpaces; + } + return(OK); +} + + + +/* + * Name: oneSideRatio + * Purpose: Compute the unique ratio 'r' which satisfies the following + * constraint: w = hs*(1-r^ns)/(1-r) + * Formals: < I > w : width of a span + * < I > hs: step at one end of the span + * argRatio: ratio found, contains initial guess at entry + * < I > ns: number of spaces to use in the span + * Returns: OK / E_PRIVATE + * Users: oneSideSpacing, maxLimSpacing + * Calls: error-message handler + */ +static int +oneSideRatio(double w, double hs, double *argRatio, int ns) +{ + double funcLow, funcUpp, func; + double ratLow, ratUpp, ratio = *argRatio; + double dns = (double)ns; + int i; + +/* Get lower bound on solution. */ + ratLow = 0.0; + funcLow = hs - w; + if ((funcLow > 0.0) || ((funcLow < 0.0)&&(ns <= 1))) { + *argRatio = 0.0; + return(E_PRIVATE); + } + +/* Find upper bound on solution. */ + ratUpp = ratio; + do { + ratUpp += 0.2; + funcUpp = hs*geomSum(ratUpp, dns) - w; + } while (funcUpp < 0.0); + +/* Do bisections to find new ratio. */ + for ( i=0; i < RAT_LIM; i++ ) { + ratio = ratLow + 0.5 * (ratUpp - ratLow); + func = hs*geomSum(ratio, dns) - w; + if ((func == 0.0) || (ratUpp - ratLow < RAT_TOL)) break; + + funcLow = hs*geomSum(ratLow, dns) - w; + if (funcLow*func > 0.0) { + ratLow = ratio; + } + else { + ratUpp = ratio; + } + } + + if (i == RAT_LIM) { /* No solution found */ + *argRatio = 0.0; + return(E_PRIVATE); + } + else { + *argRatio = ratio; + return(OK); + } +} + + + +/* Name: quadRoots + * Purpose: Find real roots of a quadratic equation if they exist. + * Formals: < I > a, b, c: coefficients in ax^2+bx+c + * < O > rp: the root using the positive sqrt value + * < O > rn: the root using the negative sqrt value + * Returns: TRUE / FALSE + * Users: general + * Calls: sqrt + */ +static int +quadRoots(double a, double b, double c, double *rp, double *rn) +{ + double d; /* Discriminant */ + double f; /* Root factor */ + + if (a == 0.0) return(FALSE); + + if (b == 0.0) { + d = -c/a; + if (d >= 0.0) { + *rn = - (*rp = sqrt(d)); + } + else { + return(FALSE); + } + } + else { + d = 1.0 - 4*a*c/(b*b); + if (d >= 0.0) { + f = (1.0 + sqrt(d))/2.0; + *rp = - (b*f)/a; + *rn = - c/(b*f); + } + else { + return(FALSE); + } + } + return(TRUE); +} + + +/* + * Name: twoSideSpacing + * Purpose: Find a compatible set of ratios and node numbers when the + * spacing is constrained at both ends of a span. + * Formals: < I > width: size the span + * < I > hStart: spacing at start of span + * < I > hEnd: spacing at end of span + * < I > rWanted: desired ratio of spacings + * < O > rSfound: ratio found for start of span + * < O > rEfound: ratio found for end of span + * < O > nSfound: number of start spaces + * < O > nEfound: number of end spaces + * Returns: OK / E_PRIVATE + * Users: MESHspacing + * Calls: twoSideRatio, error-message handler + */ +static int +twoSideSpacing(double width, double hStart, double hEnd, double rWanted, + double *rSfound, double *rEfound, int *nSfound, int *nEfound) +{ + int nSpaceS; /* Number of spaces at the start */ + int nSpaceE; /* Number of spaces at the end */ + int nSpaceT; /* Number of spaces total */ + double dSpaceS; /* Exact value of nSpaceS */ + double dSpaceE; /* Exact value of nSpaceE */ + double dSpaceT; /* Exact value of nSpaceT */ + double dDiff; /* Difference between dSpaceS & dSpaceE */ + double remaining; /* Length of span between hs and he */ + double rTempS, rTempE; /* For temporarily calc'ed ratios */ + double hsLast, heLast; /* Used to ensure ratio is valid */ + double rConnect, rMin, rMax; /* " */ + double hMax, hMin; /* Max and min between hStart and hEnd */ + double tmp; + int i,j; /* Indices for searching for best ratio */ + int solnFound; /* For partial search termination */ + int solnError; /* For partial search termination */ + int nSaveS = 0; /* Saves best solution so far */ + int nSaveE = 0; /* " */ + double rSaveS = 0.0; /* " */ + double rSaveE = 0.0; /* " */ + char errBuf[80]; + +/* + * It's an error if there isn't enough width to fit in both spaces. + */ + remaining = width - (hStart + hEnd); + if (remaining < 0.0) { + sprintf( errBuf, + "two-sided spacing can't find an acceptable solution\n"); + SPfrontEnd->IFerror( ERR_WARNING, errBuf, NIL(IFuid) ); + *rSfound = *rEfound = 0.0; + *nSfound = *nEfound = 0; + return(E_PRIVATE); + } + +/* Adjust ratio wanted to acceptable limits, and find number of extra spaces + * needed to bring the smaller ratio up to the size of the bigger one. + */ + hMax = MAX( hStart, hEnd ); + hMin = MIN( hStart, hEnd ); + + if ( hMax == hMin ) { + dDiff = 0.0; + } + else { +/* Does a solution exist if we allow the number of spaces to take on + * a non-integral value? + * If not, then adjust the ratio to lie within acceptable bounds. + * Since the choice of whether or not to require a peak in the plot + * of "spacing vs number" is arbitrary, both cases are checked, and + * the one that gives the closest answer to the original ratio + * is chosen. The function quadRoots is used to find limits for the + * ratio in the peaked case. The unpeaked case can find a lower + * bound more easily. + */ + if (quadRoots( hMax, hMax - width, remaining, &rTempS, &rTempE )) { + rWanted = MIN(rWanted, rTempS); + rTempS = 1.0 + (hMax - hMin)/(width - hMax); + rWanted = MAX(rWanted, rTempS); + if ((rWanted != rTempS) && (rTempE > rWanted)) { + if (ABS(rWanted - rTempE) < 4.0*ABS(rWanted - rTempS)) { + rWanted = rTempE; + } + else { + rWanted = rTempS; + } + } + } + else { /* Complex roots */ + rTempS = 1.0 + (hMax - hMin)/(width - hMax); + rWanted = MAX(rWanted, rTempS); + } + dDiff = log(hMax/hMin)/log(rWanted); + dDiff *= ( hStart < hEnd ) ? -1.0 : 1.0; + } + +/* Find the number of spaces at the start and at the end. */ +/* Handle ratio near 1.0 carefully. */ + if ( ABS(rWanted - 1.0) < 1.0e-4 ) { + dSpaceS = (width - dDiff*hEnd)/(hStart+hEnd); + } + else { + tmp = (hStart+hEnd-width+width*rWanted)/ + (hStart+hEnd*pow(rWanted,dDiff)); + dSpaceS = log(tmp)/log(rWanted); + } + dSpaceE = dSpaceS + dDiff; + dSpaceT = dSpaceS + dSpaceE; + +/* Search until an acceptable solution is found. Some + * cases may be repeated, but no harm is done. + */ + for (i = 0; i <= 1; i++) { + nSpaceT = (int)dSpaceT + i; +/* Guess a starting point which is guaranteed to have a solution. */ + nSpaceS = MIN( nSpaceT - 1, MAX( 4, (int) dSpaceS) ); + nSpaceE = nSpaceT - nSpaceS; + + solnFound = solnError = FALSE; + while ( !solnFound ) { + +/* Take care of special cases first. */ + if ((nSpaceE <= 0) || (nSpaceS <= 0)) { + solnError = TRUE; + } + else if (nSpaceT == 2) { /* Check for exact fit */ + if (ABS(remaining) < 1.0e-3*hMax ) { + rTempS = hEnd / hStart; + rTempE = 1.0 / rTempS; + nSpaceS = nSpaceE = 1; + } + else { + solnError = TRUE; + } + } + else if (nSpaceT == 3) { /* Trivial to solve */ + if (remaining > 0.0) { + rTempS = remaining / hStart; + rTempE = remaining / hEnd; + nSpaceS = 2; /* Always put middle space at start */ + nSpaceE = 1; + } + else { + solnError = TRUE; + } + } + else { /* Finally, the general case */ + if (remaining > 0.0) { + rTempS = rWanted; + twoSideRatio( width, hStart, hEnd, &rTempS, nSpaceS, nSpaceE ); + rTempE = rTempS; + } + else { + solnError = TRUE; + } + } + + if ( solnError ) + break; /* while loop */ + +/* Check whether the ratio discovered is good or not. */ + hsLast = hStart*pow(rTempS, (double)nSpaceS-1.0); + heLast = hEnd*pow(rTempE, (double)nSpaceE-1.0); + rConnect = heLast/hsLast; + if ( rConnect < 1.0/rTempE - RAT_TOL ) { + nSpaceS--; + nSpaceE++; + } + else if ( rConnect > rTempS + RAT_TOL ) { + nSpaceS++; + nSpaceE--; + } + else { + solnFound = TRUE; +/* Save if this solution is better than the previous one. */ + if (ABS(rWanted - rTempS) <= ABS(rWanted - rSaveS)) { + rSaveS = rTempS; + rSaveE = rTempE; + nSaveS = nSpaceS; + nSaveE = nSpaceE; + } + } + } + } + +/* Prepare return values. */ + if (rSaveS == 0.0) { + sprintf( errBuf, + "two-sided spacing can't find an acceptable solution\n"); + SPfrontEnd->IFerror( ERR_WARNING, errBuf, NIL(IFuid) ); + *rSfound = *rEfound = 0.0; + *nSfound = *nEfound = 0; + return(E_PRIVATE); + } + else { + *rSfound = rSaveS; + *rEfound = rSaveE; + *nSfound = nSaveS; + *nEfound = nSaveE; + return(OK); + } +} + + + +/* + * Name: twoSideRatio + * Purpose: Finds the unique ratio 'r' which satisfies the + * constraint: + * w = hs*(1-r^ns)/(1-r) + he*(1-r^ne)/(1-r) + * Formals: < I > w: size of a span + * < I > hs: spacing at start of span + * < I > he: spacing at end of span + * argRatio: returns r, contains initial guess for r + * < I > ns: number of steps to take at start + * < I > ne: number of steps to take at end + * Returns: OK / E_PRIVATE + * Users: twoSideSpacing + * Calls: error-message handler + */ +static int +twoSideRatio(double w, double hs, double he, double *argRatio, int ns, + int ne) +{ + double funcLow, funcUpp, func; + double ratLow, ratUpp, ratio = *argRatio; + double dns = (double)ns; + double dne = (double)ne; + int i; + +/* Get lower bound on solution. */ + ratLow = 0.0; + funcLow = hs + he - w; + if ((funcLow > 0.0) || ((funcLow < 0.0) && (MAX(ns,ne) <= 1))) { + *argRatio = 0.0; + return(E_PRIVATE); + } + +/* Find upper bound on solution. */ + ratUpp = ratio; + do { + ratUpp += 0.2; + funcUpp = hs*geomSum(ratUpp, dns) + he*geomSum(ratUpp, dne) - w; + } while (funcUpp < 0.0); + +/* Do bisections to find new ratio. */ + for ( i=0; i < RAT_LIM; i++ ) { + ratio = ratLow + 0.5 * (ratUpp - ratLow); + func = hs*geomSum(ratio, dns) + he*geomSum(ratio, dne) - w; + if ((func == 0.0) || (ratUpp - ratLow < RAT_TOL)) break; + + funcLow = hs*geomSum(ratLow, dns) + he*geomSum(ratLow, dne) - w; + if (funcLow*func > 0.0) { + ratLow = ratio; + } + else { + ratUpp = ratio; + } + } + + if (i == RAT_LIM) { /* No solution found */ + *argRatio = 0.0; + return(E_PRIVATE); + } + else { + *argRatio = ratio; + return(OK); + } +} + + + +/* + * Name: maxLimSpacing + * Purpose: Find compatible number of spaces and ratio when the spacing + * is constrained at start of a span, and has to be smaller + * than a user-specified maximum at the end. + * Formals: < I > width: width of the span + * < I > hStart: spacing constraint at one end + * < I > hMax: maximum spacing allowable + * < I > rWanted: ideal ratio of one spacing to the next + * < O > rFound: actual ratio discovered + * < O > nSfound: number of start spaces + * < O > nMfound: number of maximum spaces + * Returns: OK / E_PRIVATE + * Users: MESHspacing + * Calls: oneSideRatio, stepsInSpan + */ +static int +maxLimSpacing(double width, double hStart, double hMax, double rWanted, + double *rFound, int *nSfound, int *nMfound) +{ + int nSpaceS; /* Number of spaces at the start */ + int nSpaceM; /* Number of spaces with maximum size */ + int nSpaceT; /* Total number of spaces */ + double dSpaceS; /* Exact number of start spaces needed */ + double dSpaceM; /* Exact number of max spaces needed */ + double dSpaceT; /* Exact total number of spaces */ + double rTempS; /* For temporarily calc'ed ratio */ + double remaining; /* Width taken up by start spaces */ + double rConnect, hBiggest = 0.0; /* Used to ensure ratio is valid */ + double rSaveS = 0.0; /* Saves best solution so far */ + int nSaveS = 0; /* " */ + int nSaveM = 0; /* " */ + int i, j; /* Searching indices */ + int solnFound; /* For partial search termination */ + int solnError; /* For partial search termination */ + char errBuf[80]; + +/* Compute the ratio needed to exactly go from hStart to hMax + * in the given width. If hMax is really big, then we know + * the spaces can't exceed it. + */ + if ( width > hMax ) { + rTempS = 1.0 + (hMax - hStart)/(width - hMax); + } + else { + rTempS = 1.0e6; /* an impossibly large value */ + } + + if (rWanted <= rTempS) { /* Spacings stay below maximum allowed */ + dSpaceS = stepsInSpan( width, hStart, rWanted ); + dSpaceM = 0.0; + } + else { +/* Find number of spaces needed to increase hStart to hMax. */ + dSpaceS = log(hMax/hStart)/log(rWanted); + remaining = hStart*geomSum(rWanted, dSpaceS); + dSpaceM = (width - remaining)/hMax; + } + dSpaceT = dSpaceS + dSpaceM; + +/* Search until an acceptable solution is found. Some + * cases may be repeated, but no harm is done. + */ + for (i = 0; i <= 1; i++) { + nSpaceT = (int)dSpaceT + i; +/* Guess a starting point which is guaranteed to have a solution. */ + nSpaceS = MIN( nSpaceT, MAX( 3, (int) dSpaceS) ); + nSpaceM = nSpaceT - nSpaceS; + + solnFound = solnError = FALSE; + while ( !solnFound ) { + remaining = width - hMax*nSpaceM; + +/* Test for the various special cases first. */ + if ( nSpaceM < 0 || nSpaceS <= 0 ) { + solnError = TRUE; + } + else if (nSpaceS == 1) { /* check for exact fit */ + if ( ABS(remaining - hStart) < 1.0e-3*hStart ) { + hBiggest = hStart; + if (nSpaceM == 0) { + rTempS = 1.0; + } + else { + rTempS = hMax / hStart; + } + } + else { + solnError = TRUE; + } + } + else if (nSpaceS == 2) { /* Easy to solve */ + if (remaining > hStart) { + hBiggest = remaining - hStart; + rTempS = hBiggest / hStart; + } + else { + solnError = TRUE; + } + } + else { + if (remaining > hStart) { + rTempS = rWanted; + oneSideRatio( remaining, hStart, &rTempS, nSpaceS ); + hBiggest = hStart*pow(rTempS, (double)nSpaceS - 1.0); + } + else { + solnError = TRUE; + } + } + + if ( solnError ) + break; /* while loop */ + + rConnect = hMax / hBiggest; + if ( rConnect < 1.0 - RAT_TOL ) { + nSpaceS--; + nSpaceM++; + } + else if ( (rConnect > rTempS + RAT_TOL) && nSpaceM != 0 ) { + nSpaceS++; + nSpaceM--; + } + else { + solnFound = TRUE; +/* Save solution if it's better. */ + if ( (rTempS >= 1.0 - RAT_TOL) + && ABS(rWanted - rTempS) <= ABS(rWanted - rSaveS)) { + rSaveS = rTempS; + nSaveS = nSpaceS; + nSaveM = nSpaceM; + } + } + } + } + +/* Prepare return values. */ + if (rSaveS == 0.0) { + sprintf( errBuf, + "max-limited spacing can't find an acceptable solution\n"); + SPfrontEnd->IFerror( ERR_WARNING, errBuf, NIL(IFuid) ); + *rFound = 0.0; + *nSfound = *nMfound = 0; + return(E_PRIVATE); + } + else { + *rFound = rSaveS; + *nSfound = nSaveS; + *nMfound = nSaveM; + return(OK); + } +} diff --git a/src/ciderlib/input/method.c b/src/ciderlib/input/method.c new file mode 100644 index 000000000..a44fdfbaf --- /dev/null +++ b/src/ciderlib/input/method.c @@ -0,0 +1,114 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "numenum.h" +#include "methdefs.h" +#include "sperror.h" +#include "devdefs.h" +#include "suffix.h" + + +extern int METHnewCard(void**,void*); +extern int METHparam(int,IFvalue*,void*); + + +IFparm METHpTable[] = { + IP("devtol", METH_DABSTOL, IF_REAL, "Absolute tolerance on device equations"), + IP("dabstol", METH_DABSTOL, IF_REAL, "Absolute tolerance on device equations"), + IP("dreltol", METH_DRELTOL, IF_REAL, "Relative tolerance on device equations"), + IP("onecarrier",METH_ONEC, IF_FLAG, "Solve for majority carriers only"), + IP("ac.analysis",METH_ACANAL, IF_STRING, "AC solution technique"), + IP("frequency",METH_OMEGA, IF_REAL, "AC default frequency"), + IP("nomobderiv",METH_NOMOBDERIV,IF_FLAG, "Ignore mobility derivatives"), + IP("itlim", METH_ITLIM, IF_INTEGER, "Iteration limit"), + IP("voltpred",METH_VOLTPRED, IF_FLAG, "Perform DC voltage prediction") +}; + +IFcardInfo METHinfo = { + "method", + "Specify parameters and types of simulation methods", + NUMELEMS(METHpTable), + METHpTable, + + METHnewCard, + METHparam, + NULL +}; + +int +METHnewCard(void **inCard, void *inModel) +{ + METHcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + tmpCard = model->GENmethods; + if (!tmpCard) { /* First in list */ + newCard = NEW( METHcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->METHnextCard = (METHcard *)NULL; + *inCard = (void *)newCard; + model->GENmethods = newCard; + } else { /* Only one card of this type allowed */ + *inCard = (void *)tmpCard; + } + return(OK); +} + +int +METHparam(int param, IFvalue *value, void *inCard) +{ + METHcard *card = (METHcard *)inCard; + + switch (param) { + case METH_DABSTOL: + card->METHdabstol = value->rValue; + card->METHdabstolGiven = TRUE; + break; + case METH_DRELTOL: + card->METHdreltol = value->rValue; + card->METHdreltolGiven = TRUE; + break; + case METH_OMEGA: + card->METHomega = 2.0 * M_PI * value->rValue; + card->METHomegaGiven = TRUE; + break; + case METH_ONEC: + card->METHoneCarrier = value->iValue; + card->METHoneCarrierGiven = TRUE; + break; + case METH_NOMOBDERIV: + card->METHmobDeriv = !(value->iValue); + card->METHmobDerivGiven = TRUE; + break; + case METH_ACANAL: + if ( cinprefix( value->sValue, "direct", 1 ) ) { + card->METHacAnalysisMethod = DIRECT; + card->METHacAnalysisMethodGiven = TRUE; + } else if ( cinprefix( value->sValue, "sor", 1 ) ) { + card->METHacAnalysisMethod = SOR; + card->METHacAnalysisMethodGiven = TRUE; + } + break; + case METH_ITLIM: + card->METHitLim = value->iValue; + card->METHitLimGiven = TRUE; + break; + case METH_VOLTPRED: + card->METHvoltPred = value->iValue; + card->METHvoltPredGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/mobility.c b/src/ciderlib/input/mobility.c new file mode 100644 index 000000000..86cd0ea15 --- /dev/null +++ b/src/ciderlib/input/mobility.c @@ -0,0 +1,210 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "numenum.h" +#include "mobdefs.h" +#include "sperror.h" +#include "devdefs.h" +#include "suffix.h" + +extern int MOBnewCard(void**,void*); +extern int MOBparam(int,IFvalue*,void*); + + + +IFparm MOBpTable[] = { + IP("material",MOB_MATERIAL, IF_INTEGER, "Material index"), + IP("electron",MOB_ELEC, IF_FLAG, "Electron mobility flag"), + IP("hole", MOB_HOLE, IF_FLAG, "Hole mobility flag"), + IP("majority",MOB_MAJOR, IF_FLAG, "Majority carrier flag"), + IP("minority",MOB_MINOR, IF_FLAG, "Minority carrier flag"), + IP("mumax", MOB_MUMAX, IF_REAL, "Maximum bulk mobility"), + IP("mumin", MOB_MUMIN, IF_REAL, "Minimum bulk mobility"), + IP("ntref", MOB_NTREF, IF_REAL, "Ionized impurity ref. conc."), + IP("ntexp", MOB_NTEXP, IF_REAL, "Ionized impurity exponent"), + IP("vsat", MOB_VSAT, IF_REAL, "Saturation velocity"), + IP("vwarm", MOB_VWARM, IF_REAL, "Warm carrier ref. velocity"), + IP("mus", MOB_MUS, IF_REAL, "Initial surface mobility"), + IP("ec.a", MOB_EC_A, IF_REAL, "Surf. mobility critical field"), + IP("ec.b", MOB_EC_B, IF_REAL, "Surf. mobility critical field #2"), + IP("concmodel",MOB_CONC_MOD, IF_STRING, "Concentration dep. model"), + IP("fieldmodel",MOB_FIELD_MOD,IF_STRING, "Driving field dep. model"), + IP("init", MOB_INIT, IF_FLAG, "Initialize with defaults") +}; + +IFcardInfo MOBinfo = { + "mobility", + "Specify parameters and types of mobility models", + NUMELEMS(MOBpTable), + MOBpTable, + + MOBnewCard, + MOBparam, + NULL +}; + +int +MOBnewCard(void **inCard, void *inModel) +{ + MOBcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + newCard = NEW( MOBcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->MOBnextCard = (MOBcard *)NULL; + *inCard = (void *)newCard; + + tmpCard = model->GENmobility; + if (!tmpCard) { /* First in list */ + model->GENmobility = newCard; + } else { + /* Go to end of list */ + while (tmpCard->MOBnextCard) tmpCard = tmpCard->MOBnextCard; + /* And add new card */ + tmpCard->MOBnextCard = newCard; + } + return(OK); +} + +int +MOBparam(int param, IFvalue *value, void *inCard) +{ + MOBcard *card = (MOBcard *)inCard; + + switch (param) { + case MOB_MATERIAL: + card->MOBmaterial = value->iValue; + card->MOBmaterialGiven = TRUE; + break; + case MOB_ELEC: + if ( value->iValue ) { + card->MOBcarrier = ELEC; + card->MOBcarrierGiven = TRUE; + } else { + if ( card->MOBcarrier == ELEC ) { + card->MOBcarrier = -1; + card->MOBcarrierGiven = FALSE; + } + } + break; + case MOB_HOLE: + if ( value->iValue ) { + card->MOBcarrier = HOLE; + card->MOBcarrierGiven = TRUE; + } else { + if ( card->MOBcarrier == HOLE ) { + card->MOBcarrier = -1; + card->MOBcarrierGiven = FALSE; + } + } + break; + case MOB_MAJOR: + if ( value->iValue ) { + card->MOBcarrType = MAJOR; + card->MOBcarrTypeGiven = TRUE; + } else { + if ( card->MOBcarrType == MAJOR ) { + card->MOBcarrType = -1; + card->MOBcarrTypeGiven = FALSE; + } + } + break; + case MOB_MINOR: + if ( value->iValue ) { + card->MOBcarrType = MINOR; + card->MOBcarrTypeGiven = TRUE; + } else { + if ( card->MOBcarrType == MINOR ) { + card->MOBcarrType = -1; + card->MOBcarrTypeGiven = FALSE; + } + } + break; + case MOB_MUMAX: + card->MOBmuMax = value->rValue; + card->MOBmuMaxGiven = TRUE; + break; + case MOB_MUMIN: + card->MOBmuMin = value->rValue; + card->MOBmuMinGiven = TRUE; + break; + case MOB_NTREF: + card->MOBntRef = value->rValue; + card->MOBntRefGiven = TRUE; + break; + case MOB_NTEXP: + card->MOBntExp = value->rValue; + card->MOBntExpGiven = TRUE; + break; + case MOB_VSAT: + card->MOBvSat = value->rValue; + card->MOBvSatGiven = TRUE; + break; + case MOB_VWARM: + card->MOBvWarm = value->rValue; + card->MOBvWarmGiven = TRUE; + break; + case MOB_MUS: + card->MOBmus = value->rValue; + card->MOBmusGiven = TRUE; + break; + case MOB_EC_A: + card->MOBecA = value->rValue; + card->MOBecAGiven = TRUE; + break; + case MOB_EC_B: + card->MOBecB = value->rValue; + card->MOBecBGiven = TRUE; + break; + case MOB_CONC_MOD: + if ( cinprefix( value->sValue, "ct", 1 ) ) { + card->MOBconcModel = CT; + card->MOBconcModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "ar", 1 ) ) { + card->MOBconcModel = AR; + card->MOBconcModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "uf", 1 ) ) { + card->MOBconcModel = UF; + card->MOBconcModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "sg", 1 ) ) { + card->MOBconcModel = SG; + card->MOBconcModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "ga", 1 ) ) { + card->MOBconcModel = GA; + card->MOBconcModelGiven = TRUE; + } + break; + case MOB_FIELD_MOD: + if ( cinprefix( value->sValue, "ct", 1 ) ) { + card->MOBfieldModel = CT; + card->MOBfieldModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "ar", 1 ) ) { + card->MOBfieldModel = AR; + card->MOBfieldModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "sg", 1 ) ) { + card->MOBfieldModel = SG; + card->MOBfieldModelGiven = TRUE; + } else if ( cinprefix( value->sValue, "ga", 1 ) ) { + card->MOBfieldModel = GA; + card->MOBfieldModelGiven = TRUE; + } + break; + case MOB_INIT: + card->MOBinit = value->iValue; + card->MOBinitGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/mobset.c b/src/ciderlib/input/mobset.c new file mode 100644 index 000000000..562d2a34f --- /dev/null +++ b/src/ciderlib/input/mobset.c @@ -0,0 +1,157 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "numconst.h" +#include "numenum.h" +#include "mobdefs.h" +#include "material.h" +#include "sperror.h" +#include "suffix.h" + +extern int MOBcheck( MOBcard *, MaterialInfo * ); +extern int MOBsetup( MOBcard *, MaterialInfo * ); + +/* + * Name: MOBcheck + * Purpose: checks a list of MOBcards for input errors + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int +MOBcheck(MOBcard *cardList, MaterialInfo *matlList) +{ + MOBcard *card; + MATLmaterial *matl; + int cardNum = 0; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(MOBcard); card = card->MOBnextCard ) { + cardNum++; + if (!card->MOBmaterialGiven) { + sprintf( ebuf, + "mobility card %d is missing a material index", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } else { + /* Make sure the material exists */ + for ( matl = matlList; matl != NIL(MATLmaterial); matl = matl->next ) { + if ( card->MOBmaterial == matl->id ) { + break; + } + } + if (matl == NIL(MATLmaterial)) { + sprintf( ebuf, + "mobility card %d specifies a non-existent material", + cardNum ); + SPfrontEnd->IFerror( ERR_WARNING, ebuf, NIL(IFuid) ); + error = E_PRIVATE; + } + } + if (!card->MOBcarrierGiven) { + card->MOBcarrier = ELEC; + } + if (!card->MOBcarrTypeGiven) { + card->MOBcarrType = MAJOR; + } + if (!card->MOBinitGiven) { + card->MOBinit = FALSE; + } + +/* Return now if anything has failed */ + if (error) return(error); + + } + return(OK); +} + + + +/* + * Name: MOBsetup + * Purpose: setup the mobility model parameters + * Formals: cardList: list of cards to setup + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: MOBcheck + */ +int +MOBsetup(MOBcard *cardList, MaterialInfo *materialList) +{ + MOBcard *card; + MATLmaterial *matl; + int error; + +/* Check the card list */ + if ((error = MOBcheck( cardList, materialList ))) return( error ); + + for ( card = cardList; card != NIL(MOBcard); card = card->MOBnextCard ) { + + /* Find the right material */ + for ( matl = materialList; matl != NIL(MATLmaterial); matl = matl->next ) { + if ( card->MOBmaterial == matl->id ) { + break; + } + } + + /* Default models depend on previous value */ + if (!card->MOBconcModelGiven) { + card->MOBconcModel = matl->concModel; + } + if (!card->MOBfieldModelGiven) { + card->MOBfieldModel = matl->fieldModel; + } + + /* Load in default values if desired */ + if ( card->MOBinitGiven ) { + MOBdefaults( matl, card->MOBcarrier, card->MOBcarrType, + card->MOBconcModel, card->MOBfieldModel ); + } + + /* Override defaults */ + if ( card->MOBconcModelGiven ) { + matl->concModel = card->MOBconcModel; + } + if ( card->MOBfieldModelGiven ) { + matl->fieldModel = card->MOBfieldModel; + } + if ( card->MOBmuMaxGiven ) { + matl->muMax[card->MOBcarrier][card->MOBcarrType] = card->MOBmuMax; + } + if ( card->MOBmuMinGiven ) { + matl->muMin[card->MOBcarrier][card->MOBcarrType] = card->MOBmuMin; + } + if ( card->MOBntRefGiven ) { + matl->ntRef[card->MOBcarrier][card->MOBcarrType] = card->MOBntRef; + } + if ( card->MOBntExpGiven ) { + matl->ntExp[card->MOBcarrier][card->MOBcarrType] = card->MOBntExp; + } + if ( card->MOBvSatGiven ) { + matl->vSat[card->MOBcarrier] = card->MOBvSat; + } + if ( card->MOBvWarmGiven ) { + matl->vWarm[card->MOBcarrier] = card->MOBvWarm; + } + if ( card->MOBmusGiven ) { + matl->mus[card->MOBcarrier] = card->MOBmus; + } + if ( card->MOBecAGiven ) { + matl->thetaA[card->MOBcarrier] = 1.0 / MAX( card->MOBecA, 1e-20 ); + } + if ( card->MOBecBGiven ) { + matl->thetaB[card->MOBcarrier] = 1.0 / MAX( ABS(card->MOBecB), 1e-20 ); + matl->thetaB[card->MOBcarrier] *= matl->thetaB[card->MOBcarrier]; + matl->thetaB[card->MOBcarrier] *= SGN( card->MOBecB ); + } + } + return( OK ); +} diff --git a/src/ciderlib/input/models.c b/src/ciderlib/input/models.c new file mode 100644 index 000000000..1d9bb8958 --- /dev/null +++ b/src/ciderlib/input/models.c @@ -0,0 +1,133 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "modldefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int MODLnewCard(void**,void*); +extern int MODLparam(int,IFvalue*,void*); + + +IFparm MODLpTable[] = { + IP("bgn", MODL_BGNW, IF_FLAG, "Bandgap narrowing"), + IP("bgnw", MODL_BGNW, IF_FLAG, "Bandgap narrowing"), + IP("tmpmob", MODL_TEMPMOB, IF_FLAG, "Temp-dependent mobility"), + IP("tempmob", MODL_TEMPMOB, IF_FLAG, "Temp-dependent mobility"), + IP("conmob", MODL_CONCMOB, IF_FLAG, "Conc-dependent mobility"), + IP("concmob", MODL_CONCMOB, IF_FLAG, "Conc-dependent mobility"), + IP("fldmob", MODL_FIELDMOB, IF_FLAG, + "Lateral-field-dependent mobility"), + IP("fieldmob",MODL_FIELDMOB, IF_FLAG, + "Lateral-field-dependent mobility"), + IP("trfmob",MODL_TRANSMOB, IF_FLAG, + "Transverse-field-dependent surface mobility"), + IP("transmob",MODL_TRANSMOB, IF_FLAG, + "Transverse-field-dependent surface mobility"), + IP("srfmob", MODL_SURFMOB, IF_FLAG, "Activate surface mobility"), + IP("surfmob", MODL_SURFMOB, IF_FLAG, "Activate surface mobility"), + IP("matchmob",MODL_MATCHMOB, IF_FLAG, + "Matching low-field surface/bulk mobilities"), + IP("srh", MODL_SRH, IF_FLAG, "SRH recombination"), + IP("consrh", MODL_CONCTAU, IF_FLAG, "Conc-dependent SRH recomb"), + IP("conctau", MODL_CONCTAU, IF_FLAG, "Conc-dependent SRH recomb"), + IP("auger", MODL_AUGER, IF_FLAG, "Auger recombination"), + IP("avalanche",MODL_AVAL, IF_FLAG, "Local avalanche generation") +}; + +IFcardInfo MODLinfo = { + "models", + "Specify which physical models should be simulated", + NUMELEMS(MODLpTable), + MODLpTable, + + MODLnewCard, + MODLparam, + NULL +}; + +int +MODLnewCard(void **inCard, void *inModel) +{ + MODLcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + tmpCard = model->GENmodels; + if (!tmpCard) { /* First in list */ + newCard = NEW( MODLcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->MODLnextCard = (MODLcard *)NULL; + *inCard = (void *)newCard; + model->GENmodels = newCard; + } else { /* Only one card of this type allowed */ + *inCard = (void *)tmpCard; + } + return(OK); +} + +int +MODLparam(int param, IFvalue *value, void *inCard) +{ + MODLcard *card = (MODLcard *)inCard; + + switch (param) { + case MODL_BGNW: + card->MODLbandGapNarrowing = value->iValue; + card->MODLbandGapNarrowingGiven = TRUE; + break; + case MODL_TEMPMOB: + card->MODLtempDepMobility = value->iValue; + card->MODLtempDepMobilityGiven = TRUE; + break; + case MODL_CONCMOB: + card->MODLconcDepMobility = value->iValue; + card->MODLconcDepMobilityGiven = TRUE; + break; + case MODL_TRANSMOB: + card->MODLtransDepMobility = value->iValue; + card->MODLtransDepMobilityGiven = TRUE; + break; + case MODL_FIELDMOB: + card->MODLfieldDepMobility = value->iValue; + card->MODLfieldDepMobilityGiven = TRUE; + break; + case MODL_SURFMOB: + card->MODLsurfaceMobility = value->iValue; + card->MODLsurfaceMobilityGiven = TRUE; + break; + case MODL_MATCHMOB: + card->MODLmatchingMobility = value->iValue; + card->MODLmatchingMobilityGiven = TRUE; + break; + case MODL_SRH: + card->MODLsrh = value->iValue; + card->MODLsrhGiven = TRUE; + break; + case MODL_CONCTAU: + card->MODLconcDepLifetime = value->iValue; + card->MODLconcDepLifetimeGiven = TRUE; + break; + case MODL_AUGER: + card->MODLauger = value->iValue; + card->MODLaugerGiven = TRUE; + break; + case MODL_AVAL: + card->MODLavalancheGen = value->iValue; + card->MODLavalancheGenGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/modlset.c b/src/ciderlib/input/modlset.c new file mode 100644 index 000000000..057d53d2e --- /dev/null +++ b/src/ciderlib/input/modlset.c @@ -0,0 +1,92 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "modldefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int MODLcheck( MODLcard * ); +extern int MODLsetup( MODLcard * ); + +/* + * Name: MODLcheck + * Purpose: checks a list of MODLcards for input errors, sets defaults + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines + * Calls: error message handler + */ +int + MODLcheck(MODLcard *cardList) +{ + MODLcard *card, *card2; + int cardNum = 0, cardNum2; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(MODLcard); card = card->MODLnextCard ) { + cardNum++; + + if ( !card->MODLbandGapNarrowingGiven ) { + card->MODLbandGapNarrowing = FALSE; + } + if ( !card->MODLtempDepMobilityGiven ) { + card->MODLtempDepMobility = FALSE; + } + if ( !card->MODLconcDepMobilityGiven ) { + card->MODLconcDepMobility = FALSE; + } + if ( !card->MODLfieldDepMobilityGiven ) { + card->MODLfieldDepMobility = FALSE; + } + if ( !card->MODLtransDepMobilityGiven ) { + card->MODLtransDepMobility = FALSE; + } + if ( !card->MODLsurfaceMobilityGiven ) { + card->MODLsurfaceMobility = FALSE; + } + if ( !card->MODLmatchingMobilityGiven ) { + card->MODLmatchingMobility = FALSE; + } + if ( !card->MODLsrhGiven ) { + card->MODLsrh = FALSE; + } + if ( !card->MODLconcDepLifetimeGiven ) { + card->MODLconcDepLifetime = FALSE; + } + if ( !card->MODLaugerGiven ) { + card->MODLauger = FALSE; + } + if ( !card->MODLavalancheGenGiven ) { + card->MODLavalancheGen = FALSE; + } + } + return(OK); +} + + + +/* + * Name: MODLsetup + * Purpose: setup the physical models used + * Formals: cardList: list of cards to setup + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: MODLcheck + */ +int + MODLsetup(MODLcard *cardList) +{ + int error; + + /* Check the card list */ + if ((error = MODLcheck( cardList ))) return( error ); + + /* Nothing else to do. */ + return( OK ); +} diff --git a/src/ciderlib/input/options.c b/src/ciderlib/input/options.c new file mode 100644 index 000000000..8601de509 --- /dev/null +++ b/src/ciderlib/input/options.c @@ -0,0 +1,163 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "optndefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +#define M_TO_CM 1.0e2 +#define M2_TO_CM2 (M_TO_CM * M_TO_CM) +#define UM_TO_CM 1.0e-4 +#define UM2_TO_CM2 (UM_TO_CM * UM_TO_CM) + +extern int OPTNnewCard(void**,void*); +extern int OPTNparam(int,IFvalue*,void*); + + + +IFparm OPTNpTable[] = { +/* Supported Types of Devices. Ideally should be automatically extracted */ + IP("resistor",OPTN_RESISTOR, IF_FLAG, "Resistor"), + IP("capacitor",OPTN_CAPACITOR, IF_FLAG, "Capacitor"), + IP("diode", OPTN_DIODE, IF_FLAG, "Diode"), + IP("bipolar", OPTN_BIPOLAR, IF_FLAG, "Bipolar Transistor"), + IP("bjt", OPTN_BIPOLAR, IF_FLAG, "Bipolar Transistor"), + IP("soibjt", OPTN_SOIBJT, IF_FLAG, "SOI Bipolar"), + IP("moscap", OPTN_MOSCAP, IF_FLAG, "MOS Capacitor"), + IP("mosfet", OPTN_MOSFET, IF_FLAG, "MOSFET"), + IP("soimos", OPTN_SOIMOS, IF_FLAG, "SOI MOSFET"), + IP("jfet", OPTN_JFET, IF_FLAG, "Junction FET"), + IP("mesfet", OPTN_MESFET, IF_FLAG, "MESFET"), +/* Various layout dimensions */ + IP("defa", OPTN_DEFA, IF_REAL, "Default Mask Area"), + IP("defw", OPTN_DEFW, IF_REAL, "Default Mask Width"), + IP("defl", OPTN_DEFL, IF_REAL, "Default Mask Length"), + IP("base.area",OPTN_BASE_AREA, IF_REAL, "1D BJT Base Area"), + IP("base.length",OPTN_BASE_LENGTH,IF_REAL, "1D BJT Base Length"), + IP("base.depth",OPTN_BASE_DEPTH, IF_REAL, "1D BJT Base Depth"), +/* Values */ + IP("tnom", OPTN_TNOM, IF_REAL, "Nominal Temperature"), +/* Device Initial Condition File */ + IP("ic.file", OPTN_IC_FILE, IF_STRING, "Initial condition file"), + IP("unique", OPTN_UNIQUE, IF_FLAG, "Generate unique filename") +}; + +IFcardInfo OPTNinfo = { + "options", + "Provide optional information and hints", + NUMELEMS(OPTNpTable), + OPTNpTable, + + OPTNnewCard, + OPTNparam, + NULL +}; + +int +OPTNnewCard(void **inCard, void *inModel) +{ + OPTNcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + tmpCard = model->GENoptions; + if (!tmpCard) { /* First in list */ + newCard = NEW( OPTNcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->OPTNnextCard = (OPTNcard *)NULL; + *inCard = (void *)newCard; + model->GENoptions = newCard; + } else { /* Only one card of this type allowed */ + *inCard = (void *)tmpCard; + } + return(OK); +} + +int +OPTNparam(int param, IFvalue *value, void *inCard) +{ + OPTNcard *card = (OPTNcard *)inCard; + + switch (param) { + case OPTN_RESISTOR: + card->OPTNdeviceType = OPTN_RESISTOR; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_CAPACITOR: + card->OPTNdeviceType = OPTN_CAPACITOR; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_DIODE: + card->OPTNdeviceType = OPTN_DIODE; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_MOSCAP: + card->OPTNdeviceType = OPTN_MOSCAP; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_BIPOLAR: + case OPTN_SOIBJT: /* XXX Treat SOI as normal */ + card->OPTNdeviceType = OPTN_BIPOLAR; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_MOSFET: + case OPTN_SOIMOS: /* XXX Treat SOI as normal */ + card->OPTNdeviceType = OPTN_MOSFET; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_JFET: + case OPTN_MESFET: /* XXX Treat MES as junction */ + card->OPTNdeviceType = OPTN_JFET; + card->OPTNdeviceTypeGiven = TRUE; + break; + case OPTN_DEFA: + card->OPTNdefa = value->rValue * M2_TO_CM2; + card->OPTNdefaGiven = TRUE; + break; + case OPTN_DEFW: + card->OPTNdefw = value->rValue * M_TO_CM; + card->OPTNdefwGiven = TRUE; + break; + case OPTN_DEFL: + card->OPTNdefl = value->rValue * M_TO_CM; + card->OPTNdeflGiven = TRUE; + break; + case OPTN_BASE_AREA: + card->OPTNbaseArea = value->rValue; + card->OPTNbaseAreaGiven = TRUE; + break; + case OPTN_BASE_LENGTH: + card->OPTNbaseLength = value->rValue * UM_TO_CM; + card->OPTNbaseLengthGiven = TRUE; + break; + case OPTN_BASE_DEPTH: + card->OPTNbaseDepth = value->rValue * UM_TO_CM; + card->OPTNbaseDepthGiven = TRUE; + break; + case OPTN_TNOM: + card->OPTNtnom = value->rValue; + card->OPTNtnomGiven = TRUE; + break; + case OPTN_IC_FILE: + card->OPTNicFile = value->sValue; + card->OPTNicFileGiven = TRUE; + break; + case OPTN_UNIQUE: + card->OPTNunique = value->iValue; + card->OPTNuniqueGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/outpset.c b/src/ciderlib/input/outpset.c new file mode 100644 index 000000000..4ef8e4dac --- /dev/null +++ b/src/ciderlib/input/outpset.c @@ -0,0 +1,148 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "cktdefs.h" +#include "numenum.h" +#include "outpdefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int OUTPcheck( OUTPcard * ); +extern int OUTPsetup( OUTPcard * ); + + +/* + * Name: OUTPcheck + * Purpose: checks a list of OUTPcards for input errors, sets defaults + * Formals: cardList: the list to check + * Returns: OK/E_PRIVATE + * Users: numerical device setup routines, output routines + * Calls: error message handler + */ +int + OUTPcheck(OUTPcard *cardList) +{ + OUTPcard *card, *card2; + int cardNum = 0, cardNum2; + int error = OK; + char ebuf[512]; /* error message buffer */ + + for ( card = cardList; card != NIL(OUTPcard); card = card->OUTPnextCard ) { + cardNum++; + + card->OUTPnumVars = -1; + + if ( !card->OUTPdcDebugGiven ) { + card->OUTPdcDebug = FALSE; + } + if ( !card->OUTPtranDebugGiven ) { + card->OUTPtranDebug = FALSE; + } + if ( !card->OUTPacDebugGiven ) { + card->OUTPacDebug = FALSE; + } + if ( !card->OUTPgeomGiven ) { + card->OUTPgeom = FALSE; + } + if ( !card->OUTPmeshGiven ) { + card->OUTPmesh = FALSE; + } + if ( !card->OUTPmaterialGiven ) { + card->OUTPmaterial = FALSE; + } + if ( !card->OUTPglobalsGiven ) { + card->OUTPglobals = FALSE; + } + if ( !card->OUTPstatsGiven ) { + card->OUTPstats = TRUE; + } + if ( !card->OUTProotFileGiven ) { + card->OUTProotFile = copy(""); + } + if ( !card->OUTPfileTypeGiven ) { + card->OUTPfileType = RAWFILE; + } + if ( !card->OUTPdopingGiven ) { + card->OUTPdoping = TRUE; + } + if ( !card->OUTPpsiGiven ) { + card->OUTPpsi = TRUE; + } + if ( !card->OUTPequPsiGiven ) { + card->OUTPequPsi = FALSE; + } + if ( !card->OUTPvacPsiGiven ) { + card->OUTPvacPsi = FALSE; + } + if ( !card->OUTPnConcGiven ) { + card->OUTPnConc = TRUE; + } + if ( !card->OUTPpConcGiven ) { + card->OUTPpConc = TRUE; + } + if ( !card->OUTPphinGiven ) { + card->OUTPphin = FALSE; + } + if ( !card->OUTPphipGiven ) { + card->OUTPphip = FALSE; + } + if ( !card->OUTPphicGiven ) { + card->OUTPphic = FALSE; + } + if ( !card->OUTPphivGiven ) { + card->OUTPphiv = FALSE; + } + if ( !card->OUTPeFieldGiven ) { + card->OUTPeField = TRUE; + } + if ( !card->OUTPjcGiven ) { + card->OUTPjc = FALSE; + } + if ( !card->OUTPjdGiven ) { + card->OUTPjd = TRUE; + } + if ( !card->OUTPjnGiven ) { + card->OUTPjn = TRUE; + } + if ( !card->OUTPjpGiven ) { + card->OUTPjp = TRUE; + } + if ( !card->OUTPjtGiven ) { + card->OUTPjt = FALSE; + } + if ( !card->OUTPuNetGiven ) { + card->OUTPuNet = FALSE; + } + if ( !card->OUTPmunGiven ) { + card->OUTPmun = FALSE; + } + if ( !card->OUTPmupGiven ) { + card->OUTPmup = FALSE; + } + } + return(OK); +} + + +/* + * Name: OUTPsetup + * Purpose: setup the output card + * Formals: cardList: list of cards to setup + * Returns: OK/E_PRIVATE + * Users: numerical devices + * Calls: OUTPcheck + */ +int + OUTPsetup(OUTPcard *cardList) +{ + int error; + + /* Check the card list */ + if ((error = OUTPcheck( cardList ))) return( error ); + + return( OK ); +} diff --git a/src/ciderlib/input/output.c b/src/ciderlib/input/output.c new file mode 100644 index 000000000..f51b8f499 --- /dev/null +++ b/src/ciderlib/input/output.c @@ -0,0 +1,241 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +Modified: 2001 Paolo Nenzi +**********/ + +#include "ngspice.h" +#include "numcards.h" +#include "numgen.h" +#include "numenum.h" +#include "outpdefs.h" +#include "devdefs.h" +#include "sperror.h" +#include "suffix.h" + +extern int OUTPnewCard(void**,void*); +extern int OUTPparam(int,IFvalue*,void*); + + +IFparm OUTPpTable[] = { +/* Debugging Flags */ + IP("all.debug",OUTP_ALL_DEBUG,IF_FLAG, "Debug All Analyses"), + IP("op.debug",OUTP_DC_DEBUG, IF_FLAG, "Debug DC/OP Analyses"), + IP("dc.debug",OUTP_DC_DEBUG, IF_FLAG, "Debug DC/OP Analyses"), + IP("tran.debug",OUTP_TRAN_DEBUG,IF_FLAG, "Debug TRAN Analysis"), + IP("ac.debug",OUTP_AC_DEBUG, IF_FLAG, "Debug AC/PZ Analyses"), + IP("pz.debug",OUTP_AC_DEBUG, IF_FLAG, "Debug AC/PZ Analyses"), +/* General Information */ + IP("geometry",OUTP_GEOM, IF_FLAG, "Geometric information"), + IP("mesh", OUTP_MESH, IF_FLAG, "Mesh information"), + IP("material",OUTP_MATERIAL, IF_FLAG, "Material information"), + IP("globals", OUTP_GLOBALS, IF_FLAG, "Global information"), + IP("statistics", OUTP_STATS, IF_FLAG, "Resource usage information"), + IP("resources", OUTP_STATS, IF_FLAG, "Resource usage information"), +/* Solution Information */ + IP("rootfile", OUTP_ROOTFILE, IF_STRING, "Root of output file names"), + IP("rawfile", OUTP_RAWFILE, IF_FLAG, "SPICE rawfile data format"), + IP("hdf", OUTP_HDF, IF_FLAG, "HDF data format"), + IP("doping", OUTP_DOPING, IF_FLAG, "Net doping"), + IP("psi", OUTP_PSI, IF_FLAG, "Potential"), + IP("equ.psi", OUTP_EQU_PSI, IF_FLAG, "Equilibrium potential"), + IP("vac.psi", OUTP_VAC_PSI, IF_FLAG, "Vacuum potential"), + IP("n.conc", OUTP_N_CONC, IF_FLAG, "Electron concentration"), + IP("electrons", OUTP_N_CONC, IF_FLAG, "Electron concentration"), + IP("p.conc", OUTP_P_CONC, IF_FLAG, "Hole concentration"), + IP("holes", OUTP_P_CONC, IF_FLAG, "Hole concentration"), + IP("phin", OUTP_PHIN, IF_FLAG, "Electron quasi-fermi potential"), + IP("qfn", OUTP_PHIN, IF_FLAG, "Electron quasi-fermi potential"), + IP("phip", OUTP_PHIP, IF_FLAG, "Hole quasi-fermi potential"), + IP("qfp", OUTP_PHIP, IF_FLAG, "Hole quasi-fermi potential"), + IP("phic", OUTP_PHIC, IF_FLAG, "Conduction band potential"), + IP("band.con",OUTP_PHIC, IF_FLAG, "Conduction band potential"), + IP("phiv", OUTP_PHIV, IF_FLAG, "Valence band potential"), + IP("band.val",OUTP_PHIV, IF_FLAG, "Valence band potential"), + IP("e.field", OUTP_E_FIELD, IF_FLAG, "Electric field"), + IP("jc", OUTP_J_C, IF_FLAG, "Conduction current density"), + IP("j.conduc",OUTP_J_C, IF_FLAG, "Conduction current density"), + IP("jd", OUTP_J_D, IF_FLAG, "Displacement current density"), + IP("j.disp", OUTP_J_D, IF_FLAG, "Displacement current density"), + IP("jn", OUTP_J_N, IF_FLAG, "Electron current density"), + IP("j.electr",OUTP_J_N, IF_FLAG, "Electron current density"), + IP("jp", OUTP_J_P, IF_FLAG, "Hole current density"), + IP("j.hole", OUTP_J_P, IF_FLAG, "Hole current density"), + IP("jt", OUTP_J_T, IF_FLAG, "Total current density"), + IP("j.total", OUTP_J_T, IF_FLAG, "Total current density"), + IP("unet", OUTP_U_NET, IF_FLAG, "Net recombination"), + IP("recomb", OUTP_U_NET, IF_FLAG, "Net recombination"), + IP("mun", OUTP_MUN, IF_FLAG, "Elctron mobility"), + IP("mob.elec",OUTP_MUN, IF_FLAG, "Electron mobility"), + IP("mup", OUTP_MUP, IF_FLAG, "Hole mobility"), + IP("mob.hole",OUTP_MUP, IF_FLAG, "Hole mobility") +}; + +IFcardInfo OUTPinfo = { + "output", + "Identify information to be output to user", + NUMELEMS(OUTPpTable), + OUTPpTable, + + OUTPnewCard, + OUTPparam, + NULL +}; + +int +OUTPnewCard(void **inCard, void *inModel) +{ + OUTPcard *tmpCard, *newCard; + GENnumModel *model = (GENnumModel *)inModel; + + tmpCard = model->GENoutputs; + if (!tmpCard) { /* First in list */ + newCard = NEW( OUTPcard ); + if (!newCard) { + *inCard = (void *)NULL; + return(E_NOMEM); + } + newCard->OUTPnextCard = (OUTPcard *)NULL; + *inCard = (void *)newCard; + model->GENoutputs = newCard; + } else { /* Only one card of this type allowed */ + *inCard = (void *)tmpCard; + } + return(OK); +} + +int +OUTPparam(int param, IFvalue *value, void *inCard) +{ + OUTPcard *card = (OUTPcard *)inCard; + + switch (param) { + case OUTP_ALL_DEBUG: + card->OUTPdcDebug = value->iValue; + card->OUTPdcDebugGiven = TRUE; + card->OUTPtranDebug = value->iValue; + card->OUTPtranDebugGiven = TRUE; + card->OUTPacDebug = value->iValue; + card->OUTPacDebugGiven = TRUE; + break; + case OUTP_DC_DEBUG: + card->OUTPdcDebug = value->iValue; + card->OUTPdcDebugGiven = TRUE; + break; + case OUTP_TRAN_DEBUG: + card->OUTPtranDebug = value->iValue; + card->OUTPtranDebugGiven = TRUE; + break; + case OUTP_AC_DEBUG: + card->OUTPacDebug = value->iValue; + card->OUTPacDebugGiven = TRUE; + break; + case OUTP_GEOM: + card->OUTPgeom = value->iValue; + card->OUTPgeomGiven = TRUE; + break; + case OUTP_MESH: + card->OUTPmesh = value->iValue; + card->OUTPmeshGiven = TRUE; + break; + case OUTP_MATERIAL: + card->OUTPmaterial = value->iValue; + card->OUTPmaterialGiven = TRUE; + break; + case OUTP_GLOBALS: + card->OUTPglobals = value->iValue; + card->OUTPglobalsGiven = TRUE; + break; + case OUTP_STATS: + card->OUTPstats = value->iValue; + card->OUTPstatsGiven = TRUE; + break; + case OUTP_ROOTFILE: + card->OUTProotFile = tilde_expand(value->sValue); + card->OUTProotFileGiven = TRUE; + break; + case OUTP_RAWFILE: + card->OUTPfileType = RAWFILE; + card->OUTPfileTypeGiven = TRUE; + break; + case OUTP_HDF: + return(E_UNSUPP); + break; + case OUTP_DOPING: + card->OUTPdoping = value->iValue; + card->OUTPdopingGiven = TRUE; + break; + case OUTP_PSI: + card->OUTPpsi = value->iValue; + card->OUTPpsiGiven = TRUE; + break; + case OUTP_EQU_PSI: + card->OUTPequPsi = value->iValue; + card->OUTPequPsiGiven = TRUE; + break; + case OUTP_VAC_PSI: + card->OUTPvacPsi = value->iValue; + card->OUTPvacPsiGiven = TRUE; + break; + case OUTP_N_CONC: + card->OUTPnConc = value->iValue; + card->OUTPnConcGiven = TRUE; + break; + case OUTP_P_CONC: + card->OUTPpConc = value->iValue; + card->OUTPpConcGiven = TRUE; + break; + case OUTP_PHIN: + card->OUTPphin = value->iValue; + card->OUTPphinGiven = TRUE; + break; + case OUTP_PHIP: + card->OUTPphip = value->iValue; + card->OUTPphipGiven = TRUE; + break; + case OUTP_PHIC: + card->OUTPphic = value->iValue; + card->OUTPphicGiven = TRUE; + break; + case OUTP_PHIV: + card->OUTPphiv = value->iValue; + card->OUTPphivGiven = TRUE; + break; + case OUTP_J_C: + card->OUTPjc = value->iValue; + card->OUTPjcGiven = TRUE; + break; + case OUTP_J_D: + card->OUTPjd = value->iValue; + card->OUTPjdGiven = TRUE; + break; + case OUTP_J_N: + card->OUTPjn = value->iValue; + card->OUTPjnGiven = TRUE; + break; + case OUTP_J_P: + card->OUTPjp = value->iValue; + card->OUTPjpGiven = TRUE; + break; + case OUTP_J_T: + card->OUTPjt = value->iValue; + card->OUTPjtGiven = TRUE; + break; + case OUTP_U_NET: + card->OUTPuNet = value->iValue; + card->OUTPuNetGiven = TRUE; + break; + case OUTP_MUN: + card->OUTPmun = value->iValue; + card->OUTPmunGiven = TRUE; + break; + case OUTP_MUP: + card->OUTPmup = value->iValue; + card->OUTPmupGiven = TRUE; + break; + default: + return(E_BADPARM); + break; + } + return(OK); +} diff --git a/src/ciderlib/input/readme b/src/ciderlib/input/readme new file mode 100644 index 000000000..79dab0de0 --- /dev/null +++ b/src/ciderlib/input/readme @@ -0,0 +1,6 @@ +Directory: input +---------------- +The files in this directory serve two purposes: + 1. Serve as collection point for numerical model parameters. + 2. Translate data structures containing raw input into + a form usable by the one- and two-dimensional device simulators. diff --git a/src/ciderlib/notes b/src/ciderlib/notes new file mode 100644 index 000000000..ff1168028 --- /dev/null +++ b/src/ciderlib/notes @@ -0,0 +1,9 @@ +1. Crucial Difference between Spice3f1 and Spice3e2: + The 'states' field in the ***instance structured has been moved. + Belongs below the node id's in Spice3e2. +2. Crucial Differences between Spice3f2 and Spice3f1: + Addition of DEVunSetup call to ***itf.h files + The 3f2 input parser can't handle arithmetic operators in strings. + A special call has been added in spiceitf/inpgval.c to specially + parse strings. Quotes (either "foobar" or 'foobar') are stripped + from the string. diff --git a/src/ciderlib/oned/Makefile.am b/src/ciderlib/oned/Makefile.am new file mode 100644 index 000000000..7dd86e0d6 --- /dev/null +++ b/src/ciderlib/oned/Makefile.am @@ -0,0 +1,23 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libcideroned.a + +libcideroned_a_SOURCES = \ + oneadmit.c \ + oneaval.c \ + onecond.c \ + onecont.c \ + onedest.c \ + onedopng.c \ + onefreez.c \ + onemesh.c \ + onepoiss.c \ + oneprint.c \ + oneproj.c \ + oneread.c \ + onesetup.c \ + onesolve.c + +EXTRA_DIST = notes readme +INCLUDES = -I$(top_srcdir)/src/include +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/ciderlib/oned/notes b/src/ciderlib/oned/notes new file mode 100644 index 000000000..6da21d283 --- /dev/null +++ b/src/ciderlib/oned/notes @@ -0,0 +1,36 @@ +Some comments on keeping track of where the solution is: + +There are 3 places that the internal state variables can be hiding: + 1. In the (psi,n,p) triple of a node (pNode). + 2. In the solution vectors (dcSolution, copiedSolution). + 3. In the state tables (pDevice->devStates[]). + + To access node variables use: + pNode->psi + pNode->nConc + pNode->pConc + + To access solution vectors use: + solution[ pNode->psiEqn ] + solution[ pNode->nEqn ] + solution[ pNode->pEqn ] + + To access state vector X (0,1,etc.) use: + *(pDevice->devStateX + pNode->nodePsi) + *(pDevice->devStateX + pNode->nodeN) + *(pDevice->devStateX + pNode->nodeP) + +There are several functions that copy from one form of these +to another: + storeInitialGuess: dcSolution <- nodevars + biasSolve: nodevars <- dcSolution (DC) + state0 <- nodevars <- dcSolution (Transient) + saveState: nodevars <- state1 (Transient) + +In addition the main device-level Newton iteration copies during +the computation of currents and derivatives: + state0 <- dcSolution + +and the function predict() does a forward prediction step using +the previous state: + nodevars <- predict(state1) (Transient) diff --git a/src/ciderlib/oned/oneadmit.c b/src/ciderlib/oned/oneadmit.c new file mode 100644 index 000000000..5950a1e68 --- /dev/null +++ b/src/ciderlib/oned/oneadmit.c @@ -0,0 +1,742 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +/* Functions to compute small-signal parameters of 1D devices */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "numconst.h" +#include "onedev.h" +#include "onemesh.h" +#include "complex.h" +#include "spMatrix.h" +#include "ifsim.h" + +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" + + +extern IFfrontEnd *SPfrontEnd; + + +/* + * mmhhh this may cause troubles + * Paolo Nenzi 2002 + */ + SPcomplex yAc; + + +/* Forward Declarations */ +SPcomplex *computeAdmittance(ONEnode *, BOOLEAN, double *, + double *, SPcomplex *); +BOOLEAN ONEsorSolve(ONEdevice *pDevice, double *xReal, + double *xImag, double omega); + +int +NUMDadmittance(ONEdevice *pDevice, double omega, SPcomplex *yd) +{ + ONEnode *pNode; + ONEelem *pElem; + ONEedge *pEdge; + int index, i; + double yReal, yImag; + double *solutionReal, *solutionImag; + SPcomplex yAc, cOmega; + SPcomplex *y; + BOOLEAN SORFailed; + double startTime; + + + /* Each time we call this counts as one AC iteration. */ + pDevice->pStats->numIters[STAT_AC] += 1; + + /* + * Change context names of solution vectors for ac analysis. + * dcDeltaSolution stores the real part and copiedSolution stores the + * imaginary part of the ac solution vector. + */ + solutionReal = pDevice->dcDeltaSolution; + solutionImag = pDevice->copiedSolution; + pDevice->solverType = SLV_SMSIG; + + /* use a normalized radian frequency */ + omega *= TNorm; + CMPLX_ASSIGN_VALUE(cOmega, 0.0, omega); + yReal = 0.0; + yImag = 0.0; + + if ((AcAnalysisMethod == SOR) || (AcAnalysisMethod == SOR_ONLY)) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + /* store the new rhs vector */ + pElem = pDevice->elemArray[pDevice->numNodes - 1]; + pNode = pElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pElem->epsRel * pElem->rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = ONEsorSolve(pDevice, solutionReal, solutionImag, omega); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yd, 0.0, 0.0); + return (AcAnalysisMethod); + } + } + if (AcAnalysisMethod == DIRECT) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* solve the system of equations directly */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + pElem = pDevice->elemArray[pDevice->numNodes - 1]; + pNode = pElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pElem->epsRel * pElem->rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + ONE_jacLoad(pDevice); + spSetComplex(pDevice->matrix); + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (pElem->elemType == SEMICON) { + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -0.5 * pElem->dx * omega); + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, 0.5 * pElem->dx * omega); + } + } + } + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + spFactor(pDevice->matrix); + pDevice->pStats->factorTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + } + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + pNode = pDevice->elemArray[1]->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc, -y->real, -y->imag); + CMPLX_ASSIGN(*yd, yAc); + CMPLX_MULT_SELF_SCALAR(*yd, GNorm * pDevice->area); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + return (AcAnalysisMethod); +} + + +int +NBJTadmittance(ONEdevice *pDevice, double omega, SPcomplex *yIeVce, + SPcomplex *yIcVce, SPcomplex *yIeVbe, SPcomplex *yIcVbe) +{ + ONEelem *pCollElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEelem *pElem; + ONEedge *pEdge; + ONEnode *pNode; + int index, i; + double area = pDevice->area; + double *solutionReal, *solutionImag; + BOOLEAN SORFailed; + SPcomplex *y; + SPcomplex cOmega, pIeVce, pIcVce, pIeVbe, pIcVbe; + double startTime; + + /* Each time we call this counts as one AC iteration. */ + pDevice->pStats->numIters[STAT_AC] += 1; + + /* + * change context names of solution vectors for ac analysis dcDeltaSolution + * stores the real part and copiedSolution stores the imaginary part of the + * ac solution vector + */ + solutionReal = pDevice->dcDeltaSolution; + solutionImag = pDevice->copiedSolution; + pDevice->solverType = SLV_SMSIG; + + /* use a normalized radian frequency */ + omega *= TNorm; + CMPLX_ASSIGN_VALUE(cOmega, 0.0, omega); + + if ((AcAnalysisMethod == SOR) || (AcAnalysisMethod == SOR_ONLY)) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + /* store the new rhs vector */ + pNode = pCollElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pCollElem->epsRel * pCollElem->rDx; + if (pCollElem->elemType == SEMICON) { + pEdge = pCollElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = ONEsorSolve(pDevice, solutionReal, solutionImag, omega); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + if (SORFailed && (AcAnalysisMethod == SOR)) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yIeVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIeVbe, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVbe, 0.0, 0.0); + return (AcAnalysisMethod); + } else { + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, -y->real, -y->imag); + pNode = pCollElem->pRightNode; + y = computeAdmittance(pNode, TRUE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, -y->real, -y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* load in the base contribution to the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + pNode = pBaseElem->pRightNode; + if (pNode->baseType == N_TYPE) { + pDevice->rhs[pNode->nEqn] = pNode->nConc * pNode->eg; + } else if (pNode->baseType == P_TYPE) { + pDevice->rhs[pNode->pEqn] = pNode->pConc * pNode->eg; + } else { + printf("projectBJTsolution: unknown base type\n"); + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = ONEsorSolve(pDevice, solutionReal, solutionImag, omega); + pDevice->pStats->solveTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + if (SORFailed && (AcAnalysisMethod == SOR)) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yIeVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIeVbe, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVbe, 0.0, 0.0); + return (AcAnalysisMethod); + } + } + } + if (AcAnalysisMethod == DIRECT) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + ONE_jacLoad(pDevice); + pNode = pCollElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pCollElem->epsRel * pCollElem->rDx; + if (pCollElem->elemType == SEMICON) { + pEdge = pCollElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + spSetComplex(pDevice->matrix); + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (pElem->elemType == SEMICON) { + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -0.5 * pElem->dx * omega); + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, 0.5 * pElem->dx * omega); + } + } + } + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + spFactor(pDevice->matrix); + pDevice->pStats->factorTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, -y->real, -y->imag); + pNode = pCollElem->pRightNode; + y = computeAdmittance(pNode, TRUE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, -y->real, -y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* load in the base contribution in the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + pNode = pBaseElem->pRightNode; + if (pNode->baseType == N_TYPE) { + pDevice->rhs[pNode->nEqn] = pNode->nConc * pNode->eg; + } else if (pNode->baseType == P_TYPE) { + pDevice->rhs[pNode->pEqn] = pNode->pConc * pNode->eg; + } else { + printf("\n BJTadmittance: unknown base type"); + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR: already done, no need to repeat. */ + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + } + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVbe, -y->real, -y->imag); + pNode = pCollElem->pRightNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVbe, -y->real, -y->imag); + + CMPLX_ASSIGN(*yIeVce, pIeVce); + CMPLX_ASSIGN(*yIcVce, pIcVce); + CMPLX_ASSIGN(*yIeVbe, pIeVbe); + CMPLX_ASSIGN(*yIcVbe, pIcVbe); + CMPLX_MULT_SELF_SCALAR(*yIeVce, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIeVbe, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIcVce, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIcVbe, GNorm * area); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + return (AcAnalysisMethod); +} + +BOOLEAN +ONEsorSolve(ONEdevice *pDevice, double *xReal, double *xImag, double omega) +{ + ONEnode *pNode; + ONEelem *pElem; + double wRelax = 1.0; /* SOR relaxation parameter */ + double *rhsSOR = pDevice->rhsImag; + int numEqns = pDevice->numEqns; + int numNodes = pDevice->numNodes; + BOOLEAN SORConverged = FALSE; + BOOLEAN SORFailed = FALSE; + int i, index, indexN, indexP, iterationNum; + double dx; + + + /* clear xReal, xImag arrays */ + for (index = 1; index <= numEqns; index++) { + xReal[index] = 0.0; + xImag[index] = 0.0; + } + + iterationNum = 1; + for (; (!SORConverged) &&(!SORFailed); iterationNum++) { + /* first setup the rhs for the real part */ + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = 0.0; + } + for (index = 1; index < numNodes; index++) { + pElem = pDevice->elemArray[index]; + dx = 0.5 * pElem->dx; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if ((pNode->nodeType != CONTACT) + && (pElem->elemType == SEMICON)) { + indexN = pNode->nEqn; + indexP = pNode->pEqn; + rhsSOR[indexN] -= dx * omega * xImag[indexN]; + rhsSOR[indexP] += dx * omega * xImag[indexP]; + } + } + } + /* now setup the rhs for the SOR equations */ + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] += pDevice->rhs[index]; + } + /* compute xReal(k+1). solution stored in rhsSOR */ + spSolve(pDevice->matrix, rhsSOR, rhsSOR, NIL(spREAL), NIL(spREAL)); + + /* modify solution when wRelax is not 1 */ + if (wRelax != 1) { + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = (1 - wRelax) * xReal[index] + + wRelax * rhsSOR[index]; + } + } + if (iterationNum > 1) { + SORConverged = hasSORConverged(xReal, rhsSOR, numEqns); + } + /* copy real solution into xReal */ + for (index = 1; index <= numEqns; index++) { + xReal[index] = rhsSOR[index]; + } + + /* now compute the imaginary part of the solution, xImag */ + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = 0.0; + } + for (index = 1; index < numNodes; index++) { + pElem = pDevice->elemArray[index]; + dx = 0.5 * pElem->dx; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if ((pNode->nodeType != CONTACT) + && (pElem->elemType == SEMICON)) { + indexN = pNode->nEqn; + indexP = pNode->pEqn; + rhsSOR[indexN] += dx * omega * xReal[indexN]; + rhsSOR[indexP] -= dx * omega * xReal[indexP]; + } + } + } + /* compute xImag(k+1) */ + spSolve(pDevice->matrix, rhsSOR, rhsSOR, + NIL(spREAL), NIL(spREAL)); + /* modify solution when wRelax is not 1 */ + if (wRelax != 1) { + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = (1 - wRelax) * xImag[index] + + wRelax * rhsSOR[index]; + } + } + if (iterationNum > 1) { + SORConverged = SORConverged && hasSORConverged(xImag, rhsSOR, numEqns); + } + /* copy imag solution into xImag */ + for (index = 1; index <= numEqns; index++) { + xImag[index] = rhsSOR[index]; + } + if (ONEacDebug) + printf("SOR iteration number = %d\n", iterationNum); + if (iterationNum > 4) { + SORFailed = TRUE; + } + } + return (SORFailed); +} + +void +NUMDys(ONEdevice *pDevice, SPcomplex *s, SPcomplex *yd) +{ + ONEnode *pNode; + ONEelem *pElem; + ONEedge *pEdge; + int index, i; + double *solutionReal, *solutionImag; + SPcomplex temp, cOmega; + SPcomplex *y; + + + /* + * change context names of solution vectors for ac analysis dcDeltaSolution + * stores the real part and copiedSolution stores the imaginary part of the + * ac solution vector + */ + solutionReal = pDevice->dcDeltaSolution; + solutionImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + CMPLX_MULT_SCALAR(cOmega, *s, TNorm); + + /* solve the system of equations directly */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + ONE_jacLoad(pDevice); + pElem = pDevice->elemArray[pDevice->numNodes - 1]; + pNode = pElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pElem->epsRel * pElem->rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + spSetComplex(pDevice->matrix); + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (pElem->elemType == SEMICON) { + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + CMPLX_MULT_SCALAR(temp, cOmega, 0.5 * pElem->dx); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } + } + } + } + + + spFactor(pDevice->matrix); + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(*yd, -y->real, -y->imag); + CMPLX_MULT_SELF_SCALAR(*yd, GNorm * pDevice->area); +} + + +void +NBJTys(ONEdevice *pDevice, SPcomplex *s, SPcomplex *yIeVce, + SPcomplex *yIcVce, SPcomplex *yIeVbe, SPcomplex *yIcVbe) +{ + ONEelem *pCollElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEelem *pElem; + ONEnode *pNode; + ONEedge *pEdge; + int index, i; + SPcomplex *y; + double area = pDevice->area; + double *solutionReal, *solutionImag; + SPcomplex temp, cOmega; + SPcomplex pIeVce, pIcVce, pIeVbe, pIcVbe; + + /* + * change context names of solution vectors for ac analysis dcDeltaSolution + * stores the real part and copiedSolution stores the imaginary part of the + * ac solution vector + */ + + solutionReal = pDevice->dcDeltaSolution; + solutionImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + CMPLX_MULT_SCALAR(cOmega, *s, TNorm); + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + pDevice->rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + ONE_jacLoad(pDevice); + pNode = pCollElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pCollElem->epsRel * pCollElem->rDx; + if (pCollElem->elemType == SEMICON) { + pEdge = pCollElem->pEdge; + pDevice->rhs[pNode->nEqn] -= pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] -= pEdge->dJpDpsiP1; + } + spSetComplex(pDevice->matrix); + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (pElem->elemType == SEMICON) { + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + CMPLX_MULT_SCALAR(temp, cOmega, 0.5 * pElem->dx); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } + } + } + } + + spFactor(pDevice->matrix); + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, -y->real, -y->imag); + pNode = pCollElem->pRightNode; + y = computeAdmittance(pNode, TRUE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, -y->real, -y->imag); + + /* load in the base contribution in the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + pNode = pBaseElem->pRightNode; + if (pNode->baseType == N_TYPE) { + pDevice->rhs[pNode->nEqn] = pNode->nConc * pNode->eg; + } else if (pNode->baseType == P_TYPE) { + pDevice->rhs[pNode->pEqn] = pNode->pConc * pNode->eg; + } else { + printf("\n BJTadmittance: unknown base type"); + } + + /* don't need to LU factor the jacobian since it exists */ + spSolve(pDevice->matrix, pDevice->rhs, solutionReal, + pDevice->rhsImag, solutionImag); + pElem = pDevice->elemArray[1]; + pNode = pElem->pLeftNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVbe, -y->real, -y->imag); + pNode = pCollElem->pRightNode; + y = computeAdmittance(pNode, FALSE, solutionReal, solutionImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVbe, -y->real, -y->imag); + + + CMPLX_ASSIGN(*yIeVce, pIeVce); + CMPLX_ASSIGN(*yIcVce, pIcVce); + CMPLX_ASSIGN(*yIeVbe, pIeVbe); + CMPLX_ASSIGN(*yIcVbe, pIcVbe); + CMPLX_MULT_SELF_SCALAR(*yIeVce, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIeVbe, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIcVce, GNorm * area); + CMPLX_MULT_SELF_SCALAR(*yIcVbe, GNorm * area); +} + +/* function to compute the admittance of a one-D device */ +/* cOmega is the complex frequency */ + +SPcomplex * +computeAdmittance(ONEnode *pNode, BOOLEAN delVContact, double *xReal, + double *xImag, SPcomplex *cOmega) +{ + ONEnode *pHNode; + ONEedge *pEdge; + ONEelem *pElem; + SPcomplex psi, n, p; + SPcomplex sum, prod1, prod2; +/* SPcomplex yAc; */ + double temp; + int i; + + + CMPLX_ASSIGN_VALUE(yAc, 0.0, 0.0); + + for (i = 0; i <= 1; i++) { + pElem = pNode->pElems[i]; + if (pElem != NIL(ONEelem)) { + switch (i) { + case 0: + /* the right node of the element */ + pHNode = pElem->pLeftNode; + pEdge = pElem->pEdge; + CMPLX_ASSIGN_VALUE(psi, xReal[pHNode->psiEqn], + xImag[pHNode->psiEqn]); + if (pElem->elemType == SEMICON) { + CMPLX_ASSIGN_VALUE(n, xReal[pHNode->nEqn], + xImag[pHNode->nEqn]); + CMPLX_ASSIGN_VALUE(p, xReal[pHNode->pEqn], + xImag[pHNode->pEqn]); + + CMPLX_MULT_SCALAR(prod1, psi, -pEdge->dJnDpsiP1); + CMPLX_MULT_SCALAR(prod2, n, pEdge->dJnDn); + CMPLX_ADD(yAc, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, psi, -pEdge->dJpDpsiP1); + CMPLX_MULT_SCALAR(prod2, p, pEdge->dJpDp); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_ADD_ASSIGN(yAc, sum); + if (delVContact) { + CMPLX_ADD_SELF_SCALAR(yAc, pEdge->dJnDpsiP1 + pEdge->dJpDpsiP1); + } + } + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * pElem->rDx); + CMPLX_MULT(prod2, prod1, psi) + CMPLX_ADD_ASSIGN(yAc, prod2); + if (delVContact) { + CMPLX_SUBT_ASSIGN(yAc, prod1); + } + break; + + case 1: + /* the left node of the element */ + pHNode = pElem->pRightNode; + pEdge = pElem->pEdge; + CMPLX_ASSIGN_VALUE(psi, xReal[pHNode->psiEqn], + xImag[pHNode->psiEqn]); + if (pElem->elemType == SEMICON) { + CMPLX_ASSIGN_VALUE(n, xReal[pHNode->nEqn], + xImag[pHNode->nEqn]); + CMPLX_ASSIGN_VALUE(p, xReal[pHNode->pEqn], + xImag[pHNode->pEqn]); + CMPLX_MULT_SCALAR(prod1, psi, pEdge->dJnDpsiP1); + CMPLX_MULT_SCALAR(prod2, n, pEdge->dJnDnP1); + CMPLX_ADD(yAc, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, psi, pEdge->dJpDpsiP1); + CMPLX_MULT_SCALAR(prod2, p, pEdge->dJpDpP1); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_ADD_ASSIGN(yAc, sum); + + if (delVContact) { + CMPLX_ADD_SELF_SCALAR(yAc, -(pEdge->dJnDpsiP1 + pEdge->dJpDpsiP1)); + } + } + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * pElem->rDx); + CMPLX_MULT(prod2, prod1, psi); + CMPLX_SUBT_ASSIGN(yAc, prod2); + if (delVContact) { + CMPLX_ADD_ASSIGN(yAc, prod1); + } + break; + default: + /* should never be here. Error */ + printf("computeAdmittance: Error - unknown element\n"); + } + } + } + return (&yAc); +} diff --git a/src/ciderlib/oned/oneaval.c b/src/ciderlib/oned/oneaval.c new file mode 100644 index 000000000..573bcb93d --- /dev/null +++ b/src/ciderlib/oned/oneaval.c @@ -0,0 +1,172 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "onemesh.h" +#include "onedev.h" +#include "macros.h" + +#include "onedext.h" +#include "oneddefs.h" + +double +ONEavalanche(BOOLEAN rhsOnly, ONEdevice *pDevice, ONEnode *pNode) +{ + ONEelem *pLElem, *pRElem; + ONEedge *pLEdge, *pREdge; + int numNodes = pDevice->numNodes; + double dJnDpsiPrev, dJpDpsiPrev; + double eField, temp, jn, jp; + double signE, signN, signP, coeffR, coeffL, alphaN, alphaP; + double generation = 0.0; + double dAlphaNDpsiM1, dAlphaNDpsi, dAlphaNDpsiP1; + double dAlphaPDpsiM1, dAlphaPDpsi, dAlphaPDpsiP1; + ONEmaterial *info; + + + pRElem = pNode->pRightElem; + pLElem = pNode->pLeftElem; + + if (pRElem->evalNodes[0]) { + info = pRElem->matlInfo; + } else { + info = pLElem->matlInfo; + } + + pREdge = pRElem->pEdge; + pLEdge = pLElem->pEdge; + dJnDpsiPrev = pLEdge->dJnDpsiP1; + dJpDpsiPrev = pLEdge->dJpDpsiP1; + + temp = pRElem->dx + pLElem->dx; + coeffR = pLElem->dx / temp; + coeffL = pRElem->dx / temp; + + eField = -(coeffR * pREdge->dPsi * pRElem->rDx + + coeffL * pLEdge->dPsi * pLElem->rDx); + jn = coeffR * pREdge->jn + coeffL * pLEdge->jn; + jp = coeffR * pREdge->jp + coeffL * pLEdge->jp; + + signE = SGN(eField); + eField = ABS(eField); + if (eField == 0.0) { + return (0.0); + } + signN = SGN(jn); + if (signN * signE > 0.0) { + /* field accelerates the carriers, hence avalanche */ + if (info->bii[ELEC] / eField > 80.0) { + alphaN = 0.0; + } else { + alphaN = info->aii[ELEC] * exp(-info->bii[ELEC] / eField); + } + } else { + alphaN = 0.0; + } + signP = SGN(jp); + if (signP * signE > 0.0) { + /* field accelerates the carriers, hence avalanche */ + if (info->bii[HOLE] / eField > 80.0) { + alphaP = 0.0; + } else { + alphaP = info->aii[HOLE] * exp(-info->bii[HOLE] / eField); + } + } else { + alphaP = 0.0; + } + + if ((alphaN == 0.0) && (alphaP == 0.0)) { + return (generation); + } + generation = (alphaN * ABS(jn) + alphaP * ABS(jp)) * + 0.5 * (pRElem->dx + pLElem->dx); + if (rhsOnly) { + return (generation); + } + if (alphaN == 0.0) { + dAlphaNDpsiM1 = 0.0; + dAlphaNDpsiP1 = 0.0; + dAlphaNDpsi = 0.0; + } else { + temp = alphaN * info->bii[ELEC] / (eField * eField); + dAlphaNDpsiM1 = signE * temp * (coeffL * pLElem->rDx); + dAlphaNDpsiP1 = -signE * temp * (coeffR * pRElem->rDx); + dAlphaNDpsi = -(dAlphaNDpsiM1 + dAlphaNDpsiP1); + } + + if (alphaP == 0.0) { + dAlphaPDpsiM1 = 0.0; + dAlphaPDpsiP1 = 0.0; + dAlphaPDpsi = 0.0; + } else { + temp = alphaP * info->bii[HOLE] / (eField * eField); + dAlphaPDpsiM1 = signE * temp * (coeffL * pLElem->rDx); + dAlphaPDpsiP1 = -signE * temp * (coeffR * pRElem->rDx); + dAlphaPDpsi = -(dAlphaPDpsiM1 + dAlphaPDpsiP1); + } + + coeffR = 0.5 * pLElem->dx; + coeffL = 0.5 * pRElem->dx; + + if (pNode->nodeI != 2) { + *(pNode->fNPsiiM1) += + signN * (-alphaN * coeffL * dJnDpsiPrev + + coeffL * pLEdge->jn * dAlphaNDpsiM1) + + signP * (-alphaP * coeffL * dJpDpsiPrev + + coeffL * pLEdge->jp * dAlphaPDpsiM1); + *(pNode->fNNiM1) += signN * alphaN * coeffL * pLEdge->dJnDn; + *(pNode->fNPiM1) += signP * alphaP * coeffL * pLEdge->dJpDp; + + *(pNode->fPPsiiM1) -= + signN * (-alphaN * coeffL * dJnDpsiPrev + + coeffL * pLEdge->jn * dAlphaNDpsiM1) + + signP * (-alphaP * coeffL * dJpDpsiPrev + + coeffL * pLEdge->jp * dAlphaPDpsiM1); + *(pNode->fPPiM1) -= signP * alphaP * coeffL * pLEdge->dJpDp; + *(pNode->fPNiM1) -= signN * alphaN * coeffL * pLEdge->dJnDn; + } + if (pNode->nodeI != numNodes - 1) { + *(pNode->fNPsiiP1) += + signN * (alphaN * coeffR * pREdge->dJnDpsiP1 + + coeffR * pREdge->jn * dAlphaNDpsiP1) + + signP * (alphaP * coeffR * pREdge->dJpDpsiP1 + + coeffR * pREdge->jp * dAlphaPDpsiP1); + *(pNode->fNNiP1) += signN * alphaN * coeffR * pREdge->dJnDnP1; + *(pNode->fNPiP1) += signP * alphaP * coeffR * pREdge->dJpDpP1; + *(pNode->fPPsiiP1) -= + signN * (alphaN * coeffR * pREdge->dJnDpsiP1 + + coeffR * pREdge->jn * dAlphaNDpsiP1) + + signP * (alphaP * coeffR * pREdge->dJpDpsiP1 + + coeffR * pREdge->jp * dAlphaPDpsiP1); + *(pNode->fPPiP1) -= signP * alphaP * coeffR * pREdge->dJpDpP1; + *(pNode->fPNiP1) -= signN * alphaN * coeffR * pREdge->dJnDnP1; + } + *(pNode->fNPsi) += + signN * (alphaN * (-coeffR * pREdge->dJnDpsiP1 + + coeffL * dJnDpsiPrev) + (coeffR * pREdge->jn + + coeffL * pLEdge->jn) * dAlphaNDpsi) + + signP * (alphaP * (-coeffR * pREdge->dJpDpsiP1 + + coeffL * dJpDpsiPrev) + (coeffR * pREdge->jp + + coeffL * pLEdge->jp) * dAlphaPDpsi); + *(pNode->fNN) += signN * alphaN * (coeffR * pREdge->dJnDn + + coeffL * pLEdge->dJnDnP1); + *(pNode->fNP) += signP * alphaP * (coeffR * pREdge->dJpDp + + coeffL * pLEdge->dJpDpP1); + + *(pNode->fPPsi) -= + signN * (alphaN * (-coeffR * pREdge->dJnDpsiP1 + + coeffL * dJnDpsiPrev) + (coeffR * pREdge->jn + + coeffL * pLEdge->jn) * dAlphaNDpsi) + + signP * (alphaP * (-coeffR * pREdge->dJpDpsiP1 + + coeffL * dJpDpsiPrev) + (coeffR * pREdge->jp + + coeffL * pLEdge->jp) * dAlphaPDpsi); + *(pNode->fPN) -= signN * alphaN * (coeffR * pREdge->dJnDn + + coeffL * pLEdge->dJnDnP1); + *(pNode->fPP) -= signP * alphaP * (coeffR * pREdge->dJpDp + + coeffL * pLEdge->dJpDpP1); + + return (generation); +} diff --git a/src/ciderlib/oned/onecond.c b/src/ciderlib/oned/onecond.c new file mode 100644 index 000000000..936411e09 --- /dev/null +++ b/src/ciderlib/oned/onecond.c @@ -0,0 +1,245 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +/* Functions to compute device conductances and currents */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "macros.h" +#include "spMatrix.h" + +#include "onedext.h" +#include "oneddefs.h" + +void +NUMDconductance(ONEdevice *pDevice, BOOLEAN tranAnalysis, + double *intCoeff, double *gd) +{ + ONEelem *pElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEnode *pNode; + ONEedge *pEdge; + int index; + double dPsiDv, dNDv, dPDv, *incVpn; + + + *gd = 0.0; + + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + /* compute incremental changes due to N contact */ + pNode = pElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pElem->epsRel * pElem->rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + pDevice->rhs[pNode->nEqn] = -pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] = -pEdge->dJpDpsiP1; + } + incVpn = pDevice->dcDeltaSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVpn, NIL(spREAL), NIL(spREAL)); + + pElem = pDevice->elemArray[1]; + pNode = pElem->pRightNode; + pEdge = pElem->pEdge; + dPsiDv = incVpn[pNode->psiEqn]; + if (pElem->elemType == SEMICON) { + dNDv = incVpn[pNode->nEqn]; + dPDv = incVpn[pNode->pEqn]; + *gd += pEdge->dJnDpsiP1 * dPsiDv + pEdge->dJnDnP1 * dNDv + + pEdge->dJpDpsiP1 * dPsiDv + pEdge->dJpDpP1 * dPDv; + } + /* For transient analysis, add the displacement term */ + if (tranAnalysis) { + *gd -= intCoeff[0] * pElem->epsRel * pElem->rDx * dPsiDv; + } + *gd *= -GNorm * pDevice->area; + +} + +void +NBJTconductance(ONEdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + double *dIeDVce, double *dIcDVce, double *dIeDVbe, double *dIcDVbe) +{ + ONEelem *pLastElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEelem *pElem; + ONEnode *pNode; + ONEedge *pEdge; + int index; + double dPsiDVce, dPsiDVbe, dNDVce, dNDVbe, dPDVce, dPDVbe; + double *incVce, *incVbe; + double nConc, pConc; + double area = pDevice->area; + + *dIeDVce = 0.0; + *dIcDVce = 0.0; + *dIeDVbe = 0.0; + *dIcDVbe = 0.0; + + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + /* store the new rhs for computing CE incremental quantities */ + pNode = pLastElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pLastElem->epsRel * pLastElem->rDx; + if (pLastElem->elemType == SEMICON) { + pEdge = pLastElem->pEdge; + pDevice->rhs[pNode->nEqn] = -pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] = -pEdge->dJpDpsiP1; + } + incVce = pDevice->dcDeltaSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVce, NIL(spREAL), NIL(spREAL)); + + /* zero the rhs before loading in the new rhs base contribution */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + pNode = pBaseElem->pRightNode; + + if (pNode->baseType == N_TYPE) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pDevice->rhs[pNode->nEqn] = nConc * pNode->eg; + } else if (pNode->baseType == P_TYPE) { + pConc = *(pDevice->devState0 + pNode->nodeP); + pDevice->rhs[pNode->pEqn] = pConc * pNode->eg; + } else { + printf("NBJTconductance: unknown base type\n"); + } + + incVbe = pDevice->copiedSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVbe, NIL(spREAL), NIL(spREAL)); + + pElem = pDevice->elemArray[1];/* first element */ + pEdge = pElem->pEdge; + pNode = pElem->pRightNode; + + dPsiDVce = incVce[pNode->psiEqn]; + dPsiDVbe = incVbe[pNode->psiEqn]; + if (pElem->elemType == SEMICON) { + dNDVce = incVce[pNode->nEqn]; + dPDVce = incVce[pNode->pEqn]; + dNDVbe = incVbe[pNode->nEqn]; + dPDVbe = incVbe[pNode->pEqn]; + *dIeDVce += pEdge->dJnDpsiP1 * dPsiDVce + pEdge->dJnDnP1 * dNDVce + + pEdge->dJpDpsiP1 * dPsiDVce + pEdge->dJpDpP1 * dPDVce; + *dIeDVbe += pEdge->dJnDpsiP1 * dPsiDVbe + pEdge->dJnDnP1 * dNDVbe + + pEdge->dJpDpsiP1 * dPsiDVbe + pEdge->dJpDpP1 * dPDVbe; + } + /* For transient analysis add the displacement term */ + if (tranAnalysis) { + *dIeDVce -= intCoeff[0] * pElem->epsRel * dPsiDVce * pElem->rDx; + *dIeDVbe -= intCoeff[0] * pElem->epsRel * dPsiDVbe * pElem->rDx; + } + pElem = pDevice->elemArray[pDevice->numNodes - 1]; /* last element */ + pEdge = pElem->pEdge; + pNode = pElem->pLeftNode; + dPsiDVce = incVce[pNode->psiEqn]; + dPsiDVbe = incVbe[pNode->psiEqn]; + if (pElem->elemType == SEMICON) { + dNDVce = incVce[pNode->nEqn]; + dPDVce = incVce[pNode->pEqn]; + dNDVbe = incVbe[pNode->nEqn]; + dPDVbe = incVbe[pNode->pEqn]; + *dIcDVce += -pEdge->dJnDpsiP1 * dPsiDVce + pEdge->dJnDn * dNDVce + + -pEdge->dJpDpsiP1 * dPsiDVce + pEdge->dJpDp * dPDVce + + /* add terms since adjacent to boundary */ + pEdge->dJnDpsiP1 + pEdge->dJpDpsiP1; + *dIcDVbe += -pEdge->dJnDpsiP1 * dPsiDVbe + pEdge->dJnDn * dNDVbe + + -pEdge->dJpDpsiP1 * dPsiDVbe + pEdge->dJpDp * dPDVbe; + } + if (tranAnalysis) { + *dIcDVce += intCoeff[0] * pElem->epsRel * (dPsiDVce - 1.0) * pElem->rDx; + *dIcDVbe += intCoeff[0] * pElem->epsRel * dPsiDVbe * pElem->rDx; + } + *dIeDVce *= -GNorm * area; + *dIcDVce *= -GNorm * area; + *dIeDVbe *= -GNorm * area; + *dIcDVbe *= -GNorm * area; +} + +void +NUMDcurrent(ONEdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + double *id) +{ + ONEnode *pNode; + ONEelem *pElem; + ONEedge *pEdge; + int index; + double *delta = pDevice->dcDeltaSolution; + double dPsi, dN, dP, *solution; + + + *id = 0.0; + + pElem = pDevice->elemArray[1]; + pNode = pElem->pRightNode; + pEdge = pElem->pEdge; + dPsi = delta[pNode->psiEqn]; + *id = pEdge->jn + pEdge->jp + pElem->epsRel * pEdge->jd; + if (pElem->elemType == SEMICON) { + dN = delta[pNode->nEqn]; + dP = delta[pNode->pEqn]; + *id += pEdge->dJnDpsiP1 * dPsi + pEdge->dJnDnP1 * dN + + pEdge->dJpDpsiP1 * dPsi + pEdge->dJpDpP1 * dP; + } + /* for transient analysis add the displacement term */ + if (tranAnalysis) { + *id -= intCoeff[0] * pElem->epsRel * pElem->rDx * dPsi; + } + *id *= JNorm * pDevice->area; +} + +void +NBJTcurrent(ONEdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + double *ie, double *ic) +{ + ONEnode *pNode; + ONEelem *pElem; + ONEedge *pEdge; + int index; + double dPsi, dN, dP; + double *solution; + + solution = pDevice->dcDeltaSolution; + + /* first edge for calculating ie */ + pElem = pDevice->elemArray[1]; + pNode = pElem->pRightNode; + pEdge = pElem->pEdge; + dPsi = solution[pNode->psiEqn]; + *ie = pEdge->jn + pEdge->jp + pElem->epsRel * pEdge->jd; + if (pElem->elemType == SEMICON) { + dN = solution[pNode->nEqn]; + dP = solution[pNode->pEqn]; + *ie += pEdge->dJnDpsiP1 * dPsi + pEdge->dJnDnP1 * dN + + pEdge->dJpDpsiP1 * dPsi + pEdge->dJpDpP1 * dP; + } + /* for transient analysis add the displacement term */ + if (tranAnalysis) { + *ie -= intCoeff[0] * pElem->epsRel * dPsi * pElem->rDx; + } + /* last edge for calculating ic */ + pElem = pDevice->elemArray[pDevice->numNodes - 1]; + pNode = pElem->pLeftNode; + pEdge = pElem->pEdge; + dPsi = solution[pNode->psiEqn]; + *ic = pEdge->jn + pEdge->jp + pElem->epsRel * pEdge->jd; + if (pElem->elemType == SEMICON) { + dN = solution[pNode->nEqn]; + dP = solution[pNode->pEqn]; + *ic += -pEdge->dJnDpsiP1 * dPsi + pEdge->dJnDn * dN + + -pEdge->dJpDpsiP1 * dPsi + pEdge->dJpDp * dP; + } + if (tranAnalysis) { + *ic += intCoeff[0] * pElem->epsRel * dPsi * pElem->rDx; + } + *ic *= -JNorm * pDevice->area; + *ie *= -JNorm * pDevice->area; +} diff --git a/src/ciderlib/oned/onecont.c b/src/ciderlib/oned/onecont.c new file mode 100644 index 000000000..61b4c3fa0 --- /dev/null +++ b/src/ciderlib/oned/onecont.c @@ -0,0 +1,657 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "spMatrix.h" +#include "macros.h" +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" +#include "../../maths/misc/bernoull.h" + + +/* Forward Declarations */ + +void ONE_commonTerms( ONEdevice *, BOOLEAN, BOOLEAN, ONEtranInfo *); + + +/* functions to setup and solve the continuity equations */ +/* Both continuity equations are solved */ + + +void +ONE_jacBuild(ONEdevice *pDevice) +{ + char *matrix = pDevice->matrix; + ONEelem *pElem; + ONEnode *pNode, *pNode1; + int index, eIndex; + int psiEqn, nEqn, pEqn; /* scratch for deref'd eqn numbers */ + int psiEqnL=0, nEqnL=0, pEqnL=0; + int psiEqnR=0, nEqnR=0, pEqnR=0; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + /* first the self terms */ + for (index = 0; index <= 1; index++) { + pNode = pElem->pNodes[index]; + /* get poisson pointer */ + psiEqn = pNode->psiEqn; + pNode->fPsiPsi = spGetElement(matrix, psiEqn, psiEqn); + + if (pElem->elemType == SEMICON) { + /* get continuity-coupling terms */ + nEqn = pNode->nEqn; + pEqn = pNode->pEqn; + /* pointers for additional terms */ + pNode->fPsiN = spGetElement(matrix, psiEqn, nEqn); + pNode->fPsiP = spGetElement(matrix, psiEqn, pEqn); + pNode->fNPsi = spGetElement(matrix, nEqn, psiEqn); + pNode->fNN = spGetElement(matrix, nEqn, nEqn); + pNode->fNP = spGetElement(matrix, nEqn, pEqn); + pNode->fPPsi = spGetElement(matrix, pEqn, psiEqn); + pNode->fPP = spGetElement(matrix, pEqn, pEqn); + pNode->fPN = spGetElement(matrix, pEqn, nEqn); + } else { + nEqn = 0; + pEqn = 0; + } + + /* save indices */ + if (index == 0) { /* left node */ + psiEqnL = psiEqn; + nEqnL = nEqn; + pEqnL = pEqn; + } else { + psiEqnR = psiEqn; + nEqnR = nEqn; + pEqnR = pEqn; + } + } + + /* now terms to couple to adjacent nodes */ + pNode = pElem->pLeftNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnL, psiEqnR); + if (pElem->elemType == SEMICON) { + /* pointers for additional terms */ + pNode->fNPsiiP1 = spGetElement(matrix, nEqnL, psiEqnR); + pNode->fNNiP1 = spGetElement(matrix, nEqnL, nEqnR); + pNode->fPPsiiP1 = spGetElement(matrix, pEqnL, psiEqnR); + pNode->fPPiP1 = spGetElement(matrix, pEqnL, pEqnR); + if (AvalancheGen) { + pNode->fNPiP1 = spGetElement(matrix, nEqnL, pEqnR); + pNode->fPNiP1 = spGetElement(matrix, pEqnL, nEqnR); + } + } + pNode = pElem->pRightNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnR, psiEqnL); + if (pElem->elemType == SEMICON) { + /* pointers for additional terms */ + pNode->fNPsiiM1 = spGetElement(matrix, nEqnR, psiEqnL); + pNode->fNNiM1 = spGetElement(matrix, nEqnR, nEqnL); + pNode->fPPsiiM1 = spGetElement(matrix, pEqnR, psiEqnL); + pNode->fPPiM1 = spGetElement(matrix, pEqnR, pEqnL); + if (AvalancheGen) { + pNode->fNPiM1 = spGetElement(matrix, nEqnR, pEqnL); + pNode->fPNiM1 = spGetElement(matrix, pEqnR, nEqnL); + } + } + } +} + + +void +ONE_sysLoad(ONEdevice *pDevice, BOOLEAN tranAnalysis, + ONEtranInfo *info) +{ + ONEelem *pElem; + ONEnode *pNode, *pNode1; + ONEedge *pEdge; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dx, rDx, dPsi; + double rhsN, rhsP, generation; + double perTime = 0.0; + double fNd, fNa, fdNd, fdNa; + double netConc, dNd, dNa, psi, nConc, pConc; + + + /* first compute the currents and their derivatives */ + ONE_commonTerms(pDevice, FALSE, tranAnalysis, info); + + /* find reciprocal timestep */ + if (tranAnalysis) { + perTime = info->intCoeff[0]; + } + /* zero the rhs vector */ + for (index = 1; index <= pDevice->numEqns; index++) { + pRhs[index] = 0.0; + } + + /* zero the matrix */ + spClear(pDevice->matrix); + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + dx = 0.5 * pElem->dx; + rDx = pElem->epsRel * pElem->rDx; + + /* load for all i */ + for (index = 0; index <= 1; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + *(pNode->fPsiPsi) += rDx; + pRhs[pNode->psiEqn] += pNode->qf; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + netConc = pNode->netConc; + dNd = 0.0; + dNa = 0.0; + psi = *(pDevice->devState0 + pNode->nodePsi); + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + + + if (FreezeOut) { + ONE_freezeOut(pNode, nConc, pConc, &fNd, &fNa, &fdNd, &fdNa); + netConc = pNode->nd * fNd - pNode->na * fNa; + dNd = pNode->nd * fdNd; + dNa = pNode->na * fdNa; + } + *(pNode->fPsiN) += dx * (1.0 - dNd); + *(pNode->fPsiP) -= dx * (1.0 - dNa); + *(pNode->fNPsi) -= pEdge->dJnDpsiP1; + *(pNode->fPPsi) -= pEdge->dJpDpsiP1; + + pRhs[pNode->psiEqn] += dx * (netConc + pConc - nConc); + + /* Handle generation terms */ + *(pNode->fNN) -= dx * pNode->dUdN; + *(pNode->fNP) -= dx * pNode->dUdP; + *(pNode->fPP) += dx * pNode->dUdP; + *(pNode->fPN) += dx * pNode->dUdN; + pRhs[pNode->nEqn] -= -dx * pNode->uNet; + pRhs[pNode->pEqn] -= dx * pNode->uNet; + + /* Handle dXdT continuity terms */ + if (tranAnalysis) { + *(pNode->fNN) -= dx * perTime; + *(pNode->fPP) += dx * perTime; + pRhs[pNode->nEqn] += dx * pNode->dNdT; + pRhs[pNode->pEqn] -= dx * pNode->dPdT; + } + /* Take care of base contact if necessary */ + /* eg holds the base edge mu/dx */ + if (pNode->baseType == N_TYPE) { + pRhs[pNode->nEqn] += 0.5 * pNode->eg * nConc * + (pNode->vbe - psi + log(nConc / pNode->nie)); + *(pNode->fNPsi) += 0.5 * pNode->eg * nConc; + *(pNode->fNN) -= 0.5 * pNode->eg * + (pNode->vbe - psi + log(nConc / pNode->nie) + 1.0); + } else if (pNode->baseType == P_TYPE) { + pRhs[pNode->pEqn] += 0.5 * pNode->eg * pConc * + (pNode->vbe - psi - log(pConc / pNode->nie)); + *(pNode->fPPsi) += 0.5 * pNode->eg * pConc; + *(pNode->fPP) -= 0.5 * pNode->eg * + (pNode->vbe - psi - log(pConc / pNode->nie) - 1.0); + } + } + } + } + + pEdge = pElem->pEdge; + dPsi = pEdge->dPsi; + + pNode = pElem->pLeftNode; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->psiEqn] += rDx * dPsi; + *(pNode->fPsiPsiiP1) -= rDx; + if (pElem->elemType == SEMICON) { + pRhs[pNode->nEqn] -= pEdge->jn; + pRhs[pNode->pEqn] -= pEdge->jp; + + *(pNode->fNN) += pEdge->dJnDn; + *(pNode->fPP) += pEdge->dJpDp; + *(pNode->fNPsiiP1) += pEdge->dJnDpsiP1; + *(pNode->fNNiP1) += pEdge->dJnDnP1; + *(pNode->fPPsiiP1) += pEdge->dJpDpsiP1; + *(pNode->fPPiP1) += pEdge->dJpDpP1; + } + } + pNode = pElem->pRightNode; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->psiEqn] -= rDx * dPsi; + *(pNode->fPsiPsiiM1) -= rDx; + if (pElem->elemType == SEMICON) { + pRhs[pNode->nEqn] += pEdge->jn; + pRhs[pNode->pEqn] += pEdge->jp; + + + *(pNode->fNN) -= pEdge->dJnDnP1; + *(pNode->fPP) -= pEdge->dJpDpP1; + *(pNode->fNPsiiM1) += pEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= pEdge->dJnDn; + *(pNode->fPPsiiM1) += pEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= pEdge->dJpDp; + } + } + } + if (AvalancheGen) { + /* add the generation terms */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if ((pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON)) { + generation = ONEavalanche(FALSE, pDevice, pNode); + pRhs[pNode->nEqn] -= generation; + pRhs[pNode->pEqn] += generation; + } + } + } + } + } +} + + +void +ONE_jacLoad(ONEdevice *pDevice) +{ + /* used only for ac analysis */ + ONEelem *pElem; + ONEnode *pNode, *pNode1; + ONEedge *pEdge; + int index, eIndex; + double dx, rDx, dPsi; + double rhsN, rhsP, generation; + double fNd, fNa, fdNd, fdNa; + double netConc, dNd, dNa, psi, nConc, pConc; + + + /* first compute the currents and their derivatives */ + ONE_commonTerms(pDevice, FALSE, FALSE, NIL(ONEtranInfo)); + + /* zero the matrix */ + spClear(pDevice->matrix); + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + dx = 0.5 * pElem->dx; + rDx = pElem->epsRel * pElem->rDx; + /* load for all i */ + for (index = 0; index <= 1; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + *(pNode->fPsiPsi) += rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + dNd = 0.0; + dNa = 0.0; + psi = *(pDevice->devState0 + pNode->nodePsi); + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + if (FreezeOut) { + ONE_freezeOut(pNode, nConc, pConc, &fNd, &fNa, &fdNd, &fdNa); + dNd = pNode->nd * fdNd; + dNa = pNode->na * fdNa; + } + *(pNode->fPsiN) += dx * (1.0 - dNd); + *(pNode->fPsiP) -= dx * (1.0 - dNa); + *(pNode->fNPsi) -= pEdge->dJnDpsiP1; + *(pNode->fPPsi) -= pEdge->dJpDpsiP1; + + if (pNode->baseType == N_TYPE) { + *(pNode->fNPsi) += 0.5 * nConc * pNode->eg; + *(pNode->fNN) -= 0.5 * pNode->eg + * (pNode->vbe - psi + log(nConc / pNode->nie) + 1.0); + } + if (pNode->baseType == P_TYPE) { + *(pNode->fPPsi) += 0.5 * pConc * pNode->eg; + *(pNode->fPP) -= 0.5 * pNode->eg + * (pNode->vbe - psi - log(pConc / pNode->nie) - 1.0); + } + } + } + } + pNode = pElem->pLeftNode; + if (pNode->nodeType != CONTACT) { + pEdge = pElem->pEdge; + dPsi = pEdge->dPsi; + if (pElem->elemType == SEMICON) { + *(pNode->fNN) += pEdge->dJnDn - dx * pNode->dUdN; + *(pNode->fNP) -= dx * pNode->dUdP; + *(pNode->fPP) += pEdge->dJpDp + dx * pNode->dUdP; + *(pNode->fPN) += dx * pNode->dUdN; + } + pNode1 = pElem->pRightNode; + if (pNode1->nodeType != CONTACT) { + *(pNode->fPsiPsiiP1) -= rDx; + if (pElem->elemType == SEMICON) { + *(pNode->fNPsiiP1) += pEdge->dJnDpsiP1; + *(pNode->fNNiP1) += pEdge->dJnDnP1; + *(pNode->fPPsiiP1) += pEdge->dJpDpsiP1; + *(pNode->fPPiP1) += pEdge->dJpDpP1; + } + } + } + pNode = pElem->pRightNode; + if (pNode->nodeType != CONTACT) { + pEdge = pElem->pEdge; + dPsi = pEdge->dPsi; + if (pElem->elemType == SEMICON) { + *(pNode->fNN) += -pEdge->dJnDnP1 - dx * pNode->dUdN; + *(pNode->fNP) -= dx * pNode->dUdP; + *(pNode->fPP) += -pEdge->dJpDpP1 + dx * pNode->dUdP; + *(pNode->fPN) += dx * pNode->dUdN; + } + pNode1 = pElem->pLeftNode; + if (pNode1->nodeType != CONTACT) { + *(pNode->fPsiPsiiM1) -= rDx; + if (pElem->elemType == SEMICON) { + *(pNode->fNPsiiM1) += pEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= pEdge->dJnDn; + *(pNode->fPPsiiM1) += pEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= pEdge->dJpDp; + } + } + } + } + if (AvalancheGen) { + /* add the generation terms */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if ((pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON)) { + generation = ONEavalanche(FALSE, pDevice, pNode); + } + } + } + } + } +} + +void +ONE_rhsLoad(ONEdevice *pDevice, BOOLEAN tranAnalysis, + ONEtranInfo *info) +{ + ONEelem *pElem; + ONEnode *pNode, *pNode1; + ONEedge *pEdge; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dx, rDx, dPsi; + double rhsN, rhsP, generation; + double perTime; + double fNd, fNa, fdNd, fdNa; + double netConc, dNd, dNa, psi, nConc, pConc; + + /* first compute the currents and their derivatives */ + ONE_commonTerms(pDevice, FALSE, tranAnalysis, info); + + /* find reciprocal timestep */ + if (tranAnalysis) { + perTime = info->intCoeff[0]; + } + /* zero the rhs vector */ + for (index = 1; index <= pDevice->numEqns; index++) { + pRhs[index] = 0.0; + } + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + + dx = 0.5 * pElem->dx; + rDx = pElem->epsRel * pElem->rDx; + + /* load for all i */ + for (index = 0; index <= 1; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->psiEqn] += pNode->qf; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + netConc = pNode->netConc; + dNd = 0.0; + dNa = 0.0; + psi = *(pDevice->devState0 + pNode->nodePsi); + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + if (FreezeOut) { + ONE_freezeOut(pNode, nConc, pConc, &fNd, &fNa, &fdNd, &fdNa); + netConc = pNode->nd * fNd - pNode->na * fNa; + dNd = pNode->nd * fdNd; + dNa = pNode->na * fdNa; + } + pRhs[pNode->psiEqn] += dx * (netConc + pConc - nConc); + + /* Handle generation terms */ + pRhs[pNode->nEqn] -= -dx * pNode->uNet; + pRhs[pNode->pEqn] -= dx * pNode->uNet; + + /* Handle dXdT continuity terms */ + if (tranAnalysis) { + pRhs[pNode->nEqn] += dx * pNode->dNdT; + pRhs[pNode->pEqn] -= dx * pNode->dPdT; + } + /* Take care of base contact if necessary */ + /* eg holds the base edge mu/dx */ + if (pNode->baseType == N_TYPE) { + pRhs[pNode->nEqn] += 0.5 * pNode->eg * nConc * + (pNode->vbe - psi + log(nConc / pNode->nie)); + } else if (pNode->baseType == P_TYPE) { + pRhs[pNode->pEqn] += 0.5 * pNode->eg * pConc * + (pNode->vbe - psi - log(pConc / pNode->nie)); + } + } + } + } + + pEdge = pElem->pEdge; + dPsi = pEdge->dPsi; + + pNode = pElem->pLeftNode; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->psiEqn] += rDx * dPsi; + if (pElem->elemType == SEMICON) { + pRhs[pNode->nEqn] -= pEdge->jn; + pRhs[pNode->pEqn] -= pEdge->jp; + } + } + pNode = pElem->pRightNode; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->psiEqn] -= rDx * dPsi; + if (pElem->elemType == SEMICON) { + pRhs[pNode->nEqn] += pEdge->jn; + pRhs[pNode->pEqn] += pEdge->jp; + } + } + } + if (AvalancheGen) { + /* add the generation terms */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if ((pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON)) { + generation = ONEavalanche(TRUE, pDevice, pNode); + pRhs[pNode->nEqn] -= generation; + pRhs[pNode->pEqn] += generation; + } + } + } + } + } +} + +void +ONE_commonTerms(ONEdevice *pDevice, BOOLEAN currentOnly, + BOOLEAN tranAnalysis, ONEtranInfo *info) +{ + ONEelem *pElem; + ONEedge *pEdge; + ONEnode *pNode, *pNode1; + int index, eIndex; + double psi1, psi2, psi, nConc=0.0, pConc=0.0, nC, pC, nP1, pP1; + double dPsiN, dPsiP; + double bPsiN, dbPsiN, bMPsiN, dbMPsiN; + double bPsiP, dbPsiP, bMPsiP, dbMPsiP; + double mun, dMun, mup, dMup, rDx; + double conc1, conc2; + double cnAug, cpAug; + + + /* evaluate all node (including recombination) and edge quantities */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + cnAug = pElem->matlInfo->cAug[ELEC]; + cpAug = pElem->matlInfo->cAug[HOLE]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + psi = pDevice->dcSolution[pNode->psiEqn]; + if (pElem->elemType == SEMICON) { + nConc = pDevice->dcSolution[pNode->nEqn]; + pConc = pDevice->dcSolution[pNode->pEqn]; + if (Srh) { + recomb(nConc, pConc, + pNode->tn, pNode->tp, cnAug, cpAug, pNode->nie, + &pNode->uNet, &pNode->dUdN, &pNode->dUdP); + } else { + pNode->uNet = 0.0; + pNode->dUdN = 0.0; + pNode->dUdP = 0.0; + } + if (pNode->baseType == P_TYPE && pConc <= 0.0) { + pConc = pNode->na; + } else if (pNode->baseType == N_TYPE && nConc <= 0.0) { + nConc = pNode->nd; + } + } + } else { + /* a contact node */ + psi = pNode->psi; + if (pElem->elemType == SEMICON) { + nConc = pNode->nConc; + pConc = pNode->pConc; + } + } + /* store info in the state tables */ + *(pDevice->devState0 + pNode->nodePsi) = psi; + if (pElem->elemType == SEMICON) { + *(pDevice->devState0 + pNode->nodeN) = nConc; + *(pDevice->devState0 + pNode->nodeP) = pConc; + if (tranAnalysis && pNode->nodeType != CONTACT) { + pNode->dNdT = integrate(pDevice->devStates, info, pNode->nodeN); + pNode->dPdT = integrate(pDevice->devStates, info, pNode->nodeP); + } + } + } + } + pEdge = pElem->pEdge; + pNode = pElem->pLeftNode; + if (pNode->nodeType != CONTACT) { + psi1 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pRightNode; + if (pNode->nodeType != CONTACT) { + psi2 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi2 = pNode->psi; + } + pEdge->dPsi = psi2 - psi1; + *(pDevice->devState0 + pEdge->edgeDpsi) = pEdge->dPsi; + } + /* calculate the current densities and mobility values */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + pEdge = pElem->pEdge; + if (pElem->elemType == SEMICON) { + dPsiN = pEdge->dPsi + pEdge->dCBand; + dPsiP = pEdge->dPsi - pEdge->dVBand; + bernoulli(dPsiN, &bPsiN, &dbPsiN, &bMPsiN, &dbMPsiN, !currentOnly); + bernoulli(dPsiP, &bPsiP, &dbPsiP, &bMPsiP, &dbMPsiP, !currentOnly); + nC = *(pDevice->devState0 + pElem->pLeftNode->nodeN); + nP1 = *(pDevice->devState0 + pElem->pRightNode->nodeN); + pC = *(pDevice->devState0 + pElem->pLeftNode->nodeP); + pP1 = *(pDevice->devState0 + pElem->pRightNode->nodeP); + conc1 = pElem->pLeftNode->totalConc; + conc2 = pElem->pRightNode->totalConc; + pEdge->jn = (bPsiN * nP1 - bMPsiN * nC); + pEdge->jp = (bPsiP * pC - bMPsiP * pP1); + + + mun = pEdge->mun; + dMun = 0.0; + mup = pEdge->mup; + dMup = 0.0; + MOBfieldDep(pElem->matlInfo, ELEC, dPsiN * pElem->rDx, &mun, &dMun); + MOBfieldDep(pElem->matlInfo, HOLE, dPsiP * pElem->rDx, &mup, &dMup); + + + mun *= pElem->rDx; + dMun *= pElem->rDx * pElem->rDx; + mup *= pElem->rDx; + dMup *= pElem->rDx * pElem->rDx; + + + + /* + * The base continuity equation makes use of mu/dx in eg. The base + * length has already been calculated and converted to normalized, + * reciprocal form during setup. The name should be changed, but that's + * a big hassle. + */ + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->baseType == N_TYPE) { + pNode->eg = pEdge->mun * pDevice->baseLength; + } else if (pNode->baseType == P_TYPE) { + pNode->eg = pEdge->mup * pDevice->baseLength; + } + } + } + + pEdge->jn *= mun; + pEdge->jp *= mup; + + + if (!currentOnly) { + if (dMun == 0.0) { + pEdge->dJnDpsiP1 = mun * (dbPsiN * nP1 - dbMPsiN * nC); + } else { + pEdge->dJnDpsiP1 = dMun * (bPsiN * nP1 - bMPsiN * nC) + + mun * (dbPsiN * nP1 - dbMPsiN * nC); + } + pEdge->dJnDn = -mun * bMPsiN; + pEdge->dJnDnP1 = mun * bPsiN; + if (dMup == 0.0) { + pEdge->dJpDpsiP1 = mup * (dbPsiP * pC - dbMPsiP * pP1); + } else { + pEdge->dJpDpsiP1 = dMup * (bPsiP * pC - bMPsiP * pP1) + + mup * (dbPsiP * pC - dbMPsiP * pP1); + } + pEdge->dJpDp = mup * bPsiP; + pEdge->dJpDpP1 = -mup * bMPsiP; + } + } + if (tranAnalysis) { + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * pElem->rDx; + } + } +} diff --git a/src/ciderlib/oned/oneddefs.h b/src/ciderlib/oned/oneddefs.h new file mode 100644 index 000000000..cc36efa50 --- /dev/null +++ b/src/ciderlib/oned/oneddefs.h @@ -0,0 +1,29 @@ +/* + * 2001 Paolo Nenzi + */ + +#ifndef _ONEDDEFS_H +#define _ONEDDEFS_H + +/* Debug statements */ + +extern BOOLEAN ONEacDebug; +extern BOOLEAN ONEdcDebug; +extern BOOLEAN ONEtranDebug; +extern BOOLEAN ONEjacDebug; + +/* Now some defines for the one dimensional simulator + * library. + * Theese defines were gathered from all the code in + * oned directory. + */ + +#define LEVEL_ALPHA_SI 3.1e-8 /* From de Graaf & Klaasen, pg. 12 */ +#define MIN_DELV 1e-3 +#define NORM_RED_MAXITERS 10 + + + + + +#endif diff --git a/src/ciderlib/oned/onedest.c b/src/ciderlib/oned/onedest.c new file mode 100644 index 000000000..b91ff072e --- /dev/null +++ b/src/ciderlib/oned/onedest.c @@ -0,0 +1,74 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onedev.h" +#include "onemesh.h" +#include "spMatrix.h" +#include "onedext.h" +#include "oneddefs.h" + +void +ONEdestroy(ONEdevice *pDevice) +{ + int index, eIndex; + ONEelem *pElem; + ONEnode *pNode; + ONEedge *pEdge; + + + if (!pDevice) + return; + + switch (pDevice->solverType) { + case SLV_SMSIG: + case SLV_BIAS: + /* free up memory allocated for the bias solution */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + FREE(pDevice->rhsImag); + spDestroy(pDevice->matrix); + break; + case SLV_EQUIL: + /* free up the vectors allocated in the equilibrium solution */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + spDestroy(pDevice->matrix); + break; + case SLV_NONE: + break; + default: + fprintf(stderr, "Panic: Unknown solver type in ONEdestroy.\n"); + exit(-1); + break; + } + + /* destroy the mesh */ + if (pDevice->elemArray) { + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 2; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + FREE(pNode); + } + pEdge = pElem->pEdge; + FREE(pEdge); + } + FREE(pElem); + } + FREE(pDevice->elemArray); + } + /* destroy any other lists */ + /* NOT IMPLEMENTED */ + + FREE(pDevice); +} diff --git a/src/ciderlib/oned/onedext.h b/src/ciderlib/oned/onedext.h new file mode 100644 index 000000000..34f8cc9b1 --- /dev/null +++ b/src/ciderlib/oned/onedext.h @@ -0,0 +1,121 @@ +/* + * 2001 Paolo Nenzi + */ + + /* External symbols for ONE Dimensional simulator */ + +#ifndef _ONEDEXT_H +#define _ONEDEXT_H + +#include "profile.h" +#include "onemesh.h" +#include "onedev.h" +#include "carddefs.h" +#include "bool.h" +#include "complex.h" + +/* oneadmit.c */ +extern int NUMDadmittance(ONEdevice *, double, SPcomplex *); +extern int NBJTadmittance(ONEdevice *, double, SPcomplex *, + SPcomplex *, SPcomplex *, + SPcomplex *); +extern BOOLEAN ONEsorSolve(ONEdevice *, double *, double *, double omega); +extern void NUMDys(ONEdevice *, SPcomplex *, SPcomplex *); +extern void NBJTys(ONEdevice *,SPcomplex *, SPcomplex *, SPcomplex *, + SPcomplex *, SPcomplex *); + +extern SPcomplex *computeAdmittance(ONEnode *, BOOLEAN, double *xReal, + double *,SPcomplex *); + +/* oneaval.c */ +extern double ONEavalanche(BOOLEAN, ONEdevice *, ONEnode *); + +/* onecond.c */ +extern void NUMDconductance(ONEdevice *, BOOLEAN, double *, double *); +extern void NBJTconductance(ONEdevice *, BOOLEAN, double *, double *, + double *, double *, double *); +extern void NUMDcurrent(ONEdevice *, BOOLEAN, double *, double *); +extern void NBJTcurrent(ONEdevice *, BOOLEAN, double *, double *, + double *); + + +/* onecont */ +extern void ONE_jacBuild(ONEdevice *); +extern void ONE_sysLoad(ONEdevice *, BOOLEAN, ONEtranInfo *); +extern void ONE_jacLoad(ONEdevice *); +extern void ONE_rhsLoad(ONEdevice *, BOOLEAN, ONEtranInfo *); +extern void ONE_commonTerms(ONEdevice *, BOOLEAN, BOOLEAN, ONEtranInfo *); + + +/* onedest */ +extern void ONEdestroy(ONEdevice *); + +/* onedopng.c */ +extern double ONEdopingValue(DOPprofile *, DOPtable *, double ); +extern void ONEsetDoping(ONEdevice *, DOPprofile *, DOPtable *); + +/* onefreez.c */ +extern void ONEQfreezeOut(ONEnode *, double *, double *, double *, + double *); +extern void ONE_freezeOut(ONEnode *, double, double, double *, + double*, double *, double *); + + +/* onemesh.c */ +extern void ONEbuildMesh(ONEdevice *, ONEcoord *, ONEdomain *, + ONEmaterial *); +extern void ONEgetStatePointers(ONEdevice *, int *); +extern void adjustBaseContact(ONEdevice *, int, int); +extern void NBJTjunctions(ONEdevice *, int *, int *); +extern void ONEprnMesh(ONEdevice *); + +/* onepoiss.c */ +extern void ONEQjacBuild(ONEdevice *); +extern void ONEQsysLoad(ONEdevice *); +extern void ONEQrhsLoad(ONEdevice *); +extern void ONEQcommonTerms(ONEdevice *); + +/*oneprint.c */ +extern void ONEprnSolution(FILE *, ONEdevice *, OUTPcard *); +extern void ONEmemStats(FILE *, ONEdevice *); +extern void ONEcpuStats(FILE *f, ONEdevice *); + + +/* oneproj.c */ +extern void NUMDproject(ONEdevice *, double); +extern void NBJTproject(ONEdevice *, double, double, double); +extern void NUMDupdate(ONEdevice *,double, BOOLEAN); +extern void NBJTupdate(ONEdevice *, double, double, double, BOOLEAN); +extern void NUMDsetBCs(ONEdevice *, double); +extern void NBJTsetBCs(ONEdevice *, double, double); + +/* oneread.c */ +extern int ONEreadState(ONEdevice *, char *, int, double *, double *); + +/*onesetup.c */ +extern void ONEsetup(ONEdevice *); +extern void ONEsetBCparams(ONEdevice *, BDRYcard *, CONTcard *); +extern void ONEnormalize(ONEdevice *); + +/* onesolve.c */ +extern void ONEdcSolve(ONEdevice *, int, BOOLEAN, BOOLEAN, ONEtranInfo *); +extern BOOLEAN ONEpsiDeltaConverged(ONEdevice *, int *); +extern BOOLEAN ONEdeltaConverged(ONEdevice *); +extern BOOLEAN ONEdeltaConverged(ONEdevice *); +extern void ONEresetJacobian(ONEdevice *); +extern void ONEstoreNeutralGuess(ONEdevice *); +extern void ONEequilSolve(ONEdevice *); +extern void ONEbiasSolve(ONEdevice *, int, BOOLEAN, ONEtranInfo *); +extern void ONEstoreEquilibGuess(ONEdevice *); +extern void ONEstoreInitialGuess(ONEdevice *); +extern int ONEnewDelta(ONEdevice *, BOOLEAN, ONEtranInfo *); +extern int ONEnewDelta(ONEdevice *, BOOLEAN, ONEtranInfo *); +extern double ONEtrunc(ONEdevice *, ONEtranInfo *, double); +extern void ONEsaveState(ONEdevice *); +extern double ONEnuNorm(ONEdevice *); +extern void ONEjacCheck(ONEdevice *, BOOLEAN, ONEtranInfo *); +extern void ONEpredict(ONEdevice *, ONEtranInfo *); +extern BOOLEAN ONEdeviceConverged(ONEdevice *); + + +#endif diff --git a/src/ciderlib/oned/onedopng.c b/src/ciderlib/oned/onedopng.c new file mode 100644 index 000000000..f7a020848 --- /dev/null +++ b/src/ciderlib/oned/onedopng.c @@ -0,0 +1,166 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "profile.h" +#include "macros.h" +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" + +/* functions in this file are used to calculate the conc */ + +double +ONEdopingValue(DOPprofile *pProfile, DOPtable *pTable, double x) +{ + double argX, argP, value=0.0; + + + /* Find the appropriate lookup table if necessary */ + if (pProfile->type == LOOKUP) { + while (pTable != NIL(DOPtable)) { + if (pTable->impId == pProfile->IMPID) { + /* Found it */ + break; + } else { + pTable = pTable->next; + } + } + if (pTable == NIL(DOPtable)) { + fprintf(stderr, "Error: unknown impurity profile %d\n", + ((int)pProfile->IMPID)); + exit(1); + } + } + /* Find distances */ + if (pProfile->X_LOW > x) { + argX = pProfile->X_LOW - x; + } else if (x > pProfile->X_HIGH) { + argX = x - pProfile->X_HIGH; + } else { + argX = 0.0; + } + + argP = argX; + + /* Transform to coordinates of profile peak */ + argP -= pProfile->LOCATION; + argP /= pProfile->CHAR_LENGTH; + + switch (pProfile->type) { + case UNIF: + if (argP > 0.0) { + value = 0.0; + } else { + value = pProfile->CONC; + } + break; + case LIN: + argP = ABS(argP); + if (argP > 1.0) { + value = 0.0; + } else { + value = pProfile->CONC * (1.0 - argP); + } + break; + case GAUSS: + argP *= argP; + if (argP > 80.0) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * exp(-argP); + } + case EXP: + argP = ABS(argP); + if (argP > 80.0) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * exp(-argP); + } + break; + case ERRFC: + argP = ABS(argP); + if (argP > 10.0) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * erfc(-argP); + } + break; + case LOOKUP: + argP = ABS(argP); + value = lookup(pTable->dopData, argP); + break; + default: + break; + } + return (value); +} + + +void +ONEsetDoping(ONEdevice *pDevice, DOPprofile *pProfile, DOPtable *pTable) +{ + ONEnode *pNode; + ONEelem *pElem; + DOPprofile *pP; + double conc; + int index, eIndex; + BOOLEAN dopeMe; + + + /* Clear doping info for all nodes. */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + pNode->na = 0.0; + pNode->nd = 0.0; + pNode->netConc = 0.0; + pNode->totalConc = 0.0; + } + } + } + /* Now compute the contribution to the total doping from each profile. */ + for (pP = pProfile; pP != NIL(DOPprofile); pP = pP->next) { + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + if (pElem->elemType == SEMICON) { + if (pP->numDomains > 0) { + dopeMe = FALSE; + for (index = 0; index < pP->numDomains; index++) { + if (pElem->domain == pP->domains[index]) { + dopeMe = TRUE; + break; + } + } + } else { /* domains not given, so dope all */ + dopeMe = TRUE; + } + if (dopeMe) { + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + conc = ONEdopingValue(pP, pTable, pNode->x); + pNode->netConc += conc; + if (conc < 0.0) { + pNode->totalConc -= conc; + pNode->na -= conc; + } else { + pNode->totalConc += conc; + pNode->nd += conc; + } + } + } + } + } + } + } +} diff --git a/src/ciderlib/oned/onefreez.c b/src/ciderlib/oned/onefreez.c new file mode 100644 index 000000000..99594e3b6 --- /dev/null +++ b/src/ciderlib/oned/onefreez.c @@ -0,0 +1,121 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "onemesh.h" +#include "../../maths/misc/accuracy.h" +#include "onedext.h" +#include "oneddefs.h" + + +void +ONEQfreezeOut(ONEnode *pNode, double *ndFac, double *naFac, double *dNdFac, + double *dNaFac) +{ + double temp1, temp2; + double eLev; + ONEmaterial *info; + + + if (pNode->pRightElem && pNode->pRightElem->evalNodes[0]) { + info = pNode->pRightElem->matlInfo; + } else { + info = pNode->pLeftElem->matlInfo; + } + + eLev = info->eDon; + if (info->material != GAAS) { + eLev -= LEVEL_ALPHA_SI * pow(pNode->nd * NNorm, 1.0 / 3.0); + if (eLev < 0.0) + eLev = 0.0; + } + if (eLev >= ExpLim) { + *ndFac = 0.0; + *dNdFac = 0.0; + } else if (eLev <= -ExpLim) { + *ndFac = 1.0; + *dNdFac = 0.0; + } else { + temp1 = info->gDon * pNode->nConc * NNorm * exp(eLev) / info->nc0; + temp2 = 1.0 / (1.0 + temp1); + *ndFac = temp2; + *dNdFac = -temp2 * temp2 * temp1; + } + + eLev = info->eAcc; + if (info->material != GAAS) { + eLev -= LEVEL_ALPHA_SI * pow(pNode->na * NNorm, 1.0 / 3.0); + if (eLev < 0.0) + eLev = 0.0; + } + if (eLev >= ExpLim) { + *naFac = 0.0; + *dNaFac = 0.0; + } else if (eLev <= -ExpLim) { + *naFac = 1.0; + *dNaFac = 0.0; + } else { + temp1 = info->gAcc * pNode->pConc * NNorm * exp(eLev) / info->nv0; + temp2 = 1.0 / (1.0 + temp1); + *naFac = temp2; + *dNaFac = temp2 * temp2 * temp1; + } +} + +void +ONE_freezeOut(ONEnode *pNode, double nConc, double pConc, double *ndFac, + double *naFac, double *dNdFac, double *dNaFac) +{ + double temp1, temp2; + double eLev; + ONEmaterial *info; + + + if (pNode->pRightElem && pNode->pRightElem->evalNodes[0]) { + info = pNode->pRightElem->matlInfo; + } else { + info = pNode->pLeftElem->matlInfo; + } + + eLev = info->eDon; + if (info->material != GAAS) { + eLev -= LEVEL_ALPHA_SI * pow(pNode->nd * NNorm, 1.0 / 3.0); + if (eLev < 0.0) + eLev = 0.0; + } + if (eLev >= ExpLim) { + *ndFac = 0.0; + *dNdFac = 0.0; + } else if (eLev <= -ExpLim) { + *ndFac = 1.0; + *dNdFac = 0.0; + } else { + temp1 = info->gDon * exp(eLev) * NNorm / info->nc0; + temp2 = 1.0 / (1.0 + nConc * temp1); + *ndFac = temp2; + *dNdFac = -temp2 * temp2 * temp1; + } + + eLev = info->eAcc; + if (info->material != GAAS) { + eLev -= LEVEL_ALPHA_SI * pow(pNode->na * NNorm, 1.0 / 3.0); + if (eLev < 0.0) + eLev = 0.0; + } + if (eLev >= ExpLim) { + *naFac = 0.0; + *dNaFac = 0.0; + } else if (eLev <= -ExpLim) { + *naFac = 1.0; + *dNaFac = 0.0; + } else { + temp1 = info->gAcc * exp(eLev) * NNorm / info->nv0; + temp2 = 1.0 / (1.0 + pConc * temp1); + *naFac = temp2; + *dNaFac = -temp2 * temp2 * temp1; + } +} diff --git a/src/ciderlib/oned/onemesh.c b/src/ciderlib/oned/onemesh.c new file mode 100644 index 000000000..ca3ad50a8 --- /dev/null +++ b/src/ciderlib/oned/onemesh.c @@ -0,0 +1,371 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "macros.h" +#include "onedext.h" +#include "oneddefs.h" + + +/* Forward Declarations */ +static void ONEresetEvalFlag(ONEdevice *); + +void +ONEbuildMesh(ONEdevice *pDevice, ONEcoord *pCoord, ONEdomain *pDomain, + ONEmaterial *pMaterial) +{ + int index, i; + int elemType; + double xPos; + ONEcoord *pC; + ONEnode *pNode, *pNextNode; + ONEdomain *pD; + ONEelem *pElem; + ONEmaterial *pM; + int poiEqn, numEqn; + ONEedge *pEdge; + ONEnode **nodeArray=NULL; + BOOLEAN error = FALSE; + + + /* generate the work array for setting up nodes and elements */ + XCALLOC(nodeArray, ONEnode *, 1 + pDevice->numNodes); + + for (pC = pCoord; pC != NIL(ONEcoord); pC = pC->next) { + xPos = pC->location; + XCALLOC(pNode, ONEnode, 1); + pNode->x = xPos; + pNode->nodeI = pC->number; + nodeArray[pNode->nodeI] = pNode; + } + + /* mark the domain info on the nodes */ + if (pDomain == NIL(ONEdomain)) { + fprintf(stderr, "Error: domains not defined for device\n"); + exit(-1); + } + for (pD = pDomain; pD != NIL(ONEdomain); pD = pD->next) { + for (pM = pMaterial; pM != NIL(ONEmaterial); pM = pM->next) { + if (pD->material == pM->id) { + break; + } + } + elemType = pM->type; + for (index = pD->ixLo; index <= pD->ixHi; index++) { + pNode = nodeArray[index]; + if (!pNode->nodeType) { + pNode->nodeType = elemType; + } + } + } + + /* + * check to see if a domain has been defined for all nodes. if not flag an + * error message + */ + for (index = 2; index < pDevice->numNodes; index++) { + pNode = nodeArray[index]; + if (!pNode->nodeType) { + printf("Error: No domain defined for node %d\n", pNode->nodeI); + error = TRUE; + } + } + if (error) { + /* nodes with undefined domains -- exit */ + exit(-1); + } + /* mark the first and last nodes to be contact nodes */ + nodeArray[1]->nodeType = CONTACT; + nodeArray[pDevice->numNodes]->nodeType = CONTACT; + + + /* generate the elements and the edges */ + for (index = 1; index < pDevice->numNodes; index++) { + XCALLOC(pElem, ONEelem, 1); + XCALLOC(pEdge, ONEedge, 1); + pElem->pEdge = pEdge; + pElem->pLeftNode = nodeArray[index]; + pElem->pRightNode = nodeArray[index + 1]; + pDevice->elemArray[index] = pElem; + } + + /* now setup the nodes to which an element belongs */ + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + pElem->pLeftNode->pRightElem = pElem; + pElem->pRightNode->pLeftElem = pElem; + if (index > 1) { + pElem->pLeftElem = pDevice->elemArray[index - 1]; + } + if (index < pDevice->numNodes - 1) { + pElem->pRightElem = pDevice->elemArray[index + 1]; + } + } + + /* mark the domain info on the elements */ + for (pD = pDomain; pD != NIL(ONEdomain); pD = pD->next) { + for (pM = pMaterial; pM != NIL(ONEmaterial); pM = pM->next) { + if (pD->material == pM->id) { + break; + } + } + elemType = pM->type; + for (index = pD->ixLo; index < pD->ixHi; index++) { + pElem = pDevice->elemArray[index]; + pElem->domain = pD->id; + pElem->elemType = elemType; + pElem->matlInfo = pM; + } + } + + /* identify the interface nodes */ + for (index = 2; index < pDevice->numNodes; index++) { + pNode = nodeArray[index]; + if (pNode->pLeftElem->elemType != + pNode->pRightElem->elemType) { + /* an interface node */ + pNode->nodeType = INTERFACE; + } + } + + /* now mark the nodes to be evaluated */ + /* all interface nodes marked in silicon elements */ + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + pElem->dx = pElem->pRightNode->x - pElem->pLeftNode->x; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + pElem->evalNodes[i] = FALSE; + if (pElem->elemType == INSULATOR) { + if ((!pNode->evaluated) && + (pNode->nodeType != INTERFACE)) { + /* a non interface node in oxide domain */ + pNode->evaluated = TRUE; + pElem->evalNodes[i] = TRUE; + } + } + if (pElem->elemType == SEMICON) { + if (!pNode->evaluated) { + pNode->evaluated = TRUE; + pElem->evalNodes[i] = TRUE; + } + } + } + } + + /* set up the equation number for the nodes */ + poiEqn = numEqn = 1; + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + pNode->poiEqn = poiEqn++; + + pNode->psiEqn = numEqn; + if (pElem->elemType == INSULATOR) { + numEqn += 1; /* only poisson's equation */ + } else { + pNode->nEqn = numEqn + 1; + pNode->pEqn = numEqn + 2; + numEqn += 3; + } + } else { /* this is a contact node */ + pNode->poiEqn = 0; + pNode->psiEqn = 0; + pNode->nEqn = 0; + pNode->pEqn = 0; + } + /* + * fprintf(stdout,"NODE: %d %d\n",pNode->nodeI,pNode->poiEqn); + */ + } + } + } + pDevice->dimEquil = poiEqn; + pDevice->dimBias = numEqn; + + /* + * ONEprnMesh( pDevice ); + */ +} + +/* + * We have a separate function for this, so that the setup routines can reset + * the state pointers without rebuilding the entire mesh. + */ +void +ONEgetStatePointers(ONEdevice *pDevice, int *numStates) +{ + int eIndex, nIndex; + ONEelem *pElem; + ONEnode *pNode; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->nodeState = *numStates; + *numStates += ONEnumNodeStates; + } + } + pElem->pEdge->edgeState = *numStates; + *numStates += ONEnumEdgeStates; + } +} + +/* adjust the base contact to be the position of maximum density */ +/* index EB and BC are the indexes for the eb, bc junctions */ +void +adjustBaseContact(ONEdevice *pDevice, int indexEB, int indexBC) +{ + int index, i, newBaseIndex, midPoint; + double maxDensity; + ONEnode *pNode; + ONEelem *pElem; + ONEnode *pBaseNode = pDevice->elemArray[pDevice->baseIndex]->pNodes[0]; + + + /* Initialize the base contact to be the center of the two junctions */ + /* This should take care of uniform dopings. */ + + midPoint = 0.5 * (indexEB + indexBC); + newBaseIndex = midPoint; + + if (pBaseNode->baseType == P_TYPE) { + maxDensity = pDevice->elemArray[midPoint]->pNodes[0]->pConc; + for (index = indexEB; index < indexBC; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->pConc > maxDensity) { + maxDensity = pNode->pConc; + newBaseIndex = index; + } + } + } + } else if (pBaseNode->baseType == N_TYPE) { + maxDensity = pDevice->elemArray[midPoint]->pNodes[0]->nConc; + for (index = indexEB; index < indexBC; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nConc > maxDensity) { + maxDensity = pNode->nConc; + newBaseIndex = index; + } + } + } + } else { + printf("adjustBaseContact: unknown base type %d\n", pBaseNode->baseType); + } + /* at the conclusion of this loop have the point of max density */ + if (pDevice->baseIndex != newBaseIndex) { + /* so change the position */ + pNode = pDevice->elemArray[newBaseIndex]->pNodes[0]; + pNode->baseType = pBaseNode->baseType; + pNode->vbe = pBaseNode->vbe; + pBaseNode->baseType = FALSE; + pBaseNode->vbe = 0.0; + pDevice->baseIndex = newBaseIndex; + } +} + +void +NBJTjunctions(ONEdevice *pDevice, int *indexEB, int *indexBC) +{ + int index; + double conc1, conc2; + BOOLEAN findFirstJunction = TRUE; + BOOLEAN notFound = TRUE; + + for (index = 1; (index < pDevice->numNodes) && (notFound); index++) { + conc1 = pDevice->elemArray[index]->pNodes[0]->netConc; + conc2 = pDevice->elemArray[index]->pNodes[1]->netConc; + + if ((conc1 * conc2 < 0.0) && (findFirstJunction)) { + *indexEB = index; + findFirstJunction = FALSE; + } else if ((conc1 * conc2 < 0.0) && (!findFirstJunction)) { + *indexBC = index; + notFound = FALSE; + } + } + + if (notFound) { + fprintf(stderr, "BJT: Device does not have two junctions!\n"); + exit(-1); + } +} + +void +ONEprnMesh(ONEdevice *pDevice) +{ + int eIndex, index; + ONEelem *pElem; + ONEnode *pNode; + ONEedge *pEdge; + char *name; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + fprintf(stderr, "elem %5d:\n", eIndex); + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + switch (pNode->nodeType) { + case SEMICON: + name = "semiconductor"; + break; + case INSULATOR: + name = "insulator"; + break; + case CONTACT: + name = "contact"; + break; + case SCHOTTKY: + name = "schottky"; + break; + case INTERFACE: + name = "interface"; + break; + default: + name = "unknown"; + break; + } + + + fprintf(stderr, "node %5d: %s %5d\n", index, name, + pNode->nodeI); + + } + } + } +} + +static void +ONEresetEvalFlag(ONEdevice *pDevice) +{ + int index, eIndex; + ONEelem *pElem; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + pElem->pNodes[index]->evaluated = FALSE; + } + pElem->pEdge->evaluated = FALSE; + } +} diff --git a/src/ciderlib/oned/onepoiss.c b/src/ciderlib/oned/onepoiss.c new file mode 100644 index 000000000..0ce6ca821 --- /dev/null +++ b/src/ciderlib/oned/onepoiss.c @@ -0,0 +1,192 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "onedext.h" +#include "oneddefs.h" +#include "spMatrix.h" + +/* Functions to setup and solve the 1D poisson equation. */ + + +/* Forward Declarations */ +void ONEQcommonTerms(ONEdevice *pDevice); + +void +ONEQjacBuild(ONEdevice *pDevice) +{ + char *matrix = pDevice->matrix; + ONEelem *pElem; + ONEnode *pNode, *pNode1; + int index; + + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + pNode = pElem->pLeftNode; + pNode->fPsiPsi = spGetElement(matrix, pNode->poiEqn, pNode->poiEqn); + pNode1 = pElem->pRightNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn); + pNode = pElem->pRightNode; + pNode->fPsiPsi = spGetElement(matrix, pNode->poiEqn, pNode->poiEqn); + pNode1 = pElem->pLeftNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn); + } +} + + +void +ONEQsysLoad(ONEdevice *pDevice) +{ + ONEelem *pElem; + ONEnode *pNode, *pNode1; + int index, i; + double *pRhs = pDevice->rhs; + double rDx, dPsi; + double netConc, dNetConc; + double fNd, fdNd, fNa, fdNa; + + + ONEQcommonTerms(pDevice); + + /* zero the rhs vector */ + for (index = 1; index <= pDevice->numEqns; index++) { + pRhs[index] = 0.0; + } + + /* zero the matrix */ + spClear(pDevice->matrix); + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + rDx = pElem->epsRel * pElem->rDx; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + *(pNode->fPsiPsi) += rDx; + pRhs[pNode->poiEqn] += pNode->qf; + if (pElem->elemType == SEMICON) { + netConc = pNode->netConc; + dNetConc = 0.0; + if (FreezeOut) { + ONEQfreezeOut(pNode, &fNd, &fNa, &fdNd, &fdNa); + netConc = pNode->nd * fNd - pNode->na * fNa; + dNetConc = pNode->nd * fdNd - pNode->na * fdNa; + } + *(pNode->fPsiPsi) += 0.5 * pElem->dx * + (pNode->nConc + pNode->pConc - dNetConc); + pRhs[pNode->poiEqn] += 0.5 * pElem->dx * + (netConc + pNode->pConc - pNode->nConc); + } + } + } + + dPsi = pElem->pEdge->dPsi; + + pNode = pElem->pLeftNode; + pRhs[pNode->poiEqn] += rDx * dPsi; + *(pNode->fPsiPsiiP1) -= rDx; + + pNode = pElem->pRightNode; + pRhs[pNode->poiEqn] -= rDx * dPsi; + *(pNode->fPsiPsiiM1) -= rDx; + } +} + + +void +ONEQrhsLoad(ONEdevice *pDevice) +{ + ONEelem *pElem; + ONEnode *pNode; + int index, i; + double *pRhs = pDevice->rhs; + double rDx, dPsi; + double fNd, fNa, fdNd, fdNa; + double netConc; + + + ONEQcommonTerms(pDevice); + + /* zero the rhs vector */ + for (index = 1; index <= pDevice->numEqns; index++) { + pRhs[index] = 0.0; + } + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + rDx = pElem->epsRel * pElem->rDx; + for (i = 0; i <= 1; i++) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + pRhs[pNode->poiEqn] += pNode->qf; + if (pElem->elemType == SEMICON) { + netConc = pNode->netConc; + if (FreezeOut) { + ONEQfreezeOut(pNode, &fNd, &fNa, &fdNd, &fdNa); + netConc = pNode->nd * fNd - pNode->na * fNa; + } + pRhs[pNode->poiEqn] += 0.5 * pElem->dx * + (netConc + pNode->pConc - pNode->nConc); + } + } + } + + dPsi = pElem->pEdge->dPsi; + + pNode = pElem->pLeftNode; + pRhs[pNode->poiEqn] += rDx * dPsi; + + pNode = pElem->pRightNode; + pRhs[pNode->poiEqn] -= rDx * dPsi; + } +} + + +void +ONEQcommonTerms(ONEdevice *pDevice) +{ + ONEelem *pElem; + ONEedge *pEdge; + ONEnode *pNode, *pNode1; + int i, index; + double psi1, psi2, refPsi; + + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + refPsi = pElem->matlInfo->refPsi; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + pNode->psi = pDevice->dcSolution[pNode->poiEqn]; + if (pElem->elemType == SEMICON) { + pNode->nConc = pNode->nie * exp(pNode->psi - refPsi); + pNode->pConc = pNode->nie * exp(-pNode->psi + refPsi); + } + } + } + } + pEdge = pElem->pEdge; + pNode = pElem->pNodes[0]; + if (pNode->nodeType != CONTACT) { + psi1 = pDevice->dcSolution[pNode->poiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pNodes[1]; + if (pNode->nodeType != CONTACT) { + psi2 = pDevice->dcSolution[pNode->poiEqn]; + } else { + psi2 = pNode->psi; + } + pEdge->dPsi = psi2 - psi1; + } +} diff --git a/src/ciderlib/oned/oneprint.c b/src/ciderlib/oned/oneprint.c new file mode 100644 index 000000000..b2fdd705a --- /dev/null +++ b/src/ciderlib/oned/oneprint.c @@ -0,0 +1,534 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "carddefs.h" +#include "spMatrix.h" +#include "onedext.h" +#include "oneddefs.h" + +void +ONEprnSolution(FILE *file, ONEdevice *pDevice, OUTPcard *output) +{ + int index, i; + int numVars = 0; + ONEnode **nodeArray=NULL; + ONEnode *pNode; + ONEelem *pElem, *pPrevElem; + ONEmaterial *info=NULL; + double data[50]; + double eField, refPsi = 0.0, eGap, dGap; + double mun, mup, jc, jd, jn, jp, jt; + double coeff1, coeff2; + + + if (output->OUTPnumVars == -1) { + /* First pass. Need to count number of variables in output. */ + numVars++; /* For the X scale */ + if (output->OUTPdoping) { + numVars++; + } + if (output->OUTPpsi) { + numVars++; + } + if (output->OUTPequPsi) { + numVars++; + } + if (output->OUTPvacPsi) { + numVars++; + } + if (output->OUTPnConc) { + numVars++; + } + if (output->OUTPpConc) { + numVars++; + } + if (output->OUTPphin) { + numVars++; + } + if (output->OUTPphip) { + numVars++; + } + if (output->OUTPphic) { + numVars++; + } + if (output->OUTPphiv) { + numVars++; + } + if (output->OUTPeField) { + numVars++; + } + if (output->OUTPjc) { + numVars++; + } + if (output->OUTPjd) { + numVars++; + } + if (output->OUTPjn) { + numVars++; + } + if (output->OUTPjp) { + numVars++; + } + if (output->OUTPjt) { + numVars++; + } + if (output->OUTPuNet) { + numVars++; + } + if (output->OUTPmun) { + numVars++; + } + if (output->OUTPmup) { + numVars++; + } + output->OUTPnumVars = numVars; + } + /* generate the work array for printing node info */ + XCALLOC(nodeArray, ONEnode *, 1 + pDevice->numNodes); + + /* store the nodes in this work array and print out later */ + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (refPsi == 0.0 && pElem->matlInfo->type == SEMICON) { + refPsi = pElem->matlInfo->refPsi; + } + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + nodeArray[pNode->nodeI] = pNode; + } + } + } + + /* Initialize rawfile */ + numVars = output->OUTPnumVars; + fprintf(file, "Title: Device %s internal state\n", pDevice->name); + fprintf(file, "Plotname: Device Cross Section\n"); + fprintf(file, "Flags: real\n"); + fprintf(file, "Command: deftype p xs cross\n"); + fprintf(file, "Command: deftype v distance m\n"); + fprintf(file, "Command: deftype v concentration cm^-3\n"); + fprintf(file, "Command: deftype v electric_field V/cm\n"); + fprintf(file, "Command: deftype v current_density A/cm^2\n"); + fprintf(file, "Command: deftype v concentration/time cm^-3/s\n"); + fprintf(file, "Command: deftype v mobility cm^2/Vs\n"); + fprintf(file, "No. Variables: %d\n", numVars); + fprintf(file, "No. Points: %d\n", pDevice->numNodes); + numVars = 0; + fprintf(file, "Variables:\n"); + fprintf(file, "\t%d x distance\n", numVars++); + if (output->OUTPpsi) { + fprintf(file, "\t%d psi voltage\n", numVars++); + } + if (output->OUTPequPsi) { + fprintf(file, "\t%d equ.psi voltage\n", numVars++); + } + if (output->OUTPvacPsi) { + fprintf(file, "\t%d vac.psi voltage\n", numVars++); + } + if (output->OUTPphin) { + fprintf(file, "\t%d phin voltage\n", numVars++); + } + if (output->OUTPphip) { + fprintf(file, "\t%d phip voltage\n", numVars++); + } + if (output->OUTPphic) { + fprintf(file, "\t%d phic voltage\n", numVars++); + } + if (output->OUTPphiv) { + fprintf(file, "\t%d phiv voltage\n", numVars++); + } + if (output->OUTPdoping) { + fprintf(file, "\t%d dop concentration\n", numVars++); + } + if (output->OUTPnConc) { + fprintf(file, "\t%d n concentration\n", numVars++); + } + if (output->OUTPpConc) { + fprintf(file, "\t%d p concentration\n", numVars++); + } + if (output->OUTPeField) { + fprintf(file, "\t%d e electric_field\n", numVars++); + } + if (output->OUTPjc) { + fprintf(file, "\t%d jc current_density\n", numVars++); + } + if (output->OUTPjd) { + fprintf(file, "\t%d jd current_density\n", numVars++); + } + if (output->OUTPjn) { + fprintf(file, "\t%d jn current_density\n", numVars++); + } + if (output->OUTPjp) { + fprintf(file, "\t%d jp current_density\n", numVars++); + } + if (output->OUTPjt) { + fprintf(file, "\t%d jt current_density\n", numVars++); + } + if (output->OUTPuNet) { + fprintf(file, "\t%d unet concentration/time\n", numVars++); + } + if (output->OUTPmun) { + fprintf(file, "\t%d mun mobility\n", numVars++); + } + if (output->OUTPmup) { + fprintf(file, "\t%d mup mobility\n", numVars++); + } + fprintf(file, "Binary:\n"); + + for (index = 1; index <= pDevice->numNodes; index++) { + pNode = nodeArray[index]; + if ((index > 1) && (index < pDevice->numNodes)) { + pElem = pNode->pRightElem; + pPrevElem = pNode->pLeftElem; + if (pElem->evalNodes[0]) { + info = pElem->matlInfo; + } else if (pPrevElem->evalNodes[1]) { + info = pPrevElem->matlInfo; + } + coeff1 = pPrevElem->dx / (pPrevElem->dx + pElem->dx); + coeff2 = pElem->dx / (pPrevElem->dx + pElem->dx); + eField = -coeff1 * pElem->pEdge->dPsi * pElem->rDx + - coeff2 * pPrevElem->pEdge->dPsi * pPrevElem->rDx; + mun = coeff1 * pElem->pEdge->mun + coeff2 * pPrevElem->pEdge->mun; + mup = coeff1 * pElem->pEdge->mup + coeff2 * pPrevElem->pEdge->mup; + jn = coeff1 * pElem->pEdge->jn + coeff2 * pPrevElem->pEdge->jn; + jp = coeff1 * pElem->pEdge->jp + coeff2 * pPrevElem->pEdge->jp; + jd = coeff1 * pElem->pEdge->jd + coeff2 * pPrevElem->pEdge->jd; + } else if (index == 1) { + info = pNode->pRightElem->matlInfo; + eField = 0.0; + mun = pNode->pRightElem->pEdge->mun; + mup = pNode->pRightElem->pEdge->mup; + jn = pNode->pRightElem->pEdge->jn; + jp = pNode->pRightElem->pEdge->jp; + jd = pNode->pRightElem->pEdge->jd; + } else { + info = pNode->pLeftElem->matlInfo; + eField = 0.0; + mun = pNode->pLeftElem->pEdge->mun; + mup = pNode->pLeftElem->pEdge->mup; + jn = pNode->pLeftElem->pEdge->jn; + jp = pNode->pLeftElem->pEdge->jp; + jd = pNode->pLeftElem->pEdge->jd; + } + jc = jn + jp; + jt = jc + jd; + /* Crude hack to get around the fact that the base node wipes out 'eg' */ + if (index == pDevice->baseIndex) { + eGap = info->eg0; + dGap = 0.0; + } else { + eGap = pNode->eg * VNorm; + dGap = 0.5 * (info->eg0 - eGap); + } + + /* Now fill in the data array */ + numVars = 0; + data[numVars++] = pNode->x * 1e-2; + if (output->OUTPpsi) { + data[numVars++] = (pNode->psi - refPsi) * VNorm; + } + if (output->OUTPequPsi) { + data[numVars++] = (pNode->psi0 - refPsi) * VNorm; + } + if (output->OUTPvacPsi) { + data[numVars++] = pNode->psi * VNorm; + } + if (output->OUTPphin) { + if (info->type != INSULATOR) { + data[numVars++] = (pNode->psi - refPsi - log(pNode->nConc / pNode->nie)) + * VNorm; + } else { + data[numVars++] = 0.0; + } + } + if (output->OUTPphip) { + if (info->type != INSULATOR) { + data[numVars++] = (pNode->psi - refPsi + log(pNode->pConc / pNode->nie)) + * VNorm; + } else { + data[numVars++] = 0.0; + } + } + if (output->OUTPphic) { + data[numVars++] = (pNode->psi + pNode->eaff) * VNorm + dGap; + } + if (output->OUTPphiv) { + data[numVars++] = (pNode->psi + pNode->eaff) * VNorm + dGap + eGap; + } + if (output->OUTPdoping) { + data[numVars++] = pNode->netConc * NNorm; + } + if (output->OUTPnConc) { + data[numVars++] = pNode->nConc * NNorm; + } + if (output->OUTPpConc) { + data[numVars++] = pNode->pConc * NNorm; + } + if (output->OUTPeField) { + data[numVars++] = eField * ENorm; + } + if (output->OUTPjc) { + data[numVars++] = jc * JNorm; + } + if (output->OUTPjd) { + data[numVars++] = jd * JNorm; + } + if (output->OUTPjn) { + data[numVars++] = jn * JNorm; + } + if (output->OUTPjp) { + data[numVars++] = jp * JNorm; + } + if (output->OUTPjt) { + data[numVars++] = jt * JNorm; + } + if (output->OUTPuNet) { + data[numVars++] = pNode->uNet * NNorm / TNorm; + } + if (output->OUTPmun) { + data[numVars++] = mun; + } + if (output->OUTPmup) { + data[numVars++] = mup; + } + fwrite((char *) data, sizeof(double), numVars, file); + } + FREE(nodeArray); +} + +/* + * XXX This is what the SPARSE element structure looks like. We can't take it + * from its definition because the include file redefines all sorts of + * things. Note that we are violating data encapsulation to find out the + * size of this thing. + */ +struct MatrixElement { + spREAL Real; + spREAL Imag; + int Row; + int Col; + struct MatrixElement *NextInRow; + struct MatrixElement *NextInCol; +}; + +void +ONEmemStats(FILE *file, ONEdevice *pDevice) +{ + static char *memFormat = "%-20s%10d%10d\n"; + static char *sumFormat = "%20s %-10d\n"; + unsigned int size; + unsigned int memory; + ONEmaterial *pMaterial; + ONEcontact *pContact; + int numContactNodes; + + + fprintf(file, "----------------------------------------\n"); + fprintf(file, "Device %s Memory Usage:\n", pDevice->name); + fprintf(file, "Item Count Bytes\n"); + fprintf(file, "----------------------------------------\n"); + + size = 1; + memory = size * sizeof(ONEdevice); + fprintf(file, memFormat, "Device", size, memory); + size = pDevice->numNodes - 1; + memory = size * sizeof(ONEelem); + fprintf(file, memFormat, "Elements", size, memory); + size = pDevice->numNodes; + memory = size * sizeof(ONEnode); + fprintf(file, memFormat, "Nodes", size, memory); + size = pDevice->numNodes - 1; + memory = size * sizeof(ONEedge); + fprintf(file, memFormat, "Edges", size, memory); + + size = pDevice->numNodes; + memory = size * sizeof(ONEelem *); + size = 0; + for (pMaterial = pDevice->pMaterials; pMaterial; pMaterial = pMaterial->next) + size++; + memory += size * sizeof(ONEmaterial); + size = numContactNodes = 0; + for (pContact = pDevice->pFirstContact; pContact; pContact = pContact->next) { + numContactNodes += pContact->numNodes; + size++; + } + memory += size * sizeof(ONEcontact); + size = numContactNodes; + memory += size * sizeof(ONEnode *); + size = 0; + fprintf(file, "%-20s%10s%10d\n", "Misc Mesh", "n/a", memory); + + size = pDevice->numOrigEquil; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Equil Orig NZ", size, memory); + size = pDevice->numFillEquil; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Equil Fill NZ", size, memory); + size = pDevice->numOrigEquil + pDevice->numFillEquil; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Equil Tot NZ", size, memory); + size = pDevice->dimEquil; + memory = size * 4 * sizeof(double); + fprintf(file, memFormat, "Equil Vectors", size, memory); + + size = pDevice->numOrigBias; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Bias Orig NZ", size, memory); + size = pDevice->numFillBias; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Bias Fill NZ", size, memory); + size = pDevice->numOrigBias + pDevice->numFillBias; + memory = size * sizeof(struct MatrixElement); + fprintf(file, memFormat, "Bias Tot NZ", size, memory); + size = pDevice->dimBias; + memory = size * 5 * sizeof(double); + fprintf(file, memFormat, "Bias Vectors", size, memory); + + size = (pDevice->numNodes - 1) * ONEnumEdgeStates + + pDevice->numNodes * ONEnumNodeStates; + memory = size * sizeof(double); + fprintf(file, memFormat, "State Vector", size, memory); +} + +void +ONEcpuStats(FILE *file, ONEdevice *pDevice) +{ + static char *cpuFormat = "%-20s%10g%10g%10g%10g%10g\n"; + ONEstats *pStats = pDevice->pStats; + double total; + int iTotal; + + fprintf(file, + "----------------------------------------------------------------------\n"); + fprintf(file, + "Device %s Time Usage:\n", pDevice->name); + fprintf(file, + "Item SETUP DC TRAN AC TOTAL\n"); + fprintf(file, + "----------------------------------------------------------------------\n"); + + total = pStats->setupTime[STAT_SETUP] + + pStats->setupTime[STAT_DC] + + pStats->setupTime[STAT_TRAN] + + pStats->setupTime[STAT_AC]; + fprintf(file, cpuFormat, "Setup Time", + pStats->setupTime[STAT_SETUP], + pStats->setupTime[STAT_DC], + pStats->setupTime[STAT_TRAN], + pStats->setupTime[STAT_AC], + total); + + total = pStats->loadTime[STAT_SETUP] + + pStats->loadTime[STAT_DC] + + pStats->loadTime[STAT_TRAN] + + pStats->loadTime[STAT_AC]; + fprintf(file, cpuFormat, "Load Time", + pStats->loadTime[STAT_SETUP], + pStats->loadTime[STAT_DC], + pStats->loadTime[STAT_TRAN], + pStats->loadTime[STAT_AC], + total); + + total = pStats->orderTime[STAT_SETUP] + + pStats->orderTime[STAT_DC] + + pStats->orderTime[STAT_TRAN] + + pStats->orderTime[STAT_AC]; + fprintf(file, cpuFormat, "Order Time", + pStats->orderTime[STAT_SETUP], + pStats->orderTime[STAT_DC], + pStats->orderTime[STAT_TRAN], + pStats->orderTime[STAT_AC], + total); + + total = pStats->factorTime[STAT_SETUP] + + pStats->factorTime[STAT_DC] + + pStats->factorTime[STAT_TRAN] + + pStats->factorTime[STAT_AC]; + fprintf(file, cpuFormat, "Factor Time", + pStats->factorTime[STAT_SETUP], + pStats->factorTime[STAT_DC], + pStats->factorTime[STAT_TRAN], + pStats->factorTime[STAT_AC], + total); + + total = pStats->solveTime[STAT_SETUP] + + pStats->solveTime[STAT_DC] + + pStats->solveTime[STAT_TRAN] + + pStats->solveTime[STAT_AC]; + fprintf(file, cpuFormat, "Solve Time", + pStats->solveTime[STAT_SETUP], + pStats->solveTime[STAT_DC], + pStats->solveTime[STAT_TRAN], + pStats->solveTime[STAT_AC], + total); + + total = pStats->updateTime[STAT_SETUP] + + pStats->updateTime[STAT_DC] + + pStats->updateTime[STAT_TRAN] + + pStats->updateTime[STAT_AC]; + fprintf(file, cpuFormat, "Update Time", + pStats->updateTime[STAT_SETUP], + pStats->updateTime[STAT_DC], + pStats->updateTime[STAT_TRAN], + pStats->updateTime[STAT_AC], + total); + + total = pStats->checkTime[STAT_SETUP] + + pStats->checkTime[STAT_DC] + + pStats->checkTime[STAT_TRAN] + + pStats->checkTime[STAT_AC]; + fprintf(file, cpuFormat, "Check Time", + pStats->checkTime[STAT_SETUP], + pStats->checkTime[STAT_DC], + pStats->checkTime[STAT_TRAN], + pStats->checkTime[STAT_AC], + total); + + total = pStats->setupTime[STAT_SETUP] + + pStats->setupTime[STAT_DC] + + pStats->setupTime[STAT_TRAN] + + pStats->setupTime[STAT_AC]; + fprintf(file, cpuFormat, "Misc Time", + pStats->miscTime[STAT_SETUP], + pStats->miscTime[STAT_DC], + pStats->miscTime[STAT_TRAN], + pStats->miscTime[STAT_AC], + total); + + fprintf(file, "%-40s%10g%10s%10g\n", "LTE Time", + pStats->lteTime, + "", pStats->lteTime); + + total = pStats->totalTime[STAT_SETUP] + + pStats->totalTime[STAT_DC] + + pStats->totalTime[STAT_TRAN] + + pStats->totalTime[STAT_AC]; + fprintf(file, cpuFormat, "Total Time", + pStats->totalTime[STAT_SETUP], + pStats->totalTime[STAT_DC], + pStats->totalTime[STAT_TRAN], + pStats->totalTime[STAT_AC], + total); + + iTotal = pStats->numIters[STAT_SETUP] + + pStats->numIters[STAT_DC] + + pStats->numIters[STAT_TRAN] + + pStats->numIters[STAT_AC]; + fprintf(file, "%-20s%10d%10d%10d%10d%10d\n", "Iterations", + pStats->numIters[STAT_SETUP], + pStats->numIters[STAT_DC], + pStats->numIters[STAT_TRAN], + pStats->numIters[STAT_AC], + iTotal); +} diff --git a/src/ciderlib/oned/oneproj.c b/src/ciderlib/oned/oneproj.c new file mode 100644 index 000000000..4d23b673b --- /dev/null +++ b/src/ciderlib/oned/oneproj.c @@ -0,0 +1,348 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "macros.h" +#include "spMatrix.h" +#include "bool.h" +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" + + +/* + * Functions for projecting the next solution point for modified two-level + * newton scheme + */ + +void +NUMDproject(ONEdevice *pDevice, double delV) +{ + ONEelem *pElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEnode *pNode; + ONEedge *pEdge; + double delPsi, delN, delP, newN, newP; + double *incVpn; + int i, index; + + + delV = - delV / VNorm; + pElem->pRightNode->psi += delV; + + if (ABS(delV) < MIN_DELV) { + ONEstoreInitialGuess(pDevice); + return; + } + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + /* compute incremental changes due to N contact */ + pNode = pElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pElem->epsRel * pElem->rDx; + if (pElem->elemType == SEMICON) { + pEdge = pElem->pEdge; + pDevice->rhs[pNode->nEqn] = -pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] = -pEdge->dJpDpsiP1; + } + incVpn = pDevice->dcDeltaSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVpn, NIL(spREAL), NIL(spREAL)); + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + delPsi = incVpn[pNode->psiEqn] * delV; + pDevice->dcSolution[pNode->psiEqn] = pNode->psi + delPsi; + if (pElem->elemType == SEMICON) { + delN = incVpn[pNode->nEqn] * delV; + delP = incVpn[pNode->pEqn] * delV; + newN = pNode->nConc + delN; + newP = pNode->pConc + delP; + /* if new conc less than 0.0 then use a fraction of the guess */ + if (newN <= 0.0) { + pDevice->dcSolution[pNode->nEqn] = + guessNewConc(pNode->nConc, delN); + } else { + pDevice->dcSolution[pNode->nEqn] = newN; + } + if (newP <= 0.0) { + pDevice->dcSolution[pNode->pEqn] = + guessNewConc(pNode->pConc, delP); + } else { + pDevice->dcSolution[pNode->pEqn] = newP; + } + } + } + } + } + } +} + + +void +NBJTproject(ONEdevice *pDevice, double delVce, double delVbe, + double vbe) +{ + ONEelem *pLastElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEelem *pElem; + ONEnode *pNode; + ONEedge *pEdge; + double delPsi, delN, delP, newN, newP, *incVce, *incVbe; + double baseConc=0.0; + int i, index; + double nConc, pConc; + + /* normalize the voltages for calculations */ + delVce = delVce / VNorm; + delVbe = delVbe / VNorm; + pLastElem->pRightNode->psi += delVce; + pBaseElem->pRightNode->vbe = vbe / VNorm + pBaseElem->matlInfo->refPsi; + pNode = pBaseElem->pRightNode; + if (pNode->baseType == N_TYPE) { + baseConc = pNode->nConc; + } else if (pNode->baseType == P_TYPE) { + baseConc = pNode->pConc; + } + if (ABS(delVce) > MIN_DELV) { + + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + /* store the new rhs for computing the incremental quantities */ + pNode = pLastElem->pLeftNode; + pDevice->rhs[pNode->psiEqn] = pLastElem->epsRel * pLastElem->rDx; + if (pLastElem->elemType == SEMICON) { + pEdge = pLastElem->pEdge; + pDevice->rhs[pNode->nEqn] = -pEdge->dJnDpsiP1; + pDevice->rhs[pNode->pEqn] = -pEdge->dJpDpsiP1; + } + incVce = pDevice->dcDeltaSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVce, NIL(spREAL), NIL(spREAL)); + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + delPsi = incVce[pNode->psiEqn] * delVce; + pDevice->dcSolution[pNode->psiEqn] = pNode->psi + delPsi; + if (pElem->elemType == SEMICON) { + delN = incVce[pNode->nEqn] * delVce; + delP = incVce[pNode->pEqn] * delVce; + newN = pNode->nConc + delN; + newP = pNode->pConc + delP; + /* if new conc less than 0.0 then use a fraction of the guess */ + if (newN <= 0.0) { + pDevice->dcSolution[pNode->nEqn] = + guessNewConc(pNode->nConc, delN); + } else { + pDevice->dcSolution[pNode->nEqn] = newN; + } + if (newP <= 0.0) { + pDevice->dcSolution[pNode->pEqn] = + guessNewConc(pNode->pConc, delP); + } else { + pDevice->dcSolution[pNode->pEqn] = newP; + } + } + } + } + } + } + } else { + ONEstoreInitialGuess(pDevice); + } + + if (ABS(delVbe) > MIN_DELV) { + + /* zero the rhs before loading in the new rhs base contribution */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhs[index] = 0.0; + } + pNode = pBaseElem->pRightNode; + if (pNode->baseType == N_TYPE) { + pDevice->rhs[pNode->nEqn] = baseConc * pNode->eg; + } else if (pNode->baseType == P_TYPE) { + pDevice->rhs[pNode->pEqn] = baseConc * pNode->eg; + } + + incVbe = pDevice->copiedSolution; + spSolve(pDevice->matrix, pDevice->rhs, incVbe, NIL(spREAL), NIL(spREAL)); + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + delPsi = incVbe[pNode->psiEqn] * delVbe; + pDevice->dcSolution[pNode->psiEqn] += delPsi; + if (pElem->elemType == SEMICON) { + delN = incVbe[pNode->nEqn] * delVbe; + delP = incVbe[pNode->pEqn] * delVbe; + nConc = pDevice->dcSolution[pNode->nEqn]; + pConc = pDevice->dcSolution[pNode->pEqn]; + newN = nConc + delN; + newP = pConc + delP; + /* if new conc less than 0.0 then use a fraction of the guess */ + if (newN <= 0.0) { + pDevice->dcSolution[pNode->nEqn] = + guessNewConc(nConc, delN); + } else { + pDevice->dcSolution[pNode->nEqn] = newN; + } + if (newP <= 0.0) { + pDevice->dcSolution[pNode->pEqn] = + guessNewConc(pConc, delP); + } else { + pDevice->dcSolution[pNode->pEqn] = newP; + } + } + } + } + } + } + } +} + +/* functions to update device states for full-LU algorithm */ + +void +NUMDupdate(ONEdevice *pDevice, double delV, BOOLEAN updateBoundary) +{ + ONEelem *pElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEnode *pNode; + ONEedge *pEdge; + double delPsi, delN, delP; + int i, index; + + + delV = - delV / VNorm; + if (updateBoundary) { + pElem->pRightNode->psi += delV; + } + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + delPsi = pDevice->dcDeltaSolution[pNode->psiEqn] * delV; + pDevice->dcSolution[pNode->psiEqn] = pNode->psi + delPsi; + if (pElem->elemType == SEMICON) { + delN = pDevice->dcDeltaSolution[pNode->nEqn] * delV; + delP = pDevice->dcDeltaSolution[pNode->pEqn] * delV; + pDevice->dcSolution[pNode->nEqn] = pNode->nConc + delN; + pDevice->dcSolution[pNode->pEqn] = pNode->pConc + delP; + } + } + } + } + } +} + +void +NBJTupdate(ONEdevice *pDevice, double delVce, double delVbe, + double vbe, BOOLEAN updateBoundary) +{ + ONEelem *pLastElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEelem *pElem; + ONEnode *pNode; + double delPsi, delN, delP, *incVce, *incVbe; + int i, index; + + /* normalize the voltages for calculations */ + delVce = delVce / VNorm; + delVbe = delVbe / VNorm; + if (updateBoundary) { + pLastElem->pRightNode->psi += delVce; + pBaseElem->pRightNode->vbe = vbe / VNorm + pBaseElem->matlInfo->refPsi; + } + /* + * The incremental quantities are available from NBJTconductance. incVce + * (dcDeltaSolution) and incVbe (copiedSolution) are used to store the + * incremental quantities associated with Vce and Vbe + */ + + /* set incVce = dcDeltaSolution; incVbe = copiedSolution */ + incVce = pDevice->dcDeltaSolution; + incVbe = pDevice->copiedSolution; + + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + if (pNode->nodeType != CONTACT) { + delPsi = incVce[pNode->psiEqn] * delVce + + incVbe[pNode->psiEqn] * delVbe; + pDevice->dcSolution[pNode->psiEqn] = pNode->psi + delPsi; + if (pElem->elemType == SEMICON) { + delN = incVce[pNode->nEqn] * delVce + + incVbe[pNode->nEqn] * delVbe; + delP = incVce[pNode->pEqn] * delVce + + incVbe[pNode->pEqn] * delVbe; + pDevice->dcSolution[pNode->nEqn] = pNode->nConc + delN; + pDevice->dcSolution[pNode->pEqn] = pNode->pConc + delP; + } + } + } + } + } +} + +void +NUMDsetBCs(ONEdevice *pDevice, double vpn) +{ + ONEelem *pElem = pDevice->elemArray[pDevice->numNodes - 1]; + + + /* normalize the voltage */ + vpn = - vpn / VNorm; + + /* set the boundary conditions */ + pElem->pRightNode->psi = vpn + pElem->pRightNode->psi0; +} + +void +NBJTsetBCs(ONEdevice *pDevice, double vce, double vbe) +{ + ONEelem *pLastElem = pDevice->elemArray[pDevice->numNodes - 1]; + ONEelem *pBaseElem = pDevice->elemArray[pDevice->baseIndex - 1]; + ONEnode *pNode; + double psi, conc, sign, absConc; + double nie, ni, pi; + + /* normalize the voltages */ + vce = vce / VNorm; + vbe = vbe / VNorm; + + /* set the boundary conditions */ + pBaseElem->pRightNode->vbe = vbe + pBaseElem->matlInfo->refPsi; + pLastElem->pRightNode->psi = vce + pLastElem->pRightNode->psi0; + + /* + * if (pLastElem->elemType IS INSULATOR) { pNode->psi = RefPsi - + * pNode->eaff; pNode->nConc = 0.0; pNode->pConc = 0.0; } else if + * (pLastElem->elemType IS SEMICON) { nie = pNode->nie; conc = + * pNode->netConc / pNode->nie; psi = 0.0; ni = nie; pi = nie; sign = SGN( + * conc ); absConc = ABS( conc ); if ( conc ISNOT 0.0 ) { psi = sign * log( + * 0.5 * absConc + sqrt( 1.0 + 0.25*absConc*absConc )); ni = nie * exp( psi + * ); pi = nie * exp( -psi ); } pNode->psi = pLastElem->matlInfo->refPsi + + * psi; pNode->nConc = ni; pNode->pConc = pi; } pNode->psi = pNode->psi0; + * pNode->psi += vce; + */ +} diff --git a/src/ciderlib/oned/oneread.c b/src/ciderlib/oned/oneread.c new file mode 100644 index 000000000..c02bb82aa --- /dev/null +++ b/src/ciderlib/oned/oneread.c @@ -0,0 +1,94 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Functions needed to read solutions for 1D devices. + */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onedev.h" +#include "onemesh.h" +#include "plot.h" +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" + +int +ONEreadState(ONEdevice *pDevice, char *fileName, int numVolts, + double *pV1, double *pV2 ) +/* fileName | File containing raw data */ +/* int numVolts | Number of voltage differences */ +/* double *pV1, *pV2 | Pointer to return them in */ +{ + int dataLength; + int index, i; + ONEnode **nodeArray=NULL; + ONEnode *pNode; + ONEelem *pElem; + ONEmaterial *info; + double refPsi = 0.0; + double *psiData, *nData, *pData; + double *vData[2]; + struct plot *stateDB; + struct plot *voltsDB; + char voltName[80]; + + + stateDB = DBread( fileName ); + if (stateDB == NULL) return (-1); + voltsDB = stateDB->pl_next; + if (voltsDB == NULL) return (-1); + + for (i=0; i < numVolts; i++ ) { + sprintf( voltName, "v%d%d", i+1, numVolts+1 ); + vData[i] = DBgetData( voltsDB, voltName, 1 ); + if (vData[i] == NULL) return (-1); + } + dataLength = pDevice->numNodes; + psiData = DBgetData( stateDB, "psi", dataLength ); + nData = DBgetData( stateDB, "n", dataLength ); + pData = DBgetData( stateDB, "p", dataLength ); + if (psiData == NULL || nData == NULL || pData == NULL) return (-1); + + if (pV1 != NULL) { + *pV1 = vData[0][0]; + FREE( vData[0] ); + } + if (pV2 != NULL) { + *pV2 = vData[1][0]; + FREE( vData[1] ); + } + + /* generate the work array for copying node info */ + XCALLOC(nodeArray, ONEnode *, 1 + pDevice->numNodes); + + /* store the nodes in this work array and print out later */ + for (index = 1; index < pDevice->numNodes; index++) { + pElem = pDevice->elemArray[index]; + if (refPsi == 0.0 && pElem->matlInfo->type == SEMICON) { + refPsi = pElem->matlInfo->refPsi; + } + for (i = 0; i <= 1; i++) { + if (pElem->evalNodes[i]) { + pNode = pElem->pNodes[i]; + nodeArray[pNode->nodeI] = pNode; + } + } + } + for (index = 1; index <= pDevice->numNodes; index++) { + pNode = nodeArray[index]; + pNode->psi = psiData[index-1]/VNorm + refPsi; + pNode->nConc = nData[index-1]/NNorm; + pNode->pConc = pData[index-1]/NNorm; + } + FREE(nodeArray); + FREE(psiData); + FREE(nData); + FREE(pData); + + return (0); +} diff --git a/src/ciderlib/oned/onesetup.c b/src/ciderlib/oned/onesetup.c new file mode 100644 index 000000000..6ae807754 --- /dev/null +++ b/src/ciderlib/oned/onesetup.c @@ -0,0 +1,233 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/********** +One-Dimensional Numerical Device Setup Routines +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "onemesh.h" +#include "onedev.h" +#include "carddefs.h" /* XXX Not really modular if we need this. */ +/* #include "material.h" */ +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" + +/* compute node parameters */ +void +ONEsetup(ONEdevice *pDevice) +{ + double temp1, deltaEg, avgConc, totalConc, absNetConc; + double ncv0, dBand, dNie, psiBand[2]; + int index, eIndex; + int numContactNodes; + ONEnode *pNode; + ONEelem *pElem; + ONEedge *pEdge; + ONEmaterial *info; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + info = pElem->matlInfo; + + pElem->dx = pElem->pRightNode->x - pElem->pLeftNode->x; + pElem->epsRel = info->eps; + + if (pElem->elemType == INSULATOR) { + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType == CONTACT) { + pNode->eaff = PHI_METAL; + pNode->eg = 0.0; + } else { + pNode->eaff = info->affin; + pNode->eg = info->eg0; + } + } + } + } else if (pElem->elemType == SEMICON) { + ncv0 = sqrt(info->nc0) * sqrt(info->nv0); + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + + /* Fixed Interface Charge */ + pNode->qf = 0.0; + + /* Narrowing of Energy Band-Gap */ + if (BandGapNarrowing) { + absNetConc = ABS(pNode->netConc); + if (pNode->netConc > 0.0) { + temp1 = log(absNetConc / info->nrefBGN[ELEC]); + deltaEg = -info->dEgDn[ELEC] * (temp1 + sqrt(temp1 * temp1 + 0.5)); + pNode->eg = info->eg0 + deltaEg; + } else if (pNode->netConc < 0.0) { + temp1 = log(absNetConc / info->nrefBGN[HOLE]); + deltaEg = -info->dEgDn[HOLE] * (temp1 + sqrt(temp1 * temp1 + 0.5)); + pNode->eg = info->eg0 + deltaEg; + } else { + pNode->eg = info->eg0; + } + } else { + pNode->eg = info->eg0; + } + pNode->nie = ncv0 * exp(-0.5 * pNode->eg / Vt); + pNode->eaff = info->affin; + /* Save band structure parameter. */ + psiBand[index] = -info->refPsi; + + /* Ionized-Impurity-Scattering Reduction of Carrier Lifetime */ + if (ConcDepLifetime) { + totalConc = pNode->totalConc; + temp1 = 1.0 / (1.0 + totalConc / info->nrefSRH[ELEC]); + pNode->tn = info->tau0[ELEC] * temp1; + temp1 = 1.0 / (1.0 + totalConc / info->nrefSRH[HOLE]); + pNode->tp = info->tau0[HOLE] * temp1; + } else { + pNode->tn = info->tau0[ELEC]; + pNode->tp = info->tau0[HOLE]; + } + } + } + + pEdge = pElem->pEdge; + + /* Variable Band Built-In Potential */ + dBand = psiBand[1] - psiBand[0]; + dNie = log(pElem->pNodes[1]->nie / pElem->pNodes[0]->nie); + pEdge->dCBand = dBand + dNie; + pEdge->dVBand = -dBand + dNie; + + /* Evaluate conc.-dep. mobility. */ + avgConc = 0.5 * (pElem->pRightNode->totalConc + pElem->pLeftNode->totalConc); + MOBconcDep(info, avgConc, &pEdge->mun, &pEdge->mup); + } + } +} + +/* Transfer BC info from bdry to nodes and edges. */ +static void +ONEcopyBCinfo(ONEdevice *pDevice, ONEelem *pElem, BDRYcard *bdry, int index) +{ + ONEnode *pNode; + ONEelem *pNElem; + int eIndex, nIndex; + double length; + + + /* First add fixed charge. */ + pNode = pElem->pNodes[index]; + pNode->qf += bdry->BDRYqf; + + /* Now add surface recombination. */ + /* Compute semiconductor length around this node. */ + length = 0.0; + for (eIndex = 0; eIndex <= 3; eIndex++) { + pNElem = pNode->pElems[eIndex]; + if ((pNElem != NIL(ONEelem)) && (pElem->elemType == SEMICON)) { + length += 0.5 * pElem->dx; + } + } + if (bdry->BDRYsnGiven) { + pNode->tn = pNode->tn / + (1.0 + ((bdry->BDRYsn * TNorm) * pNode->tn) / length); + } + if (bdry->BDRYspGiven) { + pNode->tp = pNode->tp / + (1.0 + ((bdry->BDRYsp * TNorm) * pNode->tp) / length); + } + /* Finally, surface layer is irrelevant for 1d devices. */ +} + + +/* Compute boundary condition parameters. */ +void +ONEsetBCparams(ONEdevice *pDevice, BDRYcard *bdryList, CONTcard *contList) +{ + int index, xIndex; + ONEnode *pNode; + ONEelem *pElem, *pNElem; + BDRYcard *bdry; + CONTcard *cont; + + + + for (bdry = bdryList; bdry != NIL(BDRYcard); bdry = bdry->BDRYnextCard) { + for (xIndex = bdry->BDRYixLow; xIndex < bdry->BDRYixHigh; xIndex++) { + pElem = pDevice->elemArray[xIndex]; + if (pElem != NIL(ONEelem)) { + if (pElem->domain == bdry->BDRYdomain) { + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNElem = pElem->pElems[index]; + if (bdry->BDRYneighborGiven) { + if (pNElem && (pNElem->domain == bdry->BDRYneighbor)) { + /* Found an interface node. */ + ONEcopyBCinfo(pDevice, pElem, bdry, index); + } + } else { + if ((!pNElem) || (pNElem->domain != pElem->domain)) { + /* Found a boundary node. */ + ONEcopyBCinfo(pDevice, pElem, bdry, index); + } + } + } + } + } + } + } + } + for (cont = contList; cont != NIL(CONTcard); cont = cont->CONTnextCard) { + if (!cont->CONTworkfunGiven) { + cont->CONTworkfun = PHI_METAL; + } + /* + * XXX This won't work right if someone tries to change the 1d BJT base + * contact workfunction and doesn't want to change the emitter. But no + * one will probably try to do that. + */ + if (cont->CONTnumber == 1) { + pDevice->elemArray[1]->pNodes[0]->eaff = cont->CONTworkfun; + } else if ((cont->CONTnumber == 2) || (cont->CONTnumber == 3)) { + pDevice->elemArray[pDevice->numNodes - 1]->pNodes[1]->eaff = + cont->CONTworkfun; + } + } +} + +void +ONEnormalize(ONEdevice *pDevice) +{ + int index, eIndex; + ONEelem *pElem; + ONEnode *pNode; + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + + pElem->dx /= LNorm; + pElem->rDx = 1.0 / pElem->dx; + pElem->epsRel /= EpsNorm; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + pNode->netConc /= NNorm; + pNode->nd /= NNorm; + pNode->na /= NNorm; + pNode->qf /= (NNorm * LNorm); + pNode->nie /= NNorm; + pNode->eg /= VNorm; + pNode->eaff /= VNorm; + } + } + } +} diff --git a/src/ciderlib/oned/onesolve.c b/src/ciderlib/oned/onesolve.c new file mode 100644 index 000000000..2f3e27282 --- /dev/null +++ b/src/ciderlib/oned/onesolve.c @@ -0,0 +1,1084 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Functions needed to calculate solutions for 1D devices. + */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "onedev.h" +#include "onemesh.h" +#include "spMatrix.h" +#include "bool.h" +#include "macros.h" +#include "onedext.h" +#include "oneddefs.h" +#include "cidersupt.h" +#include "../../maths/misc/norm.h" + +#include "ifsim.h" +extern IFfrontEnd *SPfrontEnd; + + + +/* Forward Declarations */ +void ONEdcSolve(ONEdevice *, int, BOOLEAN, BOOLEAN, ONEtranInfo *); +BOOLEAN ONEpsiDeltaConverged(ONEdevice *, int *); +BOOLEAN ONEdeltaConverged(ONEdevice *); +int ONEnewDelta(ONEdevice *, BOOLEAN , ONEtranInfo *); +void ONEstoreNeutralGuess(ONEdevice *); +void ONEstoreEquilibGuess(ONEdevice *); +void ONEstoreInitialGuess(ONEdevice *pDevice); +double ONEnuNorm(ONEdevice *); +void ONEjacCheck(ONEdevice *, BOOLEAN , ONEtranInfo *); + + +/* The iteration driving loop and convergence check */ +void +ONEdcSolve(ONEdevice *pDevice, int iterationLimit, BOOLEAN newSolver, + BOOLEAN tranAnalysis, ONEtranInfo *info) +{ + ONEnode *pNode; + ONEelem *pElem; + int index, eIndex, error; + int timesConverged = 0, negConc = FALSE; + int size = pDevice->numEqns; + BOOLEAN quitLoop; + BOOLEAN debug = FALSE; + double *rhs = pDevice->rhs; + double *intermediate = pDevice->copiedSolution; + double *solution = pDevice->dcSolution; + double *delta = pDevice->dcDeltaSolution; + double poissNorm, contNorm; + double startTime, totalStartTime; + double totalTime, loadTime, factorTime, solveTime, updateTime, checkTime; + double orderTime = 0.0; + + quitLoop = FALSE; + debug = (!tranAnalysis && ONEdcDebug) + ||(tranAnalysis && ONEtranDebug); + pDevice->iterationNumber = 0; + pDevice->converged = FALSE; + + + totalTime = loadTime = factorTime = solveTime = updateTime = checkTime = 0.0; + totalStartTime = SPfrontEnd->IFseconds(); + + if (debug) { + if (pDevice->poissonOnly) { + fprintf(stdout, "Equilibrium Solution:\n"); + } else { + fprintf(stdout, "Bias Solution:\n"); + } + fprintf(stdout, "Iteration RHS Norm\n"); + } + while (! (pDevice->converged + || pDevice->iterationNumber > iterationLimit + || quitLoop)) { + pDevice->iterationNumber++; + + if ((!pDevice->poissonOnly) && (iterationLimit > 0) + &&(!tranAnalysis)) { + ONEjacCheck(pDevice, tranAnalysis, info); + } + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + if (pDevice->poissonOnly) { + ONEQsysLoad(pDevice); + } else { + ONE_sysLoad(pDevice, tranAnalysis, info); + } + pDevice->rhsNorm = maxNorm(rhs, size); + loadTime += SPfrontEnd->IFseconds() - startTime; + if (debug) { + fprintf(stdout, "%7d %11.4e%s\n", + pDevice->iterationNumber - 1, pDevice->rhsNorm, + negConc ? " negative conc encountered" : ""); + negConc = FALSE; + } + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + error = spFactor(pDevice->matrix); + factorTime += SPfrontEnd->IFseconds() - startTime; + if (newSolver) { + if (pDevice->iterationNumber == 1) { + orderTime = factorTime; + } else if (pDevice->iterationNumber == 2) { + orderTime -= factorTime - orderTime; + factorTime -= orderTime; + if (pDevice->poissonOnly) { + pDevice->pStats->orderTime[STAT_SETUP] += orderTime; + } else { + pDevice->pStats->orderTime[STAT_DC] += orderTime; + } + newSolver = FALSE; + } + } + if (foundError(error)) { + if (error == spSINGULAR) { + int badRow, badCol; + spWhereSingular(pDevice->matrix, &badRow, &badCol); + printf("***** singular at (%d,%d)\n", badRow, badCol); + } + /* Should probably try to recover now, but we'll punt instead. */ + exit(-1); + } + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhs, delta, NIL(spREAL), NIL(spREAL)); + solveTime += SPfrontEnd->IFseconds() - startTime; + + /* UPDATE */ + startTime = SPfrontEnd->IFseconds(); + /* + * Use norm reducing Newton method only for DC bias solutions. Since norm + * reducing can get trapped by numerical errors, turn it off when we are + * somewhat close to the solution. + */ + if ((!pDevice->poissonOnly) && (iterationLimit > 0) + && (!tranAnalysis) && (pDevice->rhsNorm > 1e-6)) { + error = ONEnewDelta(pDevice, tranAnalysis, info); + if (error) { + pDevice->converged = FALSE; + quitLoop = TRUE; + updateTime += SPfrontEnd->IFseconds() - startTime; + continue; + } + } + for (index = 1; index <= size; index++) { + solution[index] += delta[index]; + } + updateTime += SPfrontEnd->IFseconds() - startTime; + + /* CHECK CONVERGENCE */ + startTime = SPfrontEnd->IFseconds(); + /* Check if updates have gotten sufficiently small. */ + if (pDevice->iterationNumber != 1) { + /* + * pDevice->converged = ONEdeltaConverged(pDevice); + */ + pDevice->converged = ONEpsiDeltaConverged(pDevice, &negConc); + } + /* Check if the rhs residual is smaller than abstol. */ + if (pDevice->converged &&(!pDevice->poissonOnly) + &&(!tranAnalysis)) { + ONE_rhsLoad(pDevice, tranAnalysis, info); + pDevice->rhsNorm = maxNorm(rhs, size); + if (pDevice->rhsNorm > pDevice->abstol) { + pDevice->converged = FALSE; + } + if ((++timesConverged >= 2) + &&(pDevice->rhsNorm < 1e3 * pDevice->abstol)) { + pDevice->converged = TRUE; + } else if (timesConverged >= 5) { + pDevice->converged = FALSE; + quitLoop = TRUE; + } + } else if (pDevice->converged && pDevice->poissonOnly) { + ONEQrhsLoad(pDevice); + pDevice->rhsNorm = maxNorm(rhs, size); + if (pDevice->rhsNorm > pDevice->abstol) { + pDevice->converged = FALSE; + } + if (++timesConverged >= 5) { + pDevice->converged = TRUE; + } + } + /* Check if any of the carrier concentrations are negative. */ + if (pDevice->converged &&(!pDevice->poissonOnly)) { + /* Clear garbage entry since carrier-free elements reference it */ + solution[0] = 0.0; + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (solution[pNode->nEqn] < 0.0) { + pDevice->converged = FALSE; + negConc = TRUE; + if (tranAnalysis) { + quitLoop = TRUE; + } else { + solution[pNode->nEqn] = 0.0; + } + } + if (solution[pNode->pEqn] < 0.0) { + pDevice->converged = FALSE; + negConc = TRUE; + if (tranAnalysis) { + quitLoop = TRUE; + } else { + solution[pNode->pEqn] = 0.0; + } + } + } + } + } + /* Set to a consistent state if negative conc was encountered. */ + if (!pDevice->converged) { + ONE_rhsLoad(pDevice, tranAnalysis, info); + pDevice->rhsNorm = maxNorm(rhs, size); + } + } + checkTime += SPfrontEnd->IFseconds() - startTime; + } + totalTime += SPfrontEnd->IFseconds() - totalStartTime; + + if (tranAnalysis) { + pDevice->pStats->loadTime[STAT_TRAN] += loadTime; + pDevice->pStats->factorTime[STAT_TRAN] += factorTime; + pDevice->pStats->solveTime[STAT_TRAN] += solveTime; + pDevice->pStats->updateTime[STAT_TRAN] += updateTime; + pDevice->pStats->checkTime[STAT_TRAN] += checkTime; + pDevice->pStats->numIters[STAT_TRAN] += pDevice->iterationNumber; + } else if (pDevice->poissonOnly) { + pDevice->pStats->loadTime[STAT_SETUP] += loadTime; + pDevice->pStats->factorTime[STAT_SETUP] += factorTime; + pDevice->pStats->solveTime[STAT_SETUP] += solveTime; + pDevice->pStats->updateTime[STAT_SETUP] += updateTime; + pDevice->pStats->checkTime[STAT_SETUP] += checkTime; + pDevice->pStats->numIters[STAT_SETUP] += pDevice->iterationNumber; + } else { + pDevice->pStats->loadTime[STAT_DC] += loadTime; + pDevice->pStats->factorTime[STAT_DC] += factorTime; + pDevice->pStats->solveTime[STAT_DC] += solveTime; + pDevice->pStats->updateTime[STAT_DC] += updateTime; + pDevice->pStats->checkTime[STAT_DC] += checkTime; + pDevice->pStats->numIters[STAT_DC] += pDevice->iterationNumber; + } + + if (debug) { + if (!tranAnalysis) { + pDevice->rhsNorm = maxNorm(rhs, size); + fprintf(stdout, "%7d %11.4e%s\n", + pDevice->iterationNumber, pDevice->rhsNorm, + negConc ? " negative conc in solution" : ""); + } + if (pDevice->converged) { + if (!pDevice->poissonOnly) { + rhs[0] = 0.0; + poissNorm = contNorm = 0.0; + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + poissNorm = MAX(poissNorm, ABS(rhs[pNode->psiEqn])); + contNorm = MAX(contNorm, ABS(rhs[pNode->nEqn])); + contNorm = MAX(contNorm, ABS(rhs[pNode->pEqn])); + } + } + } + fprintf(stdout, + "Residual: %11.4e C/um^2 poisson, %11.4e A/um^2 continuity\n", + poissNorm * EpsNorm * ENorm * 1e-8, + contNorm * JNorm * 1e-8); + } else { + fprintf(stdout, "Residual: %11.4e C/um^2 poisson\n", + pDevice->rhsNorm * EpsNorm * ENorm * 1e-8); + } + } + } +} + +/* + * A function that checks convergence based on the convergence of the quasi + * Fermi levels. In theory, this should work better than the one currently + * being used since we are always looking at potentials: (psi, phin, phip). + */ +BOOLEAN +ONEpsiDeltaConverged(ONEdevice *pDevice, int *pNegConc) +{ + int index, nIndex, eIndex; + ONEnode *pNode; + ONEelem *pElem; + double xOld, xNew, xDelta, tol; + double psi, newPsi, nConc, pConc, newN, newP; + double phiN, phiP, newPhiN, newPhiP; + BOOLEAN converged = TRUE; + + /* Equilibrium solution. */ + if (pDevice->poissonOnly) { + for (index = 1; index <= pDevice->numEqns; index++) { + xOld = pDevice->dcSolution[index]; + xDelta = pDevice->dcDeltaSolution[index]; + xNew = xOld + xDelta; + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xDelta) > tol) { + converged = FALSE; + goto done; + } + } + return (converged); + + } + /* Bias solution. Check convergence on psi, phin, phip. */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + /* check convergence on psi */ + xOld = pDevice->dcSolution[pNode->psiEqn]; + xDelta = pDevice->dcDeltaSolution[pNode->psiEqn]; + xNew = xOld + xDelta; + tol = pDevice->abstol + + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xDelta) > tol) { + converged = FALSE; + goto done; + } + } + /* check convergence on phin and phip */ + if (pElem->elemType == SEMICON && pNode->nodeType != CONTACT) { + psi = pDevice->dcSolution[pNode->psiEqn]; + nConc = pDevice->dcSolution[pNode->nEqn]; + pConc = pDevice->dcSolution[pNode->pEqn]; + newPsi = psi + pDevice->dcDeltaSolution[pNode->psiEqn]; + newN = nConc + pDevice->dcDeltaSolution[pNode->nEqn]; + newP = pConc + pDevice->dcDeltaSolution[pNode->pEqn]; + if (newN <= 0.0 || newP <= 0.0) { + *pNegConc = TRUE; + converged = FALSE; + goto done; + } + phiN = psi - log(nConc / pNode->nie); + phiP = psi + log(pConc / pNode->nie); + newPhiN = newPsi - log(newN / pNode->nie); + newPhiP = newPsi + log(newP / pNode->nie); + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(phiN), ABS(newPhiN)); + if (ABS(newPhiN - phiN) > tol) { + converged = FALSE; + goto done; + } + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(phiP), ABS(newPhiP)); + if (ABS(newPhiP - phiP) > tol) { + converged = FALSE; + goto done; + } + } + } + } + } +done: + + return (converged); +} + +/* + * See if the update to the solution is small enough. Returns TRUE if it is. + */ +BOOLEAN +ONEdeltaConverged(ONEdevice *pDevice) +{ + int index; + BOOLEAN converged = TRUE; + double *solution = pDevice->dcSolution; + double *delta = pDevice->dcDeltaSolution; + double xOld, xNew, tol; + + + for (index = 1; index <= pDevice->numEqns; index++) { + xOld = solution[index]; + xNew = xOld + delta[index]; + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xOld - xNew) > tol) { + converged = FALSE; + break; + } + } + return (converged); +} + +/* + * See if the update to the solution is small enough and the solution is + * physically reasonable. Returns TRUE if it is. + */ +BOOLEAN +ONEdeviceConverged(ONEdevice *pDevice) +{ + int index, eIndex; + BOOLEAN converged = TRUE; + double *solution = pDevice->dcSolution; + ONEnode *pNode; + ONEelem *pElem; + double startTime; + + /* + * If the update is sufficently small, and the carrier concentrations are + * all positive, then return TRUE, else return FALSE. + */ + + + /* CHECK CONVERGENCE */ + startTime = SPfrontEnd->IFseconds(); + if ((converged = ONEdeltaConverged(pDevice)) == TRUE) { + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nEqn != 0 && solution[pNode->nEqn] < 0.0) { + converged = FALSE; + solution[pNode->nEqn] = pNode->nConc = 0.0; + } + if (pNode->pEqn != 0 && solution[pNode->pEqn] < 0.0) { + converged = FALSE; + solution[pNode->pEqn] = pNode->pConc = 0.0; + } + } + } + } + } + pDevice->pStats->checkTime[STAT_TRAN] += SPfrontEnd->IFseconds() - startTime; + + return (converged); +} + +/* + * Load and factor the Jacobian so that it is consistent with the current + * solution. + */ +void +ONEresetJacobian(ONEdevice *pDevice) +{ + int error; + + + ONE_jacLoad(pDevice); + error = spFactor(pDevice->matrix); + if (foundError(error)) { + exit(-1); + } +} + +/* + * Compute the device state assuming charge neutrality exists everywhere in + * the device. In insulators, where there is no charge, assign the potential + * at half the insulator band gap (refPsi). + */ +void +ONEstoreNeutralGuess(ONEdevice *pDevice) +{ + int nIndex, eIndex; + ONEelem *pElem; + ONEnode *pNode; + double nie, conc, absConc, refPsi, psi, ni, pi, sign; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + refPsi = pElem->matlInfo->refPsi; + if (pElem->elemType == INSULATOR) { + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType == CONTACT) { + /* + * A metal contact to insulating domain, so use work function + * difference as the value of psi. + */ + pNode->psi = RefPsi - pNode->eaff; + } else { + pNode->psi = refPsi; + } + } + } + } + if (pElem->elemType == SEMICON) { + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + nie = pNode->nie; + conc = pNode->netConc / nie; + psi = 0.0; + ni = nie; + pi = nie; + sign = SGN(conc); + absConc = ABS(conc); + if (conc != 0.0) { + psi = sign * log(0.5 * absConc + sqrt(1.0 + 0.25 * absConc * absConc)); + ni = nie * exp(psi); + pi = nie * exp(-psi); + if (FreezeOut) { + /* Use Newton's Method to solve for psi. */ + int ctr, maxiter = 10; + double rhs, deriv, fNa, fNd, fdNa, fdNd; + for (ctr = 0; ctr < maxiter; ctr++) { + pNode->nConc = ni; + pNode->pConc = pi; + ONEQfreezeOut(pNode, &fNd, &fNa, &fdNd, &fdNa); + rhs = pi - ni + pNode->nd * fNd - pNode->na * fNa; + deriv = pi + ni - pNode->nd * fdNd + pNode->na * fdNa; + psi += rhs / deriv; + ni = nie * exp(psi); + pi = nie * exp(-psi); + } + } + } + pNode->psi = refPsi + psi; + pNode->psi0 = pNode->psi; + pNode->vbe = refPsi; + pNode->nConc = ni; + pNode->pConc = pi; + /* Now store the initial guess in the dc solution vector. */ + pDevice->dcSolution[pNode->poiEqn] = pNode->psi; + } + } + } + } +} + +/* + * Compute the device state at thermal equilibrium. This state is equal to + * the solution of just Poisson's equation. The charge-neutral solution is + * taken as an initial guess. + */ +void +ONEequilSolve(ONEdevice *pDevice) +{ + BOOLEAN newSolver = FALSE; + int error; + int nIndex, eIndex; + ONEelem *pElem; + ONEnode *pNode; + double startTime, setupTime, miscTime; + + + setupTime = miscTime = 0.0; + + /* SETUP */ + startTime = SPfrontEnd->IFseconds(); + switch (pDevice->solverType) { + case SLV_SMSIG: + case SLV_BIAS: + /* free up memory allocated for the bias solution */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + FREE(pDevice->rhsImag); + spDestroy(pDevice->matrix); + /* FALLTHRU */ + case SLV_NONE: + pDevice->poissonOnly = TRUE; + pDevice->numEqns = pDevice->dimEquil - 1; + XCALLOC(pDevice->dcSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->dcDeltaSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->copiedSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->rhs, double, pDevice->dimEquil); + pDevice->matrix = spCreate(pDevice->numEqns, 0, &error); + if (error == spNO_MEMORY) { + printf("ONEequilSolve: Out of Memory\n"); + exit(-1); + } + newSolver = TRUE; + spSetReal(pDevice->matrix); + ONEQjacBuild(pDevice); + pDevice->numOrigEquil = spElementCount(pDevice->matrix); + pDevice->numFillEquil = 0; + /* FALLTHRU */ + case SLV_EQUIL: + pDevice->solverType = SLV_EQUIL; + break; + default: + fprintf(stderr, "Panic: Unknown solver type in equil solution.\n"); + exit(-1); + break; + } + ONEstoreNeutralGuess(pDevice); + setupTime += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + ONEdcSolve(pDevice, MaxIterations, newSolver, FALSE, (ONEtranInfo *) NULL); + + /* MISCELLANEOUS */ + startTime = SPfrontEnd->IFseconds(); + if (newSolver) { + pDevice->numFillEquil = spFillinCount(pDevice->matrix); + } + if (pDevice->converged) { + ONEQcommonTerms(pDevice); + + /* Save equilibrium potential. */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi0 = pNode->psi; + } + } + } + } else { + printf("ONEequilSolve: No Convergence\n"); + } + miscTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->setupTime[STAT_SETUP] += setupTime; + pDevice->pStats->miscTime[STAT_SETUP] += miscTime; +} + +/* + * Compute the device state under an applied bias. The equilibrium solution + * is taken as an initial guess the first time this is called. + */ +void +ONEbiasSolve(ONEdevice *pDevice, int iterationLimit, + BOOLEAN tranAnalysis, ONEtranInfo *info) +{ + BOOLEAN newSolver = FALSE; + int error; + int index, eIndex; + double *solution; + ONEelem *pElem; + ONEnode *pNode; + double startTime, setupTime, miscTime; + + + setupTime = miscTime = 0.0; + + + /* SETUP */ + startTime = SPfrontEnd->IFseconds(); + switch (pDevice->solverType) { + case SLV_EQUIL: + /* Free up the vectors allocated in the equilibrium solution. */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + spDestroy(pDevice->matrix); + /* FALLTHRU */ + case SLV_NONE: + pDevice->poissonOnly = FALSE; + pDevice->numEqns = pDevice->dimBias - 1; + XCALLOC(pDevice->dcSolution, double, pDevice->dimBias); + XCALLOC(pDevice->dcDeltaSolution, double, pDevice->dimBias); + XCALLOC(pDevice->copiedSolution, double, pDevice->dimBias); + XCALLOC(pDevice->rhs, double, pDevice->dimBias); + XCALLOC(pDevice->rhsImag, double, pDevice->dimBias); + pDevice->matrix = spCreate(pDevice->numEqns, 1, &error); + if (error == spNO_MEMORY) { + exit(-1); + } + newSolver = TRUE; + ONE_jacBuild(pDevice); + pDevice->numOrigBias = spElementCount(pDevice->matrix); + pDevice->numFillBias = 0; + ONEstoreInitialGuess(pDevice); + /* FALLTHRU */ + case SLV_SMSIG: + spSetReal(pDevice->matrix); + /* FALLTHRU */ + case SLV_BIAS: + pDevice->solverType = SLV_BIAS; + break; + default: + fprintf(stderr, "Panic: Unknown solver type in bias solution.\n"); + exit(-1); + break; + } + setupTime += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + ONEdcSolve(pDevice, iterationLimit, newSolver, tranAnalysis, info); + + /* MISCELLANEOUS */ + startTime = SPfrontEnd->IFseconds(); + if (newSolver) { + pDevice->numFillBias = spFillinCount(pDevice->matrix); + } + solution = pDevice->dcSolution; + if ((!pDevice->converged) && iterationLimit > 1) { + } else if (pDevice->converged) { + /* Update the nodal quantities. */ + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->psiEqn != 0) { + pNode->psi = solution[pNode->psiEqn]; + } + if (pNode->nEqn != 0) { + pNode->nConc = solution[pNode->nEqn]; + } + if (pNode->pEqn != 0) { + pNode->pConc = solution[pNode->pEqn]; + } + } + } + } + /* Update the current terms. */ + ONE_commonTerms(pDevice, FALSE, tranAnalysis, info); + } else if (iterationLimit <= 1) { + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (index = 0; index <= 1; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + pNode->psi = solution[pNode->psiEqn]; + *(pDevice->devState0 + pNode->nodePsi) = pNode->psi; + if (pElem->elemType == SEMICON) { + pNode->nConc = solution[pNode->nEqn]; + pNode->pConc = solution[pNode->pEqn]; + *(pDevice->devState0 + pNode->nodeN) = pNode->nConc; + *(pDevice->devState0 + pNode->nodeP) = pNode->pConc; + } + } + } + } + } + } + miscTime += SPfrontEnd->IFseconds() - startTime; + if (tranAnalysis) { + pDevice->pStats->setupTime[STAT_TRAN] += setupTime; + pDevice->pStats->miscTime[STAT_TRAN] += miscTime; + } else { + pDevice->pStats->setupTime[STAT_DC] += setupTime; + pDevice->pStats->miscTime[STAT_DC] += miscTime; + } +} + +/* Copy the device's equilibrium state into the solution vector. */ +void +ONEstoreEquilibGuess(ONEdevice *pDevice) +{ + int nIndex, eIndex; + double *solution = pDevice->dcSolution; + double refPsi; + ONEelem *pElem; + ONEnode *pNode; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + refPsi = pElem->matlInfo->refPsi; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + solution[pNode->psiEqn] = pNode->psi0; + if (pElem->elemType == SEMICON) { + solution[pNode->nEqn] = pNode->nie * exp(pNode->psi0 - refPsi); + solution[pNode->pEqn] = pNode->nie * exp(-pNode->psi0 + refPsi); + } + } + } + } + } +} + +/* Copy the device's internal state into the solution vector. */ +void +ONEstoreInitialGuess(ONEdevice *pDevice) +{ + int nIndex, eIndex; + double *solution = pDevice->dcSolution; + ONEelem *pElem; + ONEnode *pNode; + + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + solution[pNode->psiEqn] = pNode->psi; + if (pElem->elemType == SEMICON) { + solution[pNode->nEqn] = pNode->nConc; + solution[pNode->pEqn] = pNode->pConc; + } + } + } + } + } +} + + +int +ONEnewDelta(ONEdevice *pDevice, BOOLEAN tranAnalysis, ONEtranInfo *info) +{ + int index, iterNum; + double newNorm, fib, lambda, fibn, fibp; + BOOLEAN acceptable = FALSE, error = FALSE; + iterNum = 0; + lambda = 1.0; + fibn = 1.0; + fibp = 1.0; + + + /* + * Copy the contents of dcSolution into copiedSolution and modify + * dcSolution by adding the deltaSolution. + */ + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->copiedSolution[index] = pDevice->dcSolution[index]; + pDevice->dcSolution[index] += pDevice->dcDeltaSolution[index]; + } + + if (pDevice->poissonOnly) { + ONEQrhsLoad(pDevice); + } else { + ONE_rhsLoad(pDevice, tranAnalysis, info); + } + newNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + if (pDevice->rhsNorm <= pDevice->abstol) { + lambda = 0.0; + newNorm = pDevice->rhsNorm; + } else if (newNorm < pDevice->rhsNorm) { + acceptable = TRUE; + } else { + /* chop the step size so that deltax is acceptable */ + if (ONEdcDebug) { + fprintf(stdout, " %11.4e %11.4e\n", + newNorm, lambda); + } + while (!acceptable) { + iterNum++; + + if (iterNum > NORM_RED_MAXITERS) { + error = TRUE; + lambda = 0.0; + /* Don't break out until after we've reset the device. */ + } + fib = fibp; + fibp = fibn; + fibn += fib; + lambda *= (fibp / fibn); + + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index] + + lambda * pDevice->dcDeltaSolution[index]; + } + if (pDevice->poissonOnly) { + ONEQrhsLoad(pDevice); + } else { + ONE_rhsLoad(pDevice, tranAnalysis, info); + } + newNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + if (error) { + break; + } + if (newNorm <= pDevice->rhsNorm) { + acceptable = TRUE; + } + if (ONEdcDebug) { + fprintf(stdout, " %11.4e %11.4e\n", + newNorm, lambda); + } + } + } + /* Restore the previous dcSolution and store the new deltaSolution. */ + pDevice->rhsNorm = newNorm; + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index]; + pDevice->dcDeltaSolution[index] *= lambda; + } + return(error); +} + + +/* Predict the values of the internal variables at the next timepoint. */ +/* Needed for Predictor-Corrector LTE estimation */ +void +ONEpredict(ONEdevice *pDevice, ONEtranInfo *info) +{ + int nIndex, eIndex; + ONEnode *pNode; + ONEelem *pElem; + double startTime, miscTime = 0.0; + + + /* TRANSIENT MISC */ + startTime = SPfrontEnd->IFseconds(); + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi = *(pDevice->devState1 + pNode->nodePsi); + if (pElem->elemType == SEMICON && pNode->nodeType != CONTACT) { + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + pNode->nConc = pNode->nPred; + pNode->pConc = pNode->pPred; + } + } + } + } + miscTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->miscTime[STAT_TRAN] += miscTime; +} + + +/* Estimate the device's overall truncation error. */ +double +ONEtrunc(ONEdevice *pDevice, ONEtranInfo *info, double delta) +{ + int nIndex, eIndex; + ONEelem *pElem; + ONEnode *pNode; + double tolN, tolP, lte, relError, temp, relLTE; + double lteCoeff = info->lteCoeff; + double mult = 10.0; + double reltol; + double startTime, lteTime = 0.0; + + + /* TRANSIENT LTE */ + startTime = SPfrontEnd->IFseconds(); + + relError = 0.0; + reltol = pDevice->reltol * mult; + + /* Need to get the predictor for the current order. */ + /* The scheme currently used is very dumb. Need to fix later. */ + /* XXX Why is the scheme dumb? Never understood this. */ + computePredCoeff(info->method, info->order, info->predCoeff, info->delta); + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pElem->elemType == SEMICON && pNode->nodeType != CONTACT) { + tolN = pDevice->abstol + reltol * ABS(pNode->nConc); + tolP = pDevice->abstol + reltol * ABS(pNode->pConc); + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + lte = lteCoeff * (pNode->nConc - pNode->nPred); + temp = lte / tolN; + relError += temp * temp; + lte = lteCoeff * (pNode->pConc - pNode->pPred); + temp = lte / tolP; + relError += temp * temp; + } + } + } + } + + /* Make sure error is non-zero. */ + relError = MAX(pDevice->abstol, relError); + + /* The total relative error has been calculated norm-2 squared. */ + relError = sqrt(relError / pDevice->numEqns); + + /* Use the order of the integration method to compute new delta. */ + temp = delta / pow(relError, 1.0 / (info->order + 1)); + + lteTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->lteTime += lteTime; + + return (temp); +} + +/* Save info from state table into the internal state. */ +void +ONEsaveState(ONEdevice *pDevice) +{ + int nIndex, eIndex; + ONEnode *pNode; + ONEelem *pElem; + + for (eIndex = 1; eIndex < pDevice->numNodes; eIndex++) { + pElem = pDevice->elemArray[eIndex]; + for (nIndex = 0; nIndex <= 1; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi = *(pDevice->devState1 + pNode->nodePsi); + if (pElem->elemType == SEMICON && pNode->nodeType != CONTACT) { + pNode->nConc = *(pDevice->devState1 + pNode->nodeN); + pNode->pConc = *(pDevice->devState1 + pNode->nodeP); + } + } + } + } +} + +/* Function to compute Nu norm of the rhs vector. */ +/* Nu-norm calculation based upon work done at Stanford. */ +double +ONEnuNorm(ONEdevice *pDevice) +{ + double norm = 0.0; + double temp; + int index; + + + /* The LU Decomposed matrix is available. Use it to calculate x. */ + spSolve(pDevice->matrix, pDevice->rhs, pDevice->rhsImag, + NIL(spREAL), NIL(spREAL)); + + /* Compute L2-norm of the solution vector (stored in rhsImag) */ + return (l2Norm(pDevice->rhsImag, pDevice->numEqns)); +} + + +/* + * Check for numerical errors in the Jacobian. Useful debugging aid when new + * models are being incorporated. + */ +void +ONEjacCheck(ONEdevice *pDevice, BOOLEAN tranAnalysis, ONEtranInfo *info) +{ + int index, rIndex; + double del, diff, tol, *dptr; + + + if (ONEjacDebug) { + ONE_sysLoad(pDevice, tranAnalysis, info); + /* + * spPrint( pDevice->matrix, 0, 1, 1 ); + */ + pDevice->rhsNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + for (index = 1; index <= pDevice->numEqns; index++) { + if (1e3 * ABS(pDevice->rhs[index]) > pDevice->rhsNorm) { + fprintf(stderr, "eqn %d: res %11.4e, norm %11.4e\n", + index, pDevice->rhs[index], pDevice->rhsNorm); + } + } + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhsImag[index] = pDevice->rhs[index]; + } + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->copiedSolution[index] = pDevice->dcSolution[index]; + del = 1e-4 * pDevice->abstol + 1e-6 * ABS(pDevice->dcSolution[index]); + pDevice->dcSolution[index] += del; + ONE_rhsLoad(pDevice, tranAnalysis, info); + pDevice->dcSolution[index] = pDevice->copiedSolution[index]; + for (rIndex = 1; rIndex <= pDevice->numEqns; rIndex++) { + diff = (pDevice->rhsImag[rIndex] - pDevice->rhs[rIndex]) / del; + dptr = spFindElement(pDevice->matrix, rIndex, index); + /* + * if ( dptr ISNOT NIL(double) ) { fprintf( stderr, "[%d][%d]: FD = + * %11.4e, AJ = %11.4e\n", rIndex, index, diff, *dptr ); } else { + * fprintf( stderr, "[%d][%d]: FD = %11.4e, AJ = %11.4e\n", rIndex, + * index, diff, 0.0 ); } + */ + if (dptr != NIL(double)) { + tol = (1e-4 * pDevice->abstol) + (1e-2 * MAX(ABS(diff), ABS(*dptr))); + if ((diff != 0.0) && (ABS(diff - *dptr) > tol)) { + fprintf(stderr, "Mismatch[%d][%d]: FD = %11.4e, AJ = %11.4e\n\t FD-AJ = %11.4e vs. %11.4e\n", + rIndex, index, diff, *dptr, + ABS(diff - *dptr), tol); + } + } else { + if (diff != 0.0) { + fprintf(stderr, "Missing [%d][%d]: FD = %11.4e, AJ = 0.0\n", + rIndex, index, diff); + } + } + } + } + } +} diff --git a/src/ciderlib/oned/readme b/src/ciderlib/oned/readme new file mode 100644 index 000000000..282a53aca --- /dev/null +++ b/src/ciderlib/oned/readme @@ -0,0 +1,13 @@ +Directory: oned +--------------- +This directory contains the files that are primarily responsible for +implementing the 1D device simulator. It also contains files that help +interface the circuit simulator to the device simulator. Most functions +that are common to all 1D device simulations start with the prefix ONE, +e.g. ONEbiasSolve. The device-specific routines start with either +NUMD or NBJT, e.g. NUMDadmittance or NBJTproject. The simulator contains +both a Poisson Solver for equilibrium and a three-equation solver for bias +solutions. An attempt has been made to keep the function names parallel +in the two portions. Poisson routines are identified with a 'Q' (for charge +only) after the ONE, and Full solver routines are identified with an +underscore '_'. diff --git a/src/ciderlib/support/Makefile.am b/src/ciderlib/support/Makefile.am new file mode 100644 index 000000000..36e9273a2 --- /dev/null +++ b/src/ciderlib/support/Makefile.am @@ -0,0 +1,22 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libcidersuprt.a + +libcidersuprt_a_SOURCES = \ + database.c \ + devprint.c \ + geominfo.c \ + globals.c \ + integset.c \ + integuse.c \ + logfile.c \ + mater.c \ + misc.c \ + mobil.c \ + recomb.c \ + suprem.c \ + suprmitf.c + +EXTRA_DIST = makefile +INCLUDES = -I$(top_srcdir)/src/include +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/ciderlib/support/database.c b/src/ciderlib/support/database.c new file mode 100644 index 000000000..ccaac5808 --- /dev/null +++ b/src/ciderlib/support/database.c @@ -0,0 +1,72 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "fteext.h" +/* #include "ftedata.h" */ + +struct plot * +DBread( fileName ) +{ + struct plot *plot; + + plot = raw_read( fileName ); + + return(plot); +} + +double * +DBgetData( plot, name, lengthWanted ) +struct plot *plot; +char *name; +int lengthWanted; +{ + struct dvec *v; + double *data; + int i; + + v = vec_fromplot(name,plot); + + if (!v) { + fprintf( stderr, "Error: cannot locate variable '%s'\n", name ); + return(NULL); + } + if (v->v_length != lengthWanted ) { + fprintf( stderr, "Error: vector '%s' has incorrect length\n", name ); + return(NULL); + } + + data = (double *) malloc(sizeof (double) * v->v_length); + if (isreal(v)) { + bcopy((char *) v->v_realdata, (char *) data, sizeof (double) * v->v_length); + } else { + for (i=0; i < v->v_length; i++) { + data[i] = realpart(&v->v_compdata[i]); + } + } + return(data); +} + +void +DBfree( plot ) +struct plot *plot; +{ + struct dvec *v, *nextv; + struct plot *pl, *nextpl; + + for (pl = plot; pl; pl = nextpl) { + nextpl = pl->pl_next; + tfree( pl->pl_title ); + tfree( pl->pl_date ); + tfree( pl->pl_name ); + tfree( pl->pl_typename ); + for (v = pl->pl_dvecs; v; v = nextv) { + nextv = v->v_next; + vec_free( v ); + } + wl_free( pl->pl_commands ); + /* XXX Environment variables (pl->pl_env) will leak. */ + } +} diff --git a/src/ciderlib/support/devprint.c b/src/ciderlib/support/devprint.c new file mode 100644 index 000000000..b0e011caf --- /dev/null +++ b/src/ciderlib/support/devprint.c @@ -0,0 +1,74 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* Device-type Dependent Printing Routines */ + +#include "ngspice.h" +#include "optndefs.h" + +void +printVoltages(FILE *file, char *mName, char *iName, int devType, + int numVolt, double v1, double delV1, double v2, + double delV2, double v3, double delV3 ) +/* FILE *file output filestream */ +/* char *mName name of model */ +/* char *iName name of instance */ +{ + fprintf( file, "\n" ); + switch ( devType ) { + case OPTN_RESISTOR: + fprintf( file, "RES %s:%s voltage:\n", mName, iName ); + fprintf( file, " Vpn =% .4e delVpn =% .4e\n", v1, delV1 ); + break; + case OPTN_CAPACITOR: + fprintf( file, "CAP %s:%s voltage:\n", mName, iName ); + fprintf( file, " Vpn =% .4e delVpn =% .4e\n", v1, delV1 ); + break; + case OPTN_DIODE: + fprintf( file, "DIO %s:%s voltage:\n", mName, iName ); + fprintf( file, " Vpn =% .4e delVpn =% .4e\n", v1, delV1 ); + break; + case OPTN_MOSCAP: + fprintf( file, "MOS %s:%s voltage:\n", mName, iName ); + fprintf( file, " Vgb =% .4e delVgb =% .4e\n", v1, delV1 ); + break; + case OPTN_BIPOLAR: + fprintf( file, "BJT %s:%s voltages:\n", mName, iName ); + if ( numVolt == 3 ) { + fprintf( file, " Vce =% .4e delVce =% .4e\n", + v1 - v3, delV1 - delV3 ); + fprintf( file, " Vbe =% .4e delVbe =% .4e\n", + v2 - v3, delV2 - delV3 ); + fprintf( file, " Vcs =% .4e delVcs =% .4e\n", v1, delV1 ); + } else { + fprintf( file, " Vce =% .4e delVce =% .4e\n", v1, delV1 ); + fprintf( file, " Vbe =% .4e delVbe =% .4e\n", v2, delV2 ); + } + break; + case OPTN_MOSFET: + fprintf( file, "MOS %s:%s voltages:\n", mName, iName ); + fprintf( file, " Vdb =% .4e delVdb =% .4e\n", v1, delV1 ); + fprintf( file, " Vgb =% .4e delVgb =% .4e\n", v2, delV2 ); + fprintf( file, " Vsb =% .4e delVsb =% .4e\n", v3, delV3 ); + break; + case OPTN_JFET: + if ( numVolt == 3 ) { + fprintf( file, "JFET %s:%s voltages:\n", mName, iName ); + fprintf( file, " Vdb =% .4e delVdb =% .4e\n", v1, delV1 ); + fprintf( file, " Vgb =% .4e delVgb =% .4e\n", v2, delV2 ); + fprintf( file, " Vsb =% .4e delVsb =% .4e\n", v3, delV3 ); + } else { + fprintf( file, "JFET %s:%s voltages:\n", mName, iName ); + fprintf( file, " Vds =% .4e delVds =% .4e\n", v1, delV1 ); + fprintf( file, " Vgs =% .4e delVgs =% .4e\n", v2, delV2 ); + } + break; + case OPTN_SOIBJT: + case OPTN_SOIMOS: + case OPTN_MESFET: + default: + break; + } +} diff --git a/src/ciderlib/support/geominfo.c b/src/ciderlib/support/geominfo.c new file mode 100644 index 000000000..792e5abc3 --- /dev/null +++ b/src/ciderlib/support/geominfo.c @@ -0,0 +1,130 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "gendev.h" +#include "macros.h" +#include "memory.h" + +void printCoordInfo(CoordInfo *pFirstCoord) +{ + CoordInfo *pCoord; + + for ( pCoord = pFirstCoord; pCoord != NIL(CoordInfo); + pCoord = pCoord->next ) { + fprintf(stderr, "mesh number=%4d location=%11.4e\n", + pCoord->number, pCoord->location ); + } +} + +void killCoordInfo(CoordInfo *pFirstCoord) +{ + CoordInfo *pCoord, *pKill; + + for ( pCoord = pFirstCoord; pCoord != NIL(CoordInfo); ) { + pKill = pCoord; + pCoord = pCoord->next; + FREE( pKill ); + } +} + +void ONEprintDomainInfo(DomainInfo *pFirstDomain) +{ + DomainInfo *pDomain; + + for ( pDomain = pFirstDomain; pDomain != NIL(DomainInfo); + pDomain = pDomain->next ) { + fprintf( stderr, "domain id=%4d mat=%4d ixLo=%4d ixHi=%4d\n", + pDomain->id, pDomain->material, pDomain->ixLo, pDomain->ixHi ); + } +} + +void TWOprintDomainInfo(DomainInfo *pFirstDomain) +{ + DomainInfo *pDomain; + + for ( pDomain = pFirstDomain; pDomain != NIL(DomainInfo); + pDomain = pDomain->next ) { + fprintf( stderr, + "domain id=%4d mat=%4d ixLo=%4d ixHi=%4d iyLo=%4d iyHi=%4d\n", + pDomain->id, pDomain->material, + pDomain->ixLo, pDomain->ixHi, + pDomain->iyLo, pDomain->iyHi); + } +} + +void killDomainInfo(DomainInfo *pFirstDomain) +{ + DomainInfo *pDomain, *pKill; + + for ( pDomain = pFirstDomain; pDomain != NIL(DomainInfo); ) { + pKill = pDomain; + pDomain = pDomain->next; + FREE( pKill ); + } +} + +void ONEprintBoundaryInfo(BoundaryInfo *pFirstBoundary) +{ + BoundaryInfo *pBoundary; + + for ( pBoundary = pFirstBoundary; pBoundary != NIL(BoundaryInfo); + pBoundary = pBoundary->next ) { + fprintf( stderr, + "boundary dom=%4d nbr=%4d ixLo=%4d ixHi=%4d\n", + pBoundary->domain, pBoundary->neighbor, + pBoundary->ixLo, pBoundary->ixHi ); + } +} + +void TWOprintBoundaryInfo(BoundaryInfo *pFirstBoundary) +{ + BoundaryInfo *pBoundary; + + for ( pBoundary = pFirstBoundary; pBoundary != NIL(BoundaryInfo); + pBoundary = pBoundary->next ) { + fprintf( stderr, + "boundary dom=%4d nbr=%4d ixLo=%4d ixHi=%4d iyLo=%4d iyHi=%4d\n", + pBoundary->domain, pBoundary->neighbor, + pBoundary->ixLo, pBoundary->ixHi, + pBoundary->iyLo, pBoundary->iyHi); + } +} + +void killBoundaryInfo(BoundaryInfo *pFirstBoundary) +{ + BoundaryInfo *pBoundary, *pKill; + + for ( pBoundary = pFirstBoundary; pBoundary != NIL(BoundaryInfo); ) { + pKill = pBoundary; + pBoundary = pBoundary->next; + FREE( pKill ); + } +} + +void TWOprintElectrodeInfo(ElectrodeInfo *pFirstElectrode) +{ + ElectrodeInfo *pElectrode; + + for ( pElectrode = pFirstElectrode; pElectrode != NIL(ElectrodeInfo); + pElectrode = pElectrode->next ) { + fprintf( stderr, + "electrode id=%4d ixLo=%4d ixHi=%4d iyLo=%4d iyHi=%4d\n", + pElectrode->id, pElectrode->ixLo, pElectrode->ixHi, + pElectrode->iyLo, pElectrode->iyHi ); + } +} + +void killElectrodeInfo(ElectrodeInfo *pFirstElectrode) +{ + ElectrodeInfo *pElectrode, *pKill; + + for ( pElectrode = pFirstElectrode; pElectrode != NIL(ElectrodeInfo); ) { + pKill = pElectrode; + pElectrode = pElectrode->next; + FREE( pKill ); + } +} diff --git a/src/ciderlib/support/globals.c b/src/ciderlib/support/globals.c new file mode 100644 index 000000000..41259f189 --- /dev/null +++ b/src/ciderlib/support/globals.c @@ -0,0 +1,161 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" + +/* Forward Declarations */ +void GLOBputGlobals(GLOBvalues *); +void GLOBgetGlobals(GLOBvalues *); +void GLOBprnGlobals(FILE *, GLOBvalues *); + +/* Global Variable Declarations +char *LogFileName = "cider.log"; + +int BandGapNarrowing; +int TempDepMobility, ConcDepMobility, FieldDepMobility, TransDepMobility; +int SurfaceMobility, MatchingMobility, MobDeriv; +int CCScattering; +int Srh, Auger, ConcDepLifetime, AvalancheGen; +int FreezeOut = FALSE; +int OneCarrier; + +int MaxIterations = 100; +int AcAnalysisMethod = DIRECT; + +double Temp, RelTemp, Vt, RefPsi; +double EpsNorm, VNorm, NNorm, LNorm, TNorm, JNorm, GNorm, ENorm; + RefPsi is the potential at Infinity */ + +/* + * Compute global values for this device. + */ +void GLOBcomputeGlobals(GLOBvalues *pGlobals, double temp) +/* GLOBvalues *pGlobals Global Parameter Data Structure */ +/* double temp Instance Temperature */ +{ + double tmp1; + double mnSi, mpSi; /* electron and hole conduction mass */ + double eg0; /* band gap */ + double nc0, nv0; /* conduction/valence band states */ + + /* compute temp. dependent global parameters */ + Temp = temp; + RelTemp = Temp / 300.0; + tmp1 = pow( RelTemp, 1.5 ); + + Vt = BOLTZMANN_CONSTANT * Temp / CHARGE; + eg0 = EGAP300_SI + DGAPDT_SI * ( (300.0 * 300.0) / (300.0 + TREF_EG_SI) + - (Temp * Temp) / (Temp + TREF_EG_SI) ); + mnSi = 1.039 + 5.477e-4 * Temp - 2.326e-7 * Temp * Temp; + mpSi = 0.262 * log( 0.259 * Temp ); + nc0 = NCV_NOM * pow( mnSi, 1.5 ) * tmp1; + nv0 = NCV_NOM * pow( mpSi, 1.5 ) * tmp1; + RefPsi = 0.0; + + /* set up the normalization factors */ + EpsNorm = EPS_SI; + VNorm = Vt; + NNorm = sqrt( nc0 ) * sqrt( nv0 ); /* this way no overflow */ + LNorm = sqrt( ( VNorm * EpsNorm ) / ( CHARGE * NNorm ) ); + TNorm = LNorm * LNorm / VNorm; + JNorm = CHARGE * NNorm * VNorm / LNorm; + GNorm = JNorm / VNorm; + ENorm = VNorm / LNorm; + + RefPsi /= VNorm; + + /* Save Globals */ + GLOBputGlobals( pGlobals ); + /* + * GLOBprnGlobals( stdout, pGlobals ); + */ + +} + +void GLOBputGlobals(GLOBvalues *values) +{ + if ( values == NIL(GLOBvalues) ) { + fprintf( stderr, "Error: tried to get from NIL GLOBvalues\n"); + exit(-1); + } + + /* Temperature-related globals */ + values->Temp = Temp; + values->RelTemp = RelTemp; + values->Vt = Vt; + values->RefPsi = RefPsi; + + /* Normalization Factors */ + values->EpsNorm = EpsNorm; + values->VNorm = VNorm; + values->NNorm = NNorm; + values->LNorm = LNorm; + values->TNorm = TNorm; + values->JNorm = JNorm; + values->GNorm = GNorm; + values->ENorm = ENorm; + + return; +} + +/* + * Reload all globals needed during DEV loading routines + * and DEV output routines + */ +void GLOBgetGlobals(GLOBvalues *values) +{ + if ( values == NIL(GLOBvalues) ) { + fprintf( stderr, "Error: tried to get from NIL GLOBvalues\n"); + exit(-1); + } + + /* Temperature-related globals */ + Temp = values->Temp; + RelTemp = values->RelTemp; + Vt = values->Vt; + RefPsi = values->RefPsi; + + /* Normalization Factors */ + EpsNorm = values->EpsNorm; + VNorm = values->VNorm; + NNorm = values->NNorm; + LNorm = values->LNorm; + TNorm = values->TNorm; + JNorm = values->JNorm; + GNorm = values->GNorm; + ENorm = values->ENorm; + + return; +} + +void GLOBprnGlobals(FILE *file, GLOBvalues *values) +{ + static char *tabformat = "%12s: % .4e %-12s\t"; + static char *newformat = "%12s: % .4e %-12s\n"; + + if ( values == NIL( GLOBvalues ) ) { + fprintf( stderr, "Error: tried to print NIL GLOBvalues\n"); + exit(-1); + } + fprintf( file, "*** GLOBAL PARAMETERS AT %g deg K\n", values->Temp ); + fprintf( file, "****** Temperature-Dependent Voltages\n" ); + fprintf( file, tabformat, "Vt", values->Vt, "V" ); + fprintf( file, newformat, "RefPsi", values->RefPsi * values->VNorm, "V" ); + fprintf( file, "****** Normalization Factors\n" ); + fprintf( file, newformat, "EpsNorm", values->EpsNorm, "F/cm" ); + fprintf( file, newformat, "VNorm", values->VNorm, "V" ); + fprintf( file, newformat, "NNorm", values->NNorm, "/cm^3" ); + fprintf( file, newformat, "LNorm", values->LNorm, "cm" ); + fprintf( file, newformat, "TNorm", values->TNorm, "s" ); + fprintf( file, newformat, "JNorm", values->JNorm, "A/cm^2" ); + fprintf( file, newformat, "GNorm", values->GNorm, "A/V" ); + fprintf( file, newformat, "ENorm", values->ENorm, "V/cm" ); + + return; +} diff --git a/src/ciderlib/support/integset.c b/src/ciderlib/support/integset.c new file mode 100644 index 000000000..64105a585 --- /dev/null +++ b/src/ciderlib/support/integset.c @@ -0,0 +1,155 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numenum.h" + +/* compute the coefficient for the integration and predictor methods */ +/* based on the Lagrange polynomial method in Liniger et. al. */ + +void +computeIntegCoeff(int method, int order, double *intCoeff, double *delta) +{ + int i, j, k; + double sum, temp, preMult; + double num, denom, prod; + + switch( method ) { + case BDF: + /* determine coeff[0] first */ + + sum = 0.0; + temp = 0.0; + for( j = 0 ; j < order; j++ ) { + temp += delta[ j ]; + sum += 1.0 / temp; + } + intCoeff[ 0 ] = sum; + + /* now compute the higher order coefficients */ + for( j = 1; j <= order ; j++ ) { + /* compute the pre multiplier */ + temp = 0.0; + for( i = 0; i < j; i++ ) { + temp += delta[ i ]; + } + preMult = 1.0 / temp; + prod = 1.0; + /* now compute the product */ + for( i = 1; i <= order; i++ ) { + /* product loop */ + if( i != j ) { + num = 0.0; + for( k = 0; k < i; k++ ) { + /* first the numerator */ + num += delta[ k ]; + } + if( i > j ) { + /* denom is positive */ + denom = 0.0; + for( k = j; k < i; k++ ) { + denom += delta[ k ]; + } + } + else { + /* i < j */ + denom = 0.0; + for( k = i; k < j; k++ ) { + denom += delta[ k ]; + } + denom = -denom; + } + prod *= num / denom ; + } + } + intCoeff[ j ] = -preMult * prod; + } + break; + case TRAPEZOIDAL: + default: + switch( order ) { + case 1: + temp = 1.0 / delta[ 0 ]; + intCoeff[ 0 ] = temp; + intCoeff[ 1 ] = -temp; + break; + case 2: + temp = 2.0 / delta[ 0 ]; + intCoeff[ 0 ] = temp; + intCoeff[ 1 ] = -temp; + intCoeff[ 2 ] = -1.0; + break; + } + break; + } +} + + + +void +computePredCoeff(int method, int order, double *predCoeff, double *delta) +{ + int i, j, k; + double num, denom, prod, temp; + + if( method == TRAPEZOIDAL && order > 2 ) { + printf("\n computePredCoeff: order > 2 for trapezoidal"); + exit( -1 ); + } + for( j = 1; j <= order+1 ; j++ ) { + prod = 1.0; + /* now compute the product */ + for( i = 1; i <= order+1; i++ ) { + /* product loop */ + if( i != j ) { + num = 0.0; + for( k = 0; k < i; k++ ) { + /* first the numerator */ + num += delta[ k ]; + } + if( i > j ) { + /* denom is positive */ + denom = 0.0; + for( k = j; k < i; k++ ) { + denom += delta[ k ]; + } + } + else { + /* i < j */ + denom = 0.0; + for( k = i; k < j; k++ ) { + denom += delta[ k ]; + } + denom = -denom; + } + prod *= num / denom ; + } + } + predCoeff[ j - 1 ] = prod; + } +} + + + +/* main program to check the coefficients +main() +{ + double intCoeff[ 7 ], predCoeff[ 7 ]; + double delta[ 7 ]; + int order = 1; + int i; + + for( i = 0; i <= 6; i++ ) { + delta[ i ] = 1.0; + } + + computeIntegCoeff(TRAPEZOIDAL, order, intCoeff, delta ); + computePredCoeff(TRAPEZOIDAL, order, predCoeff, delta ); + + for(i = 0; i <= order; i++ ) { + printf("\n IntCoeff[ %d ] = %e PredCoeff[ %d ] = %e ", i, intCoeff[ i ], i, predCoeff[ i ] ); + } +} +*/ diff --git a/src/ciderlib/support/integuse.c b/src/ciderlib/support/integuse.c new file mode 100644 index 000000000..7fb03ba22 --- /dev/null +++ b/src/ciderlib/support/integuse.c @@ -0,0 +1,302 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "gendev.h" + +/* function to compute the integrated variables discretization */ + +#define ccap qcap+1 + +double +integrate(double **devStates, TranInfo *info, int qcap ) +{ + double value; + double *coeff = info->intCoeff; + + switch ( info->method ) { + case BDF: + switch( info->order ) { + case 1: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)); + break; + case 2: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)); + break; + case 3: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)); + break; + case 4: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)); + break; + case 5: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)) + + coeff[5] * (*(devStates[5]+qcap)); + break; + case 6: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)) + + coeff[5] * (*(devStates[5]+qcap)) + + coeff[6] * (*(devStates[6]+qcap)); + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + case TRAPEZOIDAL: + default: + switch( info->order ) { + case 1: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)); + *(devStates[0]+ccap) = value; + break; + case 2: + value = coeff[0] * (*(devStates[0]+qcap)) + + coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[1]+ccap)); + *(devStates[0]+ccap) = value; + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + } + + return( value ); +} + +/* function to predict the value of the variables */ + +double +predict(double **devStates, TranInfo *info, int qcap ) +{ + double value; + double *coeff = info->predCoeff; + + switch ( info->method ) { + case BDF: + switch( info->order ) { + case 1: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)); + break; + case 2: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)); + break; + case 3: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)) + + coeff[3] * (*(devStates[4]+qcap)); + break; + case 4: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)) + + coeff[3] * (*(devStates[4]+qcap)) + + coeff[4] * (*(devStates[5]+qcap)); + break; + case 5: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)) + + coeff[3] * (*(devStates[4]+qcap)) + + coeff[4] * (*(devStates[5]+qcap)) + + coeff[5] * (*(devStates[6]+qcap)); + break; + case 6: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)) + + coeff[3] * (*(devStates[4]+qcap)) + + coeff[4] * (*(devStates[5]+qcap)) + + coeff[5] * (*(devStates[6]+qcap)) + + coeff[6] * (*(devStates[7]+qcap)); + break; + default: + printf( "\n prediction order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + case TRAPEZOIDAL: + default: + switch( info->order ) { + case 1: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)); + break; + case 2: + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[3]+qcap)); + /* + value = coeff[0] * (*(devStates[1]+qcap)) + + coeff[1] * (*(devStates[2]+qcap)) + + coeff[2] * (*(devStates[1]+ccap)); + */ + break; + default: + printf( "\n prediction order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + } + + return( value ); +} + +double +computeLTECoeff( TranInfo *info ) +{ + double *delta = info->delta; + double temp, denom, lteCoeff; + + switch ( info->method ) { + case BDF: + switch( info->order ) { + case 1: + denom = delta[ 0 ] + delta[ 1 ]; + break; + case 2: + denom = delta[ 0 ] + delta[ 1 ] + delta[ 2 ]; + break; + case 3: + denom = delta[ 0 ] + delta[ 1 ] + delta[ 2 ] + + delta[ 3 ]; + break; + case 4: + denom = delta[ 0 ] + delta[ 1 ] + delta[ 2 ] + + delta[ 3 ] + delta[ 4 ]; + break; + case 5: + denom = delta[ 0 ] + delta[ 1 ] + delta[ 2 ] + + delta[ 3 ] + delta[ 4 ] + delta[ 5 ]; + break; + case 6: + denom = delta[ 0 ] + delta[ 1 ] + delta[ 2 ] + + delta[ 3 ] + delta[ 4 ] + delta[ 5 ] + delta[ 6 ]; + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + break; + } + break; + case TRAPEZOIDAL: + default: + switch( info->order ) { + case 1: + denom = delta[ 0 ] + delta[ 1 ]; + break; + case 2: + /* + denom = delta[ 0 ] + delta[ 1 ]; + */ + temp = delta[ 0 ] + delta [ 1 ]; + denom = 2.0 * temp * (temp + delta[ 2 ]) / delta[ 0 ]; + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + break; + } + break; + } + lteCoeff = delta[ 0 ] / denom; + return( lteCoeff ); +} + +/* function to integrate a linear DAE */ +double +integrateLin(double **devStates, TranInfo *info, int qcap ) +{ + double value; + double *coeff = info->intCoeff; + + switch ( info->method ) { + case BDF: + switch( info->order ) { + case 1: + value = coeff[1] * (*(devStates[1]+qcap)); + break; + case 2: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)); + break; + case 3: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)); + break; + case 4: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)); + break; + case 5: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)) + + coeff[5] * (*(devStates[5]+qcap)); + break; + case 6: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[2]+qcap)) + + coeff[3] * (*(devStates[3]+qcap)) + + coeff[4] * (*(devStates[4]+qcap)) + + coeff[5] * (*(devStates[5]+qcap)) + + coeff[6] * (*(devStates[6]+qcap)); + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + case TRAPEZOIDAL: + default: + switch( info->order ) { + case 1: + value = coeff[1] * (*(devStates[1]+qcap)); + break; + case 2: + value = coeff[1] * (*(devStates[1]+qcap)) + + coeff[2] * (*(devStates[1]+ccap)); + break; + default: + printf( "\n integration order %d !! STOP \n", info->order ); + exit( 0 ); + } + break; + } + + return( value ); +} + diff --git a/src/ciderlib/support/logfile.c b/src/ciderlib/support/logfile.c new file mode 100644 index 000000000..03fb5289c --- /dev/null +++ b/src/ciderlib/support/logfile.c @@ -0,0 +1,41 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" + +static char *LogFileName = "cider.log"; +static int LogError = 0; + +void +LOGmakeEntry(name, description) + char *name; + char *description; +{ + int procStamp; + FILE *fpLog; + +#ifdef HAS_GETPID + procStamp = getpid(); +#else + procStamp = 0; +#endif + +/* Want to make sure that multiple processes can access the log file + * without stepping on each other. + */ +#ifdef ultrix + if (!(fpLog = fopen(LogFileName, "A"))) { +#else + if (!(fpLog = fopen(LogFileName, "a"))) { +#endif + if (!LogError) + perror(LogFileName); + LogError = 1; + } else { + fprintf(fpLog, "<%05d> %s: %s\n", procStamp, name, description); + fclose(fpLog); + LogError = 0; + } +} diff --git a/src/ciderlib/support/mater.c b/src/ciderlib/support/mater.c new file mode 100644 index 000000000..ed95ddf3d --- /dev/null +++ b/src/ciderlib/support/mater.c @@ -0,0 +1,404 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "material.h" + +/* Forward Declarations */ +void printMaterialInfo(MaterialInfo *info); + +/* External Symbols */ +extern void MOBtempDep (MaterialInfo *, double); + + +/* + * Set material info values to their defaults. + */ +void +MATLdefaults(MaterialInfo *info) +{ + if ((info->material == OXIDE) || (info->material == INSULATOR)) { + info->type = INSULATOR; + info->eps = EPS_OX; + info->affin = AFFIN_OX; + info->eg0 = EGAP300_OX; + } else if (info->material == NITRIDE) { + info->type = INSULATOR; + info->eps = EPS_NI; + info->affin = AFFIN_NI; + info->eg0 = EGAP300_NI; + } else if (info->material == POLYSILICON) { + info->type = SEMICON; + info->eps = EPS_SI; + info->affin = AFFIN_SI; + info->nc0 = 0.0; + info->nv0 = 0.0; + info->eg0 = EGAP300_SI; + info->dEgDt = DGAPDT_SI; + info->trefBGN = TREF_EG_SI; + info->dEgDn[ELEC] = DGAPDN_N; + info->dEgDn[HOLE] = DGAPDN_P; + info->nrefBGN[ELEC] = NBGN_N; + info->nrefBGN[HOLE] = NBGN_P; + info->tau0[ELEC] = TAU0_N_SI; + info->tau0[HOLE] = TAU0_P_SI; + info->nrefSRH[ELEC] = NSRH_N_SI; + info->nrefSRH[HOLE] = NSRH_P_SI; + info->cAug[ELEC] = C_AUG_N_SI; + info->cAug[HOLE] = C_AUG_P_SI; + info->aRich[ELEC] = A_RICH_N_SI; + info->aRich[HOLE] = A_RICH_P_SI; + info->eDon = E_DON_SI; + info->eAcc = E_ACC_SI; + info->gDon = G_DON_SI; + info->gAcc = G_ACC_SI; + info->concModel = CT; + info->muMax[ELEC][MAJOR] = 0.07 * AR_MUMAX_N; + info->muMin[ELEC][MAJOR] = 0.07 * AR_MUMIN_N; + info->ntRef[ELEC][MAJOR] = AR_NTREF_N; + info->ntExp[ELEC][MAJOR] = AR_NTEXP_N; + info->muMax[HOLE][MAJOR] = 0.07 * AR_MUMAX_P; + info->muMin[HOLE][MAJOR] = 0.07 * AR_MUMIN_P; + info->ntRef[HOLE][MAJOR] = AR_NTREF_P; + info->ntExp[HOLE][MAJOR] = AR_NTEXP_P; + info->muMax[ELEC][MINOR] = 0.07 * UF_MUMAX_N; + info->muMin[ELEC][MINOR] = 0.07 * UF_MUMIN_N; + info->ntRef[ELEC][MINOR] = UF_NTREF_N; + info->ntExp[ELEC][MINOR] = UF_NTEXP_N; + info->muMax[HOLE][MINOR] = 0.07 * UF_MUMAX_P; + info->muMin[HOLE][MINOR] = 0.07 * UF_MUMIN_P; + info->ntRef[HOLE][MINOR] = UF_NTREF_P; + info->ntExp[HOLE][MINOR] = UF_NTEXP_P; + info->fieldModel = CT; + info->vSat[ELEC] = AR_VSAT_N; + info->vSat[HOLE] = AR_VSAT_P; + info->vWarm[ELEC] = SG_VWARM_N; + info->vWarm[HOLE] = SG_VWARM_P; + info->mus[ELEC] = 0.07 * MUS_N; + info->thetaA[ELEC] = THETAA_N; + info->thetaB[ELEC] = THETAB_N; + info->mus[HOLE] = 0.07 * MUS_P; + info->thetaA[HOLE] = THETAA_P; + info->thetaB[HOLE] = THETAB_P; + } else if ((info->material == SILICON) || (info->material == SEMICON)) { + info->type = SEMICON; + info->eps = EPS_SI; + info->affin = AFFIN_SI; + info->nc0 = 0.0; + info->nv0 = 0.0; + info->eg0 = EGAP300_SI; + info->dEgDt = DGAPDT_SI; + info->trefBGN = TREF_EG_SI; + info->dEgDn[ELEC] = DGAPDN_N; + info->dEgDn[HOLE] = DGAPDN_P; + info->nrefBGN[ELEC] = NBGN_N; + info->nrefBGN[HOLE] = NBGN_P; + info->tau0[ELEC] = TAU0_N_SI; + info->tau0[HOLE] = TAU0_P_SI; + info->nrefSRH[ELEC] = NSRH_N_SI; + info->nrefSRH[HOLE] = NSRH_P_SI; + info->cAug[ELEC] = C_AUG_N_SI; + info->cAug[HOLE] = C_AUG_P_SI; + info->aRich[ELEC] = A_RICH_N_SI; + info->aRich[HOLE] = A_RICH_P_SI; + info->eDon = E_DON_SI; + info->eAcc = E_ACC_SI; + info->gDon = G_DON_SI; + info->gAcc = G_ACC_SI; + info->concModel = CT; + info->muMax[ELEC][MAJOR] = AR_MUMAX_N; + info->muMin[ELEC][MAJOR] = AR_MUMIN_N; + info->ntRef[ELEC][MAJOR] = AR_NTREF_N; + info->ntExp[ELEC][MAJOR] = AR_NTEXP_N; + info->muMax[HOLE][MAJOR] = AR_MUMAX_P; + info->muMin[HOLE][MAJOR] = AR_MUMIN_P; + info->ntRef[HOLE][MAJOR] = AR_NTREF_P; + info->ntExp[HOLE][MAJOR] = AR_NTEXP_P; + info->muMax[ELEC][MINOR] = UF_MUMAX_N; + info->muMin[ELEC][MINOR] = UF_MUMIN_N; + info->ntRef[ELEC][MINOR] = UF_NTREF_N; + info->ntExp[ELEC][MINOR] = UF_NTEXP_N; + info->muMax[HOLE][MINOR] = UF_MUMAX_P; + info->muMin[HOLE][MINOR] = UF_MUMIN_P; + info->ntRef[HOLE][MINOR] = UF_NTREF_P; + info->ntExp[HOLE][MINOR] = UF_NTEXP_P; + info->fieldModel = CT; + info->vSat[ELEC] = AR_VSAT_N; + info->vSat[HOLE] = AR_VSAT_P; + info->vWarm[ELEC] = SG_VWARM_N; + info->vWarm[HOLE] = SG_VWARM_P; + info->mus[ELEC] = MUS_N; + info->thetaA[ELEC] = THETAA_N; + info->thetaB[ELEC] = THETAB_N; + info->mus[HOLE] = MUS_P; + info->thetaA[HOLE] = THETAA_P; + info->thetaB[HOLE] = THETAB_P; + } else if (info->material == GAAS) { + info->type = SEMICON; + info->eps = EPS_GA; + info->affin = AFFIN_GA; + info->nc0 = NCV_NOM * pow(M_N_GA, 1.5); + info->nv0 = NCV_NOM * pow(M_P_GA, 1.5); + info->eg0 = EGAP300_GA; + info->dEgDt = DGAPDT_GA; + info->trefBGN = TREF_EG_GA; + info->dEgDn[ELEC] = DGAPDN_N; + info->dEgDn[HOLE] = DGAPDN_P; + info->nrefBGN[ELEC] = NBGN_N; + info->nrefBGN[HOLE] = NBGN_P; + info->tau0[ELEC] = TAU0_N_GA; + info->tau0[HOLE] = TAU0_P_GA; + info->nrefSRH[ELEC] = NSRH_N_GA; + info->nrefSRH[HOLE] = NSRH_P_GA; + info->cAug[ELEC] = C_AUG_N_GA; + info->cAug[HOLE] = C_AUG_P_GA; + info->aRich[ELEC] = A_RICH_N_GA; + info->aRich[HOLE] = A_RICH_P_GA; + info->eDon = E_DON_GA; + info->eAcc = E_ACC_GA; + info->gDon = G_DON_GA; + info->gAcc = G_ACC_GA; + info->concModel = GA; + info->muMax[ELEC][MAJOR] = GA_MUMAX_N; + info->muMin[ELEC][MAJOR] = GA_MUMIN_N; + info->ntRef[ELEC][MAJOR] = GA_NTREF_N; + info->ntExp[ELEC][MAJOR] = GA_NTEXP_N; + info->muMax[HOLE][MAJOR] = GA_MUMAX_P; + info->muMin[HOLE][MAJOR] = GA_MUMIN_P; + info->ntRef[HOLE][MAJOR] = GA_NTREF_P; + info->ntExp[HOLE][MAJOR] = GA_NTEXP_P; + info->muMax[ELEC][MINOR] = GA_MUMAX_N; + info->muMin[ELEC][MINOR] = GA_MUMIN_N; + info->ntRef[ELEC][MINOR] = GA_NTREF_N; + info->ntExp[ELEC][MINOR] = GA_NTEXP_N; + info->muMax[HOLE][MINOR] = GA_MUMAX_P; + info->muMin[HOLE][MINOR] = GA_MUMIN_P; + info->ntRef[HOLE][MINOR] = GA_NTREF_P; + info->ntExp[HOLE][MINOR] = GA_NTEXP_P; + info->fieldModel = GA; + info->vSat[ELEC] = GA_VSAT_N; + info->vSat[HOLE] = GA_VSAT_P; + info->vWarm[ELEC] = GA_VWARM_N; + info->vWarm[HOLE] = GA_VWARM_P; + info->mus[ELEC] = MUS_N; + info->thetaA[ELEC] = THETAA_N; + info->thetaB[ELEC] = THETAB_N; + info->mus[HOLE] = MUS_P; + info->thetaA[HOLE] = THETAA_P; + info->thetaB[HOLE] = THETAB_P; + } +} + +/* + * Compute the temperature-dependent physical parameters of materials + * Normalize physical constants Actual Instance Temperature is passed in thru + * the global var 'Temp' + */ +void +MATLtempDep(MaterialInfo *info, double tnom) +/* double tnom Nominal Parameter Temperature */ +{ + double tmp1; + double relTemp, perRelTemp; + double eg0; + + if (info->type == INSULATOR) { + info->refPsi = RefPsi - (info->affin + 0.5 * info->eg0) / VNorm; + } else if (info->type == SEMICON) { + + /* compute temperature dependent semiconductor parameters */ + relTemp = Temp / tnom; + perRelTemp = 1.0 / relTemp; + tmp1 = pow(relTemp, 1.5); + + /* Bandgap and intrinsic concentration */ + eg0 = info->eg0 + (info->dEgDt * tnom * tnom) / (tnom + info->trefBGN); + info->eg0 = eg0 - (info->dEgDt * Temp * Temp) / (Temp + info->trefBGN); + if (info->nc0 > 0.0) { + info->mass[ELEC] = pow(info->nc0 / NCV_NOM / tmp1, 2.0 / 3.0); + } else { + info->mass[ELEC] = 1.039 + 5.477e-4 * Temp - 2.326e-7 * Temp * Temp; + } + if (info->nv0 > 0.0) { + info->mass[HOLE] = pow(info->nv0 / NCV_NOM / tmp1, 2.0 / 3.0); + } else { + info->mass[HOLE] = 0.262 * log(0.259 * Temp); + } + info->nc0 = NCV_NOM * pow(info->mass[ELEC], 1.5) * tmp1; + info->nv0 = NCV_NOM * pow(info->mass[HOLE], 1.5) * tmp1; + info->ni0 = sqrt(info->nc0) * sqrt(info->nv0) * + exp(-0.5 * info->eg0 / Vt); + info->refPsi = RefPsi - (info->affin + + 0.5 * (info->eg0 + Vt * log(info->nc0 / info->nv0))) / VNorm; + + /* Impurity energies */ + info->eDon /= VNorm; + info->eAcc /= VNorm; + + /* SRH lifetimes */ + tmp1 = sqrt(perRelTemp) * exp(3.8667 * (perRelTemp - 1.0)); + info->tau0[ELEC] *= tmp1 / TNorm; + info->tau0[HOLE] *= tmp1 / TNorm; + + /* Auger recombination coefficients */ + info->cAug[ELEC] *= pow(relTemp, 0.14) * NNorm * NNorm * TNorm; + info->cAug[HOLE] *= pow(relTemp, 0.18) * NNorm * NNorm * TNorm; + + /* Avalanche generation parameters */ + info->aii[ELEC] = AII_N * LNorm; + info->bii[ELEC] = BII_N / ENorm; + info->aii[HOLE] = AII_P * LNorm; + info->bii[HOLE] = BII_P / ENorm; + + /* Effective recombination velocities */ + info->vRich[ELEC] = info->aRich[ELEC] * Temp * Temp / + (CHARGE * info->nc0 * ENorm); + info->vRich[HOLE] = info->aRich[HOLE] * Temp * Temp / + (CHARGE * info->nv0 * ENorm); + + /* Mobility Temperature Dependence */ + MOBtempDep(info, Temp); + + /* Velocity Saturation Parameters */ + info->vSat[ELEC] /= ENorm; + info->vWarm[ELEC] /= ENorm; + info->vSat[HOLE] /= ENorm; + info->vWarm[HOLE] /= ENorm; + + /* Normal Field Mobility Degradation Parameters */ + info->thetaA[ELEC] *= ENorm; + info->thetaB[ELEC] *= ENorm * ENorm; + info->thetaA[HOLE] *= ENorm; + info->thetaB[HOLE] *= ENorm * ENorm; + } +} + +void +printMaterialInfo(MaterialInfo *info) +{ + static char *tabformat = "%12s: % .4e %-12s\t"; + static char *newformat = "%12s: % .4e %-12s\n"; + + char *name; + + + if (info == NIL(MaterialInfo)) { + fprintf(stderr, "Error: tried to print NIL MaterialInfo\n"); + exit(-1); + } + /* Find material name. */ + switch (info->material) { + case OXIDE: + name = "OXIDE"; + break; + case NITRIDE: + name = "NITRIDE"; + break; + case INSULATOR: + name = "INSULATOR"; + break; + case SILICON: + name = "SILICON"; + break; + case POLYSILICON: + name = "POLYSILICON"; + break; + case GAAS: + name = "GAAS"; + break; + case SEMICON: + name = "SEMICONDUCTOR"; + break; + default: + name = "MATERIAL"; + break; + } + if (info->type == INSULATOR) { + fprintf(stdout, "***** %s PARAMETERS AT %g deg K\n", name, Temp); + fprintf(stdout, "*** Poisson Equation Parameters -\n"); + fprintf(stdout, tabformat, "Eps", info->eps, "F/cm"); + fprintf(stdout, newformat, "Affin", info->affin, "eV"); + fprintf(stdout, tabformat, "Egap", info->eg0, "eV"); + fprintf(stdout, newformat, "PsiB", -info->refPsi * VNorm, "V"); + } else if (info->type == SEMICON) { + fprintf(stdout, "***** %s PARAMETERS AT %g deg K\n", name, Temp); + fprintf(stdout, "*** Poisson Equation\n"); + fprintf(stdout, tabformat, "Eps", info->eps, "F/cm"); + fprintf(stdout, newformat, "Affin", info->affin, "eV"); + fprintf(stdout, tabformat, "Vt", Vt, "V"); + fprintf(stdout, newformat, "Ni", info->ni0, "/cm^3"); + fprintf(stdout, tabformat, "Nc", info->nc0, "/cm^3"); + fprintf(stdout, newformat, "Nv", info->nv0, "/cm^3"); + fprintf(stdout, tabformat, "MnSi", info->mass[ELEC], "*m0 kg"); + fprintf(stdout, newformat, "MpSi", info->mass[HOLE], "*m0 kg"); + fprintf(stdout, tabformat, "Egap", info->eg0, "eV"); + fprintf(stdout, newformat, "PsiB", -info->refPsi * VNorm, "V"); + fprintf(stdout, tabformat, "dEg/dT", info->dEgDt, "eV"); + fprintf(stdout, newformat, "Tref", info->trefBGN, "deg K"); + fprintf(stdout, tabformat, "dEg/dN", info->dEgDn[ELEC], "eV"); + fprintf(stdout, newformat, "Nref", info->nrefBGN[ELEC], "/cm^3"); + fprintf(stdout, tabformat, "dEg/dP", info->dEgDn[HOLE], "eV"); + fprintf(stdout, newformat, "Pref", info->nrefBGN[HOLE], "/cm^3"); + fprintf(stdout, tabformat, "Edon", info->eDon * VNorm, "eV"); + fprintf(stdout, newformat, "Eacc", info->eAcc * VNorm, "eV"); + fprintf(stdout, tabformat, "Gdon", info->gDon, ""); + fprintf(stdout, newformat, "Gacc", info->gAcc, ""); + fprintf(stdout, "*** Generation - Recombination\n"); + fprintf(stdout, tabformat, "Tn0", info->tau0[ELEC] * TNorm, "s"); + fprintf(stdout, newformat, "Tp0", info->tau0[HOLE] * TNorm, "s"); + fprintf(stdout, tabformat, "CnAug", + info->cAug[ELEC] / (NNorm * NNorm * TNorm), "cm^6/s"); + fprintf(stdout, newformat, "CpAug", + info->cAug[HOLE] / (NNorm * NNorm * TNorm), "cm^6/s"); + fprintf(stdout, tabformat, "Aiin", info->aii[ELEC] / LNorm, "/cm"); + fprintf(stdout, newformat, "Aiip", info->aii[HOLE] / LNorm, "/cm"); + fprintf(stdout, tabformat, "Biin", info->bii[ELEC] * ENorm, "V/cm"); + fprintf(stdout, newformat, "Biip", info->bii[HOLE] * ENorm, "V/cm"); + fprintf(stdout, "*** Thermionic Emission\n"); + fprintf(stdout, tabformat, "Arichn", info->aRich[ELEC], "A/cm^2/oK^2"); + fprintf(stdout, newformat, "Arichp", info->aRich[HOLE], "A/cm^2/oK^2"); + fprintf(stdout, tabformat, "Vrichn", info->vRich[ELEC] * ENorm, "cm/s"); + fprintf(stdout, newformat, "Vrichp", info->vRich[HOLE] * ENorm, "cm/s"); + fprintf(stdout, "*** Majority Carrier Mobility\n"); + fprintf(stdout, tabformat, "MunMax", + info->muMax[ELEC][MAJOR], "cm^2/V-s"); + fprintf(stdout, newformat, "MupMax", + info->muMax[HOLE][MAJOR], "cm^2/V-s"); + fprintf(stdout, tabformat, "MunMin", + info->muMin[ELEC][MAJOR], "cm^2/V-s"); + fprintf(stdout, newformat, "MupMin", + info->muMin[HOLE][MAJOR], "cm^2/V-s"); + fprintf(stdout, "*** Minority Carrier Mobility\n"); + fprintf(stdout, tabformat, "MunMax", + info->muMax[ELEC][MINOR], "cm^2/V-s"); + fprintf(stdout, newformat, "MupMax", + info->muMax[HOLE][MINOR], "cm^2/V-s"); + fprintf(stdout, tabformat, "MunMin", + info->muMin[ELEC][MINOR], "cm^2/V-s"); + fprintf(stdout, newformat, "MupMin", + info->muMin[HOLE][MINOR], "cm^2/V-s"); + fprintf(stdout, "*** Surface Mobility\n"); + fprintf(stdout, tabformat, "Muns", info->mus[ELEC], "cm^2/V-s"); + fprintf(stdout, newformat, "Mups", info->mus[HOLE], "cm^2/V-s"); + fprintf(stdout, tabformat, "ThetaAN", info->thetaA[ELEC] / ENorm, "cm/V"); + fprintf(stdout, newformat, "ThetaAP", info->thetaA[HOLE] / ENorm, "cm/V"); + fprintf(stdout, tabformat, "ThetaBN", + info->thetaB[ELEC] / ENorm / ENorm, "cm^2/V^2"); + fprintf(stdout, newformat, "ThetaBP", + info->thetaB[HOLE] / ENorm / ENorm, "cm^2/V^2"); + fprintf(stdout, "*** Velocity Saturation\n"); + fprintf(stdout, tabformat, "VsatN", info->vSat[ELEC] * ENorm, "cm/s"); + fprintf(stdout, newformat, "VsatP", info->vSat[HOLE] * ENorm, "cm/s"); + if (info->fieldModel == SG || info->fieldModel == GA) { + fprintf(stdout, tabformat, "VwarmN", info->vWarm[ELEC] * ENorm, "cm/s"); + fprintf(stdout, newformat, "VwarmP", info->vWarm[HOLE] * ENorm, "cm/s"); + } + } + return; +} diff --git a/src/ciderlib/support/misc.c b/src/ciderlib/support/misc.c new file mode 100644 index 000000000..7fc0a987f --- /dev/null +++ b/src/ciderlib/support/misc.c @@ -0,0 +1,161 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Miscellaneous routines culled from the oned directory so that the twod + * code will run without having to compile the oned code + */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "spMatrix.h" + + +/* Used in Solution Projection Calculations */ +double guessNewConc(double conc, double delta) +{ + BOOLEAN acceptable = FALSE; + double fib, newConc, lambda, fibn, fibp; + lambda = 1.0; + fibn = 1.0; + fibp = 1.0; + for ( ; !acceptable ; ) { + fib = fibp; + fibp = fibn; + fibn += fib; + lambda *= fibp / fibn; + newConc = conc + delta * lambda; + if( newConc > 0.0 ) { + acceptable = TRUE; + } + else { + /* newConc is still negative but fibp and fibn are large */ + if ( (fibp > 1e6) || (fibn > 1e6) ) { + acceptable = TRUE; + newConc = conc; + } + } + } + return( newConc ); +} + +/* Used in Doping Calculation */ +/* compute the concentration at x given an array of data. + * The data is stored in a two-dimensional array x, N(x). + * x is assumed to be in ascending order. given an x + * a search is performed to determine the data points closest + * to it and linear interpolation is performed to generate N(x) + */ + +/* +#define LOGSUPREM +*/ +double lookup(double **dataTable, double x) +{ + double conc=0.0, x0, x1, y0, y1; +#ifdef LOGSUPREM + double lnconc, lny0, lny1; +#endif + int index, numPoints; + BOOLEAN done = FALSE; + + numPoints = dataTable[ 0 ][ 0 ]; + for( index = 2; index <= numPoints && (!done); index++ ) { + x1 = dataTable[ 0 ][ index ]; + /* check if x1 > x */ + if( x1 >= x ) { + /* found an x1 larger than x, so linear interpolate */ + x0 = dataTable[ 0 ][ index - 1 ]; + y0 = dataTable[ 1 ][ index - 1 ]; + y1 = dataTable[ 1 ][ index ]; +#ifdef LOGSUPREM + /* Ignore concentrations below 1.0 */ + if ( ABS(y0) < 1.0 ) + lny0 = 0.0; + else + lny0 = SGN(y0) * log( ABS(y0) ); + if ( ABS(y1) < 1.0 ) + lny1 = 0.0; + else + lny1 = SGN(y0) * log( ABS(y0) ); + lnconc = lny0 + (lny1 - lny0) * (x - x0) / (x1 - x0); + conc = SGN(lnconc) * exp( ABS(lnconc) ); +#else + conc = y0 + (y1 - y0) * (x - x0) / (x1 - x0); +#endif /* LOGSUPREM */ + done = TRUE; + } else { + if( index == numPoints ) { + /* set to concentration of last node - due to roundoff errors */ + conc = dataTable[ 1 ][ numPoints ]; + } + } + } + return ( conc ); +} + +/* Used in admittance calculations */ +/* this function returns TRUE is SOR iteration converges otherwise FALSE */ + +BOOLEAN hasSORConverged(double *oldSolution, double *newSolution, + int numEqns) +{ + BOOLEAN converged = TRUE; + int index; + double xOld, xNew, tol; + double absTol = 1e-12; + double relTol = 1e-3; + for( index = 1 ; index <= numEqns ; index++ ) { + xOld = oldSolution[ index ]; + xNew = newSolution[ index ]; + tol = absTol + relTol * MAX( ABS( xOld ), ABS( xNew )); + if( ABS( xOld - xNew ) > tol ) { + converged = FALSE; + printf("hasSORconverged failed\n"); + break; + } + } + return( converged ); +} + +/* Used to Check Sparse Matrix Errors */ +BOOLEAN +foundError(int error) +{ + BOOLEAN matrixError; + + switch( error ) { + /* Removed for Spice3e1 Compatibility + case spSMALL_PIVOT: + printf( "Warning: LU Decomposition Problem - SMALL PIVOT\n" ); + matrixError = FALSE; + break; + */ + case spPANIC: + printf( "Error: LU Decomposition Failed - PANIC\n" ); + matrixError = TRUE; + break; + case spSINGULAR: + printf( "Error: LU Decomposition Failed - SINGULAR\n" ); + matrixError = TRUE; + break; + /* Removed for Spice3e1 Compatibility + case spZERO_DIAG: + printf( "Error: LU Decomposition Failed - ZERO PIVOT\n" ); + matrixError = TRUE; + break; + */ + case spNO_MEMORY: + printf( "Error: LU Decomposition Failed - NO MEMORY\n" ); + matrixError = TRUE; + break; + default: + matrixError = FALSE; + break; + } + return( matrixError ); +} diff --git a/src/ciderlib/support/mobil.c b/src/ciderlib/support/mobil.c new file mode 100644 index 000000000..ddb2f2375 --- /dev/null +++ b/src/ciderlib/support/mobil.c @@ -0,0 +1,421 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "macros.h" +#include "material.h" + +void MOBdefaults(MaterialInfo *info , int carrier, int type, + int concmodel, int fieldmodel ) +{ + switch (concmodel) { + case CT: + info->concModel = CT; + if (carrier == ELEC) { + info->muMax[ELEC][type] = CT_MUMAX_N; + info->muMin[ELEC][type] = CT_MUMIN_N; + info->ntRef[ELEC][type] = CT_NTREF_N; + info->ntExp[ELEC][type] = CT_NTEXP_N; + } else { + info->muMax[HOLE][type] = CT_MUMAX_P; + info->muMin[HOLE][type] = CT_MUMIN_P; + info->ntRef[HOLE][type] = CT_NTREF_P; + info->ntExp[HOLE][type] = CT_NTEXP_P; + } + break; + case AR: + info->concModel = AR; + if (carrier == ELEC) { + info->muMax[ELEC][type] = AR_MUMAX_N; + info->muMin[ELEC][type] = AR_MUMIN_N; + info->ntRef[ELEC][type] = AR_NTREF_N; + info->ntExp[ELEC][type] = AR_NTEXP_N; + } else { + info->muMax[HOLE][type] = AR_MUMAX_P; + info->muMin[HOLE][type] = AR_MUMIN_P; + info->ntRef[HOLE][type] = AR_NTREF_P; + info->ntExp[HOLE][type] = AR_NTEXP_P; + } + break; + case UF: + info->concModel = UF; + if (carrier == ELEC) { + info->muMax[ELEC][type] = UF_MUMAX_N; + info->muMin[ELEC][type] = UF_MUMIN_N; + info->ntRef[ELEC][type] = UF_NTREF_N; + info->ntExp[ELEC][type] = UF_NTEXP_N; + } else { + info->muMax[HOLE][type] = UF_MUMAX_P; + info->muMin[HOLE][type] = UF_MUMIN_P; + info->ntRef[HOLE][type] = UF_NTREF_P; + info->ntExp[HOLE][type] = UF_NTEXP_P; + } + break; + case GA: + info->concModel = GA; + if (carrier == ELEC) { + info->muMax[ELEC][type] = GA_MUMAX_N; + info->muMin[ELEC][type] = GA_MUMIN_N; + info->ntRef[ELEC][type] = GA_NTREF_N; + info->ntExp[ELEC][type] = GA_NTEXP_N; + } else { + info->muMax[HOLE][type] = GA_MUMAX_P; + info->muMin[HOLE][type] = GA_MUMIN_P; + info->ntRef[HOLE][type] = GA_NTREF_P; + info->ntExp[HOLE][type] = GA_NTEXP_P; + } + break; + case SG: + default: + info->concModel = SG; + if (carrier == ELEC) { + info->muMax[ELEC][type] = SG_MUMAX_N; + info->muMin[ELEC][type] = SG_MUMIN_N; + info->ntRef[ELEC][type] = SG_NTREF_N; + info->ntExp[ELEC][type] = SG_NTEXP_N; + } else { + info->muMax[HOLE][type] = SG_MUMAX_P; + info->muMin[HOLE][type] = SG_MUMIN_P; + info->ntRef[HOLE][type] = SG_NTREF_P; + info->ntExp[HOLE][type] = SG_NTEXP_P; + } + break; + } + if (type == MAJOR) { + switch (fieldmodel) { + case CT: + info->fieldModel = CT; + if (carrier == ELEC) { + info->vSat[ELEC] = CT_VSAT_N; + } else { + info->vSat[HOLE] = CT_VSAT_P; + } + break; + case AR: + case UF: + info->fieldModel = AR; + if (carrier == ELEC) { + info->vSat[ELEC] = AR_VSAT_N; + } else { + info->vSat[HOLE] = AR_VSAT_P; + } + break; + case GA: + info->fieldModel = GA; + if (carrier == ELEC) { + info->vSat[ELEC] = GA_VSAT_N; + info->vWarm[ELEC] = GA_VWARM_N; + } else { + info->vSat[HOLE] = GA_VSAT_P; + info->vWarm[HOLE] = GA_VWARM_P; + } + break; + case SG: + default: + info->fieldModel = SG; + if (carrier == ELEC) { + info->vSat[ELEC] = SG_VSAT_N; + info->vWarm[ELEC] = SG_VWARM_N; + } else { + info->vSat[HOLE] = SG_VSAT_P; + info->vWarm[HOLE] = SG_VWARM_P; + } + break; + } + } +} + +void +MOBtempDep (MaterialInfo *info, double temp) +{ + double relTemp = temp / 300.0; + double factor, muMin, muMax, mu0; + + /* Modify if necessary. */ + if (TempDepMobility) + { + /* Do concentration dependence parameters */ + muMin = info->muMin[ELEC][MAJOR]; + mu0 = info->muMax[ELEC][MAJOR] - muMin; + factor = pow(relTemp, TD_EXPMUMIN_N); + muMin *= factor; + factor = pow(relTemp, TD_EXPMUMAX_N); + mu0 *= factor; + info->muMin[ELEC][MAJOR] = muMin; + info->muMax[ELEC][MAJOR] = mu0 + muMin; + factor = pow(relTemp, TD_EXPNTREF_N); + info->ntRef[ELEC][MAJOR] *= factor; + factor = pow(relTemp, TD_EXPNTEXP_N); + info->ntExp[ELEC][MAJOR] *= factor; + + muMin = info->muMin[ELEC][MINOR]; + mu0 = info->muMax[ELEC][MINOR] - muMin; + factor = pow(relTemp, TD_EXPMUMIN_N); + muMin *= factor; + factor = pow(relTemp, TD_EXPMUMAX_N); + mu0 *= factor; + info->muMin[ELEC][MINOR] = muMin; + info->muMax[ELEC][MINOR] = mu0 + muMin; + factor = pow(relTemp, TD_EXPNTREF_N); + info->ntRef[ELEC][MINOR] *= factor; + factor = pow(relTemp, TD_EXPNTEXP_N); + info->ntExp[ELEC][MINOR] *= factor; + + muMin = info->muMin[HOLE][MAJOR]; + mu0 = info->muMax[HOLE][MAJOR] - muMin; + factor = pow(relTemp, TD_EXPMUMIN_P); + muMin *= factor; + factor = pow(relTemp, TD_EXPMUMAX_P); + mu0 *= factor; + info->muMin[HOLE][MAJOR] = muMin; + info->muMax[HOLE][MAJOR] = mu0 + muMin; + factor = pow(relTemp, TD_EXPNTREF_P); + info->ntRef[HOLE][MAJOR] *= factor; + factor = pow(relTemp, TD_EXPNTEXP_P); + info->ntExp[HOLE][MAJOR] *= factor; + + muMin = info->muMin[HOLE][MINOR]; + mu0 = info->muMax[HOLE][MINOR] - muMin; + factor = pow(relTemp, TD_EXPMUMIN_P); + muMin *= factor; + factor = pow(relTemp, TD_EXPMUMAX_P); + mu0 *= factor; + info->muMin[HOLE][MINOR] = muMin; + info->muMax[HOLE][MINOR] = mu0 + muMin; + factor = pow(relTemp, TD_EXPNTREF_P); + info->ntRef[HOLE][MINOR] *= factor; + factor = pow(relTemp, TD_EXPNTEXP_P); + info->ntExp[HOLE][MINOR] *= factor; + + /* Modify field dependence parameters */ + /* Assume warm carrier reference velocity has same temperature dep. */ + factor = sqrt( tanh( TD_TREFVS_N / Temp ) ); + info->vSat[ELEC] *= factor; + info->vWarm[ELEC] *= factor; + factor = sqrt( tanh( TD_TREFVS_P / Temp ) ); + info->vSat[HOLE] *= factor; + info->vWarm[HOLE] *= factor; + } +} + +void +MOBconcDep (MaterialInfo *info, double conc, double *pMun, double *pMup) +{ + double s; + + /* We have to check sign of conc even when concentration dependence + * is not used because it affects whether carriers are majority or + * minority carriers. Ideally, the minority/majority carrier models + * should agree at 0.0 concentration, but often they'll be inconsistent. + */ + + if (conc >= 0.0) + { /* N type */ + if (ConcDepMobility) + { + switch (info->concModel) + { + case CT: + case AR: + case UF: + case GA: + *pMun = info->muMin[ELEC][MAJOR] + + (info->muMax[ELEC][MAJOR] - info->muMin[ELEC][MAJOR]) / + (1.0 + pow(conc / info->ntRef[ELEC][MAJOR], + info->ntExp[ELEC][MAJOR])); + + *pMup = info->muMin[HOLE][MINOR] + + (info->muMax[HOLE][MINOR] - info->muMin[HOLE][MINOR]) / + (1.0 + pow(conc / info->ntRef[HOLE][MINOR], + info->ntExp[HOLE][MINOR])); + break; + case SG: + default: + s = info->muMax[ELEC][MAJOR] / info->muMin[ELEC][MAJOR]; + s = pow(s, 1.0 / info->ntExp[ELEC][MAJOR]) - 1; + *pMun = info->muMax[ELEC][MAJOR] / + pow(1.0 + conc / (conc / s + info->ntRef[ELEC][MAJOR]), + info->ntExp[ELEC][MAJOR]); + + s = info->muMax[HOLE][MINOR] / info->muMin[HOLE][MINOR]; + s = pow(s, 1.0 / info->ntExp[HOLE][MINOR]) - 1; + *pMup = info->muMax[HOLE][MINOR] / + pow(1.0 + conc / (conc / s + info->ntRef[HOLE][MINOR]), + info->ntExp[HOLE][MINOR]); + break; + } + } + else + { + *pMun = info->muMax[ELEC][MAJOR]; + *pMup = info->muMax[HOLE][MINOR]; + } + } + else + { /* P type */ + if (ConcDepMobility) + { + conc = -conc; /* Take absolute value. */ + switch (info->concModel) + { + case CT: + case AR: + case UF: + case GA: + *pMun = info->muMin[ELEC][MINOR] + + (info->muMax[ELEC][MINOR] - info->muMin[ELEC][MINOR]) / + (1.0 + pow(conc / info->ntRef[ELEC][MINOR], + info->ntExp[ELEC][MINOR])); + + *pMup = info->muMin[HOLE][MAJOR] + + (info->muMax[HOLE][MAJOR] - info->muMin[HOLE][MAJOR]) / + (1.0 + pow(conc / info->ntRef[HOLE][MAJOR], + info->ntExp[HOLE][MAJOR])); + break; + case SG: + default: + s = info->muMax[ELEC][MINOR] / info->muMin[ELEC][MINOR]; + s = pow(s, 1.0 / info->ntExp[ELEC][MINOR]) - 1; + *pMun = info->muMax[ELEC][MINOR] / + pow(1.0 + conc / (conc / s + info->ntRef[ELEC][MINOR]), + info->ntExp[ELEC][MINOR]); + + s = info->muMax[HOLE][MAJOR] / info->muMin[HOLE][MAJOR]; + s = pow(s, 1.0 / info->ntExp[HOLE][MAJOR]) - 1; + *pMup = info->muMax[HOLE][MAJOR] / + pow(1.0 + conc / (conc / s + info->ntRef[HOLE][MAJOR]), + info->ntExp[HOLE][MAJOR]); + break; + } + } + else + { + *pMun = info->muMax[ELEC][MINOR]; + *pMup = info->muMax[HOLE][MAJOR]; + } + } + return; +} + + +void +MOBfieldDep (MaterialInfo *info, int carrier, double field, double *pMu, + double *pDMu) +{ + double eLateral, mu; + double sgnL; + double temp1, temp2, temp3, temp4, temp5, temp6; + double dMuDEl; /* Lateral Field Derivative */ + + /* Quick check to make sure we really belong here. */ + if (!FieldDepMobility) /* XXX Global */ + return; + + sgnL = SGN (field); + eLateral = ABS (field); + mu = *pMu; /* Grab temp. and conc.-dep. mobility */ + + + if (carrier == ELEC) + { + switch (info->fieldModel) + { + case CT: + case AR: + case UF: + temp1 = mu / info->vSat[ELEC]; + temp2 = temp1 * eLateral; + temp3 = 1.0 / (1.0 + temp2 * temp2); + mu *= sqrt(temp3); + dMuDEl = -sgnL * mu * temp3 * temp2 * temp1; + + + break; + case GA: + temp1 = info->vSat[ELEC] / info->vWarm[ELEC]; /* Vsat / Vwarm */ + temp2 = mu / info->vWarm[ELEC]; + temp3 = temp2 * eLateral; /* Vdrift / Vwarm */ + temp4 = temp3 * temp3 * temp3; + temp5 = 1.0 + temp1 * temp4; + temp6 = 1.0 / (1.0 + temp3 * temp4); + mu *= temp5 * temp6; + dMuDEl = - sgnL * mu * temp2 * + (4.0 * temp4 * temp6 - 3.0 * temp1 * temp3 * temp3 / temp5 ); + + /* + dMuDEl = 0.0; + */ + break; + case SG: + default: + temp1 = mu / info->vSat[ELEC]; + temp2 = temp1 * eLateral;/* Vdrift / Vsat */ + temp3 = mu / info->vWarm[ELEC]; + temp4 = temp3 * eLateral;/* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_N); + temp6 = 1.0 / (1.0 + temp4 * temp5 + temp2 * temp2); + mu *= sqrt(temp6); + dMuDEl = -sgnL * 0.5 * mu * temp6 * + (temp5 * (2.0 - temp5) * temp3 + (2.0 * temp2 * temp1)); + + + break; + } + } + else + { /* Hole Mobility */ + switch (info->fieldModel) + { + case CT: + case AR: + case UF: + temp1 = mu / info->vSat[HOLE]; + temp2 = temp1 * eLateral; + temp3 = 1.0 / (1.0 + temp2); + mu *= temp3; + dMuDEl = -sgnL * mu * temp3 * temp1; + + break; + case GA: + temp1 = info->vSat[HOLE] / info->vWarm[HOLE]; /* Vsat / Vwarm */ + temp2 = mu / info->vWarm[HOLE]; + temp3 = temp2 * eLateral; /* Vdrift / Vwarm */ + temp4 = temp3 * temp3 * temp3; + temp5 = 1.0 + temp1 * temp4; + temp6 = 1.0 / (1.0 + temp3 * temp4); + mu *= temp5 * temp6; + dMuDEl = - sgnL * mu * temp2 * + (4.0 * temp4 * temp6 - 3.0 * temp1 * temp3 * temp3 / temp5 ); + + + /* + dMuDEl = 0.0; + */ + break; + case SG: + default: + temp1 = mu / info->vSat[HOLE]; + temp2 = temp1 * eLateral;/* Vdrift / Vsat */ + temp3 = mu / info->vWarm[HOLE]; + temp4 = temp3 * eLateral;/* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_P); + temp6 = 1.0 / (1.0 + temp4 * temp5 + temp2 * temp2); + mu *= sqrt(temp6); + dMuDEl = -sgnL * 0.5 * mu * temp6 * + (temp5 * (2.0 - temp5) * temp3 + (2.0 * temp2 * temp1)); + + + break; + } + } + + *pMu = mu; + *pDMu = dMuDEl; + + return; +} diff --git a/src/ciderlib/support/readme b/src/ciderlib/support/readme new file mode 100644 index 000000000..9bdf2f1d1 --- /dev/null +++ b/src/ciderlib/support/readme @@ -0,0 +1,9 @@ +Directory: support +------------------ +This directory contains the files that support the device simulators and +their interfaces to the circuit simulators. They fall into four basic +areas: + 1. Numerical algorithms: integration, accuracy limits, special functions + 2. Semiconductor device physics: material properties, recombination, mobility + 3. File access for input: doping profiles from SUPREMIII, saved states + 4. Miscellaneous: diagnstic I/O, string routines diff --git a/src/ciderlib/support/recomb.c b/src/ciderlib/support/recomb.c new file mode 100644 index 000000000..0db0d8903 --- /dev/null +++ b/src/ciderlib/support/recomb.c @@ -0,0 +1,50 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" + +/* + * function recomb calculates the recobination rates and the + * derivatives with respect to n and p. SRH recombination is + * always assumed. Auger recombination is specified by the flag + * 'Auger' which by default is false. + */ + +void +recomb (double ni, double pi, double tn, double tp, double cn, double cp, + double nie, double *pUnet, double *pDuDn, double *pDuDp) +{ + double uSrh, uSrhNum, uSrhDen, perUdenSq, duSrhDn, duSrhDp; + double cncp, uAug, duAugDn, duAugDp, uNet, duDn, duDp; + + uSrhNum = ni * pi - nie * nie; + uSrhDen = tp * (ni + nie) + tn * (pi + nie); + uSrh = uSrhNum / uSrhDen; + perUdenSq = 1.0 / (uSrhDen * uSrhDen); + duSrhDn = (pi * uSrhDen - uSrhNum * tp) * perUdenSq; + duSrhDp = (ni * uSrhDen - uSrhNum * tn) * perUdenSq; + + + if (Auger && uSrhNum >= 0.0) { /* XXX Auger Global */ + cncp = cn * ni + cp * pi; + uAug = cncp * uSrhNum; + duAugDn = cn * uSrhNum + cncp * pi; + duAugDp = cp * uSrhNum + cncp * ni; + uNet = uSrh + uAug; + duDn = duSrhDn + duAugDn; + duDp = duSrhDp + duAugDp; + } + else { + uNet = uSrh; + duDn = duSrhDn; + duDp = duSrhDp; + } + +/* return uNet duDn duDp */ + *pUnet = uNet; + *pDuDn = duDn; + *pDuDp = duDp; +} diff --git a/src/ciderlib/support/suprem.c b/src/ciderlib/support/suprem.c new file mode 100644 index 000000000..cf7704382 --- /dev/null +++ b/src/ciderlib/support/suprem.c @@ -0,0 +1,210 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* Functions to read SUPREM (Binary or Ascii) & ASCII input files */ + +#include "ngspice.h" +#include "profile.h" + +void +readAsciiData( char *fileName, int impType, DOPtable **ppTable ) +{ + FILE *fpAscii; + int index, i; + double x, y; + int numPoints; + DOPtable *tmpTable; + double **profileData; + double sign; + + /* Open Input File */ + if (!(fpAscii = fopen( fileName, "r" ))) { + perror( fileName ); + exit(-1); + } + + /* Get sign of concentrations */ + if (impType == IMP_P_TYPE) { + sign = -1.0; + } else { + sign = 1.0; + } + + /* read the number of points */ + fscanf( fpAscii, "%d", &numPoints ); + + /* allocate 2-D array to read in data of x-coordinate and N(x) */ + XCALLOC( profileData, double *, 2 ); + for( index = 0; index <= 1; index++ ) { + XCALLOC( profileData[ index ], double, 1 + numPoints ); + } + /* the number of points is stored as profileData[0][0] */ + profileData[0][0] = numPoints; + + for( index = 1; index <= numPoints; index++ ) { + fscanf( fpAscii, "%f %f ", &x, &y ); + profileData[ 0 ][ index ] = x; + profileData[ 1 ][ index ] = sign * ABS(y); + } + + /* Now create a new lookup table */ + XCALLOC( tmpTable, DOPtable, 1 ); + if ( *ppTable == NIL(DOPtable) ) { + /* First Entry */ + tmpTable->impId = 1; + tmpTable->dopData = profileData; + tmpTable->next = NIL(DOPtable); + *ppTable = tmpTable; + } else { + tmpTable->impId = (*ppTable)->impId + 1; + tmpTable->dopData = profileData; + tmpTable->next = *ppTable; + *ppTable = tmpTable; + } + + /* for debugging print the data that has been just read */ + /* + for( index = 1; index <= numPoints; index++ ) { + printf("\n %e %e", profileData[ 0 ][ index ], profileData[ 1 ][ index ]); + } + */ + fclose(fpAscii); + return; +} + +/* interface routine based on notes provided by Steve Hansen of Stanford */ + +/* + * The material types are: + * 1 = single crystal silicon + * 2 = silicon dioxide + * 3 = poly-crystalline silicon + * 4 = silicon nitride + * 5 = aluminum + + * The impurity types are: + * 1 = boron + * 2 = phosphorus + * 3 = arsenic + * 4 = antimony + + * The crystalline orientations are: + * 1 = <111> + * 2 = <100> + * 3 = <110> + + * The layer thinkness, poly-crystalline grain size, node spacing and + * distance from the surface are all in microns. + + * The integrated dopant concentration and the phophorus implant dose are + * in atoms per square centimeter. + + * The interior polycrystalline grain concentration and the impurity + * concentrations at each node are in atoms per cubic centimeter. + */ + + +void +readSupremData( fileName, fileType, impType, ppTable ) +char *fileName; +int fileType; +int impType; +DOPtable **ppTable; +{ +#define MAX_GRID 500 + float x[ MAX_GRID ], conc[ MAX_GRID ]; + + int index, i, j; + DOPtable *tmpTable; + double **profileData; + int numNodes; + char *colon, *impName; + + /* read the Suprem data file */ + if ( fileType == 0 ) { /* BINARY FILE */ + SUPbinRead( fileName, x, conc, &impType, &numNodes ); + } + else { + SUPascRead( fileName, x, conc, &impType, &numNodes ); + } + + /* allocate 2-D array to read in data of x-coordinate and N(x) */ + XCALLOC( profileData, double *, 2 ); + for( index = 0; index <= 1; index++ ) { + XCALLOC( profileData[ index ], double, 1 + numNodes ); + } + /* the number of points is stored as profileData[0][0] */ + profileData[0][0] = numNodes; + + for( index = 1; index <= numNodes; index++ ) { + profileData[ 0 ][ index ] = x[ index ]; + profileData[ 1 ][ index ] = conc[ index ]; + } + + /* Now create a new lookup table */ + XCALLOC( tmpTable, DOPtable, 1 ); + if ( *ppTable == NIL(DOPtable) ) { + /* First Entry */ + tmpTable->impId = 1; + tmpTable->dopData = profileData; + tmpTable->next = NIL(DOPtable); + *ppTable = tmpTable; + } else { + tmpTable->impId = (*ppTable)->impId + 1; + tmpTable->dopData = profileData; + tmpTable->next = *ppTable; + *ppTable = tmpTable; + } + + /* for debugging print the data that has been just read */ + /* + for( index = 1; index <= numNodes; index++ ) { + printf("%e %e\n", profileData[ 0 ][ index ], profileData[ 1 ][ index ]); + } + */ + return; +} + + +/* main program to debug readSupremData */ + +/* +main(ac, av) +char **av; +{ + void readSupremData(); + DOPtable *supTable = NIL(DOPtable); + double **supInput; + int numPoints, index; + char *impName; + int impType; + + switch (ac) { + case 1: + printf( "Usage: %s suprem-file ...\n", av[0] ); + exit(-1); + break; + default: + break; + } + + for ( index = 1; index < ac; index++ ) { + for ( impType=1; impType <= 4; impType++ ) { + readSupremData( av[index], 1, impType, &supTable ); + } + } + for ( ; supTable ISNOT NIL(DOPtable); supTable = supTable->next ) { + fprintf( stdout, "\"Impurity Number: %d\n", supTable->impId ); + supInput = supTable->dopData; + numPoints = supInput[0][0]; + for( index = 1; index <= numPoints; index++ ) { + printf("%e\t%e\n", + supInput[ 0 ][ index ], ABS(supInput[ 1 ][ index ]) + 1e-20 ); + } + fprintf( stdout, "\n" ); + } +} +*/ diff --git a/src/ciderlib/support/suprmitf.c b/src/ciderlib/support/suprmitf.c new file mode 100644 index 000000000..1b1aa32fe --- /dev/null +++ b/src/ciderlib/support/suprmitf.c @@ -0,0 +1,357 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Translated FORTRAN subroutine to read the SUPREM-3 binary file + * the data is read and stored in two arrays x and conc + */ + + +#include "ngspice.h" + +#define MAXMAT 10 +#define MAXIMP 4 +#define MAXLAYER 10 +#define MAXGRID 500 + +#define GFREAD( fp, ptr, type, num ) if (num && (fread( ptr,\ + sizeof(type), (unsigned)num, fp ) != (unsigned)num)) {\ + return;\ + } + +#define DEBUG if (0) + +void +SUPbinRead( inFile, x, conc, impId, numNod ) +char *inFile; +float *x, *conc; +int *impId, *numNod; +{ + int idata, recordMark; + int ldata; + int i, j; + float rdata; + char cdata[21]; + int numLay, numImp, numGrid; + int impTyp[4], matTyp[10], topNod[10], siIndex, offset; + float xStart; + float layerTh[10]; + float con[500]; + FILE *fpSuprem; + + /* Clear Concentration Array */ + for ( i=0; i < MAXGRID; i++ ) { + conc[i] = 0.0; + } + + /* Open Input File */ + if (!(fpSuprem = fopen( inFile, "r" ))) { + perror(inFile); + return; + } + +/* + * The first record contains the number of layers (I4), the number of + * impurities (I4), and the number of nodes (I4) present in the structure. + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + GFREAD( fpSuprem, &numLay, int, 1 ); + GFREAD( fpSuprem, &numImp, int, 1 ); + GFREAD( fpSuprem, &numGrid, int, 1 ); + DEBUG fprintf(stderr,"rec 1: %d %d %d\n", numLay, numImp, numGrid); + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The second record contains, for each layer, the material type (I4), the + * layer thickness (R4), and the pointer to the top node of the layer (I4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numLay; i++ ) { + GFREAD( fpSuprem, &matTyp[i], int, 1 ); + GFREAD( fpSuprem, &layerTh[i], float, 1 ); + GFREAD( fpSuprem, &topNod[i], int, 1 ); + DEBUG fprintf(stderr,"rec 2: %d %f %d\n", matTyp[i], layerTh[i], topNod[i] ); + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The third record contains, for each layer, the material name (A20). + */ + /* Put a null at the end */ + cdata[20] = '\0'; + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numLay; i++ ) { + GFREAD( fpSuprem, cdata, char, 20 ); + DEBUG fprintf(stderr,"rec 3: %s\n", cdata ); + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The fourth record contains, for each layer, the crystalline orientation + * (I4), and the poly-crystalline grain size in microns (R4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numLay; i++ ) { + GFREAD( fpSuprem, &idata, int, 1 ); + GFREAD( fpSuprem, &rdata, float, 1 ); + DEBUG fprintf(stderr,"rec 4: %d %f\n", idata, rdata ); + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The fifth record contains, for each impurity, the type of impurity (I4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numImp; i++ ) { + GFREAD( fpSuprem, &impTyp[i], int, 1 ); + DEBUG fprintf(stderr,"rec 5: %d\n", impTyp[i] ); + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The sixth record contains, for each impurity, the impurity name (A20). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numImp; i++ ) { + GFREAD( fpSuprem, cdata, char, 20 ); + DEBUG fprintf(stderr,"rec 6: %s\n", cdata ); + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The seventh record contains, for each layer by each impurity, the + * integrated dopant (R4), and the interior concentration of the + * polysilicon grains (R4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( j=0; j < numLay; j++ ) { + for ( i=0; i < numImp; i++ ) { + GFREAD( fpSuprem, &rdata, float, 1 ); + DEBUG fprintf(stderr,"rec 7: %e", rdata ); + GFREAD( fpSuprem, &rdata, float, 1 ); + DEBUG fprintf(stderr," %e\n", rdata ); + } + } + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The eighth record contains, for each node in the structure, the distance + * to the next deepest node (R4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + for ( i=0; i < numGrid; i++ ) { + GFREAD( fpSuprem, &rdata, float, 1 ); + } + DEBUG fprintf(stderr,"rec 8: %f\n", rdata ); + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * The ninth record contains, for each node in the structure, the distance + * from the surface (R4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + GFREAD( fpSuprem, &x[1], float, numGrid ); + DEBUG fprintf(stderr,"rec 9: %f\n", x[1] ); + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* + * Next, for each impurity there is a record containing the impurity's + * chemical concentration at each node (R4) and a record containing the + * impurity's active concentration at each node (R4). + */ + + for ( j=0; j < numImp; j++ ) { +/* chemical concentration - not required */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + GFREAD( fpSuprem, &con[1], float, numGrid ); + DEBUG fprintf(stderr,"rec 10: %e\n", con[1] ); + GFREAD( fpSuprem, &recordMark, int, 1 ); + +/* store active concentration */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + GFREAD( fpSuprem, &con[1], float, numGrid ); + DEBUG fprintf(stderr,"rec 11: %e\n", con[1] ); + GFREAD( fpSuprem, &recordMark, int, 1 ); + + if (impTyp[j] == *impId) { +/*...Boron...*/ + if (impTyp[j] == 1) { + for ( i=1; i <= numGrid; i++ ) conc[i] = - con[i]; + } else { +/*...All Other Impurities: P, As, Sb ...*/ + for ( i=1; i <= numGrid; i++ ) conc[i] = con[i]; + } + } + } + +/* + * The last record in the file contains some random stuff that might be + * useful to some people, the temperature in degrees Kelvin of the last + * diffusion step (R4), the phosphorus implant dose (R4), the arsenic + * implant flag (L4). + */ + GFREAD( fpSuprem, &recordMark, int, 1 ); + GFREAD( fpSuprem, &rdata, float, 1 ); + DEBUG fprintf(stderr,"rec 12: %f", rdata ); + GFREAD( fpSuprem, &rdata, float, 1 ); + DEBUG fprintf(stderr," %e", rdata ); + GFREAD( fpSuprem, &ldata, int, 1 ); + DEBUG fprintf(stderr," %d\n", ldata ); + GFREAD( fpSuprem, &recordMark, int, 1 ); + + fclose( fpSuprem ); + +/* shift silicon layer to beginning of array */ + for ( j=0; j < numLay; j++ ) { + if (matTyp[ j ] == 1) { + siIndex = j; + } + } + offset = topNod[ siIndex ] - 1; + numGrid -= offset; + xStart = x[1 + offset]; + for ( i=1; i <= numGrid; i++ ) { + x[i] = x[i + offset] - xStart; + conc[i] = conc[i + offset]; + } + +/* Store number of valid nodes using pointer */ + *numNod = numGrid; + return; +} + +void +SUPascRead( inFile, x, conc, impId, numNod ) +char *inFile; +float *x, *conc; +int *impId, *numNod; +{ + int idata; + int ldata; + int i, j; + float rdata; + char cdata[21]; + int numLay, numImp, numGrid; + int impTyp[4], matTyp[10], topNod[10], siIndex, offset; + float xStart; + float layerTh[10]; + float con[500]; + FILE *fpSuprem; + + /* Clear Concentration Array */ + for ( i=0; i < MAXGRID; i++ ) { + conc[i] = 0.0; + } + + /* Open Input File */ + if (!(fpSuprem = fopen( inFile, "r" ))) { + perror(inFile); + return; + } + +/* + * The first line contains the number of layers (I4), the number of + * impurities (I4), and the number of nodes (I4) present in the structure. + */ + fscanf( fpSuprem, "%d %d %d\n", &numLay, &numImp, &numGrid ); + DEBUG fprintf( stderr, "set 1: %d %d %d\n", numLay, numImp, numGrid); + +/* + * The second set of lines contains, for each layer, the material name (A20), + * the material type (I4), the layer thickness (R4), and the pointer to + * the top node of the layer (I4), and an unknown int and unknown float. + * The material type code: + * 1 - Si + * 2 - SiO2 + * 3 - Poly + * 4 - Si3N4 + * 5 - Alum + */ + for ( i=0; i < numLay; i++ ) { + fscanf( fpSuprem, "%s\n %d %e %d %d %e\n", + cdata, &matTyp[i], &layerTh[i], &topNod[i], &idata, &rdata ); + DEBUG fprintf(stderr,"set 2: %s: %d %f %d\n", + cdata, matTyp[i], layerTh[i], topNod[i] ); + } + +/* + * The third set of lines contains, for each impurity, the name of the + * impurity (A20) and the type of impurity (I4). + */ + for ( i=0; i < numImp; i++ ) { + fscanf( fpSuprem, "%s\n %d\n", cdata, &impTyp[i] ); + DEBUG fprintf(stderr,"set 3: %s: %d\n", cdata, impTyp[i] ); + } + +/* + * The fourth set of lines contains, for each layer by each impurity, the + * integrated dopant (R4), and the interior concentration of the + * polysilicon grains (R4). + */ + for ( j=0; j < numLay; j++ ) { + for ( i=0; i < numImp; i++ ) { + fscanf( fpSuprem, "%e", &rdata ); + fscanf( fpSuprem, "%e", &rdata ); + } + } + DEBUG fprintf(stderr,"set 4:\n" ); + +/* + * The fifth set of lines contains, for each node in the structure, + * the distance to the next deepest node (R4), the distance from the + * surface (R4), and, for each impurity type, the impurity's + * chemical concentration (R4) and the impurity's active concentration (R4). + */ + for ( i=1; i <= numGrid; i++ ) { + fscanf( fpSuprem, "%e %e", &rdata, &x[i] ); + + for ( j=0; j < numImp; j++ ) { +/* chemical concentration - not required */ + fscanf( fpSuprem, "%e", &con[i] ); + +/* store active concentration */ + fscanf( fpSuprem, "%e", &con[i] ); + +/* orient sign properly */ + if (impTyp[j] == *impId) { +/*...Boron...*/ + if (impTyp[j] == 1) { + conc[i] = - con[i]; + } else { +/*...All Other Impurities: P, As, Sb ...*/ + conc[i] = con[i]; + } + } + } + } + DEBUG fprintf( stderr, "set 5: %e %e\n", x[1], conc[1] ); + +/* + * The last line in the file contains some random stuff that might be + * useful to some people, the temperature in degrees Kelvin of the last + * diffusion step (R4), the phosphorus implant dose (R4), the arsenic + * implant flag (L4). However, we can just ignore that stuff. + */ + fclose( fpSuprem ); + + +/* shift silicon layer to beginning of array */ + for ( j=0; j < numLay; j++ ) { + if (matTyp[ j ] == 1) { + siIndex = j; + } + } + offset = topNod[ siIndex ] - 1; + numGrid -= offset; + xStart = x[1 + offset]; + for ( i=1; i <= numGrid; i++ ) { + x[i] = x[i + offset] - xStart; + conc[i] = conc[i + offset]; + } + +/* Store number of valid nodes using pointer */ + *numNod = numGrid; + return; +} diff --git a/src/ciderlib/twod/Makefile.am b/src/ciderlib/twod/Makefile.am new file mode 100644 index 000000000..b52c663f0 --- /dev/null +++ b/src/ciderlib/twod/Makefile.am @@ -0,0 +1,31 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libcidertwod.a + +libcidertwod_a_SOURCES = \ + twoadmit.c \ + twoaval.c \ + twocond.c \ + twocont.c \ + twocurr.c \ + twodest.c \ + twodopng.c \ + twoelect.c \ + twofield.c \ + twomesh.c \ + twomobdv.c \ + twomobfn.c \ + twomobil.c \ + twoncont.c \ + twopcont.c \ + twopoiss.c \ + twoprint.c \ + twoproj.c \ + tworead.c \ + twosetbc.c \ + twosetup.c \ + twosolve.c + +EXTRA_DIST = readme +INCLUDES = -I$(top_srcdir)/src/include +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/ciderlib/twod/readme b/src/ciderlib/twod/readme new file mode 100644 index 000000000..e643fbf01 --- /dev/null +++ b/src/ciderlib/twod/readme @@ -0,0 +1,15 @@ +Directory: twod +--------------- +This directory contains the files that are primarily responsible for +implementing the 2D device simulator. It also contains files that help +interface the circuit simulator to the device simulator. Most functions +that are common to all 2D device simulations start with the prefix TWO, +e.g. TWObiasSolve. The device-specific routines start with either NUMD2, +NBJT2 or NUMOS, e.g. NUMD2admittance, NBJTproject or NUMOSconductance. The +simulator contains a Poisson Solver for equilibrium, and a Two-carrier +solver and One-carrier solvers for bias solutions. An attempt has been +made to keep the function names parallel in the four portions. Poisson +routines are identified with a 'Q' (for charge only) after the TWO, Full +solver routines are identified with an underscore '_', +Electron-current-only routines are identified with an 'N', and +Hole-current-only routines are identified with a 'P'. diff --git a/src/ciderlib/twod/twoadmit.c b/src/ciderlib/twod/twoadmit.c new file mode 100644 index 000000000..f1aa8a297 --- /dev/null +++ b/src/ciderlib/twod/twoadmit.c @@ -0,0 +1,1316 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +/* Functions to compute the ac admittances of a device. */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "numconst.h" +#include "twodev.h" +#include "twomesh.h" +#include "complex.h" +#include "spMatrix.h" +#include "bool.h" +#include "macros.h" +#include "ifsim.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" + +extern IFfrontEnd *SPfrontEnd; + +/* + * mmhhh this may cause troubles + * Paolo Nenzi 2002 + */ + SPcomplex yTotal; + +int +NUMD2admittance(TWOdevice *pDevice, double omega, SPcomplex *yd) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double dxdy; + double *solnReal, *solnImag; + double *rhsReal, *rhsImag; + SPcomplex yAc, cOmega, *y; + BOOLEAN deltaVContact = FALSE; + BOOLEAN SORFailed; + double startTime; + + /* Each time we call this counts as one AC iteration. */ + pDevice->pStats->numIters[STAT_AC] += 1; + + /* + * change context names of solution vectors for ac analysis dcDeltaSolution + * stores the real part and copiedSolution stores the imaginary part of the + * ac solution vector + */ + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + omega *= TNorm; + CMPLX_ASSIGN_VALUE(cOmega, 0.0, omega); + + if ((AcAnalysisMethod == SOR) || (AcAnalysisMethod == SOR_ONLY)) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* zero the rhsImag */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* store the new rhs vector */ + storeNewRhs(pDevice, pDevice->pLastContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yd, 0.0, 0.0); + return (AcAnalysisMethod); + } + } + if (AcAnalysisMethod == DIRECT) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + storeNewRhs(pDevice, pDevice->pLastContact); + + spSetComplex(pDevice->matrix); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } else if (OneCarrier == N_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + } else if (OneCarrier == P_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } + } + } + } + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + spFactor(pDevice->matrix); + pDevice->pStats->factorTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + } + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDevice->pFirstContact, deltaVContact, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc, -y->real, -y->imag); + CMPLX_ASSIGN(*yd, yAc); + CMPLX_MULT_SELF_SCALAR(*yd, GNorm * pDevice->width * LNorm); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + return (AcAnalysisMethod); +} + + +int +NBJT2admittance(TWOdevice *pDevice, double omega, SPcomplex *yIeVce, + SPcomplex *yIcVce, SPcomplex *yIeVbe, SPcomplex *yIcVbe) +{ + TWOcontact *pEmitContact = pDevice->pLastContact; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double width = pDevice->width; + double dxdy; + double *solnReal, *solnImag; + double *rhsReal, *rhsImag; + BOOLEAN SORFailed; + SPcomplex *y; + SPcomplex pIeVce, pIcVce, pIeVbe, pIcVbe; + SPcomplex cOmega; + double startTime; + + /* Each time we call this counts as one AC iteration. */ + pDevice->pStats->numIters[STAT_AC] += 1; + + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + omega *= TNorm; + CMPLX_ASSIGN_VALUE(cOmega, 0.0, omega); + + if ((AcAnalysisMethod == SOR) || (AcAnalysisMethod == SOR_ONLY)) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pColContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yIeVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIeVbe, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVbe, 0.0, 0.0); + return (AcAnalysisMethod); + } else { + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pEmitContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, y->real, y->imag); + y = contactAdmittance(pDevice, pColContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* load in the base contribution to the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pBaseContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(*yIeVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVce, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIeVbe, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(*yIcVbe, 0.0, 0.0); + return (AcAnalysisMethod); + } + } + } + if (AcAnalysisMethod == DIRECT) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + storeNewRhs(pDevice, pColContact); + spSetComplex(pDevice->matrix); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } else if (OneCarrier == N_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + } else if (OneCarrier == P_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } + } + } + } + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + spFactor(pDevice->matrix); + pDevice->pStats->factorTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pEmitContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, y->real, y->imag); + y = contactAdmittance(pDevice, pColContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pBaseContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR: already done, no need to repeat. */ + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + } + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pEmitContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVbe, y->real, y->imag); + y = contactAdmittance(pDevice, pColContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVbe, y->real, y->imag); + + CMPLX_ASSIGN(*yIeVce, pIeVce); + CMPLX_ASSIGN(*yIeVbe, pIeVbe); + CMPLX_ASSIGN(*yIcVce, pIcVce); + CMPLX_ASSIGN(*yIcVbe, pIcVbe); + CMPLX_MULT_SELF_SCALAR(*yIeVce, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIeVbe, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIcVce, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIcVbe, GNorm * width * LNorm); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + return (AcAnalysisMethod); +} + +int +NUMOSadmittance(TWOdevice *pDevice, double omega, struct mosAdmittances *yAc) +{ + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + TWOcontact *pBContact = pDevice->pLastContact; + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double width = pDevice->width; + double dxdy; + double *solnReal, *solnImag; + double *rhsReal, *rhsImag; + BOOLEAN SORFailed; + SPcomplex *y, cOmega; + double startTime; + + /* Each time we call this counts as one AC iteration. */ + pDevice->pStats->numIters[STAT_AC] += 1; + + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + omega *= TNorm; + CMPLX_ASSIGN_VALUE(cOmega, 0.0, omega); + + if ((AcAnalysisMethod == SOR) || (AcAnalysisMethod == SOR_ONLY)) { + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* zero the rhs before loading in the new rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pDContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVgb, 0.0, 0.0); + return (AcAnalysisMethod); + } else { + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* load in the source contribution to the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pSContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVgb, 0.0, 0.0); + return (AcAnalysisMethod); + } else { + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + /* load in the gate contribution to the rhs */ + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pGContact); + pDevice->pStats->loadTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + SORFailed = TWOsorSolve(pDevice, solnReal, solnImag, omega); + pDevice->pStats->solveTime[STAT_AC] += + SPfrontEnd->IFseconds() - startTime; + if (SORFailed && AcAnalysisMethod == SOR) { + AcAnalysisMethod = DIRECT; + printf("SOR failed at %g Hz, switching to direct-method ac analysis.\n", + omega / (TWO_PI * TNorm) ); + } else if (SORFailed) { /* Told to only do SOR, so give up. */ + printf("SOR failed at %g Hz, returning null admittance.\n", + omega / (TWO_PI * TNorm) ); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIdVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIsVgb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, 0.0, 0.0); + CMPLX_ASSIGN_VALUE(yAc->yIgVgb, 0.0, 0.0); + return (AcAnalysisMethod); + } + } + } + } + if (AcAnalysisMethod == DIRECT) { + /* solve the system of equations directly */ + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pDContact); + + /* Need to load & factor jacobian once. */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + spSetComplex(pDevice->matrix); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } else if (OneCarrier == N_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fNN, 0.0, -dxdy * omega); + } else if (OneCarrier == P_TYPE) { + spADD_COMPLEX_ELEMENT(pNode->fPP, 0.0, dxdy * omega); + } + } + } + } + } + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + spFactor(pDevice->matrix); + pDevice->pStats->factorTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pSContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR: already done, no need to repeat. */ + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, y->real, y->imag); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pGContact); + pDevice->pStats->loadTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + /* FACTOR: already done, no need to repeat. */ + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + pDevice->pStats->solveTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + } + /* MISC */ + startTime = SPfrontEnd->IFseconds(); + y = contactAdmittance(pDevice, pDContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVgb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVgb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVgb, y->real, y->imag); + + CMPLX_MULT_SELF_SCALAR(yAc->yIdVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIdVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIdVgb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVgb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVgb, GNorm * width * LNorm); + pDevice->pStats->miscTime[STAT_AC] += SPfrontEnd->IFseconds() - startTime; + + return (AcAnalysisMethod); +} + +BOOLEAN +TWOsorSolve(TWOdevice *pDevice, double *xReal, double *xImag, + double omega) +{ + double dxdy; + double wRelax = 1.0; /* SOR relaxation parameter */ + double *rhsReal = pDevice->rhs; + double *rhsSOR = pDevice->rhsImag; + BOOLEAN SORConverged = FALSE; + BOOLEAN SORFailed = FALSE; + int numEqns = pDevice->numEqns; + int iterationNum; + int indexN, indexP; + int index, eIndex; + TWOnode *pNode; + TWOelem *pElem; + + /* clear xReal and xImag arrays */ + for (index = 1; index <= numEqns; index++) { + xReal[index] = 0.0; + xImag[index] = 0.0; + } + + iterationNum = 1; + for (; (!SORConverged) &&(!SORFailed); iterationNum++) { + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = 0.0; + } + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if ((pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON)) { + if (!OneCarrier) { + indexN = pNode->nEqn; + indexP = pNode->pEqn; + rhsSOR[indexN] -= dxdy * omega * xImag[indexN]; + rhsSOR[indexP] += dxdy * omega * xImag[indexP]; + } else if (OneCarrier == N_TYPE) { + indexN = pNode->nEqn; + rhsSOR[indexN] -= dxdy * omega * xImag[indexN]; + } else if (OneCarrier == P_TYPE) { + indexP = pNode->pEqn; + rhsSOR[indexP] += dxdy * omega * xImag[indexP]; + } + } + } + } + + /* now add the terms from rhs to rhsImag */ + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] += rhsReal[index]; + } + + /* compute xReal(k+1). solution stored in rhsImag */ + spSolve(pDevice->matrix, rhsSOR, rhsSOR, NIL(spREAL), NIL(spREAL)); + /* modify solution when wRelax is not 1 */ + if (wRelax != 1) { + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = (1 - wRelax) * xReal[index] + + wRelax * rhsSOR[index]; + } + } + if (iterationNum > 1) { + SORConverged = hasSORConverged(xReal, rhsSOR, numEqns); + } + /* copy real solution into xReal */ + for (index = 1; index <= numEqns; index++) { + xReal[index] = rhsSOR[index]; + } + + /* now compute the imaginary part of the solution xImag */ + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = 0.0; + } + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if ((pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON)) { + if (!OneCarrier) { + indexN = pNode->nEqn; + indexP = pNode->pEqn; + rhsSOR[indexN] += dxdy * omega * xReal[indexN]; + rhsSOR[indexP] -= dxdy * omega * xReal[indexP]; + } else if (OneCarrier == N_TYPE) { + indexN = pNode->nEqn; + rhsSOR[indexN] += dxdy * omega * xReal[indexN]; + } else if (OneCarrier == P_TYPE) { + indexP = pNode->pEqn; + rhsSOR[indexP] -= dxdy * omega * xReal[indexP]; + } + } + } + } + /* compute xImag(k+1) */ + spSolve(pDevice->matrix, rhsSOR, rhsSOR, NIL(spREAL), NIL(spREAL)); + /* modify solution when wRelax is not 1 */ + if (wRelax != 1) { + for (index = 1; index <= numEqns; index++) { + rhsSOR[index] = (1 - wRelax) * xImag[index] + + wRelax * rhsSOR[index]; + } + } + if (iterationNum > 1) { + SORConverged = SORConverged && hasSORConverged(xImag, rhsSOR, numEqns); + } + /* copy imag solution into xImag */ + for (index = 1; index <= numEqns; index++) { + xImag[index] = rhsSOR[index]; + } + if ((iterationNum > 4) && !SORConverged) { + SORFailed = TRUE; + } + if (TWOacDebug) + printf("SOR iteration number = %d\n", iterationNum); + } + return (SORFailed); +} + + +SPcomplex * +contactAdmittance(TWOdevice *pDevice, TWOcontact *pContact, BOOLEAN delVContact, + double *xReal, double *xImag, SPcomplex *cOmega) +{ + TWOnode *pNode, *pHNode = NULL, *pVNode = NULL; + TWOedge *pHEdge = NULL, *pVEdge = NULL; + int index, i, indexPsi, indexN, indexP, numContactNodes; + TWOelem *pElem; + SPcomplex psiAc, nAc, pAc; + SPcomplex prod1, prod2, sum; + double yReal, yImag; + double temp; + + CMPLX_ASSIGN_VALUE(yTotal, 0.0, 0.0); + + numContactNodes = pContact->numNodes; + for (index = 0; index < numContactNodes; index++) { + pNode = pContact->pNodes[index]; + for (i = 0; i <= 3; i++) { + pElem = pNode->pElems[i]; + if (pElem != NIL(TWOelem)) { + switch (i) { + case 0: + /* the TL element */ + pHNode = pElem->pBLNode; + pVNode = pElem->pTRNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + if (pElem->elemType == SEMICON) { + /* compute the derivatives with n,p */ + if (pHNode->nodeType != CONTACT) { + indexN = pHNode->nEqn; + indexP = pHNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pHEdge->dJnDn); + CMPLX_MULT_SCALAR(prod2, pAc, pHEdge->dJpDp); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dy); + CMPLX_SUBT_ASSIGN(yTotal, prod1); + } + if (pVNode->nodeType != CONTACT) { + indexN = pVNode->nEqn; + indexP = pVNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pVEdge->dJnDn); + CMPLX_MULT_SCALAR(prod2, pAc, pVEdge->dJpDp); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dx); + CMPLX_SUBT_ASSIGN(yTotal, prod1); + } + } + break; + case 1: + /* the TR element */ + pHNode = pElem->pBRNode; + pVNode = pElem->pTLNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + if (pElem->elemType == SEMICON) { + /* compute the derivatives with n,p */ + if (pHNode->nodeType != CONTACT) { + indexN = pHNode->nEqn; + indexP = pHNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pHEdge->dJnDnP1); + CMPLX_MULT_SCALAR(prod2, pAc, pHEdge->dJpDpP1); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dy); + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + if (pVNode->nodeType != CONTACT) { + indexN = pVNode->nEqn; + indexP = pVNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pVEdge->dJnDn); + CMPLX_MULT_SCALAR(prod2, pAc, pVEdge->dJpDp); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dx); + CMPLX_SUBT_ASSIGN(yTotal, prod1); + } + } + break; + case 2: + /* the BR element */ + pHNode = pElem->pTRNode; + pVNode = pElem->pBLNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + if (pElem->elemType == SEMICON) { + /* compute the derivatives with n,p */ + if (pHNode->nodeType != CONTACT) { + indexN = pHNode->nEqn; + indexP = pHNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pHEdge->dJnDnP1); + CMPLX_MULT_SCALAR(prod2, pAc, pHEdge->dJpDpP1); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dy); + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + if (pVNode->nodeType != CONTACT) { + indexN = pVNode->nEqn; + indexP = pVNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pVEdge->dJnDnP1); + CMPLX_MULT_SCALAR(prod2, pAc, pVEdge->dJpDpP1); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dx); + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + break; + case 3: + /* the BL element */ + pHNode = pElem->pTLNode; + pVNode = pElem->pBRNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + if (pElem->elemType == SEMICON) { + /* compute the derivatives with n,p */ + if (pHNode->nodeType != CONTACT) { + indexN = pHNode->nEqn; + indexP = pHNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pHEdge->dJnDn); + CMPLX_MULT_SCALAR(prod2, pAc, pHEdge->dJpDp); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dy); + CMPLX_SUBT_ASSIGN(yTotal, prod1); + } + if (pVNode->nodeType != CONTACT) { + indexN = pVNode->nEqn; + indexP = pVNode->pEqn; + CMPLX_ASSIGN_VALUE(nAc, xReal[indexN], xImag[indexN]); + CMPLX_ASSIGN_VALUE(pAc, xReal[indexP], xImag[indexP]); + CMPLX_MULT_SCALAR(prod1, nAc, pVEdge->dJnDnP1); + CMPLX_MULT_SCALAR(prod2, pAc, pVEdge->dJpDpP1); + CMPLX_ADD(sum, prod1, prod2); + CMPLX_MULT_SCALAR(prod1, sum, 0.5 * pElem->dx); + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + break; + } + if (pElem->elemType == SEMICON) { + if (pHNode->nodeType != CONTACT) { + indexPsi = pHNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + temp = 0.5 * pElem->dy * (pHEdge->dJnDpsiP1 + pHEdge->dJpDpsiP1); + CMPLX_MULT_SCALAR(prod1, psiAc, temp); + CMPLX_ADD_ASSIGN(yTotal, prod1); + if (delVContact) { + CMPLX_ADD_SELF_SCALAR(yTotal, -temp); + } + } + if (pVNode->nodeType != CONTACT) { + indexPsi = pVNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + temp = 0.5 * pElem->dx * (pVEdge->dJnDpsiP1 + pVEdge->dJpDpsiP1); + CMPLX_MULT_SCALAR(prod1, psiAc, temp); + CMPLX_ADD_ASSIGN(yTotal, prod1); + if (delVContact) { + CMPLX_ADD_SELF_SCALAR(yTotal, -temp); + } + } + } + /* displacement current terms */ + if (pHNode->nodeType != CONTACT) { + indexPsi = pHNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * 0.5 * pElem->dyOverDx); + CMPLX_MULT(prod2, prod1, psiAc); + CMPLX_SUBT_ASSIGN(yTotal, prod2); + if (delVContact) { + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + if (pVNode->nodeType != CONTACT) { + indexPsi = pVNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * 0.5 * pElem->dxOverDy); + CMPLX_MULT(prod2, prod1, psiAc); + CMPLX_SUBT_ASSIGN(yTotal, prod2); + if (delVContact) { + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + } + } + } + return (&yTotal); /* XXX */ +} + + +SPcomplex * +oxideAdmittance(TWOdevice *pDevice, TWOcontact *pContact, BOOLEAN delVContact, + double *xReal, double *xImag, SPcomplex *cOmega) +{ + TWOnode *pNode, *pHNode = NULL, *pVNode = NULL; + TWOedge *pHEdge, *pVEdge; + int index, i, indexPsi, numContactNodes; + TWOelem *pElem; + SPcomplex psiAc, nAc, pAc; + SPcomplex prod1, prod2, sum; + double yReal, yImag; + + CMPLX_ASSIGN_VALUE(yTotal, 0.0, 0.0); + + numContactNodes = pContact->numNodes; + for (index = 0; index < numContactNodes; index++) { + pNode = pContact->pNodes[index]; + for (i = 0; i <= 3; i++) { + pElem = pNode->pElems[i]; + if (pElem != NIL(TWOelem)) { + switch (i) { + case 0: + /* the TL element */ + pHNode = pElem->pBLNode; + pVNode = pElem->pTRNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + break; + case 1: + /* the TR element */ + pHNode = pElem->pBRNode; + pVNode = pElem->pTLNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + break; + case 2: + /* the BR element */ + pHNode = pElem->pTRNode; + pVNode = pElem->pBLNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + break; + case 3: + /* the BL element */ + pHNode = pElem->pTLNode; + pVNode = pElem->pBRNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + break; + } + /* displacement current terms */ + if (pHNode->nodeType != CONTACT) { + indexPsi = pHNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * 0.5 * pElem->dyOverDx); + CMPLX_MULT(prod2, prod1, psiAc); + CMPLX_SUBT_ASSIGN(yTotal, prod2); + if (delVContact) { + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + if (pVNode->nodeType != CONTACT) { + indexPsi = pVNode->psiEqn; + CMPLX_ASSIGN_VALUE(psiAc, xReal[indexPsi], xImag[indexPsi]); + CMPLX_MULT_SCALAR(prod1, *cOmega, pElem->epsRel * 0.5 * pElem->dxOverDy); + CMPLX_MULT(prod2, prod1, psiAc); + CMPLX_SUBT_ASSIGN(yTotal, prod2); + if (delVContact) { + CMPLX_ADD_ASSIGN(yTotal, prod1); + } + } + } + } + } + return (&yTotal); +} + +void +NUMD2ys(TWOdevice *pDevice, SPcomplex *s, SPcomplex *yIn) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double dxdy; + double *solnReal, *solnImag; + double *rhsReal, *rhsImag; + SPcomplex yAc, *y; + BOOLEAN deltaVContact = FALSE; + SPcomplex temp, cOmega; + + /* + * change context names of solution vectors for ac analysis dcDeltaSolution + * stores the real part and copiedSolution stores the imaginary part of the + * ac solution vector + */ + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + CMPLX_MULT_SCALAR(cOmega, *s, TNorm); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + storeNewRhs(pDevice, pDevice->pLastContact); + + spSetComplex(pDevice->matrix); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } else if (OneCarrier == N_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + } else if (OneCarrier == P_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } + } + } + } + } + + spFactor(pDevice->matrix); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + y = contactAdmittance(pDevice, pDevice->pFirstContact, deltaVContact, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc, y->real, y->imag); + CMPLX_ASSIGN(*yIn, yAc); + CMPLX_NEGATE_SELF(*yIn); + CMPLX_MULT_SELF_SCALAR(*yIn, GNorm * pDevice->width * LNorm); +} + + +void +NBJT2ys(TWOdevice *pDevice, SPcomplex *s, SPcomplex *yIeVce, SPcomplex *yIcVce, + SPcomplex *yIeVbe, SPcomplex *yIcVbe) +{ + TWOcontact *pEmitContact = pDevice->pLastContact; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double width = pDevice->width; + double dxdy; + double *solnReal, *solnImag; + double *rhsReal, *rhsImag; + SPcomplex *y; + SPcomplex pIeVce, pIcVce, pIeVbe, pIcVbe; + SPcomplex temp, cOmega; + + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + CMPLX_MULT_SCALAR(cOmega, *s, TNorm); + + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + storeNewRhs(pDevice, pColContact); + spSetComplex(pDevice->matrix); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } else if (OneCarrier == N_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + } else if (OneCarrier == P_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } + } + } + } + } + spFactor(pDevice->matrix); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + + y = contactAdmittance(pDevice, pEmitContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVce, y->real, y->imag); + y = contactAdmittance(pDevice, pColContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVce, y->real, y->imag); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pBaseContact); + /* don't need to LU factor the jacobian since it exists */ + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + y = contactAdmittance(pDevice, pEmitContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIeVbe, y->real, y->imag); + y = contactAdmittance(pDevice, pColContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(pIcVbe, y->real, y->imag); + + + CMPLX_ASSIGN(*yIeVce, pIeVce); + CMPLX_ASSIGN(*yIeVbe, pIeVbe); + CMPLX_ASSIGN(*yIcVce, pIcVce); + CMPLX_ASSIGN(*yIcVbe, pIcVbe); + CMPLX_MULT_SELF_SCALAR(*yIeVce, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIeVbe, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIcVce, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(*yIcVbe, GNorm * width * LNorm); +} + +void +NUMOSys(TWOdevice *pDevice, SPcomplex *s, struct mosAdmittances *yAc) +{ + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + TWOcontact *pBContact = pDevice->pLastContact; + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex; + double width = pDevice->width; + double dxdy; + double *rhsReal, *rhsImag; + double *solnReal, *solnImag; + SPcomplex *y; + SPcomplex temp, cOmega; + + pDevice->solverType = SLV_SMSIG; + rhsReal = pDevice->rhs; + rhsImag = pDevice->rhsImag; + solnReal = pDevice->dcDeltaSolution; + solnImag = pDevice->copiedSolution; + + /* use a normalized radian frequency */ + CMPLX_MULT_SCALAR(cOmega, *s, TNorm); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + /* solve the system of equations directly */ + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } + storeNewRhs(pDevice, pDContact); + spSetComplex(pDevice->matrix); + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + if (pElem->elemType == SEMICON) { + dxdy = 0.25 * pElem->dx * pElem->dy; + for (index = 0; index <= 3; index++) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + if (!OneCarrier) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } else if (OneCarrier == N_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fNN, -temp.real, -temp.imag); + } else if (OneCarrier == P_TYPE) { + CMPLX_MULT_SCALAR(temp, cOmega, dxdy); + spADD_COMPLEX_ELEMENT(pNode->fPP, temp.real, temp.imag); + } + } + } + } + } + + spFactor(pDevice->matrix); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + + y = contactAdmittance(pDevice, pDContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVdb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVdb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVdb, y->real, y->imag); + + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pSContact); + /* don't need to LU factor the jacobian since it exists */ + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + y = contactAdmittance(pDevice, pDContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVsb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVsb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVsb, y->real, y->imag); + for (index = 1; index <= pDevice->numEqns; index++) { + rhsImag[index] = 0.0; + } + storeNewRhs(pDevice, pGContact); + spSolve(pDevice->matrix, rhsReal, solnReal, rhsImag, solnImag); + y = contactAdmittance(pDevice, pDContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIdVgb, y->real, y->imag); + y = contactAdmittance(pDevice, pSContact, FALSE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIsVgb, y->real, y->imag); + y = GateTypeAdmittance(pDevice, pGContact, TRUE, + solnReal, solnImag, &cOmega); + CMPLX_ASSIGN_VALUE(yAc->yIgVgb, y->real, y->imag); + + CMPLX_MULT_SELF_SCALAR(yAc->yIdVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIdVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIdVgb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIsVgb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVdb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVsb, GNorm * width * LNorm); + CMPLX_MULT_SELF_SCALAR(yAc->yIgVgb, GNorm * width * LNorm); +} diff --git a/src/ciderlib/twod/twoaval.c b/src/ciderlib/twod/twoaval.c new file mode 100644 index 000000000..c372cfca3 --- /dev/null +++ b/src/ciderlib/twod/twoaval.c @@ -0,0 +1,208 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1990 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twoddefs.h" +#include "twodext.h" + + +double +TWOavalanche(TWOelem *pElem, TWOnode *pNode) +{ + + TWOelem *pElemTL, *pElemTR, *pElemBL, *pElemBR; + TWOedge *pEdgeT, *pEdgeB, *pEdgeL, *pEdgeR; + int materT = 0 , materB = 0, materL = 0 , materR = 0; + double dxL = 0.0, dxR = 0.0, dyT = 0.0, dyB = 0.0; + double ef1, ef2, coeff1, coeff2; + double enx, eny, epx, epy, jnx, jny, jpx, jpy; + double current, eField; + double generation = 0.0; + double eiip2 = 4.0e5 / ENorm; + double aiip2 = 6.71e5 * LNorm; + double biip2 = 1.693e6 / ENorm; + TWOmaterial *info = pElem->matlInfo; + + /* Find all four neighboring elements */ + pElemTL = pNode->pTLElem; + pElemTR = pNode->pTRElem; + pElemBL = pNode->pBLElem; + pElemBR = pNode->pBRElem; + + /* Null edge pointers */ + pEdgeT = pEdgeB = pEdgeL = pEdgeR = NIL(TWOedge); + + /* Find edges next to node */ + if ( pElemTL != NIL(TWOelem) ) { + if (pElemTL->evalEdges[1]) { + pEdgeT = pElemTL->pRightEdge; + materT = pElemTL->elemType; + dyT = pElemTL->dy; + } + if (pElemTL->evalEdges[2]) { + pEdgeL = pElemTL->pBotEdge; + materL = pElemTL->elemType; + dxL = pElemTL->dx; + } + } + if ( pElemTR != NIL(TWOelem) ) { + if (pElemTR->evalEdges[3]) { + pEdgeT = pElemTR->pLeftEdge; + materT = pElemTR->elemType; + dyT = pElemTR->dy; + } + if (pElemTR->evalEdges[2]) { + pEdgeR = pElemTR->pBotEdge; + materR = pElemTR->elemType; + dxR = pElemTR->dx; + } + } + if ( pElemBR != NIL(TWOelem) ) { + if (pElemBR->evalEdges[3]) { + pEdgeB = pElemBR->pLeftEdge; + materB = pElemBR->elemType; + dyB = pElemBR->dy; + } + if (pElemBR->evalEdges[0]) { + pEdgeR = pElemBR->pTopEdge; + materR = pElemBR->elemType; + dxR = pElemBR->dx; + } + } + if ( pElemBL != NIL(TWOelem) ) { + if (pElemBL->evalEdges[1]) { + pEdgeB = pElemBL->pRightEdge; + materB = pElemBL->elemType; + dyB = pElemBL->dy; + } + if (pElemBL->evalEdges[0]) { + pEdgeL = pElemBL->pTopEdge; + materL = pElemBL->elemType; + dxL = pElemBL->dx; + } + } + + /* compute horizontal vector components */ + /* No more than one of Left Edge or Right Edge is absent */ + /* If one is absent the other is guaranteed to be from silicon */ + if (pEdgeL == NIL(TWOedge)) { + if ( pNode->nodeType == CONTACT ) { + enx = -(pEdgeR->dPsi + pEdgeR->dCBand) / dxR; + epx = -(pEdgeR->dPsi - pEdgeR->dVBand) / dxR; + jnx = pEdgeR->jn; + jpx = pEdgeR->jp; + } else { + enx = 0.0; + epx = 0.0; + jnx = 0.0; + jpx = 0.0; + } + } else if (pEdgeR == NIL(TWOedge)) { + if ( pNode->nodeType == CONTACT ) { + enx = -(pEdgeL->dPsi + pEdgeL->dCBand) / dxL; + epx = -(pEdgeL->dPsi - pEdgeL->dVBand) / dxL; + jnx = pEdgeL->jn; + jpx = pEdgeL->jp; + } else { + enx = 0.0; + epx = 0.0; + jnx = 0.0; + jpx = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dxL / (dxL + dxR); + coeff2 = dxR / (dxL + dxR); + ef1 = -(pEdgeL->dPsi + pEdgeL->dCBand) / dxL; + ef2 = -(pEdgeR->dPsi + pEdgeR->dCBand) / dxR; + enx = coeff2 * ef1 + coeff1 * ef2; + ef1 = -(pEdgeL->dPsi - pEdgeL->dVBand) / dxL; + ef2 = -(pEdgeR->dPsi - pEdgeR->dVBand) / dxR; + epx = coeff2 * ef1 + coeff1 * ef2; + if ( (materL == INSULATOR) || (materR == INSULATOR) ) { + jnx = 0.0; + jpx = 0.0; + } else { + jnx = coeff2 * pEdgeL->jn + coeff1 * pEdgeR->jn; + jpx = coeff2 * pEdgeL->jp + coeff1 * pEdgeR->jp; + } + } + + /* compute vertical vector components */ + /* No more than one of Top Edge or Bottom Edge is absent */ + /* If one is absent the other is guaranteed to be from silicon */ + if (pEdgeT == NIL(TWOedge)) { + if ( pNode->nodeType == CONTACT ) { + eny = -(pEdgeB->dPsi + pEdgeB->dCBand) / dyB; + epy = -(pEdgeB->dPsi - pEdgeB->dVBand) / dyB; + jny = pEdgeB->jn; + jpy = pEdgeB->jp; + } else { + eny = 0.0; + epy = 0.0; + jny = 0.0; + jpy = 0.0; + } + } else if (pEdgeB == NIL(TWOedge)) { + if ( pNode->nodeType == CONTACT ) { + eny = -(pEdgeT->dPsi + pEdgeT->dCBand) / dyT; + epy = -(pEdgeT->dPsi - pEdgeT->dVBand) / dyT; + jny = pEdgeT->jn; + jpy = pEdgeT->jp; + } else { + eny = 0.0; + epy = 0.0; + jny = 0.0; + jpy = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dyT / (dyT + dyB); + coeff2 = dyB / (dyT + dyB); + ef1 = -(pEdgeT->dPsi + pEdgeT->dCBand) / dyT; + ef2 = -(pEdgeB->dPsi + pEdgeB->dCBand) / dyB; + eny = coeff2 * ef1 + coeff1 * ef2; + ef1 = -(pEdgeT->dPsi - pEdgeT->dVBand) / dyT; + ef2 = -(pEdgeB->dPsi - pEdgeB->dVBand) / dyB; + epy = coeff2 * ef1 + coeff1 * ef2; + if ( (materT == INSULATOR) || (materB == INSULATOR) ) { + jny = 0.0; + jpy = 0.0; + } else { + jny = coeff2 * pEdgeT->jn + coeff1 * pEdgeB->jn; + jpy = coeff2 * pEdgeT->jp + coeff1 * pEdgeB->jp; + } + } + +/* + fprintf(stderr,"%% en = (%9.2e,%9.2e), jn = (%9.2e,%9.2e)\n", + enx,eny,jnx,jny); + fprintf(stderr,"%% ep = (%9.2e,%9.2e), jp = (%9.2e,%9.2e)\n", + epx,epy,jpx,jpy); +*/ + + /* now calculate the avalanche generation rate */ + current = sqrt( jnx * jnx + jny * jny ); + if ( current != 0.0 ) { + eField = (enx * jnx + eny * jny) / current; + if ( (eField > 0) && ( info->bii[ELEC] / eField <= 80.0) ) { + generation += current * info->aii[ELEC] + * exp( - info->bii[ELEC] / eField ); + } + } + current = sqrt( jpx * jpx + jpy * jpy ); + if ( current != 0.0 ) { + eField = (epx * jpx + epy * jpy) / current; + if ( eField > eiip2 ) { + generation += current * aiip2 * exp( - biip2 / eField ); + } else if ( (eField > 0) && ( info->bii[HOLE] / eField <= 80.0) ) { + generation += current * info->aii[HOLE] + * exp( - info->bii[HOLE] / eField ); + } + } + return( generation ); +} diff --git a/src/ciderlib/twod/twocond.c b/src/ciderlib/twod/twocond.c new file mode 100644 index 000000000..a113c8db6 --- /dev/null +++ b/src/ciderlib/twod/twocond.c @@ -0,0 +1,612 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +/* Functions to compute terminal conductances & currents. */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" + + +void + NUMD2conductance(TWOdevice *pDevice, BOOLEAN tranAnalysis, + double *intCoeff, double *gd) +{ + TWOcontact *pContact = pDevice->pFirstContact; + int index; + double *incVpn; + BOOLEAN deltaVContact = FALSE; + + /* + * store the new rhs for computing the incremental quantities + * with the second to last node. solve the system of equations + */ + incVpn = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pDevice->pLastContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVpn, NIL(spREAL), NIL(spREAL)); + + incVpn = pDevice->dcDeltaSolution; + *gd = contactConductance( pDevice, pContact, deltaVContact, incVpn, + tranAnalysis, intCoeff ); + *gd *= - GNorm * pDevice->width * LNorm; +} + +void + NBJT2conductance(TWOdevice *pDevice, BOOLEAN tranAnalysis, + double *intCoeff, double *dIeDVce, double *dIcDVce, + double *dIeDVbe, double *dIcDVbe) +{ + TWOcontact *pEmitContact = pDevice->pLastContact; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + int index; + double width = pDevice->width; + double *incVce, *incVbe; + + /* + * store the new rhs for computing the incremental quantities + * incVce (dcDeltaSolution) and incVbe (copiedSolution) are used to + * store the incremental quantities associated with Vce and Vbe + */ + + incVce = pDevice->dcDeltaSolution; + incVbe = pDevice->copiedSolution; + storeNewRhs( pDevice, pColContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVce, NIL(spREAL), NIL(spREAL)); + storeNewRhs( pDevice, pBaseContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVbe, NIL(spREAL), NIL(spREAL)); + + *dIeDVce = contactConductance( pDevice, pEmitContact, FALSE, incVce, + tranAnalysis, intCoeff ); + *dIeDVbe = contactConductance( pDevice, pEmitContact, FALSE, incVbe, + tranAnalysis, intCoeff ); + + *dIcDVce = contactConductance( pDevice, pColContact, TRUE, incVce, + tranAnalysis, intCoeff ); + *dIcDVbe = contactConductance( pDevice, pColContact, FALSE, incVbe, + tranAnalysis, intCoeff ); + *dIeDVce *= GNorm * width * LNorm; + *dIcDVce *= GNorm * width * LNorm; + *dIeDVbe *= GNorm * width * LNorm; + *dIcDVbe *= GNorm * width * LNorm; +} + +void + NUMOSconductance(TWOdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + struct mosConductances *dIdV) +{ + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + TWOcontact *pBContact = pDevice->pLastContact; + int index; + double width = pDevice->width; + double *incVdb, *incVsb, *incVgb; + + /* + * store the new rhs for computing the incremental quantities + * incVdb (dcDeltaSolution) + */ + + incVdb = pDevice->dcDeltaSolution; + incVsb = pDevice->copiedSolution; + incVgb = pDevice->rhsImag; + storeNewRhs( pDevice, pDContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVdb, NIL(spREAL), NIL(spREAL)); + storeNewRhs( pDevice, pSContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVsb, NIL(spREAL), NIL(spREAL)); + storeNewRhs( pDevice, pGContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVgb, NIL(spREAL), NIL(spREAL)); + + dIdV->dIdDVdb = contactConductance( pDevice, pDContact, TRUE, + incVdb, tranAnalysis, intCoeff ); + dIdV->dIsDVdb = contactConductance( pDevice, pSContact, FALSE, + incVdb, tranAnalysis, intCoeff ); + dIdV->dIgDVdb = GateTypeConductance( pDevice, pGContact, FALSE, + incVdb, tranAnalysis, intCoeff ); + dIdV->dIdDVsb = contactConductance( pDevice, pDContact, FALSE, + incVsb, tranAnalysis, intCoeff ); + dIdV->dIsDVsb = contactConductance( pDevice, pSContact, TRUE, + incVsb, tranAnalysis, intCoeff ); + dIdV->dIgDVsb = GateTypeConductance( pDevice, pGContact, FALSE, + incVsb, tranAnalysis, intCoeff ); + dIdV->dIdDVgb = contactConductance( pDevice, pDContact, FALSE, + incVgb, tranAnalysis, intCoeff ); + dIdV->dIsDVgb = contactConductance( pDevice, pSContact, FALSE, + incVgb, tranAnalysis, intCoeff ); + dIdV->dIgDVgb = GateTypeConductance( pDevice, pGContact, TRUE, + incVgb, tranAnalysis, intCoeff ); + + dIdV->dIdDVdb *= GNorm * width * LNorm; + dIdV->dIdDVsb *= GNorm * width * LNorm; + dIdV->dIdDVgb *= GNorm * width * LNorm; + dIdV->dIsDVdb *= GNorm * width * LNorm; + dIdV->dIsDVsb *= GNorm * width * LNorm; + dIdV->dIsDVgb *= GNorm * width * LNorm; + dIdV->dIgDVdb *= GNorm * width * LNorm; + dIdV->dIgDVsb *= GNorm * width * LNorm; + dIdV->dIgDVgb *= GNorm * width * LNorm; + +} + +double + contactCurrent(TWOdevice *pDevice, TWOcontact *pContact) +{ + /* computes the current through the contact given in pContact */ + int index, i, numContactNodes; + TWOnode *pNode; + TWOelem *pElem; + TWOedge *pHEdge, *pVEdge; + double dx, dy; + double jTotal = 0.0; + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem) ) { + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + switch ( i ) { + case 0: + /* Bottom Right node */ + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + jTotal += pElem->epsRel * ( -dy * pHEdge->jd - dx * pVEdge->jd ); + if ( pElem->elemType == SEMICON ) { + jTotal += -dy * (pHEdge->jn + pHEdge->jp) + -dx * (pVEdge->jn + pVEdge->jp); + } + break; + case 1: + /* Bottom Left node */ + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + jTotal += pElem->epsRel * ( dy * pHEdge->jd - dx * pVEdge->jd ); + if ( pElem->elemType == SEMICON ) { + jTotal += dy * (pHEdge->jn + pHEdge->jp) + -dx * (pVEdge->jn + pVEdge->jp); + } + break; + case 2: + /* Top Left node */ + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + jTotal += pElem->epsRel * ( dy * pHEdge->jd + dx * pVEdge->jd ); + if ( pElem->elemType == SEMICON ) { + jTotal += dy * (pHEdge->jn + pHEdge->jp) + + dx * (pVEdge->jn + pVEdge->jp); + } + break; + case 3: + /* Top Right Node */ + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + jTotal += pElem->epsRel * ( -dy * pHEdge->jd + dx * pVEdge->jd ); + if ( pElem->elemType == SEMICON ) { + jTotal += -dy * (pHEdge->jn + pHEdge->jp) + + dx * (pVEdge->jn + pVEdge->jp); + } + break; + } + } + } + } + + return( jTotal * pDevice->width * LNorm * JNorm ); +} + +double + oxideCurrent(TWOdevice *pDevice, TWOcontact *pContact, + BOOLEAN tranAnalysis) +{ + /* computes the current through the contact given in pContact */ + int index, i, numContactNodes; + TWOnode *pNode; + TWOelem *pElem; + TWOedge *pHEdge, *pVEdge; + double dx, dy; + double jTotal = 0.0; + + if ( !tranAnalysis ) { + return( jTotal ); + } + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem) ) { + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + switch ( i ) { + case 0: + /* Bottom Right node */ + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + jTotal += pElem->epsRel * ( -dy * pHEdge->jd - dx * pVEdge->jd ); + break; + case 1: + /* Bottom Left node */ + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + jTotal += pElem->epsRel * ( dy * pHEdge->jd - dx * pVEdge->jd ); + break; + case 2: + /* Top Left node */ + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + jTotal += pElem->epsRel * ( dy * pHEdge->jd + dx * pVEdge->jd ); + break; + case 3: + /* Top Right Node */ + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + jTotal += pElem->epsRel * ( -dy * pHEdge->jd + dx * pVEdge->jd ); + break; + } + } + } + } + + return( jTotal * pDevice->width * LNorm * JNorm ); +} + + +double + contactConductance(TWOdevice *pDevice, TWOcontact *pContact, + BOOLEAN delVContact, double *dxDv, + BOOLEAN tranAnalysis, double *intCoeff) +{ + /* computes the conductance of the contact given in pContact */ + int index, i, numContactNodes; + TWOnode *pNode, *pHNode = NULL, *pVNode = NULL; + TWOelem *pElem; + TWOedge *pHEdge = NULL, *pVEdge = NULL; + double dPsiDv, dnDv, dpDv; + double gTotal = 0.0; + int nInc, pInc; + + /* for one carrier the rest of this code relies on appropriate + current derivative term to be zero */ + if ( !OneCarrier ) { + nInc = 1; + pInc = 2; + } else { + nInc = 1; + pInc = 1; + } + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem) ) { + switch ( i ) { + case 0: + /* the TL element */ + pHNode = pElem->pBLNode; + pVNode = pElem->pTRNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + if ( pElem->elemType == SEMICON ) { + /* compute the derivatives with n,p */ + if ( pHNode->nodeType != CONTACT ) { + dnDv = dxDv[ pHNode->nEqn ]; + dpDv = dxDv[ pHNode->pEqn ]; + gTotal -= 0.5 * pElem->dy * (pHEdge->dJnDn * dnDv + + pHEdge->dJpDp * dpDv); + } + if ( pVNode->nodeType != CONTACT ) { + dnDv = dxDv[ pVNode->nEqn ]; + dpDv = dxDv[ pVNode->pEqn ]; + gTotal -= 0.5 * pElem->dx * (pVEdge->dJnDn * dnDv + + pVEdge->dJpDp * dpDv); + } + } + break; + case 1: + /* the TR element */ + pHNode = pElem->pBRNode; + pVNode = pElem->pTLNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + if ( pElem->elemType == SEMICON ) { + /* compute the derivatives with n,p */ + if ( pHNode->nodeType != CONTACT ) { + dnDv = dxDv[ pHNode->nEqn ]; + dpDv = dxDv[ pHNode->pEqn ]; + gTotal += 0.5 * pElem->dy * (pHEdge->dJnDnP1 * dnDv + + pHEdge->dJpDpP1 * dpDv); + } + if ( pVNode->nodeType != CONTACT ) { + dnDv = dxDv[ pVNode->nEqn ]; + dpDv = dxDv[ pVNode->pEqn ]; + gTotal -= 0.5 * pElem->dx * (pVEdge->dJnDn * dnDv + + pVEdge->dJpDp * dpDv); + } + } + break; + case 2: + /* the BR element*/ + pHNode = pElem->pTRNode; + pVNode = pElem->pBLNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + if ( pElem->elemType == SEMICON ) { + /* compute the derivatives with n,p */ + if ( pHNode->nodeType != CONTACT ) { + dnDv = dxDv[ pHNode->nEqn ]; + dpDv = dxDv[ pHNode->pEqn ]; + gTotal += 0.5 * pElem->dy * (pHEdge->dJnDnP1 * dnDv + + pHEdge->dJpDpP1 * dpDv); + } + if ( pVNode->nodeType != CONTACT ) { + dnDv = dxDv[ pVNode->nEqn ]; + dpDv = dxDv[ pVNode->pEqn ]; + gTotal += 0.5 * pElem->dx * (pVEdge->dJnDnP1 * dnDv + + pVEdge->dJpDpP1 * dpDv); + } + } + break; + case 3: + /* the BL element */ + pHNode = pElem->pTLNode; + pVNode = pElem->pBRNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + if ( pElem->elemType == SEMICON ) { + /* compute the derivatives with n,p */ + if ( pHNode->nodeType != CONTACT ) { + dnDv = dxDv[ pHNode->nEqn ]; + dpDv = dxDv[ pHNode->pEqn ]; + gTotal -= 0.5 * pElem->dy * (pHEdge->dJnDn * dnDv + + pHEdge->dJpDp * dpDv); + } + if ( pVNode->nodeType != CONTACT ) { + dnDv = dxDv[ pVNode->nEqn ]; + dpDv = dxDv[ pVNode->pEqn ]; + gTotal += 0.5 * pElem->dx * (pVEdge->dJnDnP1 * dnDv + + pVEdge->dJpDpP1 * dpDv); + } + } + break; + } + if ( pElem->elemType == SEMICON ) { + if ( pHNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pHNode->psiEqn ]; + gTotal += 0.5 * pElem->dy * dPsiDv * (pHEdge->dJnDpsiP1 + pHEdge->dJpDpsiP1 ); + if ( delVContact ) { + gTotal -= 0.5 * pElem->dy * (pHEdge->dJnDpsiP1 + pHEdge->dJpDpsiP1 ); + } + } + if ( pVNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pVNode->psiEqn ]; + gTotal += 0.5 * pElem->dx * dPsiDv * (pVEdge->dJnDpsiP1 + pVEdge->dJpDpsiP1 ); + if ( delVContact ) { + gTotal -= 0.5 * pElem->dx * (pVEdge->dJnDpsiP1 + pVEdge->dJpDpsiP1 ); + } + } + } + if ( tranAnalysis ) { + /* add the displacement current terms */ + if ( pHNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pHNode->psiEqn ]; + gTotal -= intCoeff[0] * pElem->epsRel * 0.5 * pElem->dyOverDx * dPsiDv; + if ( delVContact ) { + gTotal += intCoeff[0] * pElem->epsRel * 0.5 * pElem->dyOverDx; + } + } + if ( pVNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pVNode->psiEqn ]; + gTotal -= intCoeff[0] * pElem->epsRel * 0.5 * pElem->dxOverDy * dPsiDv; + if ( delVContact ) { + gTotal += intCoeff[0] * pElem->epsRel * 0.5 * pElem->dxOverDy; + } + } + } + } + } + } + + return( gTotal ); +} + + +double + oxideConductance(TWOdevice *pDevice, TWOcontact *pContact, + BOOLEAN delVContact, double *dxDv, + BOOLEAN tranAnalysis, double *intCoeff) +{ + /* computes the conductance of the contact given in pContact */ + int index, i, numContactNodes; + TWOnode *pNode, *pHNode = NULL, *pVNode = NULL; + TWOelem *pElem; + double dPsiDv, dnDv, dpDv; + double gTotal = 0.0; + + if ( !tranAnalysis ) { + return( gTotal ); + } + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem) ) { + switch ( i ) { + case 0: + /* the TL element */ + pHNode = pElem->pBLNode; + pVNode = pElem->pTRNode; + break; + case 1: + /* the TR element */ + pHNode = pElem->pBRNode; + pVNode = pElem->pTLNode; + break; + case 2: + /* the BR element*/ + pHNode = pElem->pTRNode; + pVNode = pElem->pBLNode; + break; + case 3: + /* the BL element */ + pHNode = pElem->pTLNode; + pVNode = pElem->pBRNode; + break; + } + /* add the displacement current terms */ + if ( pHNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pHNode->psiEqn ]; + gTotal -= intCoeff[0] * pElem->epsRel * 0.5 * pElem->dyOverDx * dPsiDv; + if ( delVContact ) { + gTotal += intCoeff[0] * pElem->epsRel * 0.5 * pElem->dyOverDx; + } + } + if ( pVNode->nodeType != CONTACT ) { + dPsiDv = dxDv[ pVNode->psiEqn ]; + gTotal -= intCoeff[0] * pElem->epsRel * 0.5 * pElem->dxOverDy * dPsiDv; + if ( delVContact ) { + gTotal += intCoeff[0] * pElem->epsRel * 0.5 * pElem->dxOverDy; + } + } + } + } + } + + return( gTotal ); +} + +/* these functions are used for solving the complete system of + * equations directly using LU decomposition 1/22/88 + */ + +void + NUMD2current(TWOdevice *pDevice, BOOLEAN tranAnalysis, + double *intCoeff, double *id) +{ + TWOcontact *pPContact = pDevice->pFirstContact; + TWOcontact *pNContact = pDevice->pLastContact; + int index; + double ip, ipPrime, *solution; + double in; + BOOLEAN deltaVContact = FALSE; + + solution = pDevice->dcDeltaSolution; + ip = contactCurrent( pDevice, pPContact ); + /* + in = contactCurrent( pDevice, pNContact ); + fprintf(stdout, "DIO current: ( %11.4e error )\n", ip+in ); + fprintf(stdout, " Ip = %11.4e In = %11.4e\n", ip, in ); + */ + + /* + * for the additional contribution to id will make use of + * contactConductance. This function will be called + * with the dcDeltaSolution vector instead of the incremental quantities + */ + ipPrime = contactConductance( pDevice, pPContact, deltaVContact, + solution, tranAnalysis, intCoeff ); + + ipPrime *= JNorm * pDevice->width * LNorm; + ip += ipPrime; + *id = ip; +} + +void + NBJT2current(TWOdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + double *ie, double *ic) +{ + TWOcontact *pEmitContact = pDevice->pLastContact; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + double *solution, iePrime, icPrime; + double ib; + + solution = pDevice->dcDeltaSolution; + + *ie = contactCurrent( pDevice, pEmitContact ); + *ic = contactCurrent( pDevice, pColContact ); + /* + ib = contactCurrent( pDevice, pBaseContact ); + fprintf(stdout, "BJT current: ( %11.4e error )\n", *ic+ib+*ie ); + fprintf(stdout, " Ic = %11.4e Ib = %11.4e\n", *ic, ib ); + fprintf(stdout, " Ie = %11.4e\n", *ie ); + */ + + iePrime = contactConductance( pDevice, pEmitContact, FALSE, solution, + tranAnalysis, intCoeff ); + + icPrime = contactConductance( pDevice, pColContact, FALSE, solution, + tranAnalysis, intCoeff ); + + iePrime *= JNorm * pDevice->width * LNorm; + icPrime *= JNorm * pDevice->width * LNorm; + + *ie += iePrime; + *ic += icPrime; +} + +void + NUMOScurrent(TWOdevice *pDevice, BOOLEAN tranAnalysis, double *intCoeff, + double *id, double *is, double *ig) +{ + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + TWOcontact *pBContact = pDevice->pLastContact; + double *solution, idPrime, isPrime, igPrime; + double ib; + + solution = pDevice->dcDeltaSolution; + + *id = contactCurrent( pDevice, pDContact ); + +/* + * This is a terrible hack + */ + +#ifdef NORMAL_GATE + *ig = GateTypeCurrent( pDevice, pGContact, tranAnalysis ); +#else + *ig = GateTypeCurrent( pDevice, pGContact); +#endif + + *is = contactCurrent( pDevice, pSContact ); + /* + ib = contactCurrent( pDevice, pBContact ); + fprintf(stdout, "MOS current: ( %11.4e error )\n", *id+*ig+*is+ib ); + fprintf(stdout, " Id = %11.4e Is = %11.4e\n", *id, *is ); + fprintf(stdout, " Ig = %11.4e Ib = %11.4e\n", *ig, ib ); + */ + + idPrime = contactConductance( pDevice, pDContact, FALSE, + solution, tranAnalysis, intCoeff ); + + isPrime = contactConductance( pDevice, pSContact, FALSE, + solution, tranAnalysis, intCoeff ); + + igPrime = GateTypeConductance( pDevice, pGContact, FALSE, + solution, tranAnalysis, intCoeff ); + + idPrime *= JNorm * pDevice->width * LNorm; + isPrime *= JNorm * pDevice->width * LNorm; + igPrime *= JNorm * pDevice->width * LNorm; + + *id += idPrime; + *is += isPrime; + *ig += igPrime; +} diff --git a/src/ciderlib/twod/twocont.c b/src/ciderlib/twod/twocont.c new file mode 100644 index 000000000..b8a53d036 --- /dev/null +++ b/src/ciderlib/twod/twocont.c @@ -0,0 +1,1052 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" +#include "../../maths/misc/bernoull.h" + +/* + * Functions to setup and solve the continuity equations. + * Both continuity equations are solved. + * Separate functions are used for one continuity equation. + */ + + +/* + * setup matrix pointers to Jacobian values and + * store direct pointers with the nodes + */ + +void + TWO_jacBuild(TWOdevice *pDevice) +{ + char *matrix = pDevice->matrix; + double *spGetElement(); + TWOelem *pElem; + TWOnode *pNode; + TWOchannel *pCh; + int eIndex, nIndex; + int nextIndex; /* index of node to find next element */ + int psiEqn, nEqn, pEqn; /* scratch for deref'd eqn numbers */ + int psiEqnTL = 0, nEqnTL = 0, pEqnTL = 0; + int psiEqnTR = 0, nEqnTR = 0, pEqnTR = 0; + int psiEqnBR = 0, nEqnBR = 0, pEqnBR = 0; + int psiEqnBL = 0, nEqnBL = 0, pEqnBL = 0; + int psiEqnInM = 0, psiEqnInP = 0; /* scratch for deref'd surface eqns */ + int psiEqnOxM = 0, psiEqnOxP = 0; /* M= more negative, P= more positive */ + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + /* first the self terms */ + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + /* get poisson-only pointer */ + psiEqn = pNode->psiEqn; + pNode->fPsiPsi = spGetElement( matrix, psiEqn, psiEqn ); + + if ( pElem->elemType == SEMICON ) { + /* get continuity-coupling terms */ + nEqn = pNode->nEqn; + pEqn = pNode->pEqn; + /* pointers for additional terms */ + pNode->fPsiN = spGetElement( matrix, psiEqn, nEqn ); + pNode->fPsiP = spGetElement( matrix, psiEqn, pEqn ); + pNode->fNPsi = spGetElement( matrix, nEqn, psiEqn ); + pNode->fNN = spGetElement( matrix, nEqn, nEqn ); + pNode->fNP = spGetElement( matrix, nEqn, pEqn ); + pNode->fPPsi = spGetElement( matrix, pEqn, psiEqn ); + pNode->fPN = spGetElement( matrix, pEqn, nEqn ); + pNode->fPP = spGetElement( matrix, pEqn, pEqn ); + } else { + nEqn = 0; + pEqn = 0; + } + /* save equation indices */ + switch ( nIndex ) { + case 0: /* TL Node */ + psiEqnTL = psiEqn; + nEqnTL = nEqn; + pEqnTL = pEqn; + break; + case 1: /* TR Node */ + psiEqnTR = psiEqn; + nEqnTR = nEqn; + pEqnTR = pEqn; + break; + case 2: /* BR Node */ + psiEqnBR = psiEqn; + nEqnBR = nEqn; + pEqnBR = pEqn; + break; + case 3: /* BL Node */ + psiEqnBL = psiEqn; + nEqnBL = nEqn; + pEqnBL = pEqn; + break; + default: + break; + } + } + + /* now terms to couple to adjacent nodes */ + pNode = pElem->pTLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnTL, psiEqnTR ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTL, psiEqnBL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiP1 = spGetElement( matrix, nEqnTL, psiEqnTR ); + pNode->fNNiP1 = spGetElement( matrix, nEqnTL, nEqnTR ); + pNode->fNPsijP1 = spGetElement( matrix, nEqnTL, psiEqnBL ); + pNode->fNNjP1 = spGetElement( matrix, nEqnTL, nEqnBL ); + pNode->fPPsiiP1 = spGetElement( matrix, pEqnTL, psiEqnTR ); + pNode->fPPiP1 = spGetElement( matrix, pEqnTL, pEqnTR ); + pNode->fPPsijP1 = spGetElement( matrix, pEqnTL, psiEqnBL ); + pNode->fPPjP1 = spGetElement( matrix, pEqnTL, pEqnBL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiP1jP1 = spGetElement( matrix, nEqnTL, psiEqnBR ); + pNode->fNNiP1jP1 = spGetElement( matrix, nEqnTL, nEqnBR ); + pNode->fPPsiiP1jP1 = spGetElement( matrix, pEqnTL, psiEqnBR ); + pNode->fPPiP1jP1 = spGetElement( matrix, pEqnTL, pEqnBR ); + } + } + + pNode = pElem->pTRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnTR, psiEqnTL ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTR, psiEqnBR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiM1 = spGetElement( matrix, nEqnTR, psiEqnTL ); + pNode->fNNiM1 = spGetElement( matrix, nEqnTR, nEqnTL ); + pNode->fNPsijP1 = spGetElement( matrix, nEqnTR, psiEqnBR ); + pNode->fNNjP1 = spGetElement( matrix, nEqnTR, nEqnBR ); + pNode->fPPsiiM1 = spGetElement( matrix, pEqnTR, psiEqnTL ); + pNode->fPPiM1 = spGetElement( matrix, pEqnTR, pEqnTL ); + pNode->fPPsijP1 = spGetElement( matrix, pEqnTR, psiEqnBR ); + pNode->fPPjP1 = spGetElement( matrix, pEqnTR, pEqnBR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiM1jP1 = spGetElement( matrix, nEqnTR, psiEqnBL ); + pNode->fNNiM1jP1 = spGetElement( matrix, nEqnTR, nEqnBL ); + pNode->fPPsiiM1jP1 = spGetElement( matrix, pEqnTR, psiEqnBL ); + pNode->fPPiM1jP1 = spGetElement( matrix, pEqnTR, pEqnBL ); + } + } + + pNode = pElem->pBRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnBR, psiEqnBL ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBR, psiEqnTR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiM1 = spGetElement( matrix, nEqnBR, psiEqnBL ); + pNode->fNNiM1 = spGetElement( matrix, nEqnBR, nEqnBL ); + pNode->fNPsijM1 = spGetElement( matrix, nEqnBR, psiEqnTR ); + pNode->fNNjM1 = spGetElement( matrix, nEqnBR, nEqnTR ); + pNode->fPPsiiM1 = spGetElement( matrix, pEqnBR, psiEqnBL ); + pNode->fPPiM1 = spGetElement( matrix, pEqnBR, pEqnBL ); + pNode->fPPsijM1 = spGetElement( matrix, pEqnBR, psiEqnTR ); + pNode->fPPjM1 = spGetElement( matrix, pEqnBR, pEqnTR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiM1jM1 = spGetElement( matrix, nEqnBR, psiEqnTL ); + pNode->fNNiM1jM1 = spGetElement( matrix, nEqnBR, nEqnTL ); + pNode->fPPsiiM1jM1 = spGetElement( matrix, pEqnBR, psiEqnTL ); + pNode->fPPiM1jM1 = spGetElement( matrix, pEqnBR, pEqnTL ); + } + } + + pNode = pElem->pBLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnBL, psiEqnBR ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBL, psiEqnTL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiP1 = spGetElement( matrix, nEqnBL, psiEqnBR ); + pNode->fNNiP1 = spGetElement( matrix, nEqnBL, nEqnBR ); + pNode->fNPsijM1 = spGetElement( matrix, nEqnBL, psiEqnTL ); + pNode->fNNjM1 = spGetElement( matrix, nEqnBL, nEqnTL ); + pNode->fPPsiiP1 = spGetElement( matrix, pEqnBL, psiEqnBR ); + pNode->fPPiP1 = spGetElement( matrix, pEqnBL, pEqnBR ); + pNode->fPPsijM1 = spGetElement( matrix, pEqnBL, psiEqnTL ); + pNode->fPPjM1 = spGetElement( matrix, pEqnBL, pEqnTL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiP1jM1 = spGetElement( matrix, nEqnBL, psiEqnTR ); + pNode->fNNiP1jM1 = spGetElement( matrix, nEqnBL, nEqnTR ); + pNode->fPPsiiP1jM1 = spGetElement( matrix, pEqnBL, psiEqnTR ); + pNode->fPPiP1jM1 = spGetElement( matrix, pEqnBL, pEqnTR ); + } + } + } + /* + * Add terms for surface-field of inversion-layer mobility model. + * Elements MUST be made from silicon for this to work. + * No empty elements are allowed. + * Don't need these pointers if SurfaceMobility isn't set. + */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + psiEqnInM = pElem->pBLNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pTRNode->psiEqn; + break; + case 1: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pBLNode->psiEqn; + psiEqnOxM = pElem->pTRNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 2: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pTRNode->psiEqn; + psiEqnOxM = pElem->pBLNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 3: + psiEqnInM = pElem->pTRNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pBLNode->psiEqn; + break; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + psiEqn = pNode->psiEqn; + nEqn = pNode->nEqn; + pEqn = pNode->pEqn; + if ( pCh->type % 2 == 0 ) { /* Vertical Slice */ + if ( nIndex == 0 || nIndex == 3 ) { /* Left Side */ + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiInP1 = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOxP1 = spGetElement( matrix, nEqn, psiEqnOxP ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiInP1 = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOxP1 = spGetElement( matrix, pEqn, psiEqnOxP ); + } else { /* Right Side */ + pNode->fNPsiInM1 = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOxM1 = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxP ); + pNode->fPPsiInM1 = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOxM1 = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxP ); + } + } else { /* Horizontal Slice */ + if ( nIndex <= 1 ) { /* Top Side */ + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiInP1 = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOxP1 = spGetElement( matrix, nEqn, psiEqnOxP ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiInP1 = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOxP1 = spGetElement( matrix, pEqn, psiEqnOxP ); + } else { /* Bottom Side */ + pNode->fNPsiInM1 = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOxM1 = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxP ); + pNode->fPPsiInM1 = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOxM1 = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxP ); + } + } + } /* endfor nIndex */ + pElem = pElem->pElems[ nextIndex ]; + } /* endwhile pElem */ + } /* endfor pCh */ + } /* endif SurfaceMobility */ +} + + +/* + * The Jacobian and Rhs are loaded by the following function. + * Inputs are the transient analysis flag and the transient + * information structure + */ + +void + TWO_sysLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsN, rhsP; + double generation, TWOavalanche(); + double nConc, pConc; + double perTime = 0.0; + + /* first compute the currents and derivatives */ + TWO_commonTerms( pDevice, FALSE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPsiN) += dxdy; + *(pNode->fPsiP) -= dxdy; + *(pNode->fNPsi) -= dy * pHEdge->dJnDpsiP1 + + dx * pVEdge->dJnDpsiP1; + *(pNode->fPPsi) -= dy * pHEdge->dJpDpsiP1 + + dx * pVEdge->dJpDpsiP1; + + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + *(pNode->fNN) -= dxdy * pNode->dUdN; + *(pNode->fNP) -= dxdy * pNode->dUdP; + *(pNode->fPP) += dxdy * pNode->dUdP; + *(pNode->fPN) += dxdy * pNode->dUdN; + rhsN = - dxdy * pNode->uNet; + rhsP = dxdy * pNode->uNet; + if ( AvalancheGen ) { + generation = TWOavalanche( pElem, pNode ); + rhsN += dxdy * generation; + rhsP -= dxdy * generation; + } + pRhs[ pNode->nEqn ] -= rhsN; + pRhs[ pNode->pEqn ] -= rhsP; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + *(pNode->fNN) -= dxdy * perTime; + *(pNode->fPP) += dxdy * perTime; + pRhs[ pNode->nEqn ] += dxdy * pNode->dNdT; + pRhs[ pNode->pEqn ] -= dxdy * pNode->dPdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pTEdge->jn + dx * pLEdge->jn; + pRhs[ pNode->pEqn ] -= dy * pTEdge->jp + dx * pLEdge->jp; + *(pNode->fNN) += dy * pTEdge->dJnDn + dx * pLEdge->dJnDn; + *(pNode->fPP) += dy * pTEdge->dJpDp + dx * pLEdge->dJpDp; + *(pNode->fNPsiiP1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pTEdge->dJnDnP1; + *(pNode->fPPsiiP1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pTEdge->dJpDpP1; + *(pNode->fNPsijP1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pLEdge->dJnDnP1; + *(pNode->fPPsijP1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pLEdge->dJpDpP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pTEdge->jn + dx * pREdge->jn; + pRhs[ pNode->pEqn ] -= -dy * pTEdge->jp + dx * pREdge->jp; + *(pNode->fNN) += -dy * pTEdge->dJnDnP1 + dx * pREdge->dJnDn; + *(pNode->fPP) += -dy * pTEdge->dJpDpP1 + dx * pREdge->dJpDp; + *(pNode->fNPsiiM1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pTEdge->dJnDn; + *(pNode->fPPsiiM1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pTEdge->dJpDp; + *(pNode->fNPsijP1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pREdge->dJnDnP1; + *(pNode->fPPsijP1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pREdge->dJpDpP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pBEdge->jn - dx * pREdge->jn; + pRhs[ pNode->pEqn ] -= -dy * pBEdge->jp - dx * pREdge->jp; + *(pNode->fNN) += -dy * pBEdge->dJnDnP1 - dx * pREdge->dJnDnP1; + *(pNode->fPP) += -dy * pBEdge->dJpDpP1 - dx * pREdge->dJpDpP1; + *(pNode->fNPsiiM1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pBEdge->dJnDn; + *(pNode->fPPsiiM1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pBEdge->dJpDp; + *(pNode->fNPsijM1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pREdge->dJnDn; + *(pNode->fPPsijM1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pREdge->dJpDp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pBEdge->jn - dx * pLEdge->jn; + pRhs[ pNode->pEqn ] -= dy * pBEdge->jp - dx * pLEdge->jp; + *(pNode->fNN) += dy * pBEdge->dJnDn - dx * pLEdge->dJnDnP1; + *(pNode->fPP) += dy * pBEdge->dJpDp - dx * pLEdge->dJpDpP1; + *(pNode->fNPsiiP1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pBEdge->dJnDnP1; + *(pNode->fPPsiiP1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pBEdge->dJpDpP1; + *(pNode->fNPsijM1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pLEdge->dJnDn; + *(pNode->fPPsijM1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pLEdge->dJpDp; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWO_mobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + + +/* this function used only for direct method ac analysis + Used to load only the dc Jacobian matrix. Rhs is unaffected + */ + +void + TWO_jacLoad(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + void spClear(), TWO_commonTerms(); + + /* first compute the currents and derivatives */ + TWO_commonTerms( pDevice, FALSE, FALSE, NIL(TWOtranInfo) ); + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( pElem->elemType == SEMICON ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + *(pNode->fPsiN) += dxdy; + *(pNode->fPsiP) -= dxdy; + *(pNode->fNPsi) -= dy * pHEdge->dJnDpsiP1 + + dx * pVEdge->dJnDpsiP1; + *(pNode->fPPsi) -= dy * pHEdge->dJpDpsiP1 + + dx * pVEdge->dJpDpsiP1; + + /* Handle generation terms */ + *(pNode->fNN) -= dxdy * pNode->dUdN; + *(pNode->fNP) -= dxdy * pNode->dUdP; + *(pNode->fPP) += dxdy * pNode->dUdP; + *(pNode->fPN) += dxdy * pNode->dUdN; + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += dy * pTEdge->dJnDn + dx * pLEdge->dJnDn; + *(pNode->fPP) += dy * pTEdge->dJpDp + dx * pLEdge->dJpDp; + *(pNode->fNPsiiP1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pTEdge->dJnDnP1; + *(pNode->fPPsiiP1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pTEdge->dJpDpP1; + *(pNode->fNPsijP1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pLEdge->dJnDnP1; + *(pNode->fPPsijP1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pLEdge->dJpDpP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += -dy * pTEdge->dJnDnP1 + dx * pREdge->dJnDn; + *(pNode->fPP) += -dy * pTEdge->dJpDpP1 + dx * pREdge->dJpDp; + *(pNode->fNPsiiM1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pTEdge->dJnDn; + *(pNode->fPPsiiM1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pTEdge->dJpDp; + *(pNode->fNPsijP1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pREdge->dJnDnP1; + *(pNode->fPPsijP1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pREdge->dJpDpP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += -dy * pBEdge->dJnDnP1 - dx * pREdge->dJnDnP1; + *(pNode->fPP) += -dy * pBEdge->dJpDpP1 - dx * pREdge->dJpDpP1; + *(pNode->fNPsiiM1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pBEdge->dJnDn; + *(pNode->fPPsiiM1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pBEdge->dJpDp; + *(pNode->fNPsijM1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pREdge->dJnDn; + *(pNode->fPPsijM1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pREdge->dJpDp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += dy * pBEdge->dJnDn - dx * pLEdge->dJnDnP1; + *(pNode->fPP) += dy * pBEdge->dJpDp - dx * pLEdge->dJpDpP1; + *(pNode->fNPsiiP1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pBEdge->dJnDnP1; + *(pNode->fPPsiiP1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pBEdge->dJpDpP1; + *(pNode->fNPsijM1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pLEdge->dJnDn; + *(pNode->fPPsijM1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pLEdge->dJpDp; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWO_mobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + +/* load only the Rhs vector */ +void + TWO_rhsLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsN, rhsP; + double generation, TWOavalanche(); + double nConc, pConc; + double perTime; + void TWO_commonTerms(); + + /* first compute the currents */ + TWO_commonTerms( pDevice, TRUE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + rhsN = - dxdy * pNode->uNet; + rhsP = dxdy * pNode->uNet; + if ( AvalancheGen ) { + generation = TWOavalanche( pElem, pNode ); + rhsN += dxdy * generation; + rhsP -= dxdy * generation; + } + pRhs[ pNode->nEqn ] -= rhsN; + pRhs[ pNode->pEqn ] -= rhsP; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + pRhs[ pNode->nEqn ] += dxdy * pNode->dNdT; + pRhs[ pNode->pEqn ] -= dxdy * pNode->dPdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pTEdge->jn + dx * pLEdge->jn; + pRhs[ pNode->pEqn ] -= dy * pTEdge->jp + dx * pLEdge->jp; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pTEdge->jn + dx * pREdge->jn; + pRhs[ pNode->pEqn ] -= -dy * pTEdge->jp + dx * pREdge->jp; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pBEdge->jn - dx * pREdge->jn; + pRhs[ pNode->pEqn ] -= -dy * pBEdge->jp - dx * pREdge->jp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pBEdge->jn - dx * pLEdge->jn; + pRhs[ pNode->pEqn ] -= dy * pBEdge->jp - dx * pLEdge->jp; + } + } + } +} + +/* + * computation of current densities, recombination rates, + * mobilities and their derivatives + */ +void + TWO_commonTerms(TWOdevice *pDevice, BOOLEAN currentOnly, + BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem, *pElem1; + TWOedge *pEdge; + TWOnode *pNode; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double psi1, psi2, nC, pC, nP1, pP1; + double dPsiN, dPsiP; + double bPsiN, dbPsiN, bMPsiN, dbMPsiN; + double bPsiP, dbPsiP, bMPsiP, dbMPsiP; + double muN, dMuN, muP, dMuP, rDx, rDy; + double psi, nConc = 0.0, pConc = 0.0; + double cnAug, cpAug; + double eSurf = 0.0; /* For channel mobilities */ + double qInt = 0.0; + TWOchannel *pCh; + + /* evaluate all node (including recombination) and edge quantities */ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + cnAug = pElem->matlInfo->cAug[ELEC]; + cpAug = pElem->matlInfo->cAug[HOLE]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi = pDevice->dcSolution[ pNode->psiEqn ]; + if ( pElem->elemType == SEMICON ) { + nConc = pDevice->dcSolution[ pNode->nEqn ]; + pConc = pDevice->dcSolution[ pNode->pEqn ]; + if ( Srh ) { + recomb(nConc, pConc, + pNode->tn, pNode->tp, cnAug, cpAug, pNode->nie, + &pNode->uNet, &pNode->dUdN, &pNode->dUdP); + } else { + pNode->uNet = 0.0; + pNode->dUdN = 0.0; + pNode->dUdP = 0.0; + } + } + } else { + /* a contact node */ + psi = pNode->psi; + if ( pElem->elemType == SEMICON ) { + nConc = pNode->nConc; + pConc = pNode->pConc; + } + } + + /* store info in the state tables */ + *(pDevice->devState0 + pNode->nodePsi) = psi; + if ( pElem->elemType == SEMICON ) { + *(pDevice->devState0 + pNode->nodeN) = nConc; + *(pDevice->devState0 + pNode->nodeP) = pConc; + if ( tranAnalysis && (pNode->nodeType != CONTACT) ) { + pNode->dNdT = integrate( pDevice->devStates, info, pNode->nodeN ); + pNode->dPdT = integrate( pDevice->devStates, info, pNode->nodeP ); + } + } + } + } + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi1 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pNodes[ (index + 1) % 4 ]; + if ( pNode->nodeType != CONTACT ) { + psi2 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi2 = pNode->psi; + } + if ( index <= 1 ) { + pEdge->dPsi = psi2 - psi1; + } else { + pEdge->dPsi = psi1 - psi2; + } + *(pDevice->devState0 + pEdge->edgeDpsi) = pEdge->dPsi; + + if ( pElem->elemType == SEMICON ) { + /* Calculate weighted driving forces - wdfn & wdfp for the edge */ + dPsiN = pEdge->dPsi + pEdge->dCBand; + dPsiP = pEdge->dPsi - pEdge->dVBand; + bernoulli( dPsiN, &bPsiN, &dbPsiN, + &bMPsiN, &dbMPsiN, !currentOnly ); + bernoulli( dPsiP, &bPsiP, &dbPsiP, + &bMPsiP, &dbMPsiP, !currentOnly); + if ( index <= 1 ) { + nC = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeN); + nP1 = *(pDevice->devState0 + pElem->pNodes[ index+1 ]->nodeN); + pC = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeP); + pP1 = *(pDevice->devState0 + pElem->pNodes[ index+1 ]->nodeP); + } else { + nC = *(pDevice->devState0 + pElem->pNodes[(index+1)%4]->nodeN); + nP1 = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeN); + pC = *(pDevice->devState0 + pElem->pNodes[(index+1)%4]->nodeP); + pP1 = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeP); + } + pEdge->wdfn = bPsiN * nP1 - bMPsiN * nC; + pEdge->wdfp = bPsiP * pC - bMPsiP * pP1; + pEdge->jn = 0.0; + pEdge->jp = 0.0; + if ( !currentOnly ) { + pEdge->dWnDpsiP1 = dbPsiN * nP1 - dbMPsiN * nC; + pEdge->dWnDn = - bMPsiN; + pEdge->dWnDnP1 = bPsiN; + pEdge->dWpDpsiP1 = dbPsiP * pC - dbMPsiP * pP1; + pEdge->dWpDp = bPsiP; + pEdge->dWpDpP1 = - bMPsiP; + pEdge->dJnDpsiP1 = 0.0; + pEdge->dJnDn = 0.0; + pEdge->dJnDnP1 = 0.0; + pEdge->dJpDpsiP1 = 0.0; + pEdge->dJpDp = 0.0; + pEdge->dJpDpP1 = 0.0; + } + } + } + } + } + + /* DAG: calculate mobilities for channel elems */ + if ( SurfaceMobility ) { + for ( pCh = pDevice->pChannel; + pCh != NIL(TWOchannel); pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pBotEdge->qf; + break; + case 1: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pLeftEdge->qf; + break; + case 2: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pTopEdge->qf; + break; + case 3: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pRightEdge->qf; + break; + } + eSurf += qInt; + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWO_mobility( pElem, eSurf ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCH != NIL */ + } /* endif SurfaceMobility */ + + /* calculate the current densities assuming mobility value depend on Ex,Ey*/ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + rDx = 1.0 / pElem->dx; + rDy = 1.0 / pElem->dy; + for ( index = 0; index <= 3; index++ ) { + pEdge = pElem->pEdges[ index ]; + /* calculate conductive currents */ + if ( pElem->elemType == SEMICON ) { + /* get mobility for this edge */ + if ( !pElem->channel ) { + /* Calculate mobility for non-channel elements */ + muN = pElem->mun0; + dMuN = 0.0; + muP = pElem->mup0; + dMuP = 0.0; + dPsiN = pEdge->dPsi + pEdge->dCBand; + dPsiP = pEdge->dPsi - pEdge->dVBand; + if ( index%2 == 0 ) { + MOBfieldDep( pElem->matlInfo, ELEC, - dPsiN * rDx, &muN, &dMuN ); + MOBfieldDep( pElem->matlInfo, HOLE, - dPsiP * rDx, &muP, &dMuP ); + } else { + MOBfieldDep( pElem->matlInfo, ELEC, - dPsiN * rDy, &muN, &dMuN ); + MOBfieldDep( pElem->matlInfo, HOLE, - dPsiP * rDy, &muP, &dMuP ); + } + } else { + /* Retrieve previously calculated value. */ + muN = pElem->mun; + dMuN = 0.0; + muP = pElem->mup; + dMuP = 0.0; + } + switch ( index ) { + case 0: + muN *= pEdge->kPos * rDx; + dMuN *= pEdge->kPos * rDx * rDx; + muP *= pEdge->kPos * rDx; + dMuP *= pEdge->kPos * rDx * rDx; + break; + case 1: + muN *= pEdge->kNeg * rDy; + dMuN *= pEdge->kNeg * rDy * rDy; + muP *= pEdge->kNeg * rDy; + dMuP *= pEdge->kNeg * rDy * rDy; + break; + case 2: + muN *= pEdge->kNeg * rDx; + dMuN *= pEdge->kNeg * rDx * rDx; + muP *= pEdge->kNeg * rDx; + dMuP *= pEdge->kNeg * rDx * rDx; + break; + case 3: + muN *= pEdge->kPos * rDy; + dMuN *= pEdge->kPos * rDy * rDy; + muP *= pEdge->kPos * rDy; + dMuP *= pEdge->kPos * rDy * rDy; + break; + } + /* Now that the mobility for this edge is known, do current */ + pEdge->jn += muN * pEdge->wdfn; + pEdge->jp += muP * pEdge->wdfp; + if ( !currentOnly ) { + pEdge->dJnDpsiP1 += muN * pEdge->dWnDpsiP1; + pEdge->dJnDn += muN * pEdge->dWnDn; + pEdge->dJnDnP1 += muN * pEdge->dWnDnP1; + pEdge->dJpDpsiP1 += muP * pEdge->dWpDpsiP1; + pEdge->dJpDp += muP * pEdge->dWpDp; + pEdge->dJpDpP1 += muP * pEdge->dWpDpP1; + if ( MobDeriv && (!pElem->channel) ) { + pEdge->dJnDpsiP1 -= dMuN * pEdge->wdfn; + pEdge->dJpDpsiP1 -= dMuP * pEdge->wdfp; + } + } + } + /* calculate displacement current only once */ + if ( pElem->evalEdges[ index ] ) { + if ( tranAnalysis ) { + if ( index == 0 || index == 2 ) { + /* horizontal edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDx; + } else { + /* vertical edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDy; + } + } + } + } + } +} diff --git a/src/ciderlib/twod/twocurr.c b/src/ciderlib/twod/twocurr.c new file mode 100644 index 000000000..44c633aee --- /dev/null +++ b/src/ciderlib/twod/twocurr.c @@ -0,0 +1,183 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twoddefs.h" +#include "twodext.h" + +void +nodeCurrents(TWOelem *pElem, TWOnode *pNode, double *mun, double *mup, + double *jnx, double *jny, double *jpx, double *jpy, + double *jdx, double *jdy) +{ + + TWOelem *pElemTL, *pElemTR, *pElemBL, *pElemBR; + TWOedge *pEdgeT, *pEdgeB, *pEdgeL, *pEdgeR; + int materT = 0, materB = 0, materL = 0, materR = 0; + int numFound = 0; + double dxL = 0.0, dxR = 0.0, dyT = 0.0, dyB = 0.0; + double epsL = 0.0, epsR = 0.0, epsT = 0.0, epsB = 0.0; + double coeff1, coeff2; + + /* Find all four neighboring elements */ + pElemTL = pNode->pTLElem; + pElemTR = pNode->pTRElem; + pElemBL = pNode->pBLElem; + pElemBR = pNode->pBRElem; + + /* Null edge pointers */ + pEdgeT = pEdgeB = pEdgeL = pEdgeR = NIL(TWOedge); + + /* Zero mobilities */ + *mun = *mup = 0.0; + + /* Find edges next to node */ + if (pElemTL != NIL(TWOelem)) { + numFound++; + *mun += pElemTL->mun0; + *mup += pElemTL->mup0; + if (pElemTL->evalEdges[1]) { + pEdgeT = pElemTL->pRightEdge; + materT = pElemTL->elemType; + dyT = pElemTL->dy; + epsT = pElemTL->epsRel; + } + if (pElemTL->evalEdges[2]) { + pEdgeL = pElemTL->pBotEdge; + materL = pElemTL->elemType; + dxL = pElemTL->dx; + epsL = pElemTL->epsRel; + } + } + if (pElemTR != NIL(TWOelem)) { + numFound++; + *mun += pElemTR->mun0; + *mup += pElemTR->mup0; + if (pElemTR->evalEdges[3]) { + pEdgeT = pElemTR->pLeftEdge; + materT = pElemTR->elemType; + epsT = pElemTR->epsRel; + } + if (pElemTR->evalEdges[2]) { + pEdgeR = pElemTR->pBotEdge; + materR = pElemTR->elemType; + dxR = pElemTR->dx; + epsR = pElemTR->epsRel; + } + } + if (pElemBR != NIL(TWOelem)) { + numFound++; + *mun += pElemBR->mun0; + *mup += pElemBR->mup0; + if (pElemBR->evalEdges[3]) { + pEdgeB = pElemBR->pLeftEdge; + materB = pElemBR->elemType; + dyB = pElemBR->dy; + epsB = pElemBR->epsRel; + } + if (pElemBR->evalEdges[0]) { + pEdgeR = pElemBR->pTopEdge; + materR = pElemBR->elemType; + dxR = pElemBR->dx; + epsR = pElemBR->epsRel; + } + } + if (pElemBL != NIL(TWOelem)) { + numFound++; + *mun += pElemBL->mun0; + *mup += pElemBL->mup0; + if (pElemBL->evalEdges[1]) { + pEdgeB = pElemBL->pRightEdge; + materB = pElemBL->elemType; + dyB = pElemBL->dy; + epsB = pElemBL->epsRel; + } + if (pElemBL->evalEdges[0]) { + pEdgeL = pElemBL->pTopEdge; + materL = pElemBL->elemType; + dxL = pElemBL->dx; + epsL = pElemBL->epsRel; + } + } + *mun /= (double) numFound; + *mup /= (double) numFound; + /* compute horizontal vector components */ + /* No more than one of Left Edge or Right Edge is absent */ + /* If one is absent the other is guaranteed to be from silicon */ + if (pEdgeL == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *jnx = pEdgeR->jn; + *jpx = pEdgeR->jp; + *jdx = pEdgeR->jd; + } else { + *jnx = 0.0; + *jpx = 0.0; + *jdx = 0.0; + } + } else if (pEdgeR == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *jnx = pEdgeL->jn; + *jpx = pEdgeL->jp; + *jdx = pEdgeL->jd; + } else { + *jnx = 0.0; + *jpx = 0.0; + *jdx = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dxL / (dxL + dxR); + coeff2 = dxR / (dxL + dxR); + if ((materL == INSULATOR) || (materR == INSULATOR)) { + *jnx = 0.0; + *jpx = 0.0; + *jdx = coeff2 * epsL * pEdgeL->jd + coeff1 * epsR * pEdgeR->jd; + } else { + *jnx = coeff2 * pEdgeL->jn + coeff1 * pEdgeR->jn; + *jpx = coeff2 * pEdgeL->jp + coeff1 * pEdgeR->jp; + *jdx = coeff2 * pEdgeL->jd + coeff1 * pEdgeR->jd; + } + } + + /* compute vertical vector components */ + /* No more than one of Top Edge or Bottom Edge is absent */ + /* If one is absent the other is guaranteed to be from silicon */ + if (pEdgeT == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *jny = pEdgeB->jn; + *jpy = pEdgeB->jp; + *jdy = pEdgeB->jd; + } else { + *jny = 0.0; + *jpy = 0.0; + *jdy = 0.0; + } + } else if (pEdgeB == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *jny = pEdgeT->jn; + *jpy = pEdgeT->jp; + *jdy = pEdgeT->jd; + } else { + *jny = 0.0; + *jpy = 0.0; + *jdy = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dyT / (dyT + dyB); + coeff2 = dyB / (dyT + dyB); + if ((materT == INSULATOR) || (materB == INSULATOR)) { + *jny = 0.0; + *jpy = 0.0; + *jdy = coeff2 * epsT * pEdgeT->jd + coeff1 * epsB * pEdgeB->jd; + } else { + *jny = coeff2 * pEdgeT->jn + coeff1 * pEdgeB->jn; + *jpy = coeff2 * pEdgeT->jp + coeff1 * pEdgeB->jp; + *jdy = coeff2 * pEdgeT->jd + coeff1 * pEdgeB->jd; + } + } +} diff --git a/src/ciderlib/twod/twoddefs.h b/src/ciderlib/twod/twoddefs.h new file mode 100644 index 000000000..c63326549 --- /dev/null +++ b/src/ciderlib/twod/twoddefs.h @@ -0,0 +1,57 @@ +/* + * 2001 Paolo Nenzi + */ + +#ifndef _TWODDEFS_H +#define _TWODDEFS_H + +/* Debug statements */ + +extern BOOLEAN TWOacDebug; +extern BOOLEAN TWOdcDebug; +extern BOOLEAN TWOtranDebug; +extern BOOLEAN TWOjacDebug; + +/* Now some defines for the two dimensional simulator + * library. + * Theese defines were gathered from all the code in + * oned directory. + */ + + +/* Temporary hack to remove NUMOS gate special case */ +#ifdef NORMAL_GATE +#define GateTypeAdmittance oxideAdmittance +#else +#define GateTypeAdmittance contactAdmittance +#endif /* NORMAL_GATE */ + +/* Temporary hack to remove NUMOS gate special case */ +#ifdef NORMAL_GATE +#define GateTypeConductance oxideConductance +#define GateTypeCurrent oxideCurrent +#else +#define GateTypeConductance contactConductance +#define GateTypeCurrent contactCurrent +#endif /* NORMAL_GATE */ + + /* This structure was moved up from twoadmit.c */ +struct mosAdmittances { + SPcomplex yIdVdb; + SPcomplex yIdVsb; + SPcomplex yIdVgb; + SPcomplex yIsVdb; + SPcomplex yIsVsb; + SPcomplex yIsVgb; + SPcomplex yIgVdb; + SPcomplex yIgVsb; + SPcomplex yIgVgb; +}; + +#define MAXTERMINALS 5 /* One more than max number of terminals */ +#define ELCT_ID poiEqn + +#define MIN_DELV 1e-3 +#define NORM_RED_MAXITERS 10 + +#endif diff --git a/src/ciderlib/twod/twodest.c b/src/ciderlib/twod/twodest.c new file mode 100644 index 000000000..2ef027444 --- /dev/null +++ b/src/ciderlib/twod/twodest.c @@ -0,0 +1,76 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twodev.h" +#include "twomesh.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" + +void +TWOdestroy(TWOdevice *pDevice) +{ + int index, eIndex; + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pEdge; + + if ( !pDevice ) return; + + switch ( pDevice->solverType ) { + case SLV_SMSIG: + case SLV_BIAS: + /* free up memory allocated for the bias solution */ + FREE( pDevice->dcSolution ); + FREE( pDevice->dcDeltaSolution ); + FREE( pDevice->copiedSolution ); + FREE( pDevice->rhs ); + FREE( pDevice->rhsImag ); + spDestroy( pDevice->matrix ); + break; + case SLV_EQUIL: + /* free up the vectors allocated in the equilibrium solution */ + FREE( pDevice->dcSolution ); + FREE( pDevice->dcDeltaSolution ); + FREE( pDevice->copiedSolution ); + FREE( pDevice->rhs ); + spDestroy( pDevice->matrix ); + break; + case SLV_NONE: + break; + default: + fprintf( stderr, "Panic: Unknown solver type in TWOdestroy.\n" ); + exit( -1 ); + break; + } + + /* destroy the mesh */ + if ( pDevice->elements ) { + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + FREE( pNode ); + } + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + FREE( pEdge ); + } + } + FREE( pElem ); + } + FREE( pDevice->elements ); + FREE( pDevice->elemArray ); + } + + /* destroy the contacts & channels */ + /* NOT IMPLEMENTED */ + + FREE( pDevice ); +} diff --git a/src/ciderlib/twod/twodext.h b/src/ciderlib/twod/twodext.h new file mode 100644 index 000000000..9be5ea349 --- /dev/null +++ b/src/ciderlib/twod/twodext.h @@ -0,0 +1,169 @@ +/* + * 2001 Paolo Nenzi + */ + + /* External symbols for Two Dimensional simulator */ + +#ifndef _TWODEXT_H +#define _TWODEXT_H + +#include "profile.h" +#include "twomesh.h" +#include "twodev.h" +#include "carddefs.h" +#include "bool.h" +#include "complex.h" + +/* twoadmit.c */ +extern int NUMD2admittance(TWOdevice *, double, SPcomplex *); +extern int NBJT2admittance(TWOdevice *, double, SPcomplex *, + SPcomplex *, SPcomplex *, SPcomplex *); +extern int NUMOSadmittance(TWOdevice *, double, struct mosAdmittances *); +extern BOOLEAN TWOsorSolve(TWOdevice *, double *, double *, double); +extern SPcomplex *contactAdmittance(TWOdevice *, TWOcontact *, BOOLEAN, + double *, double *, SPcomplex *); +extern SPcomplex *oxideAdmittance(TWOdevice *, TWOcontact *,BOOLEAN, + double *, double *, SPcomplex *); + +extern void NUMD2ys(TWOdevice *, SPcomplex *, SPcomplex *); +extern void NBJT2ys(TWOdevice *,SPcomplex *, SPcomplex *, SPcomplex *, + SPcomplex *, SPcomplex *); +extern void NUMOSys(TWOdevice *, SPcomplex *, struct mosAdmittances *); + +/* twoaval.c */ +extern double TWOavalanche(TWOelem *, TWOnode *); + +/* twocond.c */ +extern void NUMD2conductance(TWOdevice *, BOOLEAN, double *, double *); +extern void NBJT2conductance(TWOdevice *, BOOLEAN, double *, double *, + double *, double *, double *); +extern void NUMOSconductance(TWOdevice *, BOOLEAN, double *, + struct mosConductances *); +extern double contactCurrent(TWOdevice *, TWOcontact *); +extern double oxideCurrent(TWOdevice *, TWOcontact *, BOOLEAN); +extern double contactConductance(TWOdevice *, TWOcontact *, BOOLEAN, + double *, BOOLEAN, double *); +extern double oxideConductance(TWOdevice *, TWOcontact *, BOOLEAN, + double *, BOOLEAN, double *); +extern void NUMD2current(TWOdevice *, BOOLEAN, double *, double *); +extern void NBJT2current(TWOdevice *, BOOLEAN, double *, double *, + double *); +extern void NUMOScurrent(TWOdevice *, BOOLEAN , double *, double *, double *, + double *); + +/* twocont */ +extern void TWO_jacBuild(TWOdevice *); +extern void TWO_sysLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWO_jacLoad(TWOdevice *); +extern void TWO_rhsLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWO_commonTerms(TWOdevice *, BOOLEAN, BOOLEAN, TWOtranInfo *); + +/* twocurr.c */ +extern void nodeCurrents(TWOelem *, TWOnode *, double *, double *, + double *, double *, double *, double *, double *, double *); + +/* twodest */ +extern void TWOdestroy(TWOdevice *); + +/* twodopng.c */ +extern double TWOdopingValue(DOPprofile *, DOPtable *, double, double); +extern void TWOsetDoping(TWOdevice *, DOPprofile *, DOPtable *); + +/* twoelect.c */ +extern int TWOcmpElectrode(TWOelectrode *, TWOelectrode *); +extern void checkElectrodes(TWOelectrode *, int); +extern void setupContacts(TWOdevice *, TWOelectrode *, TWOnode ***); + +/* twofield.c */ +extern void nodeFields(TWOelem *, TWOnode *, double *, double *); + +/* twomesh.c */ +extern void TWObuildMesh(TWOdevice *, TWOdomain *, TWOelectrode *, + TWOmaterial *); +extern void TWOprnMesh(TWOdevice *); +extern void TWOgetStatePointers(TWOdevice *, int *); + +/* twomobdv.c */ +extern void TWO_mobDeriv(TWOelem *, int, double); +extern void TWONmobDeriv(TWOelem *, int, double); +extern void TWOPmobDeriv(TWOelem *, int, double); + +/* twomobfn.c */ +extern void MOBsurfElec(TWOmaterial *, TWOelem *, double, double, + double, double, double, double); +extern void MOBsurfHole(TWOmaterial *, TWOelem *, double, double, + double, double, double, double); + +/* twomobil.c */ +extern void TWO_mobility(TWOelem *, double); +extern void TWONmobility(TWOelem *, double); +extern void TWOPmobility(TWOelem *, double); + +/* twoncont.c */ +extern void TWONjacBuild(TWOdevice *); +extern void TWONsysLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWONjacLoad(TWOdevice *); +extern void TWONrhsLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWONcommonTerms(TWOdevice *, BOOLEAN, BOOLEAN, TWOtranInfo *); + +/* twopcont.c */ +extern void TWOPjacBuild(TWOdevice *); +extern void TWOPsysLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWOPjacLoad(TWOdevice *); +extern void TWOPrhsLoad(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWOPcommonTerms(TWOdevice *, BOOLEAN, BOOLEAN, TWOtranInfo *); + +/* twopoiss.c */ +extern void TWOQjacBuild(TWOdevice *); +extern void TWOQsysLoad(TWOdevice *); +extern void TWOQrhsLoad(TWOdevice *); +extern void TWOQcommonTerms(TWOdevice *); + +/*twoprint.c */ +extern void TWOprnSolution(FILE *, TWOdevice *, OUTPcard *); +extern void TWOmemStats(FILE *, TWOdevice *); +extern void TWOcpuStats(FILE *, TWOdevice *); + + +/* twoproj.c */ +extern void NUMD2project(TWOdevice *, double); +extern void NBJT2project(TWOdevice *, double, double); +extern void NUMOSproject(TWOdevice *, double, double, double); +extern void NUMD2update(TWOdevice *,double, BOOLEAN); +extern void NBJT2update(TWOdevice *, double, double, BOOLEAN); +extern void NUMOSupdate(TWOdevice *, double, double, double, BOOLEAN); +extern void storeNewRhs(TWOdevice *, TWOcontact *); + +/* tworead.c */ +extern int TWOreadState(TWOdevice *, char *, int, double *, double *, double *); + +/*twosetbc.c */ +extern void NUMD2setBCs(TWOdevice *, double); +extern void NBJT2setBCs(TWOdevice *, double, double); +extern void NUMOSsetBCs(TWOdevice *, double, double, double); + +/*twosetup.c */ +extern void TWOsetup(TWOdevice *); +extern void TWOsetBCparams(TWOdevice *, BDRYcard *); +extern void TWOnormalize(TWOdevice *); + +/* twosolve.c */ +extern void TWOdcSolve(TWOdevice *, int, BOOLEAN, BOOLEAN, TWOtranInfo *); +extern BOOLEAN TWOdeltaConverged(TWOdevice *); +extern BOOLEAN TWOdeviceConverged(TWOdevice *); +extern void TWOresetJacobian(TWOdevice *); +extern void TWOstoreNeutralGuess(TWOdevice *); +extern void TWOequilSolve(TWOdevice *); +extern void TWObiasSolve(TWOdevice *, int, BOOLEAN, TWOtranInfo *); +extern void TWOstoreEquilibGuess(TWOdevice *); +extern void TWOstoreInitialGuess(TWOdevice *); +extern void oldTWOnewDelta(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern int TWOnewDelta(TWOdevice *, BOOLEAN, TWOtranInfo *); +extern void TWOpredict(TWOdevice *, TWOtranInfo *); +extern double TWOtrunc(TWOdevice *, TWOtranInfo *, double); +extern void TWOsaveState(TWOdevice *); +extern BOOLEAN TWOpsiDeltaConverged(TWOdevice *); +extern double TWOnuNorm(TWOdevice *); +extern void TWOjacCheck(TWOdevice *, BOOLEAN, TWOtranInfo *); + +#endif diff --git a/src/ciderlib/twod/twodopng.c b/src/ciderlib/twod/twodopng.c new file mode 100644 index 000000000..7f252e8d3 --- /dev/null +++ b/src/ciderlib/twod/twodopng.c @@ -0,0 +1,234 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "profile.h" +#include "macros.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" + +/* functions in this file are used to calculate the conc */ + +double +TWOdopingValue(DOPprofile *pProfile, DOPtable *pTable, double x, + double y) +{ + double argX, argY, argP, argL, value = 0.0; + + /* Find the appropriate lookup table if necessary */ + if (pProfile->type == LOOKUP) { + while ( pTable != NIL(DOPtable) ) { + if (pTable->impId == pProfile->IMPID) { + /* Found it */ + break; + } else { + pTable = pTable->next; + } + } + if ( pTable == NIL(DOPtable) ) { + fprintf( stderr, "Error: unknown impurity profile %d\n", + ((int)pProfile->IMPID) ); + exit(1); + } + } + /* Find distances */ + if ( pProfile->Y_LOW > y ) { + argY = pProfile->Y_LOW - y; + } else if ( y > pProfile->Y_HIGH ) { + argY = y - pProfile->Y_HIGH; + } else { + argY = 0.0; + } + if ( pProfile->X_LOW > x ) { + argX = pProfile->X_LOW - x; + } else if ( x > pProfile->X_HIGH ) { + argX = x - pProfile->X_HIGH; + } else { + argX = 0.0; + } + + if ( pProfile->DIRECTION == Y ) { + argP = argY; + argL = argX / pProfile->LAT_RATIO; + } + else { + argP = argX; + argL = argY / pProfile->LAT_RATIO; + } + if ( pProfile->rotate ) { + argP = sqrt(argP*argP + argL*argL); + argL = 0.0; + } + + /* Transform to coordinates of profile peak */ + argP -= pProfile->LOCATION; + argP /= pProfile->CHAR_LENGTH; + argL -= pProfile->LOCATION; + argL /= pProfile->CHAR_LENGTH; + + switch (pProfile->type) { + case UNIF: + if (argP > 0.0) { + value = 0.0; + } else { + value = pProfile->CONC; + } + break; + case LIN: + argP = ABS(argP); + if (argP > 1.0) { + value = 0.0; + } else { + value = pProfile->CONC * ( 1.0 - argP ); + } + break; + case GAUSS: + argP *= argP; + if ( argP > 80.0 ) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * exp( -argP ); + } + case EXP: + argP = ABS(argP); + if ( argP > 80.0 ) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * exp( -argP ); + } + break; + case ERRFC: + argP = ABS(argP); + if ( argP > 10.0 ) { + value = 0.0; + } else { + value = pProfile->PEAK_CONC * erfc( -argP ); + } + break; + case LOOKUP: + argP = ABS(argP); + value = lookup( pTable->dopData, argP ); + break; + default: + break; + } + if (!pProfile->rotate) { /* Tensor product in lateral direction */ + switch (pProfile->latType) { + case UNIF: + if (argL > 0.0) { + value = 0.0; + } + break; + case LIN: + argL = ABS(argL); + if (argL > 1.0) { + value = 0.0; + } else { + value *= ( 1.0 - argL ); + } + break; + case GAUSS: + argL *= argL; + if ( argL > 80.0 ) { + value = 0.0; + } else { + value *= exp( -argL ); + } + case EXP: + argL = ABS(argL); + if ( argL > 80.0 ) { + value = 0.0; + } else { + value *= exp( -argL ); + } + break; + case ERRFC: + argL = ABS(argL); + if ( argP > 10.0 ) { + value = 0.0; + } else { + value *= erfc( -argL ); + } + break; + case LOOKUP: + argL = ABS(argL); + value *= lookup( pTable->dopData, argL ) / lookup( pTable->dopData, 0.0 ); + break; + default: + break; + } + } /* end: not rotated */ + return( value ); +} + +void +TWOsetDoping(TWOdevice *pDevice, DOPprofile *pProfile, DOPtable *pTable) +{ + TWOnode *pNode; + TWOelem *pElem; + DOPprofile *pP; + double conc; + int index, eIndex; + BOOLEAN dopeMe; + + /* Clear doping info for all nodes. */ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + pNode->na = 0.0; + pNode->nd = 0.0; + pNode->netConc = 0.0; + pNode->totalConc = 0.0; + } + } + } + /* Now compute the contribution to the total doping from each profile. */ + for ( pP = pProfile; pP != NIL(DOPprofile); pP = pP->next ) { + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + if ( pElem->elemType == SEMICON ) { + if ( pP->numDomains > 0 ) { + dopeMe = FALSE; + for ( index = 0; index < pP->numDomains; index++ ) { + if ( pElem->domain == pP->domains[ index ] ) { + dopeMe = TRUE; + break; + } + } + } else { /* domains not given, so dope all */ + dopeMe = TRUE; + } + if ( dopeMe ) { + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + conc = TWOdopingValue( pP, pTable, + pDevice->xScale[ pNode->nodeI ], + pDevice->yScale[ pNode->nodeJ ] ); + pNode->netConc += conc; + if ( conc < 0.0 ) { + pNode->totalConc -= conc; + pNode->na -= conc; + } + else { + pNode->totalConc += conc; + pNode->nd += conc; + } + } + } + } + } + } + } +} diff --git a/src/ciderlib/twod/twoelect.c b/src/ciderlib/twod/twoelect.c new file mode 100644 index 000000000..a7a0f9112 --- /dev/null +++ b/src/ciderlib/twod/twoelect.c @@ -0,0 +1,180 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" + +/* Use list-sorting header file to create electrode sorter */ +#define TYPE TWOelectrode +#define NEXT next +#define SORT TWOssortElectrodes +#define SORT1 TWOsortElectrodes +#include "lsort.h" + +#define ARG_MIN(a,b,c) ((a) > (b) ? 1 : ((a) < (b) ? -1 : (c))) + +int +TWOcmpElectrode(TWOelectrode *a, TWOelectrode *b) +{ + return ARG_MIN(a->id, b->id, 0); +} + +/* + * checkElectrodes + * debug list of electrodes for errors, exit on any error: + * 1. list doesn't have only contiguous id's from 1 to idHigh + * 2. electrodes with box shapes + * 3. order of node numbers is correct + */ +void +checkElectrodes(TWOelectrode *pElectrode, int idHigh) +{ + TWOelectrode *pE; + int id; + BOOLEAN error = FALSE; + + /* + * order the electrodes + * assign electrode numbers to uninitialized electrodes ( id == -1 ) + * uninit electrodes are in reverse order from order in input file + * resort electrodes + */ + pElectrode = TWOsortElectrodes( pElectrode, TWOcmpElectrode ); + id = 1; + for (pE = pElectrode; pE != NIL(TWOelectrode); pE = pE->next) { + if (pE->id == -1) pE->id = id++; + } + pElectrode = TWOsortElectrodes( pElectrode, TWOcmpElectrode ); + + for (pE = pElectrode, id = 1; pE != NIL(TWOelectrode); pE = pE->next) { + /* Check id's */ + if ( pE->id < 1 || pE->id > idHigh) { + fprintf(stderr, "Error: electrode %d out of range\n",pE->id); + error = TRUE; + } else if ( pE->id != id ) { + if ( pE->id != ++id ) { + fprintf(stderr, "Error: electrode(s) %d to %d missing\n",id,pE->id-1); + id = pE->id; + error = TRUE; + } + } + } + + /* Make sure total number of distinct electrodes is correct */ + if ( id != idHigh ) { + fprintf(stderr, "Error: %d electrode%s not equal to %d required\n", + id, (id != 1) ? "s are" : " is", idHigh); + error = TRUE; + } + + if (error) { + exit(-1); + } +} + +/* + * setupContacts + * go through each set of electrode segments, find its size and find nodes + */ + + +void +setupContacts(TWOdevice *pDevice, TWOelectrode *pElectrode, + TWOnode ***nodeArray) +{ + TWOelectrode *pE; + TWOcontact *pNew = NULL, *pTail; + TWOnode *pNode; + int xIndex, yIndex, nIndex = 0; + int id = 0, electrodeSize[MAXTERMINALS], electrodeType; + int error = FALSE; + + /* INITIALIZATION + * 0. Assume ELCT_ID's are initialized to 0 before setup is called + * 1. Add a node only once to only one electrode + * 2. Compute number of nodes in each electrode + * 3. Overwrite SEMICON or INSULATOR nodeType at electrodes + */ + for ( pE = pElectrode; pE != NIL(TWOelectrode); pE = pE->next ) { + if (pE->id != id) { /* Found the next electrode */ + id = pE->id; + electrodeSize[id] = 0; + electrodeType = 0; + } + for ( xIndex = pE->ixLo; xIndex <= pE->ixHi; xIndex++ ) { + for ( yIndex = pE->iyLo; yIndex <= pE->iyHi; yIndex++ ) { + pNode = nodeArray[ xIndex ][ yIndex ]; + if ( pNode != NIL( TWOnode ) ) { + pNode->nodeType = CONTACT; + + /* Assign each node to an electrode only once */ + if (pNode->ELCT_ID == 0) { /* Is this a new node? */ + pNode->ELCT_ID = id; /* Mark electrode ownership */ + electrodeSize[id]++; /* Increase electrode size */ + } else if (pNode->ELCT_ID != id) { + /* Node already owned by another electrode */ + fprintf(stderr, "Error: electrodes %d and %d overlap at (%d,%d)\n", + pNode->ELCT_ID, id, xIndex, yIndex); + error = TRUE; + } + } + } + } + } + if (error) { + exit(-1); + } + + /* ALLOCATION AND NODE GRABBING + * 1. For each electrode, create a new contact structure for the electrode. + * 2. Fill in entries of contact structure + * 3. Update First and Last Contact pointers of device + */ +/* + printElectrodes( pDevice->pFirstContact ); +*/ + id = 0; + pDevice->pFirstContact = pTail = NIL(TWOcontact); + for ( pE = pElectrode; pE != NIL(TWOelectrode); pE = pE->next ) { + if (pE->id != id) { /* Found the next electrode */ + if ( pDevice->pFirstContact == NIL(TWOcontact) ) { + XCALLOC( pNew, TWOcontact, 1 ); + pDevice->pFirstContact = pTail = pNew; + } else { + XCALLOC( pNew->next, TWOcontact, 1 ); + pTail = pNew = pNew->next; + } + pNew->next = NIL(TWOcontact); + id = pNew->id = pE->id; + pNew->workf = pE->workf; + pNew->numNodes = electrodeSize[id]; + nIndex = 0; + XCALLOC( pNew->pNodes, TWOnode *, electrodeSize[id] ); + } + for ( xIndex = pE->ixLo; xIndex <= pE->ixHi; xIndex++ ) { + for ( yIndex = pE->iyLo; yIndex <= pE->iyHi; yIndex++ ) { + pNode = nodeArray[ xIndex ][ yIndex ]; + if ( pNode != NIL( TWOnode ) ) { + /* Make sure node belongs to this electrode, then + * clear ELCT_ID so that it is not grabbed again. + */ + if (pNode->ELCT_ID == id) { + pNew->pNodes[ nIndex++ ] = pNode; + pNode->ELCT_ID = 0; + } + } + } + } + } + pDevice->pLastContact = pTail; +} diff --git a/src/ciderlib/twod/twofield.c b/src/ciderlib/twod/twofield.c new file mode 100644 index 000000000..6ef7766c0 --- /dev/null +++ b/src/ciderlib/twod/twofield.c @@ -0,0 +1,125 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twoddefs.h" +#include "twodext.h" + +void +nodeFields(TWOelem *pElem, TWOnode *pNode, double *ex, double *ey) +{ + + TWOelem *pElemTL, *pElemTR, *pElemBL, *pElemBR; + TWOedge *pEdgeT, *pEdgeB, *pEdgeL, *pEdgeR; + int materT, materB, materL, materR; + double dxL = 0.0, dxR = 0.0, dyT = 0.0, dyB = 0.0; + double ef1, ef2, coeff1, coeff2; + + /* Find all four neighboring elements */ + pElemTL = pNode->pTLElem; + pElemTR = pNode->pTRElem; + pElemBL = pNode->pBLElem; + pElemBR = pNode->pBRElem; + + /* Null edge pointers */ + pEdgeT = pEdgeB = pEdgeL = pEdgeR = NIL(TWOedge); + + /* Find edges next to node */ + if (pElemTL != NIL(TWOelem)) { + if (pElemTL->evalEdges[1]) { + pEdgeT = pElemTL->pRightEdge; + materT = pElemTL->elemType; + dyT = pElemTL->dy; + } + if (pElemTL->evalEdges[2]) { + pEdgeL = pElemTL->pBotEdge; + materL = pElemTL->elemType; + dxL = pElemTL->dx; + } + } + if (pElemTR != NIL(TWOelem)) { + if (pElemTR->evalEdges[3]) { + pEdgeT = pElemTR->pLeftEdge; + materT = pElemTR->elemType; + dyT = pElemTR->dy; + } + if (pElemTR->evalEdges[2]) { + pEdgeR = pElemTR->pBotEdge; + materR = pElemTR->elemType; + dxR = pElemTR->dx; + } + } + if (pElemBR != NIL(TWOelem)) { + if (pElemBR->evalEdges[3]) { + pEdgeB = pElemBR->pLeftEdge; + materB = pElemBR->elemType; + dyB = pElemBR->dy; + } + if (pElemBR->evalEdges[0]) { + pEdgeR = pElemBR->pTopEdge; + materR = pElemBR->elemType; + dxR = pElemBR->dx; + } + } + if (pElemBL != NIL(TWOelem)) { + if (pElemBL->evalEdges[1]) { + pEdgeB = pElemBL->pRightEdge; + materB = pElemBL->elemType; + dyB = pElemBL->dy; + } + if (pElemBL->evalEdges[0]) { + pEdgeL = pElemBL->pTopEdge; + materL = pElemBL->elemType; + dxL = pElemBL->dx; + } + } + /* compute horizontal vector components */ + /* No more than one of Left Edge or Right Edge is absent */ + if (pEdgeL == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *ex = -pEdgeR->dPsi / dxR; + } else { + *ex = 0.0; + } + } else if (pEdgeR == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *ex = -pEdgeL->dPsi / dxL; + } else { + *ex = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dxL / (dxL + dxR); + coeff2 = dxR / (dxL + dxR); + ef1 = -pEdgeL->dPsi / dxL; + ef2 = -pEdgeR->dPsi / dxR; + *ex = coeff2 * ef1 + coeff1 * ef2; + } + + /* compute vertical vector components */ + /* No more than one of Top Edge or Bottom Edge is absent */ + if (pEdgeT == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *ey = -pEdgeB->dPsi / dyB; + } else { + *ey = 0.0; + } + } else if (pEdgeB == NIL(TWOedge)) { + if (pNode->nodeType == CONTACT) { + *ey = -pEdgeT->dPsi / dyT; + } else { + *ey = 0.0; + } + } else { /* Both edges are present */ + coeff1 = dyT / (dyT + dyB); + coeff2 = dyB / (dyT + dyB); + ef1 = -pEdgeT->dPsi / dyT; + ef2 = -pEdgeB->dPsi / dyB; + *ey = coeff2 * ef1 + coeff1 * ef2; + } +} diff --git a/src/ciderlib/twod/twomesh.c b/src/ciderlib/twod/twomesh.c new file mode 100644 index 000000000..a2cfd6f34 --- /dev/null +++ b/src/ciderlib/twod/twomesh.c @@ -0,0 +1,626 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" + + + +static void doMobCoeffs(TWOelem *, int); +static void resetEvalFlag(TWOdevice *pDevice); + + +void +TWObuildMesh(TWOdevice *pDevice, TWOdomain *pDomain, + TWOelectrode *pElectrode, TWOmaterial *pMaterial) +{ + int xIndex, yIndex, eIndex, index; + int elemType; + TWOcoord *pX, *pY; + TWOelem *pElem, *pElem1; + TWOnode *pNode, *pNode1, *pNextHNode, *pNextVNode, *pNextDNode; + TWOnode ***nodeArray = NULL; + TWOedge *pEdge; + TWOedge ***edgeArrayH = NULL, ***edgeArrayV = NULL; + TWOdomain *pD; + TWOelectrode *pE; + TWOmaterial *pM; + BOOLEAN error = FALSE; + BOOLEAN interiorNode; + int poiEqn, numEqn, numElem, numNodes, numEdges; + int numXNodes = pDevice->numXNodes; + int numYNodes = pDevice->numYNodes; + double *xScale = pDevice->xScale; + double *yScale = pDevice->yScale; + FILE *meshFile; + + /* Generate work arrays. */ + XCALLOC(nodeArray, TWOnode **, 1 + numXNodes); + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + XCALLOC(nodeArray[xIndex], TWOnode *, 1 + numYNodes); + } + + /* Generate the nodes. */ + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + for (yIndex = 1; yIndex <= numYNodes; yIndex++) { + XCALLOC(pNode, TWOnode, 1); + pNode->nodeI = xIndex; + pNode->nodeJ = yIndex; + pNode->poiEqn = 0; + nodeArray[xIndex][yIndex] = pNode; + } + } + + /* Mark the semiconductor/insulator domains. */ + if (pDomain == NIL(TWOdomain)) { + fprintf(stderr, "Error: domains not defined for device\n"); + exit(-1); + } + for (pD = pDomain; pD != NIL(TWOdomain); pD = pD->next) { + for (pM = pMaterial; pM != NIL(TWOmaterial); pM = pM->next) { + if (pD->material == pM->id) { + break; + } + } + elemType = pM->type; + for (xIndex = pD->ixLo; xIndex <= pD->ixHi; xIndex++) { + for (yIndex = pD->iyLo; yIndex <= pD->iyHi; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + pNode->nodeType = elemType; + } + } + } + /* Now mark all the metallic domains */ + for (pE = pElectrode; pE != NIL(TWOelectrode); pE = pE->next) { + for (xIndex = pE->ixLo; xIndex <= pE->ixHi; xIndex++) { + for (yIndex = pE->iyLo; yIndex <= pE->iyHi; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + pNode->nodeType = CONTACT; + } + } + } + /* + * Now mark as NULL any node in the interior of an electrode, i.e. none of + * its neighbors is a different material. + */ + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + for (yIndex = 1; yIndex <= numYNodes; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + if (pNode->nodeType == CONTACT) { + interiorNode = TRUE; + if (xIndex > 1) { + pNode1 = nodeArray[xIndex - 1][yIndex]; + if (pNode1->nodeType != NULL + && pNode1->nodeType != CONTACT) { + interiorNode = FALSE; + } + } + if (interiorNode && xIndex < numXNodes) { + pNode1 = nodeArray[xIndex + 1][yIndex]; + if (pNode1->nodeType != NULL + && pNode1->nodeType != CONTACT) { + interiorNode = FALSE; + } + } + if (interiorNode && yIndex > 1) { + pNode1 = nodeArray[xIndex][yIndex - 1]; + if (pNode1->nodeType != NULL + && pNode1->nodeType != CONTACT) { + interiorNode = FALSE; + } + } + if (interiorNode && yIndex < numYNodes) { + pNode1 = nodeArray[xIndex][yIndex + 1]; + if (pNode1->nodeType != NULL + && pNode1->nodeType != CONTACT) { + interiorNode = FALSE; + } + } + if (interiorNode) { + pNode->nodeType = NULL; + } + } + } + } + + /* Delete nodes that do not belong to any domain. */ + numNodes = 0; + for (yIndex = 1; yIndex <= numYNodes; yIndex++) { + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + pNode = nodeArray[xIndex][yIndex]; + if (pNode->nodeType == NULL) { + /* This node doesn't belong to a domain so delete it. */ + nodeArray[xIndex][yIndex] = NIL(TWOnode); + FREE(pNode); + } else { + numNodes++; + } + } + } + pDevice->numNodes = numNodes; + + /* Now relabel any remaining nodes that are part of electrodes. */ + setupContacts(pDevice, pElectrode, nodeArray); + + /* Done with the nodes. Now construct the elements and the edges. */ + numEdges = 0; + + /* Generate the horizontal edges and store in a work array. */ + XCALLOC(edgeArrayH, TWOedge **, numXNodes); + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + XCALLOC(edgeArrayH[xIndex], TWOedge *, 1 + numYNodes); + } + for (yIndex = 1; yIndex <= numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pNode = nodeArray[xIndex][yIndex]; + pNextHNode = nodeArray[xIndex + 1][yIndex]; + if (pNode != NIL(TWOnode) && + pNextHNode != NIL(TWOnode)) { + XCALLOC(pEdge, TWOedge, 1); + numEdges++; + edgeArrayH[xIndex][yIndex] = pEdge; + } + } + } + + /* Generate the vertical edges and store in a work array. */ + XCALLOC(edgeArrayV, TWOedge **, 1 + numXNodes); + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + XCALLOC(edgeArrayV[xIndex], TWOedge *, numYNodes); + } + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + pNextVNode = nodeArray[xIndex][yIndex + 1]; + if (pNode != NIL(TWOnode) && + pNextVNode != NIL(TWOnode)) { + XCALLOC(pEdge, TWOedge, 1); + numEdges++; + edgeArrayV[xIndex][yIndex] = pEdge; + } + } + } + pDevice->numEdges = numEdges; + + /* Now construct and count the elements and store the node/edge pointers. */ + numElem = 1; + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pNode = nodeArray[xIndex][yIndex]; + pNextHNode = nodeArray[xIndex + 1][yIndex]; + pNextVNode = nodeArray[xIndex][yIndex + 1]; + pNextDNode = nodeArray[xIndex + 1][yIndex + 1]; + if (pNode != NIL(TWOnode) && + pNextHNode != NIL(TWOnode) && + pNextVNode != NIL(TWOnode) && + pNextDNode != NIL(TWOnode)) { + numElem++; + XCALLOC(pElem, TWOelem, 1); + pElem->pTLNode = pNode; + pElem->pTRNode = pNextHNode; + pElem->pBLNode = pNextVNode; + pElem->pBRNode = pNextDNode; + pElem->pTopEdge = edgeArrayH[xIndex][yIndex]; + pElem->pBotEdge = edgeArrayH[xIndex][yIndex + 1]; + pElem->pLeftEdge = edgeArrayV[xIndex][yIndex]; + pElem->pRightEdge = edgeArrayV[xIndex + 1][yIndex]; + pDevice->elemArray[xIndex][yIndex] = pElem; + } else { + pDevice->elemArray[xIndex][yIndex] = NIL(TWOelem); + } + } + } + + /* Create and pack the 1D element array. */ + pDevice->numElems = numElem - 1; + XCALLOC(pDevice->elements, TWOelem *, 1 + numElem); + numElem = 1; + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + pDevice->elements[numElem++] = pElem; + } + } + } + + /* Now create back links from elements to nodes. */ + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + pElem->pTLNode->pBRElem = pElem; + pElem->pTRNode->pBLElem = pElem; + pElem->pBLNode->pTRElem = pElem; + pElem->pBRNode->pTLElem = pElem; + if (xIndex > 1) { + pElem->pLeftElem = pDevice->elemArray[xIndex-1][yIndex]; + } + if (xIndex < numXNodes - 1) { + pElem->pRightElem = pDevice->elemArray[xIndex+1][yIndex]; + } + if (yIndex > 1) { + pElem->pTopElem = pDevice->elemArray[xIndex][yIndex-1]; + } + if (yIndex < numYNodes - 1) { + pElem->pBotElem = pDevice->elemArray[xIndex][yIndex+1]; + } + } + } + } + + /* Establish the element types using domain info. */ + for (pD = pDomain; pD != NIL(TWOdomain); pD = pD->next) { + for (pM = pMaterial; pM != NIL(TWOmaterial); pM = pM->next) { + if (pD->material == pM->id) { + break; + } + } + elemType = pM->type; + for (yIndex = pD->iyLo; yIndex < pD->iyHi; yIndex++) { + for (xIndex = pD->ixLo; xIndex < pD->ixHi; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + pElem->domain = pD->id; + pElem->elemType = elemType; + pElem->matlInfo = pM; + } + } + } + } + + /* Establish the edge types. */ + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + for (index = 0; index <= 3; index++) { + pEdge = pElem->pEdges[index]; + pNode = pElem->pNodes[index]; + pNode1 = pElem->pNodes[(index + 1) % 4]; + pElem1 = pNode1->pElems[index]; + + if (pNode->nodeType == CONTACT && + pNode1->nodeType == CONTACT) { + /* Contact edge */ + pEdge->edgeType = CONTACT; + } else if (pNode->nodeType == SCHOTTKY && + pNode1->nodeType == SCHOTTKY) { + /* Schottky edge */ + pEdge->edgeType = SCHOTTKY; + } else if (pElem1 == NIL(TWOelem)) { + /* Neumann edge */ + pEdge->edgeType = pElem->elemType; + } else if (pElem->elemType != pElem1->elemType) { + /* Interface edge */ + pEdge->edgeType = INTERFACE; + } else { /* Ignore heterojnxns for now */ + /* Heterojunction or Homojunction edge */ + pEdge->edgeType = pElem->elemType; + } + } + } + } + } + + resetEvalFlag(pDevice); + /* Set evaluation flags on nodes and edges. */ + /* Assign the dx and dy terms in the elements while we're at it. */ + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + pElem->dx = xScale[xIndex + 1] - xScale[xIndex]; + pElem->dy = yScale[yIndex + 1] - yScale[yIndex]; + pElem->dxOverDy = pElem->dx / pElem->dy; + pElem->dyOverDx = pElem->dy / pElem->dx; + + /* + * Semiconductor elements take precedence over Insulator elements, so + * set them up first. + */ + for (index = 0; index <= 3; index++) { + if (pElem->elemType == SEMICON) { + pNode = pElem->pNodes[index]; + if (!pNode->evaluated) { + pNode->evaluated = TRUE; + pElem->evalNodes[index] = TRUE; + } else { + pElem->evalNodes[index] = FALSE; + } + pEdge = pElem->pEdges[index]; + if (!pEdge->evaluated) { + pEdge->evaluated = TRUE; + pElem->evalEdges[index] = TRUE; + } else { + pElem->evalEdges[index] = FALSE; + } + } + } + } + } + } + + /* Do a second setup pass for Insulator elements */ + /* Do mobility coefficients now, because we set up dx and dy + * in the previous pass + */ + for (yIndex = 1; yIndex < numYNodes; yIndex++) { + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + pElem->direction = 0; + pElem->channel = 0; + pElem->surface = FALSE; + for (index = 0; index <= 3; index++) { + if (pElem->elemType == SEMICON) { + doMobCoeffs( pElem, index ); + } else if (pElem->elemType == INSULATOR) { + pNode = pElem->pNodes[index]; + if (!pNode->evaluated) { + pNode->evaluated = TRUE; + pElem->evalNodes[index] = TRUE; + } else { + pElem->evalNodes[index] = FALSE; + } + pEdge = pElem->pEdges[index]; + if (!pEdge->evaluated) { + pEdge->evaluated = TRUE; + pElem->evalEdges[index] = TRUE; + } else { + pElem->evalEdges[index] = FALSE; + } + } + } + } + } + } + + /* Set up the equation numbers for nodes. */ + poiEqn = numEqn = 1; + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + /* First assign potential equation numbers */ + if (pNode->nodeType != SCHOTTKY) { + pNode->poiEqn = poiEqn++; + pNode->psiEqn = numEqn++; + } + /* Now assign carrier-concentration equation numbers */ + if (pElem->elemType == INSULATOR) { + pNode->nEqn = 0; + pNode->pEqn = 0; + } else { + if (OneCarrier) { + /* n and p get same number */ + pNode->nEqn = numEqn; + pNode->pEqn = numEqn++; + } else { + pNode->nEqn = numEqn++; + pNode->pEqn = numEqn++; + } + } + } else { /* This is a contact node. */ + pNode->poiEqn = 0; + pNode->psiEqn = 0; + pNode->nEqn = 0; + pNode->pEqn = 0; + } + } + } + } + pDevice->dimEquil = poiEqn; + pDevice->dimBias = numEqn; + + /* Open and Print Mesh Output File for Debugging */ + /* Nuked from release version */ +#ifdef NOTDEF + if (!(meshFile = fopen("mesh.out", "w"))) { + perror("mesh.out"); + exit(-1); + } + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + fprintf(meshFile, "node: %5d %5d %5d %5d\n", pNode->nodeI, + pNode->nodeJ, pNode->poiEqn, pNode->psiEqn); + } + } + } + fflush(meshFile); + fclose(meshFile); +#endif + + /* Delete work arrays. */ + for (xIndex = 1; xIndex <= numXNodes; xIndex++) { + FREE(nodeArray[xIndex]); + FREE(edgeArrayV[xIndex]); + } + for (xIndex = 1; xIndex < numXNodes; xIndex++) { + FREE(edgeArrayH[xIndex]); + } + FREE(nodeArray); + FREE(edgeArrayV); + FREE(edgeArrayH); + + /* + * TWOprnMesh( pDevice ); + */ +} + +void +TWOprnMesh(TWOdevice *pDevice) +{ + int eIndex, index; + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pEdge; + char *name; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + fprintf(stderr, "elem %5d:\n", eIndex); + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + switch (pNode->nodeType) { + case SEMICON: + name = "semiconductor"; + break; + case INSULATOR: + name = "insulator"; + break; + case CONTACT: + name = "contact"; + break; + case SCHOTTKY: + name = "schottky"; + break; + case INTERFACE: + name = "interface"; + break; + default: + name = "unknown"; + break; + } + fprintf(stderr, "node %5d: %s %5d %5d\n", index, name, + pNode->nodeI, pNode->nodeJ); + } + if (pElem->evalEdges[index]) { + pEdge = pElem->pEdges[index]; + switch (pEdge->edgeType) { + case SEMICON: + name = "semiconductor"; + break; + case INSULATOR: + name = "insulator"; + break; + case CONTACT: + name = "contact"; + break; + case SCHOTTKY: + name = "schottky"; + break; + case INTERFACE: + name = "interface"; + break; + default: + name = "unknown"; + break; + } + fprintf(stderr, "edge %5d: %s\n", index, name); + } + } + } +} + +/* + * We have a separate function for this, so that the setup routines can + * reset the state pointers without rebuilding the entire mesh. + */ +void +TWOgetStatePointers(TWOdevice *pDevice, int *numStates) +{ + int eIndex, index; + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pEdge; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + pNode->nodeState = *numStates; + *numStates += TWOnumNodeStates; + } + if (pElem->evalEdges[index]) { + pEdge = pElem->pEdges[index]; + pEdge->edgeState = *numStates; + *numStates += TWOnumEdgeStates; + } + } + } +} + +/* + * This function computes the percentages of the total semiconductor + * width of an edge on the negative and positive sides of the edge + */ +static void +doMobCoeffs(TWOelem *pElem, int index) +{ + TWOelem *pNElem; + TWOedge *pEdge; + double dl1 = 0.0, dl2 = 0.0; + + pNElem = pElem->pElems[ index ]; + pEdge = pElem->pEdges[ index ]; + + /* If neighbor is not SEMICON, assign and return */ + if ( (pNElem == NIL(TWOelem)) || (pNElem->elemType == INSULATOR) ) { + if ( index == 0 || index == 3 ) { + pEdge->kNeg = 0.0; + pEdge->kPos = 1.0; + } else { + pEdge->kNeg = 1.0; + pEdge->kPos = 0.0; + } + return; + } + + /* Find appropriate dimensions of the elements */ + switch ( index ) { + case 0: + dl1 = pNElem->dy; + dl2 = pElem->dy; + break; + case 1: + dl1 = pElem->dx; + dl2 = pNElem->dx; + break; + case 2: + dl1 = pElem->dy; + dl2 = pNElem->dy; + break; + case 3: + dl1 = pNElem->dx; + dl2 = pElem->dx; + break; + } + + /* Assign coefficients */ + pEdge->kNeg = dl1 / (dl1 + dl2); + pEdge->kPos = dl2 / (dl1 + dl2); + +} + +static void +resetEvalFlag(TWOdevice *pDevice) +{ + int index, eIndex; + TWOelem *pElem; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + pElem->pNodes[index]->evaluated = FALSE; + pElem->pEdges[index]->evaluated = FALSE; + } + } +} diff --git a/src/ciderlib/twod/twomobdv.c b/src/ciderlib/twod/twomobdv.c new file mode 100644 index 000000000..b7c054610 --- /dev/null +++ b/src/ciderlib/twod/twomobdv.c @@ -0,0 +1,1399 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "twomesh.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" + +/* + * Load the derivatives of the current with respect to changes in + * the mobility, for all edges of a silicon element. + * It is known a priori that the element is made of semiconductor. + * These routines work for both channel and bulk elements. + */ + +void + TWO_mobDeriv( TWOelem* pElem, int chanType, double ds ) + /* TWOelem *pElem: channel or bulk element */ + /* int chanType: flag for direction of channel */ + /* double ds: normalized hgt (len) of interface element */ +{ + TWOnode *pNode; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + BOOLEAN channel = pElem->channel; + double dx, dy, rDx, rDy; + double coeffHx, coeffHy, coeffHs = 0.0; + double coeffVx, coeffVy, coeffVs = 0.0; + double dFnxDMun, dFnyDMun, dFnDEs = 0.0; + double dFpxDMup, dFpyDMup, dFpDEs = 0.0; + double dMnDEs, dMnDEx, dMnDEy, dMnDWx, dMnDWy; + double dMpDEs, dMpDEx, dMpDEy, dMpDWx, dMpDWy; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 1.0 / dx; + rDy = 1.0 / dy; + + /* compute length-dependent parameters */ + coeffHx = 0.25 * dy * rDx; /* For horizontal edges */ + coeffHy = 0.25; + coeffVx = 0.25; /* For vertical edges */ + coeffVy = 0.25 * dx * rDy; + switch ( chanType ) { + case 0: + case 3: + coeffHs = 0.25 * dy / ds; + coeffVs = 0.25 * dx / ds; + break; + case 1: + case 2: + coeffHs = - 0.25 * dy / ds; + coeffVs = - 0.25 * dx / ds; + break; + } + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Get element mobility derivatives for fast access later */ + dMnDEs = pElem->dMunDEs; + dMnDEx = pElem->dMunDEx; + dMnDEy = pElem->dMunDEy; + dMnDWx = pElem->dMunDWx; + dMnDWy = pElem->dMunDWy; + dMpDEs = pElem->dMupDEs; + dMpDEx = pElem->dMupDEx; + dMpDEy = pElem->dMupDEy; + dMpDWx = pElem->dMupDWx; + dMpDWy = pElem->dMupDWy; + + /* Add mobility derivatives due to Top Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffHx * (pTEdge->wdfn * rDx); + dFnyDMun = coeffHy * (pTEdge->wdfn * rDx); + dFpxDMup = coeffHx * (pTEdge->wdfp * rDx); + dFpyDMup = coeffHy * (pTEdge->wdfp * rDx); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffHs * (pTEdge->wdfn * rDx) * dMnDEs; + dFpDEs = coeffHs * (pTEdge->wdfp * rDx) * dMpDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1jP1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1jP1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNjP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1jP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPjP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1jP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + + /* Add mobility derivatives due to Bottom Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffHx * (pBEdge->wdfn * rDx); + dFnyDMun = coeffHy * (pBEdge->wdfn * rDx); + dFpxDMup = coeffHx * (pBEdge->wdfp * rDx); + dFpyDMup = coeffHy * (pBEdge->wdfp * rDx); + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsijM1) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jM1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsijM1) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jM1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNjM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1jM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPjM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1jM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if (channel ) { + dFnDEs = coeffHs * (pBEdge->wdfn * rDx) * dMnDEs; + dFpDEs = coeffHs * (pBEdge->wdfp * rDx) * dMpDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1jM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsijM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1jM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsijM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + /* Add mobility derivatives due to Left Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffVx * (pLEdge->wdfn * rDy); + dFnyDMun = coeffVy * (pLEdge->wdfn * rDy); + dFpxDMup = coeffVx * (pLEdge->wdfp * rDy); + dFpyDMup = coeffVy * (pLEdge->wdfp * rDy); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffVs * (pLEdge->wdfn * rDy) * dMnDEs; + dFpDEs = coeffVs * (pLEdge->wdfp * rDy) * dMpDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsijM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsijM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiInP1) += dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + *(pNode->fNPsiOxP1) -= dFnDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiInP1) += dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + *(pNode->fPPsiOxP1) -= dFpDEs; + } + + /* Add mobility derivatives due to Right Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffVx * (pREdge->wdfn * rDy); + dFnyDMun = coeffVy * (pREdge->wdfn * rDy); + dFpxDMup = coeffVx * (pREdge->wdfp * rDy); + dFpyDMup = coeffVy * (pREdge->wdfp * rDy); + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsi) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1jP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsi) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1jP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffVs * (pREdge->wdfn * rDy) * dMnDEs; + dFpDEs = coeffVs * (pREdge->wdfp * rDy) * dMpDEs; + *(pNode->fNPsiInM1) -= dFnDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiOxM1) += dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fPPsiInM1) -= dFpDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiOxM1) += dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1jM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsijM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1jM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsijM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + return; +} + +void + TWONmobDeriv(TWOelem *pElem, int chanType, double ds) + /* TWOelem *pElem: channel or bulk element */ + /* int chanType: flag for direction of channel */ + /* double ds: normalized hgt (len) of interface element */ +{ + TWOnode *pNode; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + BOOLEAN channel = pElem->channel; + double dx, dy, rDx, rDy; + double coeffHx, coeffHy, coeffHs = 0.0; + double coeffVx, coeffVy, coeffVs = 0.0; + double dFnxDMun, dFnyDMun, dFnDEs = 0.0; + double dMnDEs, dMnDEx, dMnDEy, dMnDWx, dMnDWy; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 1.0 / dx; + rDy = 1.0 / dy; + + /* compute length-dependent parameters */ + coeffHx = 0.25 * dy * rDx; /* For horizontal edges */ + coeffHy = 0.25; + coeffVx = 0.25; /* For vertical edges */ + coeffVy = 0.25 * dx * rDy; + switch ( chanType ) { + case 0: + case 3: + coeffHs = 0.25 * dy / ds; + coeffVs = 0.25 * dx / ds; + break; + case 1: + case 2: + coeffHs = - 0.25 * dy / ds; + coeffVs = - 0.25 * dx / ds; + break; + } + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Get element mobility derivatives for fast access later */ + dMnDEs = pElem->dMunDEs; + dMnDEx = pElem->dMunDEx; + dMnDEy = pElem->dMunDEy; + dMnDWx = pElem->dMunDWx; + dMnDWy = pElem->dMunDWy; + + /* Add mobility derivatives due to Top Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffHx * (pTEdge->wdfn * rDx); + dFnyDMun = coeffHy * (pTEdge->wdfn * rDx); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffHs * (pTEdge->wdfn * rDx) * dMnDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + } + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1jP1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNjP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1jP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + } + + + /* Add mobility derivatives due to Bottom Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffHx * (pBEdge->wdfn * rDx); + dFnyDMun = coeffHy * (pBEdge->wdfn * rDx); + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsijM1) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jM1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNjM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1jM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if (channel ) { + dFnDEs = coeffHs * (pBEdge->wdfn * rDx) * dMnDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1jM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsijM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + } + + /* Add mobility derivatives due to Left Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffVx * (pLEdge->wdfn * rDy); + dFnyDMun = coeffVy * (pLEdge->wdfn * rDy); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsi) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffVs * (pLEdge->wdfn * rDy) * dMnDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiInP1) -= dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + *(pNode->fNPsiOxP1) += dFnDEs; + } + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsijM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1jM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiP1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNiP1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNiP1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiInP1) += dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + *(pNode->fNPsiOxP1) -= dFnDEs; + } + + /* Add mobility derivatives due to Right Edge */ + /* First compute derivatives of cont. eqn's */ + dFnxDMun = coeffVx * (pREdge->wdfn * rDy); + dFnyDMun = coeffVy * (pREdge->wdfn * rDy); + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1) += + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsi) += + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsijP1) += + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1jP1) += + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNN) += + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNNjP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1jP1) += + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFnDEs = coeffVs * (pREdge->wdfn * rDy) * dMnDEs; + *(pNode->fNPsiInM1) -= dFnDEs; + *(pNode->fNPsiIn) -= dFnDEs; + *(pNode->fNPsiOxM1) += dFnDEs; + *(pNode->fNPsiOx) += dFnDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* n continuity wrto potential derivatives */ + *(pNode->fNPsiiM1jM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + *(pNode->fNPsijM1) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pTEdge->dWnDpsiP1 ) + + dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsi) -= + - dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pREdge->dWnDpsiP1 ); + *(pNode->fNPsiiM1) -= + dFnxDMun * ( dMnDEx - dMnDWx * pBEdge->dWnDpsiP1 ) + - dFnyDMun * ( dMnDEy - dMnDWy * pLEdge->dWnDpsiP1 ); + /* n continuity wrto n derivatives */ + *(pNode->fNNiM1jM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDn ); + *(pNode->fNNjM1) -= + dFnxDMun * ( dMnDWx * pTEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDn ); + *(pNode->fNN) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDnP1 ) + + dFnyDMun * ( dMnDWy * pREdge->dWnDnP1 ); + *(pNode->fNNiM1) -= + dFnxDMun * ( dMnDWx * pBEdge->dWnDn ) + + dFnyDMun * ( dMnDWy * pLEdge->dWnDnP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fNPsiInM1) += dFnDEs; + *(pNode->fNPsiIn) += dFnDEs; + *(pNode->fNPsiOxM1) -= dFnDEs; + *(pNode->fNPsiOx) -= dFnDEs; + } + + return; +} + +void + TWOPmobDeriv( TWOelem *pElem, int chanType, double ds ) + /* TWOelem *pElem: channel or bulk element */ + /*int chanType: flag for direction of channel */ + /*double ds: normalized hgt (len) of interface element */ +{ + TWOnode *pNode; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + BOOLEAN channel = pElem->channel; + double dx, dy, rDx, rDy; + double coeffHx, coeffHy, coeffHs = 0.0; + double coeffVx, coeffVy, coeffVs = 0.0; + double dFpxDMup, dFpyDMup, dFpDEs = 0.0; + double dMpDEs, dMpDEx, dMpDEy, dMpDWx, dMpDWy; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 1.0 / dx; + rDy = 1.0 / dy; + + /* compute length-dependent parameters */ + coeffHx = 0.25 * dy * rDx; /* For horizontal edges */ + coeffHy = 0.25; + coeffVx = 0.25; /* For vertical edges */ + coeffVy = 0.25 * dx * rDy; + switch ( chanType ) { + case 0: + case 3: + coeffHs = 0.25 * dy / ds; + coeffVs = 0.25 * dx / ds; + break; + case 1: + case 2: + coeffHs = - 0.25 * dy / ds; + coeffVs = - 0.25 * dx / ds; + break; + } + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Get element mobility derivatives for fast access later */ + dMpDEs = pElem->dMupDEs; + dMpDEx = pElem->dMupDEx; + dMpDEy = pElem->dMupDEy; + dMpDWx = pElem->dMupDWx; + dMpDWy = pElem->dMupDWy; + + /* Add mobility derivatives due to Top Edge */ + /* First compute derivatives of cont. eqn's */ + dFpxDMup = coeffHx * (pTEdge->wdfp * rDx); + dFpyDMup = coeffHy * (pTEdge->wdfp * rDx); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFpDEs = coeffHs * (pTEdge->wdfp * rDx) * dMpDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1jP1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPjP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1jP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + + /* Add mobility derivatives due to Bottom Edge */ + /* First compute derivatives of cont. eqn's */ + dFpxDMup = coeffHx * (pBEdge->wdfp * rDx); + dFpyDMup = coeffHy * (pBEdge->wdfp * rDx); + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsijM1) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jM1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPjM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1jM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if (channel ) { + dFpDEs = coeffHs * (pBEdge->wdfp * rDx) * dMpDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1jM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsijM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + /* Add mobility derivatives due to Left Edge */ + /* First compute derivatives of cont. eqn's */ + dFpxDMup = coeffVx * (pLEdge->wdfp * rDy); + dFpyDMup = coeffVy * (pLEdge->wdfp * rDy); + + /* Do Top-Left (TL) Node of Element */ + pNode = pElem->pTLNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsi) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFpDEs = coeffVs * (pLEdge->wdfp * rDy) * dMpDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiInP1) -= dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + *(pNode->fPPsiOxP1) += dFpDEs; + } + + /* Do Bottom-Left (BL) Node of Element */ + pNode = pElem->pBLNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsijM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1jM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiP1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPiP1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPiP1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiInP1) += dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + *(pNode->fPPsiOxP1) -= dFpDEs; + } + + /* Add mobility derivatives due to Right Edge */ + /* First compute derivatives of cont. eqn's */ + dFpxDMup = coeffVx * (pREdge->wdfp * rDy); + dFpyDMup = coeffVy * (pREdge->wdfp * rDy); + + /* Do Top-Right (TR) Node of Element */ + pNode = pElem->pTRNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1) += + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsi) += + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsijP1) += + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1jP1) += + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPP) += + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPPjP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1jP1) += + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + dFpDEs = coeffVs * (pREdge->wdfp * rDy) * dMpDEs; + *(pNode->fPPsiInM1) -= dFpDEs; + *(pNode->fPPsiIn) -= dFpDEs; + *(pNode->fPPsiOxM1) += dFpDEs; + *(pNode->fPPsiOx) += dFpDEs; + } + + /* Do Bottom-Right (BR) Node of Element */ + pNode = pElem->pBRNode; + /* p continuity wrto potential derivatives */ + *(pNode->fPPsiiM1jM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + *(pNode->fPPsijM1) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pTEdge->dWpDpsiP1 ) + + dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsi) -= + - dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pREdge->dWpDpsiP1 ); + *(pNode->fPPsiiM1) -= + dFpxDMup * ( dMpDEx - dMpDWx * pBEdge->dWpDpsiP1 ) + - dFpyDMup * ( dMpDEy - dMpDWy * pLEdge->dWpDpsiP1 ); + /* p continuity wrto p derivatives */ + *(pNode->fPPiM1jM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDp ); + *(pNode->fPPjM1) -= + dFpxDMup * ( dMpDWx * pTEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDp ); + *(pNode->fPP) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDpP1 ) + + dFpyDMup * ( dMpDWy * pREdge->dWpDpP1 ); + *(pNode->fPPiM1) -= + dFpxDMup * ( dMpDWx * pBEdge->dWpDp ) + + dFpyDMup * ( dMpDWy * pLEdge->dWpDpP1 ); + + /* both continuity wrto surface potential derivatives */ + if ( channel ) { + *(pNode->fPPsiInM1) += dFpDEs; + *(pNode->fPPsiIn) += dFpDEs; + *(pNode->fPPsiOxM1) -= dFpDEs; + *(pNode->fPPsiOx) -= dFpDEs; + } + + return; +} diff --git a/src/ciderlib/twod/twomobfn.c b/src/ciderlib/twod/twomobfn.c new file mode 100644 index 000000000..1575c1068 --- /dev/null +++ b/src/ciderlib/twod/twomobfn.c @@ -0,0 +1,411 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1990 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "twomesh.h" +#include "material.h" +#include "twoddefs.h" +#include "twodext.h" + +/* + * These functions calculate the variable-dependence + * of the surface mobilities + */ + +void +MOBsurfElec(TWOmaterial *info, TWOelem *pElem, double ex, double ey, + double es, double wx, double wy, double totalConc) +{ + double thetaA = info->thetaA[ELEC]; + double thetaB = info->thetaB[ELEC]; + double eL, eN, eD, e0, mun; + double temp1, temp2, temp3, temp4, temp5; + double temp6, temp7, temp8, temp9, temp10; + double sgnN, sgnL; + double dMunDEs; /* Surface Field Derivative */ + double dMunDEn; /* (Local) Normal Field Derivative */ + double dMunDEl; /* Tangent Field Derivative */ + double muHC, muSR, muLV; + double dMuSRDEn; + double d2MuSRDEn2; + double dMuHCDEl; + double dMuHCDMuSR; + double d2MuHCDMuSR2; + double d2MuHCDElDMuSR; + double dEnDEx; /* Normal Derivative x Component */ + double dEnDEy; /* Normal Derivative y Component */ + double dEnDWx; /* Normal Derivative x Component */ + double dEnDWy; /* Normal Derivative y Component */ + double dElDEx; /* Lateral Derivative x Component */ + double dElDEy; /* Lateral Derivative y Component */ + double dElDWx; /* Lateral Derivative x Component */ + double dElDWy; /* Lateral Derivative y Component */ + + if ( pElem->surface ) { /* replace one field component with surface field */ + if ( pElem->direction == 0 ) { + ey = es; + } else { + ex = es; + } + } + + e0 = 1.0 / ENorm; + if ( pElem->direction == 0 ) { + eN = ABS( SALPHA_N*ey + SBETA_N*es ); + sgnN = SGN( SALPHA_N*ey + SBETA_N*es ); + eD = SALPHA_N*( es - ey ); + dEnDEx = 0.0; + dEnDEy = 1.0; + dEnDWx = 0.0; + dEnDWy = 0.0; + eL = ABS( ex ); + sgnL = SGN( ex ); + dElDEx = 1.0; + dElDEy = 0.0; + dElDWx = 0.0; + dElDWy = 0.0; + } else { /* pElem->direction == Y */ + eN = ABS( SALPHA_N*ex + SBETA_N*es ); + sgnN = SGN( SALPHA_N*ex + SBETA_N*es ); + eD = SALPHA_N*( es - ex ); + dEnDEx = 1.0; + dEnDEy = 0.0; + dEnDWx = 0.0; + dEnDWy = 0.0; + eL = ABS( ey ); + sgnL = SGN( ey ); + dElDEx = 0.0; + dElDEy = 1.0; + dElDWx = 0.0; + dElDWy = 0.0; + } + /* + fprintf(stderr,"En = %e, Ep = %e, Ey = %e, Es= %e\n",eN,eL,ey,es); + */ + + muLV = pElem->mun0; + if ( TransDepMobility ) { + /* Compute various partial derivatives of muSR */ + temp1 = 1.0 / ( 1.0 + thetaA*eN + thetaB*eN*eN ); + temp2 = (thetaA + 2.0*thetaB*eN); + muSR = muLV * temp1; + dMuSRDEn = - muSR * temp1 * temp2; + d2MuSRDEn2 = - 2.0 * (dMuSRDEn * temp1 * temp2 + muSR * temp1 * thetaB); + if ( FieldDepMobility ) { + /* Compute various partial derivatives of muHC */ + switch ( info->fieldModel ) { + case CT: + case AR: + case UF: + temp1 = 1.0 / info->vSat[ELEC]; + temp2 = muSR * temp1; + temp3 = eL * temp1; + temp4 = eL * temp2; + temp5 = 1.0 / ( 1.0 + temp4 * temp4 ); + temp6 = sqrt( temp5 ); + muHC = muSR * temp6; + dMuHCDMuSR = temp5 * temp6; + temp7 = temp4 * dMuHCDMuSR; + temp8 = - 3.0 * temp7 * temp5; + dMuHCDEl = - muSR * temp7 * temp2; + d2MuHCDMuSR2 = temp8 * temp3; + d2MuHCDElDMuSR = temp8 * temp2; + break; + case SG: + default: + temp1 = 1.0 / info->vSat[ELEC]; + temp2 = muSR * eL * temp1; /* Vdrift / Vsat */ + temp3 = 1.0 / info->vWarm[ELEC]; + temp4 = muSR * eL * temp3; /* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_N); + temp6 = 1.0 / (1.0 + temp5*temp4 + temp2*temp2); + temp7 = sqrt(temp6); + muHC = muSR * temp7; + temp7 *= temp6; + temp8 = (2.0 - temp5)*temp5*temp3 + 2.0*temp2*temp1; + dMuHCDEl = - 0.5*muSR*temp7*temp8 * muSR; + temp9 = temp5*temp5; + dMuHCDMuSR = (1.0 + 0.5*temp9*temp4) * temp7; + temp9 = (1.5 - temp5)*temp9*temp3 * temp7; + temp9 -= 1.5 * dMuHCDMuSR * temp6 * temp8; + d2MuHCDMuSR2 = temp9 * eL; + d2MuHCDElDMuSR = temp9 * muSR; + break; + } + + /* Now compute total derivatives */ + temp1 = dMuHCDMuSR * dMuSRDEn * sgnN; + temp2 = d2MuHCDMuSR2 * dMuSRDEn * dMuSRDEn + dMuHCDMuSR * d2MuSRDEn2; + temp3 = temp1 - temp2 * eD; + mun = muHC - temp1 * eD; + dMunDEn = (temp3 + temp1) * SALPHA_N; + dMunDEs = temp3 * SBETA_N - temp1 * SALPHA_N; + dMunDEl = (dMuHCDEl - d2MuHCDElDMuSR * dMuSRDEn * sgnN * eD) * sgnL; + } else { + /* Now compute total derivatives */ + temp1 = dMuSRDEn * sgnN; + temp3 = temp1 - d2MuSRDEn2 * eD; + mun = muSR - temp1 * eD; + dMunDEn = (temp3 + temp1) * SALPHA_N; + dMunDEs = temp3 * SBETA_N - temp1 * SALPHA_N; + dMunDEl = 0.0; + } + } else { + if ( FieldDepMobility ) { + /* Compute various partial derivatives of muHC */ + switch ( info->fieldModel ) { + case CT: + case AR: + case UF: + temp1 = muLV / info->vSat[ELEC]; + temp2 = eL * temp1; + temp3 = 1.0 / ( 1.0 + temp2 * temp2 ); + temp4 = sqrt( temp3 ); + muHC = muLV * temp4; + dMuHCDEl = - muHC*temp2*temp3 * temp1; + break; + case SG: + default: + temp1 = 1.0 / info->vSat[ELEC]; + temp2 = muLV * eL * temp1; /* Vdrift / Vsat */ + temp3 = 1.0 / info->vWarm[ELEC]; + temp4 = muLV * eL * temp3; /* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_N); + temp6 = 1.0 / (1.0 + temp5*temp4 + temp2*temp2); + temp7 = sqrt(temp6); + muHC = muLV * temp7; + temp8 = (2.0 - temp5)*temp5*temp3 + 2.0*temp2*temp1; + dMuHCDEl = - 0.5*muHC*temp6*temp8 * muLV; + break; + } + + /* Now compute total derivatives */ + mun = muHC; + dMunDEn = 0.0; + dMunDEs = 0.0; + dMunDEl = dMuHCDEl * sgnL; + } else { + mun = muLV; + dMunDEn = 0.0; + dMunDEs = 0.0; + dMunDEl = 0.0; + } + } + + pElem->mun = mun; + pElem->dMunDEs = dMunDEs; + pElem->dMunDEx = dMunDEn * dEnDEx + dMunDEl * dElDEx; + pElem->dMunDEy = dMunDEn * dEnDEy + dMunDEl * dElDEy; + pElem->dMunDWx = dMunDEn * dEnDWx + dMunDEl * dElDWx; + pElem->dMunDWy = dMunDEn * dEnDWy + dMunDEl * dElDWy; + + if ( pElem->surface ) { /* replace one field component with surface field */ + if ( pElem->direction == 0 ) { + pElem->dMunDEs += pElem->dMunDEy; + pElem->dMunDEy = 0.0; + } else { + pElem->dMunDEs += pElem->dMunDEx; + pElem->dMunDEx = 0.0; + } + } + + return; +} + +void +MOBsurfHole(TWOmaterial *info, TWOelem *pElem, double ex, double ey, + double es, double wx, double wy, double totalConc) +{ + double thetaA = info->thetaA[HOLE]; + double thetaB = info->thetaB[HOLE]; + double eL, eN, eD, mup; + double temp1, temp2, temp3, temp4, temp5; + double temp6, temp7, temp8, temp9, temp10; + double sgnN, sgnL; + double dMupDEs; /* Surface Field Derivative */ + double dMupDEn; /* (Local) Normal Field Derivative */ + double dMupDEl; /* Tangent Field Derivative */ + double muHC, muSR, muLV; + double dMuSRDEn; + double d2MuSRDEn2; + double dMuHCDEl; + double dMuHCDMuSR; + double d2MuHCDMuSR2; + double d2MuHCDElDMuSR; + double dEnDEx; /* Normal Derivative x Component */ + double dEnDEy; /* Normal Derivative y Component */ + double dEnDWx; /* Normal Derivative x Component */ + double dEnDWy; /* Normal Derivative y Component */ + double dElDEx; /* Lateral Derivative x Component */ + double dElDEy; /* Lateral Derivative y Component */ + double dElDWx; /* Lateral Derivative x Component */ + double dElDWy; /* Lateral Derivative y Component */ + + if ( pElem->surface ) { /* replace one field component with surface field */ + if ( pElem->direction == 0 ) { + ey = es; + } else { + ex = es; + } + } + + if ( pElem->direction == 0 ) { + eN = ABS( SALPHA_P*ey + SBETA_P*es ); + sgnN = SGN( SALPHA_P*ey + SBETA_P*es ); + eD = SALPHA_P*( es - ey ); + dEnDEx = 0.0; + dEnDEy = 1.0; + dEnDWx = 0.0; + dEnDWy = 0.0; + eL = ABS( ex ); + sgnL = SGN( ex ); + dElDEx = 1.0; + dElDEy = 0.0; + dElDWx = 0.0; + dElDWy = 0.0; + } else { /* pElem->direction == Y */ + eN = ABS( SALPHA_P*ex + SBETA_P*es ); + sgnN = SGN( SALPHA_P*ex + SBETA_P*es ); + eD = SALPHA_P*( es - ex ); + dEnDEx = 1.0; + dEnDEy = 0.0; + dEnDWx = 0.0; + dEnDWy = 0.0; + eL = ABS( ey ); + sgnL = SGN( ey ); + dElDEx = 0.0; + dElDEy = 1.0; + dElDWx = 0.0; + dElDWy = 0.0; + } + + muLV = pElem->mup0; + if ( TransDepMobility ) { + /* Compute various partial derivatives of muSR */ + temp1 = 1.0 / ( 1.0 + thetaA*eN + thetaB*eN*eN ); + temp2 = thetaA + 2.0*thetaB*eN; + muSR = muLV * temp1; + dMuSRDEn = - muSR * temp1 * temp2; + d2MuSRDEn2 = - 2.0 * (dMuSRDEn * temp1 * temp2 + muSR * temp1 * thetaB); + if ( FieldDepMobility ) { + /* Compute various partial derivatives of muHC */ + switch ( info->fieldModel ) { + case CT: + case AR: + case UF: + temp1 = 1.0 / info->vSat[HOLE]; + temp2 = muSR * temp1; + temp3 = eL * temp1; + temp4 = eL * temp2; + temp5 = 1.0 / ( 1.0 + temp4 ); + muHC = muSR * temp5; + dMuHCDMuSR = temp5 * temp5; + dMuHCDEl = - muSR * dMuHCDMuSR * temp2; + temp6 = - 2.0 * dMuHCDMuSR * temp5; + d2MuHCDMuSR2 = temp6 * temp3; + d2MuHCDElDMuSR = temp6 * temp2; + break; + case SG: + default: + temp1 = 1.0 / info->vSat[HOLE]; + temp2 = muSR * eL * temp1; /* Vdrift / Vsat */ + temp3 = 1.0 / info->vWarm[HOLE]; + temp4 = muSR * eL * temp3; /* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_P); + temp6 = 1.0 / (1.0 + temp5*temp4 + temp2*temp2); + temp7 = sqrt(temp6); + muHC = muSR * temp7; + temp7 *= temp6; + temp8 = (2.0 - temp5)*temp5*temp3 + 2.0*temp2*temp1; + dMuHCDEl = - 0.5*muSR*temp7*temp8 * muSR; + temp9 = temp5*temp5; + dMuHCDMuSR = (1.0 + 0.5*temp9*temp4) * temp7; + temp9 = (1.5 - temp5)*temp9*temp3 * temp7; + temp9 -= 1.5 * dMuHCDMuSR * temp6 * temp8; + d2MuHCDMuSR2 = temp9 * eL; + d2MuHCDElDMuSR = temp9 * muSR; + break; + } + + /* Now compute total derivatives */ + temp1 = dMuHCDMuSR * dMuSRDEn * sgnN; + temp2 = d2MuHCDMuSR2 * dMuSRDEn * dMuSRDEn + dMuHCDMuSR * d2MuSRDEn2; + temp3 = temp1 - temp2 * eD; + mup = muHC - temp1 * eD; + dMupDEn = (temp3 + temp1) * SALPHA_P; + dMupDEs = temp3 * SBETA_P - temp1 * SALPHA_P; + dMupDEl = (dMuHCDEl - d2MuHCDElDMuSR * dMuSRDEn * sgnN * eD ) * sgnL; + } else { + /* Now compute total derivatives */ + temp1 = dMuSRDEn * sgnN; + temp3 = temp1 - d2MuSRDEn2 * eD; + mup = muSR - temp1 * eD; + dMupDEn = (temp3 + temp1) * SALPHA_P; + dMupDEs = temp3 * SBETA_P - temp1 * SALPHA_P; + dMupDEl = 0.0; + } + } else { + if ( FieldDepMobility ) { + /* Compute various partial derivatives of muHC */ + switch ( info->fieldModel ) { + case CT: + case AR: + case UF: + temp1 = muLV / info->vSat[HOLE]; + temp2 = eL * temp1; + temp3 = 1.0 / ( 1.0 + temp2 ); + muHC = muLV * temp3; + dMuHCDEl = - muHC * temp3 * temp1; + break; + case SG: + default: + temp1 = 1.0 / info->vSat[HOLE]; + temp2 = muLV * eL * temp1; /* Vdrift / Vsat */ + temp3 = 1.0 / info->vWarm[HOLE]; + temp4 = muLV * eL * temp3; /* Vdrift / Vwarm */ + temp5 = temp4 / (temp4 + SG_FIT_P); + temp6 = 1.0 / (1.0 + temp5*temp4 + temp2*temp2); + temp7 = sqrt(temp6); + muHC = muLV * temp7; + temp8 = (2.0 - temp5)*temp5*temp3 + 2.0*temp2*temp1; + dMuHCDEl = - 0.5*muHC*temp6*temp8 * muLV; + break; + } + + /* Now compute total derivatives */ + mup = muHC; + dMupDEn = 0.0; + dMupDEs = 0.0; + dMupDEl = dMuHCDEl * sgnL; + } else { + mup = muLV; + dMupDEn = 0.0; + dMupDEs = 0.0; + dMupDEl = 0.0; + } + } + + pElem->mup = mup; + pElem->dMupDEs = dMupDEs; + pElem->dMupDEx = dMupDEn * dEnDEx + dMupDEl * dElDEx; + pElem->dMupDEy = dMupDEn * dEnDEy + dMupDEl * dElDEy; + pElem->dMupDWx = dMupDEn * dEnDWx + dMupDEl * dElDWx; + pElem->dMupDWy = dMupDEn * dEnDWy + dMupDEl * dElDWy; + + if ( pElem->surface ) { /* replace one field component with surface field */ + if ( pElem->direction == 0 ) { + pElem->dMupDEs += pElem->dMupDEy; + pElem->dMupDEy = 0.0; + } else { + pElem->dMupDEs += pElem->dMupDEx; + pElem->dMupDEx = 0.0; + } + } + + return; +} diff --git a/src/ciderlib/twod/twomobil.c b/src/ciderlib/twod/twomobil.c new file mode 100644 index 000000000..d3d48c1b8 --- /dev/null +++ b/src/ciderlib/twod/twomobil.c @@ -0,0 +1,131 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1990 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "twomesh.h" +#include "twoddefs.h" +#include "twodext.h" + +/* + * Compute the 2-D field-dependent mobility at the center of an element. + * It is known a priori that the element belongs to a semiconductor domain. + */ + +void +TWO_mobility(TWOelem *pElem, double eSurf) +{ + + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + double dx, dy, rDx, rDy; + double enx, eny, wnx, wny, concav; + double epx, epy, wpx, wpy; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 0.5 / dx; /* Includes averaging factor of 0.5 */ + rDy = 0.5 / dy; /* Includes averaging factor of 0.5 */ + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Calculate electric field at element center */ + enx = -rDx *(pTEdge->dPsi + pTEdge->dCBand + pBEdge->dPsi + pBEdge->dCBand); + epx = -rDx *(pTEdge->dPsi - pTEdge->dVBand + pBEdge->dPsi - pBEdge->dVBand); + eny = -rDy *(pLEdge->dPsi + pLEdge->dCBand + pREdge->dPsi + pREdge->dCBand); + epy = -rDy *(pLEdge->dPsi - pLEdge->dVBand + pREdge->dPsi - pREdge->dVBand); + + /* Calculate weighted carrier driving force at element center */ + wnx = rDx * (pTEdge->wdfn + pBEdge->wdfn); + wpx = rDx * (pTEdge->wdfp + pBEdge->wdfp); + wny = rDy * (pLEdge->wdfn + pREdge->wdfn); + wpy = rDy * (pLEdge->wdfp + pREdge->wdfp); + + /* compute the mobility for the element */ + /* Average concentrations at the four corners */ + concav = 0.25 * ( pElem->pTLNode->totalConc + pElem->pTRNode->totalConc + + pElem->pBLNode->totalConc + pElem->pBRNode->totalConc ); + MOBsurfElec(pElem->matlInfo, pElem, enx, eny, eSurf, wnx, wny, concav); + MOBsurfHole(pElem->matlInfo, pElem, epx, epy, eSurf, wpx, wpy, concav); + return; +} + +void +TWONmobility(TWOelem *pElem, double eSurf) +{ + + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + double dx, dy, rDx, rDy; + double enx, eny, wnx, wny, concav; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 0.5 / dx; /* Includes averaging factor of 0.5 */ + rDy = 0.5 / dy; /* Includes averaging factor of 0.5 */ + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Calculate electric field at element center */ + enx = -rDx *(pTEdge->dPsi + pTEdge->dCBand + pBEdge->dPsi + pBEdge->dCBand); + eny = -rDy *(pLEdge->dPsi + pLEdge->dCBand + pREdge->dPsi + pREdge->dCBand); + + /* Calculate weighted carrier driving force at element center */ + wnx = rDx * (pTEdge->wdfn + pBEdge->wdfn); + wny = rDy * (pLEdge->wdfn + pREdge->wdfn); + + /* compute the mobility for the element */ + /* Average concentrations at the four corners */ + concav = 0.25 * ( pElem->pTLNode->totalConc + pElem->pTRNode->totalConc + + pElem->pBLNode->totalConc + pElem->pBRNode->totalConc ); + MOBsurfElec(pElem->matlInfo, pElem, enx, eny, eSurf, wnx, wny, concav); + + return; +} + +void +TWOPmobility(TWOelem *pElem, double eSurf) +{ + + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + double dx, dy, rDx, rDy; + double epx, epy, wpx, wpy, concav; + + /* Initialize various quantities */ + dx = pElem->dx; + dy = pElem->dy; + rDx = 0.5 / dx; /* Includes averaging factor of 0.5 */ + rDy = 0.5 / dy; /* Includes averaging factor of 0.5 */ + + /* Get pointers to element's edges */ + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* Calculate electric field at element center */ + epx = -rDx *(pTEdge->dPsi - pTEdge->dVBand + pBEdge->dPsi - pBEdge->dVBand); + epy = -rDy *(pLEdge->dPsi - pLEdge->dVBand + pREdge->dPsi - pREdge->dVBand); + + /* Calculate weighted carrier driving force at element center */ + wpx = rDx * (pTEdge->wdfp + pBEdge->wdfp); + wpy = rDy * (pLEdge->wdfp + pREdge->wdfp); + + /* compute the mobility for the element */ + /* Average concentrations at the four corners */ + concav = 0.25 * ( pElem->pTLNode->totalConc + pElem->pTRNode->totalConc + + pElem->pBLNode->totalConc + pElem->pBRNode->totalConc ); + MOBsurfHole(pElem->matlInfo, pElem, epx, epy, eSurf, wpx, wpy, concav); + + return; +} diff --git a/src/ciderlib/twod/twoncont.c b/src/ciderlib/twod/twoncont.c new file mode 100644 index 000000000..9779563d3 --- /dev/null +++ b/src/ciderlib/twod/twoncont.c @@ -0,0 +1,889 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" +#include "../../maths/misc/bernoull.h" + + +/* + * Functions to setup and solve the continuity equations. + * Both continuity equations are solved. + * Separate functions are used for one continuity equation. + */ + + +/* + * Setup matrix pointers to Jacobian entries and + * store direct pointers with the nodes. + */ + +void + TWONjacBuild(TWOdevice *pDevice) +{ + char *matrix = pDevice->matrix; + TWOelem *pElem; + TWOnode *pNode; + TWOchannel *pCh; + int eIndex, nIndex; + int nextIndex; /* index of node to find next element */ + int psiEqn, nEqn; /* scratch for deref'd eqn numbers */ + int psiEqnTL = 0, nEqnTL = 0; + int psiEqnTR = 0, nEqnTR = 0; + int psiEqnBR = 0, nEqnBR = 0; + int psiEqnBL = 0, nEqnBL = 0; + int psiEqnInM = 0, psiEqnInP = 0; /* scratch for deref'd surface eqns */ + int psiEqnOxM = 0, psiEqnOxP = 0; /* M= more negative, P= more positive */ + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + /* first the self terms */ + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + /* get poisson-only pointer */ + psiEqn = pNode->psiEqn; + pNode->fPsiPsi = spGetElement( matrix, psiEqn, psiEqn ); + + if ( pElem->elemType == SEMICON ) { + /* get continuity-coupling terms */ + nEqn = pNode->nEqn; + pNode->pEqn = 0; /* Throw pEqn number into garbage. */ + /* pointers for additional terms */ + pNode->fPsiN = spGetElement( matrix, psiEqn, nEqn ); + pNode->fNPsi = spGetElement( matrix, nEqn, psiEqn ); + pNode->fNN = spGetElement( matrix, nEqn, nEqn ); + } else { + nEqn = 0; + } + /* save equation indices */ + switch ( nIndex ) { + case 0: /* TL Node */ + psiEqnTL = psiEqn; + nEqnTL = nEqn; + break; + case 1: /* TR Node */ + psiEqnTR = psiEqn; + nEqnTR = nEqn; + break; + case 2: /* BR Node */ + psiEqnBR = psiEqn; + nEqnBR = nEqn; + break; + case 3: /* BL Node */ + psiEqnBL = psiEqn; + nEqnBL = nEqn; + break; + default: + break; + } + } + + /* now terms to couple to adjacent nodes */ + pNode = pElem->pTLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnTL, psiEqnTR ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTL, psiEqnBL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiP1 = spGetElement( matrix, nEqnTL, psiEqnTR ); + pNode->fNNiP1 = spGetElement( matrix, nEqnTL, nEqnTR ); + pNode->fNPsijP1 = spGetElement( matrix, nEqnTL, psiEqnBL ); + pNode->fNNjP1 = spGetElement( matrix, nEqnTL, nEqnBL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiP1jP1 = spGetElement( matrix, nEqnTL, psiEqnBR ); + pNode->fNNiP1jP1 = spGetElement( matrix, nEqnTL, nEqnBR ); + } + } + + pNode = pElem->pTRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnTR, psiEqnTL ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTR, psiEqnBR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiM1 = spGetElement( matrix, nEqnTR, psiEqnTL ); + pNode->fNNiM1 = spGetElement( matrix, nEqnTR, nEqnTL ); + pNode->fNPsijP1 = spGetElement( matrix, nEqnTR, psiEqnBR ); + pNode->fNNjP1 = spGetElement( matrix, nEqnTR, nEqnBR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiM1jP1 = spGetElement( matrix, nEqnTR, psiEqnBL ); + pNode->fNNiM1jP1 = spGetElement( matrix, nEqnTR, nEqnBL ); + } + } + + pNode = pElem->pBRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnBR, psiEqnBL ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBR, psiEqnTR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiM1 = spGetElement( matrix, nEqnBR, psiEqnBL ); + pNode->fNNiM1 = spGetElement( matrix, nEqnBR, nEqnBL ); + pNode->fNPsijM1 = spGetElement( matrix, nEqnBR, psiEqnTR ); + pNode->fNNjM1 = spGetElement( matrix, nEqnBR, nEqnTR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiM1jM1 = spGetElement( matrix, nEqnBR, psiEqnTL ); + pNode->fNNiM1jM1 = spGetElement( matrix, nEqnBR, nEqnTL ); + } + } + + pNode = pElem->pBLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnBL, psiEqnBR ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBL, psiEqnTL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fNPsiiP1 = spGetElement( matrix, nEqnBL, psiEqnBR ); + pNode->fNNiP1 = spGetElement( matrix, nEqnBL, nEqnBR ); + pNode->fNPsijM1 = spGetElement( matrix, nEqnBL, psiEqnTL ); + pNode->fNNjM1 = spGetElement( matrix, nEqnBL, nEqnTL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fNPsiiP1jM1 = spGetElement( matrix, nEqnBL, psiEqnTR ); + pNode->fNNiP1jM1 = spGetElement( matrix, nEqnBL, nEqnTR ); + } + } + } + /* + * Add terms for surface-field of inversion-layer mobility model. + * Elements MUST be made from silicon for this to work. + * No empty elements are allowed. + * Don't need these pointers if SurfaceMobility isn't set. + */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + psiEqnInM = pElem->pBLNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pTRNode->psiEqn; + break; + case 1: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pBLNode->psiEqn; + psiEqnOxM = pElem->pTRNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 2: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pTRNode->psiEqn; + psiEqnOxM = pElem->pBLNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 3: + psiEqnInM = pElem->pTRNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pBLNode->psiEqn; + break; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + psiEqn = pNode->psiEqn; + nEqn = pNode->nEqn; + if ( pCh->type % 2 == 0 ) { /* Vertical Slice */ + if ( nIndex == 0 || nIndex == 3 ) { /* Left Side */ + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiInP1 = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOxP1 = spGetElement( matrix, nEqn, psiEqnOxP ); + } else { /* Right Side */ + pNode->fNPsiInM1 = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOxM1 = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxP ); + } + } else { /* Horizontal Slice */ + if ( nIndex <= 1 ) { /* Top Side */ + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiInP1 = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOxP1 = spGetElement( matrix, nEqn, psiEqnOxP ); + } else { /* Bottom Side */ + pNode->fNPsiInM1 = spGetElement( matrix, nEqn, psiEqnInM ); + pNode->fNPsiIn = spGetElement( matrix, nEqn, psiEqnInP ); + pNode->fNPsiOxM1 = spGetElement( matrix, nEqn, psiEqnOxM ); + pNode->fNPsiOx = spGetElement( matrix, nEqn, psiEqnOxP ); + } + } + } /* endfor nIndex */ + pElem = pElem->pElems[ nextIndex ]; + } /* endwhile pElem */ + } /* endfor pCh */ + } /* endif SurfaceMobility */ +} + + +/* + * The Jacobian and Rhs are loaded by the following function. + * Inputs are the transient analysis flag and the transient + * information structure + */ + +void + TWONsysLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsN; + double nConc, pConc; + double perTime = 0.0; + + /* first compute the currents and derivatives */ + TWONcommonTerms( pDevice, FALSE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + + *(pNode->fPsiN) += dxdy; + *(pNode->fPsiPsi) += dxdy * pConc; + *(pNode->fNPsi) -= dy * pHEdge->dJnDpsiP1 + dx * pVEdge->dJnDpsiP1; + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + *(pNode->fNN) -= dxdy * pNode->dUdN; + *(pNode->fNPsi) += dxdy * pNode->dUdP * pConc; + rhsN = - dxdy * pNode->uNet; + pRhs[ pNode->nEqn ] -= rhsN; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + *(pNode->fNN) -= dxdy * perTime; + pRhs[ pNode->nEqn ] += dxdy * pNode->dNdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pTEdge->jn + dx * pLEdge->jn; + *(pNode->fNN) += dy * pTEdge->dJnDn + dx * pLEdge->dJnDn; + *(pNode->fNPsiiP1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pTEdge->dJnDnP1; + *(pNode->fNPsijP1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pLEdge->dJnDnP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pTEdge->jn + dx * pREdge->jn; + *(pNode->fNN) += -dy * pTEdge->dJnDnP1 + dx * pREdge->dJnDn; + *(pNode->fNPsiiM1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pTEdge->dJnDn; + *(pNode->fNPsijP1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pREdge->dJnDnP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pBEdge->jn - dx * pREdge->jn; + *(pNode->fNN) += -dy * pBEdge->dJnDnP1 - dx * pREdge->dJnDnP1; + *(pNode->fNPsiiM1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pBEdge->dJnDn; + *(pNode->fNPsijM1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pREdge->dJnDn; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pBEdge->jn - dx * pLEdge->jn; + *(pNode->fNN) += dy * pBEdge->dJnDn - dx * pLEdge->dJnDnP1; + *(pNode->fNPsiiP1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pBEdge->dJnDnP1; + *(pNode->fNPsijM1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pLEdge->dJnDn; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWONmobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + + +/* + * This function used only for direct method ac analysis. + * Used to load only the dc Jacobian matrix. Rhs is unaffected + */ + +void + TWONjacLoad(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + double pConc; + + /* first compute the currents and derivatives */ + TWONcommonTerms( pDevice, FALSE, FALSE, NIL(TWOtranInfo) ); + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( pElem->elemType == SEMICON ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + pConc = *(pDevice->devState0 + pNode->nodeP); + *(pNode->fPsiN) += dxdy; + *(pNode->fPsiPsi) += dxdy * pConc; + *(pNode->fNPsi) -= dy * pHEdge->dJnDpsiP1 + dx * pVEdge->dJnDpsiP1; + + /* Handle generation terms */ + *(pNode->fNN) -= dxdy * pNode->dUdN; + *(pNode->fNPsi) += dxdy * pNode->dUdP * pConc; + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += dy * pTEdge->dJnDn + dx * pLEdge->dJnDn; + *(pNode->fNPsiiP1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pTEdge->dJnDnP1; + *(pNode->fNPsijP1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pLEdge->dJnDnP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += -dy * pTEdge->dJnDnP1 + dx * pREdge->dJnDn; + *(pNode->fNPsiiM1) += dy * pTEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pTEdge->dJnDn; + *(pNode->fNPsijP1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjP1) += dx * pREdge->dJnDnP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += -dy * pBEdge->dJnDnP1 - dx * pREdge->dJnDnP1; + *(pNode->fNPsiiM1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiM1) -= dy * pBEdge->dJnDn; + *(pNode->fNPsijM1) += dx * pREdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pREdge->dJnDn; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fNN) += dy * pBEdge->dJnDn - dx * pLEdge->dJnDnP1; + *(pNode->fNPsiiP1) += dy * pBEdge->dJnDpsiP1; + *(pNode->fNNiP1) += dy * pBEdge->dJnDnP1; + *(pNode->fNPsijM1) += dx * pLEdge->dJnDpsiP1; + *(pNode->fNNjM1) -= dx * pLEdge->dJnDn; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWONmobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + +/* load only the Rhs vector */ +void + TWONrhsLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsN; + double nConc, pConc; + double perTime; + + /* first compute the currents */ + TWONcommonTerms( pDevice, TRUE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + rhsN = - dxdy * pNode->uNet; + pRhs[ pNode->nEqn ] -= rhsN; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + pRhs[ pNode->nEqn ] += dxdy * pNode->dNdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pTEdge->jn + dx * pLEdge->jn; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pTEdge->jn + dx * pREdge->jn; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= -dy * pBEdge->jn - dx * pREdge->jn; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->nEqn ] -= dy * pBEdge->jn - dx * pLEdge->jn; + } + } + } +} + +/* + * computation of current densities, recombination rates, + * mobilities and their derivatives + */ +void + TWONcommonTerms(TWOdevice *pDevice, BOOLEAN currentOnly, + BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem, *pElem1; + TWOedge *pEdge; + TWOnode *pNode; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double psi1, psi2, refPsi, nC, nP1; + double dPsiN; + double bPsiN, dbPsiN, bMPsiN, dbMPsiN; + double muN, dMuN, rDx, rDy; + double psi, nConc = 0.0, pConc = 0.0; + double cnAug, cpAug; + double eSurf = 0.0; /* For channel mobilities */ + double qInt = 0.0; + TWOchannel *pCh; + + /* evaluate all node (including recombination) and edge quantities */ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + refPsi = pElem->matlInfo->refPsi; + cnAug = pElem->matlInfo->cAug[ELEC]; + cpAug = pElem->matlInfo->cAug[HOLE]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi = pDevice->dcSolution[ pNode->psiEqn ]; + if ( pElem->elemType == SEMICON ) { + nConc = pDevice->dcSolution[ pNode->nEqn ]; + pConc = pNode->nie * exp( - psi + refPsi ); + if ( Srh ) { + recomb(nConc, pConc, + pNode->tn, pNode->tp, cnAug, cpAug, pNode->nie, + &pNode->uNet, &pNode->dUdN, &pNode->dUdP); + } else { + pNode->uNet = 0.0; + pNode->dUdN = 0.0; + pNode->dUdP = 0.0; + } + } + } else { + /* a contact node */ + psi = pNode->psi; + if ( pElem->elemType == SEMICON ) { + nConc = pNode->nConc; + pConc = pNode->pConc; + } + } + + /* store info in the state tables */ + *(pDevice->devState0 + pNode->nodePsi) = psi; + if ( pElem->elemType == SEMICON ) { + *(pDevice->devState0 + pNode->nodeN) = nConc; + *(pDevice->devState0 + pNode->nodeP) = pConc; + if ( tranAnalysis && pNode->nodeType != CONTACT ) { + pNode->dNdT = integrate( pDevice->devStates, info, pNode->nodeN ); + } + } + } + } + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi1 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pNodes[ (index + 1) % 4 ]; + if ( pNode->nodeType != CONTACT ) { + psi2 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi2 = pNode->psi; + } + if ( index <= 1 ) { + pEdge->dPsi = psi2 - psi1; + } else { + pEdge->dPsi = psi1 - psi2; + } + *(pDevice->devState0 + pEdge->edgeDpsi) = pEdge->dPsi; + + if ( pElem->elemType == SEMICON ) { + /* Calculate weighted driving forces - wdfn & wdfp for the edge */ + dPsiN = pEdge->dPsi + pEdge->dCBand; + bernoulli( dPsiN, &bPsiN, &dbPsiN, + &bMPsiN, &dbMPsiN, !currentOnly ); + if ( index <= 1 ) { + nC = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeN); + nP1 = *(pDevice->devState0 + pElem->pNodes[ index+1 ]->nodeN); + } else { + nC = *(pDevice->devState0 + pElem->pNodes[(index+1)%4]->nodeN); + nP1 = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeN); + } + pEdge->wdfn = bPsiN * nP1 - bMPsiN * nC; + pEdge->jn = 0.0; + if ( !currentOnly ) { + pEdge->dWnDpsiP1 = dbPsiN * nP1 - dbMPsiN * nC; + pEdge->dWnDn = - bMPsiN; + pEdge->dWnDnP1 = bPsiN; + pEdge->dJnDpsiP1 = 0.0; + pEdge->dJnDn = 0.0; + pEdge->dJnDnP1 = 0.0; + } + } + } + } + } + + /* DAG: calculate mobilities for channel elems */ + if ( SurfaceMobility ) { + for ( pCh = pDevice->pChannel; + pCh != NIL(TWOchannel); pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pBotEdge->qf; + break; + case 1: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pLeftEdge->qf; + break; + case 2: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pTopEdge->qf; + break; + case 3: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pRightEdge->qf; + break; + } + eSurf += qInt; + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWONmobility( pElem, eSurf ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCH != NIL */ + } /* endif SurfaceMobility */ + + /* calculate the current densities assuming mobility value depend on Ex,Ey*/ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + rDx = 1.0 / pElem->dx; + rDy = 1.0 / pElem->dy; + for ( index = 0; index <= 3; index++ ) { + pEdge = pElem->pEdges[ index ]; + /* calculate conductive currents */ + if ( pElem->elemType == SEMICON ) { + /* get mobility for this edge */ + if ( !pElem->channel ) { + /* Calculate mobility for non-channel elements */ + muN = pElem->mun0; + dMuN = 0.0; + dPsiN = pEdge->dPsi + pEdge->dCBand; + if ( index%2 == 0 ) { + MOBfieldDep( pElem->matlInfo, ELEC, - dPsiN * rDx, &muN, &dMuN ); + } else { + MOBfieldDep( pElem->matlInfo, ELEC, - dPsiN * rDy, &muN, &dMuN ); + } + } else { + /* Retrieve previously calculated value. */ + muN = pElem->mun; + dMuN = 0.0; + } + switch ( index ) { + case 0: + muN *= pEdge->kPos * rDx; + dMuN *= pEdge->kPos * rDx * rDx; + break; + case 1: + muN *= pEdge->kNeg * rDy; + dMuN *= pEdge->kNeg * rDy * rDy; + break; + case 2: + muN *= pEdge->kNeg * rDx; + dMuN *= pEdge->kNeg * rDx * rDx; + break; + case 3: + muN *= pEdge->kPos * rDy; + dMuN *= pEdge->kPos * rDy * rDy; + break; + } + /* Now that the mobility for this edge is known, do current */ + pEdge->jn += muN * pEdge->wdfn; + if ( !currentOnly ) { + pEdge->dJnDpsiP1 += muN * pEdge->dWnDpsiP1; + pEdge->dJnDn += muN * pEdge->dWnDn; + pEdge->dJnDnP1 += muN * pEdge->dWnDnP1; + if ( MobDeriv && (!pElem->channel) ) { + pEdge->dJnDpsiP1 -= dMuN * pEdge->wdfn; + } + } + } + /* calculate displacement current only once */ + if ( pElem->evalEdges[ index ] ) { + if ( tranAnalysis ) { + if ( index == 0 || index == 2 ) { + /* horizontal edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDx; + } else { + /* vertical edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDy; + } + } + } + } + } +} diff --git a/src/ciderlib/twod/twopcont.c b/src/ciderlib/twod/twopcont.c new file mode 100644 index 000000000..28f5f994c --- /dev/null +++ b/src/ciderlib/twod/twopcont.c @@ -0,0 +1,888 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" +#include "../../maths/misc/bernoull.h" + +/* + * Functions to setup and solve the continuity equations. + * Both continuity equations are solved. + * Separate functions are used for one continuity equation. + */ + + +/* + * setup matrix pointers to Jacobian values and + * store direct pointers with the nodes + */ + +void + TWOPjacBuild(TWOdevice *pDevice) +{ + char *matrix = pDevice->matrix; + TWOelem *pElem; + TWOnode *pNode; + TWOchannel *pCh; + int eIndex, nIndex; + int nextIndex; /* index of node to find next element */ + int psiEqn, pEqn; /* scratch for deref'd eqn numbers */ + int psiEqnTL = 0, pEqnTL = 0; + int psiEqnTR = 0, pEqnTR = 0; + int psiEqnBR = 0, pEqnBR = 0; + int psiEqnBL = 0, pEqnBL = 0; + int psiEqnInM = 0, psiEqnInP = 0; /* scratch for deref'd surface eqns */ + int psiEqnOxM = 0, psiEqnOxP = 0; /* M= more negative, P= more positive */ + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + /* first the self terms */ + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + /* get poisson-only pointer */ + psiEqn = pNode->psiEqn; + pNode->fPsiPsi = spGetElement( matrix, psiEqn, psiEqn ); + + if ( pElem->elemType == SEMICON ) { + /* get continuity-coupling terms */ + pEqn = pNode->pEqn; + pNode->nEqn = 0; + /* pointers for additional terms */ + pNode->fPsiP = spGetElement( matrix, psiEqn, pEqn ); + pNode->fPPsi = spGetElement( matrix, pEqn, psiEqn ); + pNode->fPP = spGetElement( matrix, pEqn, pEqn ); + } else { + pEqn = 0; + } + /* save equation indices */ + switch ( nIndex ) { + case 0: /* TL Node */ + psiEqnTL = psiEqn; + pEqnTL = pEqn; + break; + case 1: /* TR Node */ + psiEqnTR = psiEqn; + pEqnTR = pEqn; + break; + case 2: /* BR Node */ + psiEqnBR = psiEqn; + pEqnBR = pEqn; + break; + case 3: /* BL Node */ + psiEqnBL = psiEqn; + pEqnBL = pEqn; + break; + default: + break; + } + } + + /* now terms to couple to adjacent nodes */ + pNode = pElem->pTLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnTL, psiEqnTR ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTL, psiEqnBL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fPPsiiP1 = spGetElement( matrix, pEqnTL, psiEqnTR ); + pNode->fPPiP1 = spGetElement( matrix, pEqnTL, pEqnTR ); + pNode->fPPsijP1 = spGetElement( matrix, pEqnTL, psiEqnBL ); + pNode->fPPjP1 = spGetElement( matrix, pEqnTL, pEqnBL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fPPsiiP1jP1 = spGetElement( matrix, pEqnTL, psiEqnBR ); + pNode->fPPiP1jP1 = spGetElement( matrix, pEqnTL, pEqnBR ); + } + } + + pNode = pElem->pTRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnTR, psiEqnTL ); + pNode->fPsiPsijP1 = spGetElement(matrix, psiEqnTR, psiEqnBR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fPPsiiM1 = spGetElement( matrix, pEqnTR, psiEqnTL ); + pNode->fPPiM1 = spGetElement( matrix, pEqnTR, pEqnTL ); + pNode->fPPsijP1 = spGetElement( matrix, pEqnTR, psiEqnBR ); + pNode->fPPjP1 = spGetElement( matrix, pEqnTR, pEqnBR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fPPsiiM1jP1 = spGetElement( matrix, pEqnTR, psiEqnBL ); + pNode->fPPiM1jP1 = spGetElement( matrix, pEqnTR, pEqnBL ); + } + } + + pNode = pElem->pBRNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, psiEqnBR, psiEqnBL ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBR, psiEqnTR ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fPPsiiM1 = spGetElement( matrix, pEqnBR, psiEqnBL ); + pNode->fPPiM1 = spGetElement( matrix, pEqnBR, pEqnBL ); + pNode->fPPsijM1 = spGetElement( matrix, pEqnBR, psiEqnTR ); + pNode->fPPjM1 = spGetElement( matrix, pEqnBR, pEqnTR ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fPPsiiM1jM1 = spGetElement( matrix, pEqnBR, psiEqnTL ); + pNode->fPPiM1jM1 = spGetElement( matrix, pEqnBR, pEqnTL ); + } + } + + pNode = pElem->pBLNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, psiEqnBL, psiEqnBR ); + pNode->fPsiPsijM1 = spGetElement(matrix, psiEqnBL, psiEqnTL ); + if ( pElem->elemType == SEMICON ) { + /* continuity equation pointers */ + pNode->fPPsiiP1 = spGetElement( matrix, pEqnBL, psiEqnBR ); + pNode->fPPiP1 = spGetElement( matrix, pEqnBL, pEqnBR ); + pNode->fPPsijM1 = spGetElement( matrix, pEqnBL, psiEqnTL ); + pNode->fPPjM1 = spGetElement( matrix, pEqnBL, pEqnTL ); + /* Surface Mobility Model depends on diagonal node values */ + if ( MobDeriv && SurfaceMobility && pElem->channel ) { + pNode->fPPsiiP1jM1 = spGetElement( matrix, pEqnBL, psiEqnTR ); + pNode->fPPiP1jM1 = spGetElement( matrix, pEqnBL, pEqnTR ); + } + } + } + /* + * Add terms for surface-field of inversion-layer mobility model. + * Elements MUST be made from silicon for this to work. + * No empty elements are allowed. + * Don't need these pointers if SurfaceMobility isn't set. + */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + psiEqnInM = pElem->pBLNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pTRNode->psiEqn; + break; + case 1: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pBLNode->psiEqn; + psiEqnOxM = pElem->pTRNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 2: + psiEqnInM = pElem->pTLNode->psiEqn; + psiEqnInP = pElem->pTRNode->psiEqn; + psiEqnOxM = pElem->pBLNode->psiEqn; + psiEqnOxP = pElem->pBRNode->psiEqn; + break; + case 3: + psiEqnInM = pElem->pTRNode->psiEqn; + psiEqnInP = pElem->pBRNode->psiEqn; + psiEqnOxM = pElem->pTLNode->psiEqn; + psiEqnOxP = pElem->pBLNode->psiEqn; + break; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + pNode = pElem->pNodes[ nIndex ]; + psiEqn = pNode->psiEqn; + pEqn = pNode->pEqn; + if ( pCh->type % 2 == 0 ) { /* Vertical Slice */ + if ( nIndex == 0 || nIndex == 3 ) { /* Left Side */ + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiInP1 = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOxP1 = spGetElement( matrix, pEqn, psiEqnOxP ); + } else { /* Right Side */ + pNode->fPPsiInM1 = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOxM1 = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxP ); + } + } else { /* Horizontal Slice */ + if ( nIndex <= 1 ) { /* Top Side */ + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiInP1 = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOxP1 = spGetElement( matrix, pEqn, psiEqnOxP ); + } else { /* Bottom Side */ + pNode->fPPsiInM1 = spGetElement( matrix, pEqn, psiEqnInM ); + pNode->fPPsiIn = spGetElement( matrix, pEqn, psiEqnInP ); + pNode->fPPsiOxM1 = spGetElement( matrix, pEqn, psiEqnOxM ); + pNode->fPPsiOx = spGetElement( matrix, pEqn, psiEqnOxP ); + } + } + } /* endfor nIndex */ + pElem = pElem->pElems[ nextIndex ]; + } /* endwhile pElem */ + } /* endfor pCh */ + } /* endif SurfaceMobility */ +} + + +/* + * The Jacobian and Rhs are loaded by the following function. + * Inputs are the transient analysis flag and the transient + * information structure + */ + +void + TWOPsysLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsP; + double nConc, pConc; + double perTime = 0.0; + + /* first compute the currents and derivatives */ + TWOPcommonTerms( pDevice, FALSE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( pElem->elemType == SEMICON ) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + + *(pNode->fPsiPsi) += dxdy * nConc; + *(pNode->fPsiP) -= dxdy; + *(pNode->fPPsi) -= dy * pHEdge->dJpDpsiP1 + dx * pVEdge->dJpDpsiP1; + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + *(pNode->fPP) += dxdy * pNode->dUdP; + *(pNode->fPPsi) += dxdy * pNode->dUdN * nConc; + rhsP = dxdy * pNode->uNet; + pRhs[ pNode->pEqn ] -= rhsP; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + *(pNode->fPP) += dxdy * perTime; + pRhs[ pNode->pEqn ] -= dxdy * pNode->dPdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= dy * pTEdge->jp + dx * pLEdge->jp; + *(pNode->fPP) += dy * pTEdge->dJpDp + dx * pLEdge->dJpDp; + *(pNode->fPPsiiP1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pTEdge->dJpDpP1; + *(pNode->fPPsijP1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pLEdge->dJpDpP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= -dy * pTEdge->jp + dx * pREdge->jp; + *(pNode->fPP) += -dy * pTEdge->dJpDpP1 + dx * pREdge->dJpDp; + *(pNode->fPPsiiM1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pTEdge->dJpDp; + *(pNode->fPPsijP1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pREdge->dJpDpP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= -dy * pBEdge->jp - dx * pREdge->jp; + *(pNode->fPP) += -dy * pBEdge->dJpDpP1 - dx * pREdge->dJpDpP1; + *(pNode->fPPsiiM1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pBEdge->dJpDp; + *(pNode->fPPsijM1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pREdge->dJpDp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= dy * pBEdge->jp - dx * pLEdge->jp; + *(pNode->fPP) += dy * pBEdge->dJpDp - dx * pLEdge->dJpDpP1; + *(pNode->fPPsiiP1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pBEdge->dJpDpP1; + *(pNode->fPPsijM1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pLEdge->dJpDp; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWOPmobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + + +/* this function used only for direct method ac analysis + Used to load only the dc Jacobian matrix. Rhs is unaffected + */ + +void + TWOPjacLoad(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double dx, dy, dxdy, dyOverDx, dxOverDy; + double ds; + double nConc; + + /* first compute the currents and derivatives */ + TWOPcommonTerms( pDevice, FALSE, FALSE, NIL(TWOtranInfo) ); + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( pElem->elemType == SEMICON ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + nConc = *(pDevice->devState0 + pNode->nodeN); + *(pNode->fPsiPsi) += dxdy * nConc; + *(pNode->fPsiP) -= dxdy; + *(pNode->fPPsi) -= dy * pHEdge->dJpDpsiP1 + dx * pVEdge->dJpDpsiP1; + + /* Handle generation terms */ + *(pNode->fPP) += dxdy * pNode->dUdP; + *(pNode->fPPsi) += dxdy * pNode->dUdN * nConc; + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPP) += dy * pTEdge->dJpDp + dx * pLEdge->dJpDp; + *(pNode->fPPsiiP1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pTEdge->dJpDpP1; + *(pNode->fPPsijP1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pLEdge->dJpDpP1; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPP) += -dy * pTEdge->dJpDpP1 + dx * pREdge->dJpDp; + *(pNode->fPPsiiM1) += dy * pTEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pTEdge->dJpDp; + *(pNode->fPPsijP1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjP1) += dx * pREdge->dJpDpP1; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPP) += -dy * pBEdge->dJpDpP1 - dx * pREdge->dJpDpP1; + *(pNode->fPPsiiM1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiM1) -= dy * pBEdge->dJpDp; + *(pNode->fPPsijM1) += dx * pREdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pREdge->dJpDp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPP) += dy * pBEdge->dJpDp - dx * pLEdge->dJpDpP1; + *(pNode->fPPsiiP1) += dy * pBEdge->dJpDpsiP1; + *(pNode->fPPiP1) += dy * pBEdge->dJpDpP1; + *(pNode->fPPsijM1) += dx * pLEdge->dJpDpsiP1; + *(pNode->fPPjM1) -= dx * pLEdge->dJpDp; + } + } + } + + /* Calculate the Inversion-Layer Mobility Dependent Terms in Jac. */ + if ( MobDeriv && SurfaceMobility ) { + for ( pCh = pDevice->pChannel; pCh != NIL(TWOchannel); + pCh = pCh->next ) { + /* Find effective height of oxide element at interface. */ + if ( pCh->type%2 == 0 ) { /* Vertical slice */ + ds = pCh->pNElem->dy / pCh->pNElem->epsRel; + } else { /* Horizontal slice */ + ds = pCh->pNElem->dx / pCh->pNElem->epsRel; + } + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWOPmobDeriv( pElem, pCh->type, ds ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCh != NIL */ + } /* endif MobDeriv and SurfaceMobility */ +} + +/* load only the Rhs vector */ +void + TWOPrhsLoad(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pHEdge, *pVEdge; + TWOedge *pTEdge, *pBEdge, *pLEdge, *pREdge; + TWOchannel *pCh; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dx, dy, dxdy, dyOverDx, dxOverDy; + double dPsiT, dPsiB, dPsiL, dPsiR; + double rhsP; + double nConc, pConc; + double perTime; + + /* first compute the currents */ + TWOPcommonTerms( pDevice, TRUE, tranAnalysis, info ); + + /* find reciprocal timestep */ + if ( tranAnalysis ) { + perTime = info->intCoeff[0]; + } + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dx = 0.5 * pElem->dx; + dy = 0.5 * pElem->dy; + dxdy = dx * dy; + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + + pTEdge = pElem->pTopEdge; + pBEdge = pElem->pBotEdge; + pLEdge = pElem->pLeftEdge; + pREdge = pElem->pRightEdge; + dPsiT = pTEdge->dPsi; + dPsiB = pBEdge->dPsi; + dPsiL = pLEdge->dPsi; + dPsiR = pREdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + if ( index <= 1 ) { + pHEdge = pTEdge; + } else { + pHEdge = pBEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pLEdge; + } else { + pVEdge = pREdge; + } + /* Add surface state charges. */ + pRhs[ pNode->psiEqn ] += dx * pHEdge->qf; + pRhs[ pNode->psiEqn ] += dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + nConc = *(pDevice->devState0 + pNode->nodeN); + pConc = *(pDevice->devState0 + pNode->nodeP); + pRhs[ pNode->psiEqn ] += dxdy * (pNode->netConc + pConc - nConc); + + /* Handle generation terms */ + rhsP = dxdy * pNode->uNet; + pRhs[ pNode->pEqn ] -= rhsP; + + /* Handle dXdT continuity terms */ + if ( tranAnalysis ) { + pRhs[ pNode->pEqn ] -= dxdy * pNode->dPdT; + } + } + } + } + + /* Handle neighbor and edge dependent terms */ + pNode = pElem->pTLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= dy * pTEdge->jp + dx * pLEdge->jp; + } + } + pNode = pElem->pTRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= -dy * pTEdge->jp + dx * pREdge->jp; + } + } + pNode = pElem->pBRNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= -dy * pBEdge->jp - dx * pREdge->jp; + } + } + pNode = pElem->pBLNode; + if ( pNode->nodeType != CONTACT ) { + pRhs[ pNode->psiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + if ( pElem->elemType == SEMICON ) { + pRhs[ pNode->pEqn ] -= dy * pBEdge->jp - dx * pLEdge->jp; + } + } + } +} + +/* + * computation of current densities, recombination rates, + * mobilities and their derivatives + */ +void + TWOPcommonTerms(TWOdevice *pDevice, BOOLEAN currentOnly, + BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOelem *pElem, *pElem1; + TWOedge *pEdge; + TWOnode *pNode; + int index, eIndex; + int nextIndex; /* index of node to find next element */ + double psi1, psi2, refPsi, pC, pP1; + double dPsiP; + double bPsiP, dbPsiP, bMPsiP, dbMPsiP; + double muP, dMuP, rDx, rDy; + double psi, nConc = 0.0, pConc = 0.0; + double cnAug, cpAug; + double eSurf = 0.0; /* For channel mobilities */ + double qInt = 0.0; + TWOchannel *pCh; + + /* evaluate all node (including recombination) and edge quantities */ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + refPsi = pElem->matlInfo->refPsi; + cnAug = pElem->matlInfo->cAug[ELEC]; + cpAug = pElem->matlInfo->cAug[HOLE]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi = pDevice->dcSolution[ pNode->psiEqn ]; + if ( pElem->elemType == SEMICON ) { + nConc = pNode->nie * exp( psi - refPsi ); + pConc = pDevice->dcSolution[ pNode->pEqn ]; + if ( Srh ) { + recomb(nConc, pConc, + pNode->tn, pNode->tp, cnAug, cpAug, pNode->nie, + &pNode->uNet, &pNode->dUdN, &pNode->dUdP); + } else { + pNode->uNet = 0.0; + pNode->dUdN = 0.0; + pNode->dUdP = 0.0; + } + } + } else { + /* a contact node */ + psi = pNode->psi; + if ( pElem->elemType == SEMICON ) { + nConc = pNode->nConc; + pConc = pNode->pConc; + } + } + + /* store info in the state tables */ + *(pDevice->devState0 + pNode->nodePsi) = psi; + if ( pElem->elemType == SEMICON ) { + *(pDevice->devState0 + pNode->nodeN) = nConc; + *(pDevice->devState0 + pNode->nodeP) = pConc; + if ( tranAnalysis && pNode->nodeType != CONTACT ) { + pNode->dNdT = integrate( pDevice->devStates, info, pNode->nodeN ); + pNode->dPdT = integrate( pDevice->devStates, info, pNode->nodeP ); + } + } + } + } + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi1 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pNodes[ (index + 1) % 4 ]; + if ( pNode->nodeType != CONTACT ) { + psi2 = pDevice->dcSolution[pNode->psiEqn]; + } else { + psi2 = pNode->psi; + } + if ( index <= 1 ) { + pEdge->dPsi = psi2 - psi1; + } else { + pEdge->dPsi = psi1 - psi2; + } + *(pDevice->devState0 + pEdge->edgeDpsi) = pEdge->dPsi; + + if ( pElem->elemType == SEMICON ) { + /* Calculate weighted driving forces - wdfn & wdfp for the edge */ + dPsiP = pEdge->dPsi - pEdge->dVBand; + bernoulli( dPsiP, &bPsiP, &dbPsiP, + &bMPsiP, &dbMPsiP, !currentOnly); + if ( index <= 1 ) { + pC = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeP); + pP1 = *(pDevice->devState0 + pElem->pNodes[ index+1 ]->nodeP); + } else { + pC = *(pDevice->devState0 + pElem->pNodes[(index+1)%4]->nodeP); + pP1 = *(pDevice->devState0 + pElem->pNodes[ index ]->nodeP); + } + pEdge->wdfp = bPsiP * pC - bMPsiP * pP1; + pEdge->jp = 0.0; + if ( !currentOnly ) { + pEdge->dWpDpsiP1 = dbPsiP * pC - dbMPsiP * pP1; + pEdge->dWpDp = bPsiP; + pEdge->dWpDpP1 = - bMPsiP; + pEdge->dJpDpsiP1 = 0.0; + pEdge->dJpDp = 0.0; + pEdge->dJpDpP1 = 0.0; + } + } + } + } + } + + /* DAG: calculate mobilities for channel elems */ + if ( SurfaceMobility ) { + for ( pCh = pDevice->pChannel; + pCh != NIL(TWOchannel); pCh = pCh->next ) { + pElem = pCh->pNElem; + switch (pCh->type) { + case 0: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pBotEdge->qf; + break; + case 1: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pLeftEdge->qf; + break; + case 2: + eSurf = - 0.5 * (pElem->pLeftEdge->dPsi + pElem->pRightEdge->dPsi ) + * pElem->epsRel / pElem->dy; + qInt = 0.5 * pElem->pTopEdge->qf; + break; + case 3: + eSurf = - 0.5 * (pElem->pTopEdge->dPsi + pElem->pBotEdge->dPsi ) + * pElem->epsRel / pElem->dx; + qInt = 0.5 * pElem->pRightEdge->qf; + break; + } + eSurf += qInt; + pElem = pCh->pSeed; + nextIndex = (pCh->type + 2)%4; + while (pElem && pElem->channel == pCh->id) { + TWOPmobility( pElem, eSurf ); + pElem = pElem->pElems[ nextIndex ]; + } + } /* endfor pCH != NIL */ + } /* endif SurfaceMobility */ + + /* calculate the current densities assuming mobility value depend on Ex,Ey*/ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + rDx = 1.0 / pElem->dx; + rDy = 1.0 / pElem->dy; + for ( index = 0; index <= 3; index++ ) { + pEdge = pElem->pEdges[ index ]; + /* calculate conductive currents */ + if ( pElem->elemType == SEMICON ) { + /* get mobility for this edge */ + if ( !pElem->channel ) { + /* Calculate mobility for non-channel elements */ + muP = pElem->mup0; + dMuP = 0.0; + dPsiP = pEdge->dPsi - pEdge->dVBand; + if ( index%2 == 0 ) { + MOBfieldDep( pElem->matlInfo, HOLE, - dPsiP * rDx, &muP, &dMuP ); + } else { + MOBfieldDep( pElem->matlInfo, HOLE, - dPsiP * rDy, &muP, &dMuP ); + } + } else { + /* Retrieve previously calculated value. */ + muP = pElem->mup; + dMuP = 0.0; + } + switch ( index ) { + case 0: + muP *= pEdge->kPos * rDx; + dMuP *= pEdge->kPos * rDx * rDx; + break; + case 1: + muP *= pEdge->kNeg * rDy; + dMuP *= pEdge->kNeg * rDy * rDy; + break; + case 2: + muP *= pEdge->kNeg * rDx; + dMuP *= pEdge->kNeg * rDx * rDx; + break; + case 3: + muP *= pEdge->kPos * rDy; + dMuP *= pEdge->kPos * rDy * rDy; + break; + } + /* Now that the mobility for this edge is known, do current */ + pEdge->jp += muP * pEdge->wdfp; + if ( !currentOnly ) { + pEdge->dJpDpsiP1 += muP * pEdge->dWpDpsiP1; + pEdge->dJpDp += muP * pEdge->dWpDp; + pEdge->dJpDpP1 += muP * pEdge->dWpDpP1; + if ( MobDeriv && (!pElem->channel) ) { + pEdge->dJpDpsiP1 -= dMuP * pEdge->wdfp; + } + } + } + /* calculate displacement current only once */ + if ( pElem->evalEdges[ index ] ) { + if ( tranAnalysis ) { + if ( index == 0 || index == 2 ) { + /* horizontal edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDx; + } else { + /* vertical edges */ + pEdge->jd = -integrate(pDevice->devStates, info, + pEdge->edgeDpsi) * rDy; + } + } + } + } + } +} diff --git a/src/ciderlib/twod/twopoiss.c b/src/ciderlib/twod/twopoiss.c new file mode 100644 index 000000000..7866ebd51 --- /dev/null +++ b/src/ciderlib/twod/twopoiss.c @@ -0,0 +1,283 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" + + +/* functions to setup and solve the 2D poisson equation */ + +void +TWOQjacBuild(TWOdevice *pDevice) +{ + char *matrix = pDevice->matrix; + TWOelem *pElem; + TWOnode *pNode, *pNode1; + int eIndex, nIndex; + + /* set up matrix pointers */ + /* establish main diagonal first */ + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( nIndex = 0; nIndex <= 3; nIndex++ ) { + if ( pElem->evalNodes[ nIndex ] ) { + pNode = pElem->pNodes[ nIndex ]; + pNode->fPsiPsi = spGetElement( matrix, pNode->poiEqn, pNode->poiEqn ); + } + } + } + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + pNode = pElem->pTLNode; + pNode1 = pElem->pTRNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pBLNode; + pNode->fPsiPsijP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pTRNode; + pNode1 = pElem->pTLNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pBRNode; + pNode->fPsiPsijP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pBRNode; + pNode1 = pElem->pBLNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pTRNode; + pNode->fPsiPsijM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pBLNode; + pNode1 = pElem->pBRNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pTLNode; + pNode->fPsiPsijM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + } + /* + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + pNode = pElem->pTLNode; + pNode->fPsiPsi = spGetElement( matrix, pNode->poiEqn, pNode->poiEqn ); + pNode1 = pElem->pTRNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pBLNode; + pNode->fPsiPsijP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pTRNode; + pNode->fPsiPsi = spGetElement( matrix, pNode->poiEqn, pNode->poiEqn ); + pNode1 = pElem->pTLNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pBRNode; + pNode->fPsiPsijP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pBRNode; + pNode->fPsiPsi = spGetElement( matrix, pNode->poiEqn, pNode->poiEqn ); + pNode1 = pElem->pBLNode; + pNode->fPsiPsiiM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pTRNode; + pNode->fPsiPsijM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode = pElem->pBLNode; + pNode->fPsiPsi = spGetElement( matrix, pNode->poiEqn, pNode->poiEqn ); + pNode1 = pElem->pBRNode; + pNode->fPsiPsiiP1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + pNode1 = pElem->pTLNode; + pNode->fPsiPsijM1 = spGetElement(matrix, pNode->poiEqn, pNode1->poiEqn ); + } + */ +} + +void +TWOQsysLoad(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOnode *pNode, *pNode1; + TWOedge *pHEdge, *pVEdge; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dyOverDx, dxOverDy, dPsiT, dPsiB, dPsiL, dPsiR; + + TWOQcommonTerms( pDevice ); + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + /* zero the matrix */ + spClear( pDevice->matrix ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + dPsiT = pElem->pTopEdge->dPsi; + dPsiB = pElem->pBotEdge->dPsi; + dPsiL = pElem->pLeftEdge->dPsi; + dPsiR = pElem->pRightEdge->dPsi; + + /* load for all i,j */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + *(pNode->fPsiPsi) += dyOverDx + dxOverDy; + if ( index <= 1 ) { + pHEdge = pElem->pTopEdge; + } else { + pHEdge = pElem->pBotEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pElem->pLeftEdge; + } else { + pVEdge = pElem->pRightEdge; + } + /* add surface state charges */ + pRhs[ pNode->poiEqn ] += 0.5 * pElem->dx * pHEdge->qf; + pRhs[ pNode->poiEqn ] += 0.5 * pElem->dy * pVEdge->qf; + if ( pElem->elemType == SEMICON ) { + *(pNode->fPsiPsi) += 0.25 * pElem->dx * pElem->dy * + (pNode->nConc + pNode->pConc); + pRhs[ pNode->poiEqn ] += 0.25 * pElem->dx * pElem->dy * + (pNode->netConc + pNode->pConc - pNode->nConc); + } + } + } + + pNode = pElem->pTLNode; + pRhs[ pNode->poiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + + pNode = pElem->pTRNode; + pRhs[ pNode->poiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijP1) -= dxOverDy; + + pNode = pElem->pBRNode; + pRhs[ pNode->poiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + *(pNode->fPsiPsiiM1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + + pNode = pElem->pBLNode; + pRhs[ pNode->poiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + *(pNode->fPsiPsiiP1) -= dyOverDx; + *(pNode->fPsiPsijM1) -= dxOverDy; + } +} + + +void +TWOQrhsLoad(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOnode *pNode, *pNode1; + TWOedge *pHEdge, *pVEdge; + int index, eIndex; + double *pRhs = pDevice->rhs; + double dyOverDx, dxOverDy, dPsiT, dPsiB, dPsiL, dPsiR; + + TWOQcommonTerms( pDevice ); + + /* zero the rhs vector */ + for ( index = 1 ; index <= pDevice->numEqns ; index++ ) { + pRhs[ index ] = 0.0; + } + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + dxOverDy = 0.5 * pElem->epsRel * pElem->dxOverDy; + dyOverDx = 0.5 * pElem->epsRel * pElem->dyOverDx; + dPsiT = pElem->pTopEdge->dPsi; + dPsiB = pElem->pBotEdge->dPsi; + dPsiL = pElem->pLeftEdge->dPsi; + dPsiR = pElem->pRightEdge->dPsi; + + /* load nodal terms */ + for ( index = 0; index <= 3; index++ ) { + pNode = pElem->pNodes[ index ]; + if ( (pNode->nodeType != CONTACT) && (pElem->elemType == SEMICON) ) { + pRhs[ pNode->poiEqn ] += 0.25 * pElem->dx * pElem->dy * + (pNode->netConc + pNode->pConc - pNode->nConc); + } + if ( index <= 1 ) { + pHEdge = pElem->pTopEdge; + } else { + pHEdge = pElem->pBotEdge; + } + if ( index == 0 || index == 3 ) { + pVEdge = pElem->pLeftEdge; + } else { + pVEdge = pElem->pRightEdge; + } + /* add surface state charges */ + pRhs[ pNode->poiEqn ] += 0.5 * pElem->dx * pHEdge->qf; + pRhs[ pNode->poiEqn ] += 0.5 * pElem->dy * pVEdge->qf; + } + + /* load edge terms */ + pNode = pElem->pTLNode; + pRhs[ pNode->poiEqn ] -= -dyOverDx * dPsiT - dxOverDy * dPsiL; + + pNode = pElem->pTRNode; + pRhs[ pNode->poiEqn ] -= dyOverDx * dPsiT - dxOverDy * dPsiR; + + pNode = pElem->pBRNode; + pRhs[ pNode->poiEqn ] -= dyOverDx * dPsiB + dxOverDy * dPsiR; + + pNode = pElem->pBLNode; + pRhs[ pNode->poiEqn ] -= -dyOverDx * dPsiB + dxOverDy * dPsiL; + } +} + +void +TWOQcommonTerms(TWOdevice *pDevice) +{ + TWOelem *pElem; + TWOedge *pEdge; + TWOnode *pNode, *pNode1; + int index, eIndex; + double psi1, psi2, refPsi; + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + refPsi = pElem->matlInfo->refPsi; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + pNode->psi = pDevice->dcSolution[ pNode->poiEqn ]; + if ( pElem->elemType == SEMICON ) { + pNode->nConc = pNode->nie * exp( pNode->psi - refPsi ); + pNode->pConc = pNode->nie * exp( - pNode->psi + refPsi ); + } + } + } + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + psi1 = pDevice->dcSolution[pNode->poiEqn]; + } else { + psi1 = pNode->psi; + } + pNode = pElem->pNodes[ (index + 1) % 4 ]; + if ( pNode->nodeType != CONTACT ) { + psi2 = pDevice->dcSolution[pNode->poiEqn]; + } else { + psi2 = pNode->psi; + } + if ( index <= 1 ) { + pEdge->dPsi = psi2 - psi1; + } else { + pEdge->dPsi = psi1 - psi2; + } + } + } + } +} diff --git a/src/ciderlib/twod/twoprint.c b/src/ciderlib/twod/twoprint.c new file mode 100644 index 000000000..d55f6e6d5 --- /dev/null +++ b/src/ciderlib/twod/twoprint.c @@ -0,0 +1,561 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "carddefs.h" +#include "spMatrix.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" + +void +TWOprnSolution(FILE *file, TWOdevice *pDevice, OUTPcard *output) +{ + int i, index, xIndex, yIndex; + int numVars = 0; + TWOnode ***nodeArray = NULL; + TWOnode *pNode; + TWOelem *pElem, *pNextElem; + TWOmaterial *info; + double data[50]; + double ex, ey, refPsi = 0.0, eGap, dGap; + double mun, mup; + double jcx, jdx, jnx, jpx, jtx; + double jcy, jdy, jny, jpy, jty; + double *xScale = pDevice->xScale; + double *yScale = pDevice->yScale; + BOOLEAN foundElem; + + if (output->OUTPnumVars == -1) { + /* First pass. Need to count number of variables in output. */ + numVars += 2; /* For the X & Y scales */ + if (output->OUTPdoping) { + numVars++; + } + if (output->OUTPpsi) { + numVars++; + } + if (output->OUTPequPsi) { + numVars++; + } + if (output->OUTPvacPsi) { + numVars++; + } + if (output->OUTPnConc) { + numVars++; + } + if (output->OUTPpConc) { + numVars++; + } + if (output->OUTPphin) { + numVars++; + } + if (output->OUTPphip) { + numVars++; + } + if (output->OUTPphic) { + numVars++; + } + if (output->OUTPphiv) { + numVars++; + } + if (output->OUTPeField) { + numVars += 2; + } + if (output->OUTPjc) { + numVars += 2; + } + if (output->OUTPjd) { + numVars += 2; + } + if (output->OUTPjn) { + numVars += 2; + } + if (output->OUTPjp) { + numVars += 2; + } + if (output->OUTPjt) { + numVars += 2; + } + if (output->OUTPuNet) { + numVars++; + } + if (output->OUTPmun) { + numVars++; + } + if (output->OUTPmup) { + numVars++; + } + output->OUTPnumVars = numVars; + } + /* generate the work array for printing node info */ + XCALLOC(nodeArray, TWOnode **, 1 + pDevice->numXNodes); + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + XCALLOC(nodeArray[xIndex], TWOnode *, 1 + pDevice->numYNodes); + } + + /* store the nodes in this work array and print out later */ + for (xIndex = 1; xIndex < pDevice->numXNodes; xIndex++) { + for (yIndex = 1; yIndex < pDevice->numYNodes; yIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + if (refPsi == 0.0 && pElem->matlInfo->type == SEMICON) { + refPsi = pElem->matlInfo->refPsi; + } + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + nodeArray[pNode->nodeI][pNode->nodeJ] = pNode; + } + } + } + } + } + + /* Initialize rawfile */ + numVars = output->OUTPnumVars; + fprintf(file, "Title: Device %s internal state\n", pDevice->name); + fprintf(file, "Plotname: Device Cross Section\n"); + fprintf(file, "Flags: real\n"); + fprintf(file, "Command: deftype p xs cross\n"); + fprintf(file, "Command: deftype v distance m\n"); + fprintf(file, "Command: deftype v concentration cm^-3\n"); + fprintf(file, "Command: deftype v electric_field V/cm\n"); + fprintf(file, "Command: deftype v current_density A/cm^2\n"); + fprintf(file, "Command: deftype v concentration/time cm^-3/s\n"); + fprintf(file, "Command: deftype v mobility cm^2/Vs\n"); + fprintf(file, "No. Variables: %d\n", numVars); + fprintf(file, "No. Points: %d\n", + pDevice->numXNodes * pDevice->numYNodes); + fprintf(file, "Dimensions: %d,%d\n", + pDevice->numXNodes, pDevice->numYNodes); + numVars = 0; + fprintf(file, "Variables:\n"); + fprintf(file, "\t%d y distance\n", numVars++); + fprintf(file, "\t%d x distance\n", numVars++); + if (output->OUTPpsi) { + fprintf(file, "\t%d psi voltage\n", numVars++); + } + if (output->OUTPequPsi) { + fprintf(file, "\t%d equ.psi voltage\n", numVars++); + } + if (output->OUTPvacPsi) { + fprintf(file, "\t%d vac.psi voltage\n", numVars++); + } + if (output->OUTPphin) { + fprintf(file, "\t%d phin voltage\n", numVars++); + } + if (output->OUTPphip) { + fprintf(file, "\t%d phip voltage\n", numVars++); + } + if (output->OUTPphic) { + fprintf(file, "\t%d phic voltage\n", numVars++); + } + if (output->OUTPphiv) { + fprintf(file, "\t%d phiv voltage\n", numVars++); + } + if (output->OUTPdoping) { + fprintf(file, "\t%d dop concentration\n", numVars++); + } + if (output->OUTPnConc) { + fprintf(file, "\t%d n concentration\n", numVars++); + } + if (output->OUTPpConc) { + fprintf(file, "\t%d p concentration\n", numVars++); + } + if (output->OUTPeField) { + fprintf(file, "\t%d ex electric_field\n", numVars++); + fprintf(file, "\t%d ey electric_field\n", numVars++); + } + if (output->OUTPjc) { + fprintf(file, "\t%d jcx current_density\n", numVars++); + fprintf(file, "\t%d jcy current_density\n", numVars++); + } + if (output->OUTPjd) { + fprintf(file, "\t%d jdx current_density\n", numVars++); + fprintf(file, "\t%d jdy current_density\n", numVars++); + } + if (output->OUTPjn) { + fprintf(file, "\t%d jnx current_density\n", numVars++); + fprintf(file, "\t%d jny current_density\n", numVars++); + } + if (output->OUTPjp) { + fprintf(file, "\t%d jpx current_density\n", numVars++); + fprintf(file, "\t%d jpy current_density\n", numVars++); + } + if (output->OUTPjt) { + fprintf(file, "\t%d jtx current_density\n", numVars++); + fprintf(file, "\t%d jty current_density\n", numVars++); + } + if (output->OUTPuNet) { + fprintf(file, "\t%d unet concentration/time\n", numVars++); + } + if (output->OUTPmun) { + fprintf(file, "\t%d mun mobility\n", numVars++); + } + if (output->OUTPmup) { + fprintf(file, "\t%d mup mobility\n", numVars++); + } + fprintf(file, "Binary:\n"); + + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + for (yIndex = 1; yIndex <= pDevice->numYNodes; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + if (pNode != NIL(TWOnode)) { + /* Find the element to which this node belongs. */ + foundElem = FALSE; + for (index = 0; (index <= 3) && (!foundElem); index++) { + pElem = pNode->pElems[index]; + if (pElem != NIL(TWOelem) && pElem->evalNodes[(index + 2) % 4]) { + foundElem = TRUE; + } + } + nodeFields(pElem, pNode, &ex, &ey); + nodeCurrents(pElem, pNode, &mun, &mup, + &jnx, &jny, &jpx, &jpy, &jdx, &jdy); + jcx = jnx + jpx; + jcy = jny + jpy; + jtx = jcx + jdx; + jty = jcy + jdy; + + info = pElem->matlInfo; + eGap = pNode->eg * VNorm; + dGap = 0.5 * (info->eg0 - eGap); + + /* Now fill in the data array */ + numVars = 0; + data[numVars++] = yScale[yIndex] * 1e-2; + data[numVars++] = xScale[xIndex] * 1e-2; + if (output->OUTPpsi) { + data[numVars++] = (pNode->psi - refPsi) * VNorm; + } + if (output->OUTPequPsi) { + data[numVars++] = (pNode->psi0 - refPsi) * VNorm; + } + if (output->OUTPvacPsi) { + data[numVars++] = pNode->psi * VNorm; + } + if (output->OUTPphin) { + if (info->type != INSULATOR) { + data[numVars++] = (pNode->psi - refPsi + - log(pNode->nConc / pNode->nie)) * VNorm; + } else { + data[numVars++] = 0.0; + } + } + if (output->OUTPphip) { + if (info->type != INSULATOR) { + data[numVars++] = (pNode->psi - refPsi + + log(pNode->pConc / pNode->nie)) * VNorm; + } else { + data[numVars++] = 0.0; + } + } + if (output->OUTPphic) { + data[numVars++] = (pNode->psi + pNode->eaff) * VNorm + dGap; + } + if (output->OUTPphiv) { + data[numVars++] = (pNode->psi + pNode->eaff) * VNorm + dGap + eGap; + } + if (output->OUTPdoping) { + data[numVars++] = pNode->netConc * NNorm; + } + if (output->OUTPnConc) { + data[numVars++] = pNode->nConc * NNorm; + } + if (output->OUTPpConc) { + data[numVars++] = pNode->pConc * NNorm; + } + if (output->OUTPeField) { + data[numVars++] = ex * ENorm; + data[numVars++] = ey * ENorm; + } + if (output->OUTPjc) { + data[numVars++] = jcx * JNorm; + data[numVars++] = jcy * JNorm; + } + if (output->OUTPjd) { + data[numVars++] = jdx * JNorm; + data[numVars++] = jdy * JNorm; + } + if (output->OUTPjn) { + data[numVars++] = jnx * JNorm; + data[numVars++] = jny * JNorm; + } + if (output->OUTPjp) { + data[numVars++] = jpx * JNorm; + data[numVars++] = jpy * JNorm; + } + if (output->OUTPjt) { + data[numVars++] = jtx * JNorm; + data[numVars++] = jty * JNorm; + } + if (output->OUTPuNet) { + data[numVars++] = pNode->uNet * NNorm / TNorm; + } + if (output->OUTPmun) { + data[numVars++] = mun; + } + if (output->OUTPmup) { + data[numVars++] = mup; + } + fwrite((char *) data, sizeof(double), numVars, file); + } else { + for (index = 0; index < output->OUTPnumVars; index++) { + data[index] = 0.0; + } + data[0] = yScale[yIndex] * 1e-2; + data[1] = xScale[xIndex] * 1e-2; + fwrite((char *) data, sizeof(double), numVars, file); + } + } + } + /* Delete work array. */ + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + FREE(nodeArray[xIndex]); + } + FREE(nodeArray); +} + +/* XXX This is what the SPARSE element structure looks like. + * We can't take it from its definition because the include + * file redefines all sorts of things. Note that we are + * violating data encapsulation to find out the size of this + * thing. + */ +struct MatrixElement +{ spREAL Real; + spREAL Imag; + int Row; + int Col; + struct MatrixElement *NextInRow; + struct MatrixElement *NextInCol; +}; + +void +TWOmemStats(FILE *file, TWOdevice *pDevice) +{ + static char *memFormat = "%-20s%10d%10d\n"; + static char *sumFormat = "%20s %-10d\n"; + unsigned int size; + unsigned int memory; + TWOmaterial *pMaterial; + TWOcontact *pContact; + TWOchannel *pChannel; + int numContactNodes; + + fprintf(file, "----------------------------------------\n"); + fprintf(file, "Device %s Memory Usage:\n", pDevice->name ); + fprintf(file, "Item Count Bytes\n"); + fprintf(file, "----------------------------------------\n"); + + size = 1; + memory = size * sizeof(TWOdevice); + fprintf( file, memFormat, "Device", size, memory ); + size = pDevice->numElems; + memory = size * sizeof(TWOelem); + fprintf( file, memFormat, "Elements", size, memory ); + size = pDevice->numNodes; + memory = size * sizeof(TWOnode); + fprintf( file, memFormat, "Nodes", size, memory ); + size = pDevice->numEdges; + memory = size * sizeof(TWOedge); + fprintf( file, memFormat, "Edges", size, memory ); + + size = pDevice->numXNodes; + memory = size * sizeof(TWOelem **); + size = (pDevice->numXNodes-1) * pDevice->numYNodes; + memory += size * sizeof(TWOelem *); + size = pDevice->numElems + 1; + memory += size * sizeof(TWOelem *); + size = pDevice->numXNodes + pDevice->numYNodes; + memory += size * sizeof(double); + size = 0; + for (pMaterial = pDevice->pMaterials; pMaterial; pMaterial = pMaterial->next) + size++; + memory += size * sizeof(TWOmaterial); + size = numContactNodes = 0; + for (pContact = pDevice->pFirstContact; pContact; pContact = pContact->next) { + numContactNodes += pContact->numNodes; + size++; + } + memory += size * sizeof(TWOcontact); + size = numContactNodes; + memory += size * sizeof(TWOnode *); + size = 0; + for (pChannel = pDevice->pChannel; pChannel; pChannel = pChannel->next) + size++; + memory += size * sizeof(TWOchannel); + fprintf(file, "%-20s%10s%10d\n", "Misc Mesh", "n/a", memory); + + size = pDevice->numOrigEquil; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Equil Orig NZ", size, memory ); + size = pDevice->numFillEquil; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Equil Fill NZ", size, memory ); + size = pDevice->numOrigEquil + pDevice->numFillEquil; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Equil Tot NZ", size, memory ); + size = pDevice->dimEquil; + memory = size * 4 * sizeof(double); + fprintf( file, memFormat, "Equil Vectors", size, memory ); + + size = pDevice->numOrigBias; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Bias Orig NZ", size, memory ); + size = pDevice->numFillBias; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Bias Fill NZ", size, memory ); + size = pDevice->numOrigBias + pDevice->numFillBias; + memory = size * sizeof(struct MatrixElement); + fprintf( file, memFormat, "Bias Tot NZ", size, memory ); + size = pDevice->dimBias; + memory = size * 5 * sizeof(double); + fprintf( file, memFormat, "Bias Vectors", size, memory ); + + size = pDevice->numEdges * TWOnumEdgeStates + + pDevice->numNodes * TWOnumNodeStates; + memory = size * sizeof(double); + fprintf( file, memFormat, "State Vector", size, memory ); +} + +void +TWOcpuStats(FILE *file, TWOdevice *pDevice) +{ + static char *cpuFormat = "%-20s%10g%10g%10g%10g%10g\n"; + TWOstats *pStats = pDevice->pStats; + double total; + int iTotal; + + fprintf(file, + "----------------------------------------------------------------------\n"); + fprintf(file, + "Device %s Time Usage:\n", pDevice->name); + fprintf(file, + "Item SETUP DC TRAN AC TOTAL\n"); + fprintf(file, + "----------------------------------------------------------------------\n"); + + total = pStats->setupTime[STAT_SETUP] + + pStats->setupTime[STAT_DC] + + pStats->setupTime[STAT_TRAN] + + pStats->setupTime[STAT_AC]; + fprintf(file, cpuFormat, "Setup Time", + pStats->setupTime[STAT_SETUP], + pStats->setupTime[STAT_DC], + pStats->setupTime[STAT_TRAN], + pStats->setupTime[STAT_AC], + total); + + total = pStats->loadTime[STAT_SETUP] + + pStats->loadTime[STAT_DC] + + pStats->loadTime[STAT_TRAN] + + pStats->loadTime[STAT_AC]; + fprintf(file, cpuFormat, "Load Time", + pStats->loadTime[STAT_SETUP], + pStats->loadTime[STAT_DC], + pStats->loadTime[STAT_TRAN], + pStats->loadTime[STAT_AC], + total); + + total = pStats->orderTime[STAT_SETUP] + + pStats->orderTime[STAT_DC] + + pStats->orderTime[STAT_TRAN] + + pStats->orderTime[STAT_AC]; + fprintf(file, cpuFormat, "Order Time", + pStats->orderTime[STAT_SETUP], + pStats->orderTime[STAT_DC], + pStats->orderTime[STAT_TRAN], + pStats->orderTime[STAT_AC], + total); + + total = pStats->factorTime[STAT_SETUP] + + pStats->factorTime[STAT_DC] + + pStats->factorTime[STAT_TRAN] + + pStats->factorTime[STAT_AC]; + fprintf(file, cpuFormat, "Factor Time", + pStats->factorTime[STAT_SETUP], + pStats->factorTime[STAT_DC], + pStats->factorTime[STAT_TRAN], + pStats->factorTime[STAT_AC], + total); + + total = pStats->solveTime[STAT_SETUP] + + pStats->solveTime[STAT_DC] + + pStats->solveTime[STAT_TRAN] + + pStats->solveTime[STAT_AC]; + fprintf(file, cpuFormat, "Solve Time", + pStats->solveTime[STAT_SETUP], + pStats->solveTime[STAT_DC], + pStats->solveTime[STAT_TRAN], + pStats->solveTime[STAT_AC], + total); + + total = pStats->updateTime[STAT_SETUP] + + pStats->updateTime[STAT_DC] + + pStats->updateTime[STAT_TRAN] + + pStats->updateTime[STAT_AC]; + fprintf(file, cpuFormat, "Update Time", + pStats->updateTime[STAT_SETUP], + pStats->updateTime[STAT_DC], + pStats->updateTime[STAT_TRAN], + pStats->updateTime[STAT_AC], + total); + + total = pStats->checkTime[STAT_SETUP] + + pStats->checkTime[STAT_DC] + + pStats->checkTime[STAT_TRAN] + + pStats->checkTime[STAT_AC]; + fprintf(file, cpuFormat, "Check Time", + pStats->checkTime[STAT_SETUP], + pStats->checkTime[STAT_DC], + pStats->checkTime[STAT_TRAN], + pStats->checkTime[STAT_AC], + total); + + total = pStats->setupTime[STAT_SETUP] + + pStats->setupTime[STAT_DC] + + pStats->setupTime[STAT_TRAN] + + pStats->setupTime[STAT_AC]; + fprintf(file, cpuFormat, "Misc Time", + pStats->miscTime[STAT_SETUP], + pStats->miscTime[STAT_DC], + pStats->miscTime[STAT_TRAN], + pStats->miscTime[STAT_AC], + total); + + fprintf(file, "%-40s%10g%10s%10g\n", "LTE Time", + pStats->lteTime, + "", pStats->lteTime); + + total = pStats->totalTime[STAT_SETUP] + + pStats->totalTime[STAT_DC] + + pStats->totalTime[STAT_TRAN] + + pStats->totalTime[STAT_AC]; + fprintf(file, cpuFormat, "Total Time", + pStats->totalTime[STAT_SETUP], + pStats->totalTime[STAT_DC], + pStats->totalTime[STAT_TRAN], + pStats->totalTime[STAT_AC], + total); + + iTotal = pStats->numIters[STAT_SETUP] + + pStats->numIters[STAT_DC] + + pStats->numIters[STAT_TRAN] + + pStats->numIters[STAT_AC]; + fprintf(file, "%-20s%10d%10d%10d%10d%10d\n", "Iterations", + pStats->numIters[STAT_SETUP], + pStats->numIters[STAT_DC], + pStats->numIters[STAT_TRAN], + pStats->numIters[STAT_AC], + iTotal); +} diff --git a/src/ciderlib/twod/twoproj.c b/src/ciderlib/twod/twoproj.c new file mode 100644 index 000000000..ddd851dc9 --- /dev/null +++ b/src/ciderlib/twod/twoproj.c @@ -0,0 +1,679 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Functions for projecting the next solution point for use with the modified + * two-level Newton scheme + */ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "bool.h" +#include "spMatrix.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" + + +/* Forward Declarations */ + + +void NUMD2project(TWOdevice *pDevice, double delV) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pContact = pDevice->pLastContact; + double *incVpn, *solution = pDevice->dcSolution; + double delPsi, delN, delP, newN, newP; + + delV = -delV / VNorm; + /* update the boundary condition for the last contact */ + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + pNode->psi += delV; + } + + /* + * store the new rhs for computing the incremental quantities + * with the second to last node. solve the system of equations + */ + + if ( ABS(delV) < MIN_DELV ) { + TWOstoreInitialGuess( pDevice ); + return; + } + incVpn = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVpn, NIL(spREAL), NIL(spREAL) ); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVpn[ pNode->psiEqn ] * delV; + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVpn[ pNode->nEqn ] * delV; + newN = pNode->nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( pNode->nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVpn[ pNode->pEqn ] * delV; + newP = pNode->pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pNode->pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } +} + + +void NBJT2project(TWOdevice *pDevice, double delVce, double delVbe) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + double *incVce, *incVbe, *solution = pDevice->dcSolution; + double delPsi, delN, delP, newN, newP; + double nConc, pConc; + + /* Normalize the voltages for calculations. */ + if ( delVce != 0.0 ) { + delVce = delVce / VNorm; + numContactNodes = pColContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pColContact->pNodes[ index ]; + pNode->psi += delVce; + } + } + if ( delVbe != 0.0 ) { + delVbe = delVbe / VNorm; + numContactNodes = pBaseContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pBaseContact->pNodes[ index ]; + pNode->psi += delVbe; + } + } + + /* + * store the new rhs for computing the incremental quantities + * incVce (dcDeltaSolution) and incVbe (copiedSolution) are used to + * store the incremental quantities associated with Vce and Vbe + */ + + /* set incVce = dcDeltaSolution; incVbe = copiedSolution */ + + if ( ABS( delVce ) > MIN_DELV ) { + incVce = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pColContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVce, NIL(spREAL), NIL(spREAL)); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVce[ pNode->psiEqn ] * delVce; + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVce[ pNode->nEqn ] * delVce; + newN = pNode->nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( pNode->nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier | (OneCarrier == P_TYPE)) ) { + delP = incVce[ pNode->pEqn ] * delVce; + newP = pNode->pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pNode->pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } + } + else { + TWOstoreInitialGuess( pDevice ); + } + + if ( ABS( delVbe ) > MIN_DELV ) { + incVbe = pDevice->copiedSolution; + storeNewRhs( pDevice, pBaseContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVbe, NIL(spREAL), NIL(spREAL)); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVbe[ pNode->psiEqn ] * delVbe; + solution[ pNode->psiEqn ] += delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVbe[ pNode->nEqn ] * delVbe; + nConc = solution[ pNode->nEqn ]; + newN = nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVbe[ pNode->pEqn ] * delVbe; + pConc = solution[ pNode->pEqn ]; + newP = pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } + } +} + + +void NUMOSproject(TWOdevice *pDevice, double delVdb, double delVsb, + double delVgb) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + double *incVdb, *incVsb, *incVgb, *solution = pDevice->dcSolution; + double delPsi, delN, delP, newN, newP; + double nConc, pConc; + + /* normalize the voltages for calculations */ + if ( delVdb != 0.0 ) { + delVdb = delVdb / VNorm; + numContactNodes = pDContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pDContact->pNodes[ index ]; + pNode->psi += delVdb; + } + } + if ( delVsb != 0.0 ) { + delVsb = delVsb / VNorm; + numContactNodes = pSContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pSContact->pNodes[ index ]; + pNode->psi += delVsb; + } + } + if ( delVgb != 0.0 ) { + delVgb = delVgb / VNorm; + numContactNodes = pGContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pGContact->pNodes[ index ]; + pNode->psi += delVgb; + } + } + + /* + * store the new rhs for computing the incremental quantities + * incVdb (dcDeltaSolution), incVsb, incVgb + */ + + if ( ABS( delVdb ) > MIN_DELV ) { + + incVdb = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pDContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVdb, NIL(spREAL), NIL(spREAL)); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVdb[ pNode->psiEqn ] * delVdb; + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVdb[ pNode->nEqn ] * delVdb; + newN = pNode->nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( pNode->nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVdb[ pNode->pEqn ] * delVdb; + newP = pNode->pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pNode->pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } + } + else { + TWOstoreInitialGuess( pDevice ); + } + + if ( ABS( delVsb ) > MIN_DELV ) { + + incVsb = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pSContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVsb, NIL(spREAL), NIL(spREAL)); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVsb[ pNode->psiEqn ] * delVsb; + solution[ pNode->psiEqn ] += delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVsb[ pNode->nEqn ] * delVsb; + nConc = solution[ pNode->nEqn ]; + newN = nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVsb[ pNode->pEqn ] * delVsb; + pConc = solution[ pNode->pEqn ]; + newP = pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } + } + + if ( ABS( delVgb ) > MIN_DELV ) { + + incVgb = pDevice->dcDeltaSolution; + storeNewRhs( pDevice, pGContact ); + spSolve( pDevice->matrix, pDevice->rhs, incVgb, NIL(spREAL), NIL(spREAL)); + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVgb[ pNode->psiEqn ] * delVgb; + solution[ pNode->psiEqn ] += delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVgb[ pNode->nEqn ] * delVgb; + nConc = solution[ pNode->nEqn ]; + newN = nConc + delN; + if ( newN <= 0.0 ) { + solution[ pNode->nEqn ] = guessNewConc( nConc, delN ); + } + else { + solution[ pNode->nEqn ] = newN; + } + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVgb[ pNode->pEqn ] * delVgb; + pConc = solution[ pNode->pEqn ]; + newP = pConc + delP; + if ( newP <= 0.0 ) { + solution[ pNode->pEqn ] = guessNewConc( pConc, delP ); + } + else { + solution[ pNode->pEqn ] = newP; + } + } + } + } + } + } + } +} + +/* functions to update the solution for the full-LU and + modified two-level Newton methods + */ + +void NUMD2update(TWOdevice *pDevice, double delV, BOOLEAN updateBoundary) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pContact = pDevice->pLastContact; + double delPsi, delN, delP, *incVpn, *solution = pDevice->dcSolution; + + delV = -delV / VNorm; + if ( updateBoundary ) { + /* update the boundary condition for the last contact */ + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + pNode->psi += delV; + } + } + + /* the equations have been solved while computing the conductances */ + /* solution is in dcDeltaSolution, so use it */ + + incVpn = pDevice->dcDeltaSolution; + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = incVpn[ pNode->psiEqn ] * delV; + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = incVpn[ pNode->nEqn ] * delV; + solution[ pNode->nEqn ] = pNode->nConc + delN; + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = incVpn[ pNode->pEqn ] * delV; + solution[ pNode->pEqn ] = pNode->pConc + delP; + } + } + } + } + } +} + + +void NBJT2update(TWOdevice *pDevice, double delVce, double delVbe, + BOOLEAN updateBoundary) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pColContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + double delPsi, delN, delP, *incVce, *incVbe, *solution = pDevice->dcSolution; + + /* normalize the voltages for calculations */ + + if ( delVce != 0.0 ) { + delVce = delVce / VNorm; + if ( updateBoundary ) { + numContactNodes = pColContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pColContact->pNodes[ index ]; + pNode->psi += delVce; + } + } + } + if ( delVbe != 0.0 ) { + delVbe = delVbe / VNorm; + if ( updateBoundary ) { + numContactNodes = pBaseContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pBaseContact->pNodes[ index ]; + pNode->psi += delVbe; + } + } + } + + /* use solution from computeConductance to do update */ + /* set incVce = dcDeltaSolution; incVbe = copiedSolution */ + + incVce = pDevice->dcDeltaSolution; + incVbe = pDevice->copiedSolution; + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = (incVce[ pNode->psiEqn ] * delVce + + incVbe[ pNode->psiEqn ] * delVbe); + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = (incVce[ pNode->nEqn ] * delVce + + incVbe[ pNode->nEqn ] * delVbe); + solution[ pNode->nEqn ] = pNode->nConc + delN; + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = (incVce[ pNode->pEqn ] * delVce + + incVbe[ pNode->pEqn ] * delVbe); + solution[ pNode->pEqn ] = pNode->pConc + delP; + } + } + } + } + } +} + + +void NUMOSupdate(TWOdevice *pDevice, double delVdb, double delVsb, + double delVgb, BOOLEAN updateBoundary) +{ + TWOnode *pNode; + TWOelem *pElem; + int index, eIndex, numContactNodes; + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + double delPsi, delN, delP; + double *incVdb, *incVsb, *incVgb, *solution = pDevice->dcSolution; + + /* normalize the voltages for calculations */ + if ( delVdb != 0.0 ) { + delVdb = delVdb / VNorm; + if ( updateBoundary ) { + numContactNodes = pDContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pDContact->pNodes[ index ]; + pNode->psi += delVdb; + } + } + } + if ( delVsb != 0.0 ) { + delVsb = delVsb / VNorm; + if ( updateBoundary ) { + numContactNodes = pSContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pSContact->pNodes[ index ]; + pNode->psi += delVsb; + } + } + } + if ( delVgb != 0.0 ) { + delVgb = delVgb / VNorm; + if ( updateBoundary ) { + numContactNodes = pGContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pGContact->pNodes[ index ]; + pNode->psi += delVgb; + } + } + } + + /* use solution from computeConductance to do update */ + + incVdb = pDevice->dcDeltaSolution; + incVsb = pDevice->copiedSolution; + incVgb = pDevice->rhsImag; + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if ( pNode->nodeType != CONTACT ) { + delPsi = (incVdb[ pNode->psiEqn ] * delVdb + + incVsb[ pNode->psiEqn ] * delVsb + + incVgb[ pNode->psiEqn ] * delVgb); + solution[ pNode->psiEqn ] = pNode->psi + delPsi; + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == N_TYPE)) ) { + delN = (incVdb[ pNode->nEqn ] * delVdb + + incVsb[ pNode->nEqn ] * delVsb + + incVgb[ pNode->nEqn ] * delVgb); + solution[ pNode->nEqn ] = pNode->nConc + delN; + } + if ( pElem->elemType == SEMICON + && (!OneCarrier || (OneCarrier == P_TYPE)) ) { + delP = (incVdb[ pNode->pEqn ] * delVdb + + incVsb[ pNode->pEqn ] * delVsb + + incVgb[ pNode->pEqn ] * delVgb); + solution[ pNode->pEqn ] = pNode->pConc + delP; + } + } + } + } + } +} + + +void storeNewRhs(TWOdevice *pDevice, TWOcontact *pContact) +{ + int index, i, numContactNodes; + TWOelem *pElem; + TWOnode *pNode, *pHNode = NULL, *pVNode = NULL; + TWOedge *pHEdge = NULL, *pVEdge = NULL; + double *rhs = pDevice->rhs; + + /* zero the rhs before loading in the new rhs */ + for ( index = 1; index <= pDevice->numEqns ; index++ ) { + rhs[ index ] = 0.0; + } + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem)) { + /* found an element to which this node belongs */ + switch ( i ) { + case 0: + /* the TL element of this node */ + pHNode = pElem->pBLNode; + pVNode = pElem->pTRNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pRightEdge; + break; + case 1: + /* the TR element */ + pHNode = pElem->pBRNode; + pVNode = pElem->pTLNode; + pHEdge = pElem->pBotEdge; + pVEdge = pElem->pLeftEdge; + break; + case 2: + /* the BR element */ + pHNode = pElem->pTRNode; + pVNode = pElem->pBLNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pLeftEdge; + break; + case 3: + /* the BL element */ + pHNode = pElem->pTLNode; + pVNode = pElem->pBRNode; + pHEdge = pElem->pTopEdge; + pVEdge = pElem->pRightEdge; + break; + default: + printf( "storeNewRhs: shouldn't be here\n"); + break; + } + if ( pHNode->nodeType != CONTACT ) { + /* contribution to the x nodes */ + rhs[ pHNode->psiEqn ] += pElem->epsRel * 0.5 * pElem->dyOverDx; + if ( pElem->elemType == SEMICON ) { + if ( !OneCarrier ) { + rhs[ pHNode->nEqn ] -= 0.5 * pElem->dy * pHEdge->dJnDpsiP1; + rhs[ pHNode->pEqn ] -= 0.5 * pElem->dy * pHEdge->dJpDpsiP1; + } else if ( OneCarrier == N_TYPE ) { + rhs[ pHNode->nEqn ] -= 0.5 * pElem->dy * pHEdge->dJnDpsiP1; + } else if ( OneCarrier == P_TYPE ) { + rhs[ pHNode->pEqn ] -= 0.5 * pElem->dy * pHEdge->dJpDpsiP1; + } + } + } + if ( pVNode->nodeType != CONTACT ) { + /* contribution to the y nodes */ + rhs[ pVNode->psiEqn ] += pElem->epsRel * 0.5 * pElem->dxOverDy; + if ( pElem->elemType == SEMICON ) { + if ( !OneCarrier ) { + rhs[ pVNode->nEqn ] -= 0.5 * pElem->dx * pVEdge->dJnDpsiP1; + rhs[ pVNode->pEqn ] -= 0.5 * pElem->dx * pVEdge->dJpDpsiP1; + } else if ( OneCarrier == N_TYPE ) { + rhs[ pVNode->nEqn ] -= 0.5 * pElem->dx * pVEdge->dJnDpsiP1; + } else if ( OneCarrier == P_TYPE ) { + rhs[ pVNode->pEqn ] -= 0.5 * pElem->dx * pVEdge->dJpDpsiP1; + } + } + } + } + } + } +} diff --git a/src/ciderlib/twod/tworead.c b/src/ciderlib/twod/tworead.c new file mode 100644 index 000000000..156e51409 --- /dev/null +++ b/src/ciderlib/twod/tworead.c @@ -0,0 +1,116 @@ +/********** +Copyright 1992 Regents of the University of California. All rights reserved. +Author: 1992 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/* + * Functions needed to read solutions for 2D devices. + */ + +#include "ngspice.h" +#include "plot.h" +#include "numglobs.h" +#include "numenum.h" +#include "twodev.h" +#include "twomesh.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" + + +int +TWOreadState(TWOdevice *pDevice, char *fileName, int numVolts, double *pV1, + double *pV2, double *pV3) + /* char *fileName: File containing raw data */ + /* int numVolts: Number of voltage differences */ + /* double *pV1, *pV2, *pV3: Pointer to return them in */ +{ + int dataLength; + int i, index, xIndex, yIndex; + TWOnode ***nodeArray = NULL; + TWOnode *pNode; + TWOelem *pElem; + TWOmaterial *info; + double refPsi = 0.0; + double *psiData, *nData, *pData; + double *vData[3]; + struct plot *stateDB; + struct plot *voltsDB; + char voltName[80]; + + stateDB = DBread( fileName ); + if (stateDB == NULL) return (-1); + voltsDB = stateDB->pl_next; + if (voltsDB == NULL) return (-1); + + for (i=0; i < numVolts; i++ ) { + sprintf( voltName, "v%d%d", i+1, numVolts+1 ); + vData[i] = DBgetData( voltsDB, voltName, 1 ); + if (vData[i] == NULL) return (-1); + } + dataLength = pDevice->numXNodes * pDevice->numYNodes; + psiData = DBgetData( stateDB, "psi", dataLength ); + nData = DBgetData( stateDB, "n", dataLength ); + pData = DBgetData( stateDB, "p", dataLength ); + if (psiData == NULL || nData == NULL || pData == NULL) return (-1); + + if (pV1 != NULL) { + *pV1 = vData[0][0]; + FREE( vData[0] ); + } + if (pV2 != NULL) { + *pV2 = vData[1][0]; + FREE( vData[1] ); + } + if (pV3 != NULL) { + *pV3 = vData[2][0]; + FREE( vData[2] ); + } + + /* generate the work array for copying node info */ + XCALLOC(nodeArray, TWOnode **, 1 + pDevice->numXNodes); + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + XCALLOC(nodeArray[xIndex], TWOnode *, 1 + pDevice->numYNodes); + } + + /* store the nodes in this work array and use later */ + for (xIndex = 1; xIndex < pDevice->numXNodes; xIndex++) { + for (yIndex = 1; yIndex < pDevice->numYNodes; yIndex++) { + pElem = pDevice->elemArray[xIndex][yIndex]; + if (pElem != NIL(TWOelem)) { + if (refPsi == 0.0 && pElem->matlInfo->type == SEMICON) { + refPsi = pElem->matlInfo->refPsi; + } + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + nodeArray[pNode->nodeI][pNode->nodeJ] = pNode; + } + } + } + } + } + index = 0; + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + for (yIndex = 1; yIndex <= pDevice->numYNodes; yIndex++) { + pNode = nodeArray[xIndex][yIndex]; + index++; + if (pNode != NIL(TWOnode)) { + pNode->psi = psiData[index-1]/VNorm + refPsi; + pNode->nConc = nData[index-1]/NNorm; + pNode->pConc = pData[index-1]/NNorm; + } + } + } + /* Delete work array. */ + for (xIndex = 1; xIndex <= pDevice->numXNodes; xIndex++) { + FREE(nodeArray[xIndex]); + } + FREE(nodeArray); + + FREE(psiData); + FREE(nData); + FREE(pData); + + return (0); +} diff --git a/src/ciderlib/twod/twosetbc.c b/src/ciderlib/twod/twosetbc.c new file mode 100644 index 000000000..98ac7f1fe --- /dev/null +++ b/src/ciderlib/twod/twosetbc.c @@ -0,0 +1,94 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "twoddefs.h" +#include "twodext.h" + +/* Forward declarations */ +static void setDirichlet(TWOcontact *, double); + + +void NUMD2setBCs(TWOdevice *pDevice, double vd) +{ + TWOcontact *pContact = pDevice->pLastContact; + + setDirichlet( pContact, - vd ); +} + +void NBJT2setBCs(TWOdevice *pDevice, double vce, double vbe) +{ + TWOcontact *pCollContact = pDevice->pFirstContact; + TWOcontact *pBaseContact = pDevice->pFirstContact->next; + + setDirichlet( pCollContact, vce ); + setDirichlet( pBaseContact, vbe ); +} + +void NUMOSsetBCs(TWOdevice *pDevice, double vdb, double vsb, double vgb) +{ + TWOcontact *pDContact = pDevice->pFirstContact; + TWOcontact *pGContact = pDevice->pFirstContact->next; + TWOcontact *pSContact = pDevice->pFirstContact->next->next; + + setDirichlet( pDContact, vdb ); + setDirichlet( pSContact, vsb ); + setDirichlet( pGContact, vgb ); +} + +static void + setDirichlet(TWOcontact *pContact, double voltage) +{ + int index, numContactNodes, i; + TWOelem *pElem = NULL; + TWOnode *pNode; + double psi, ni, pi, nie; + double conc, sign, absConc; + + voltage /= VNorm; + + numContactNodes = pContact->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pContact->pNodes[ index ]; + + /* Find this node's owner element. */ + for ( i = 0; i <= 3; i++ ) { + pElem = pNode->pElems[ i ]; + if ( pElem != NIL(TWOelem) && pElem->evalNodes[ (i+2)%4 ] ) { + break; /* got it */ + } + } + + if (pElem->elemType == INSULATOR) { + pNode->psi = RefPsi - pNode->eaff; + pNode->nConc = 0.0; + pNode->pConc = 0.0; + } + else if (pElem->elemType == SEMICON) { + nie = pNode->nie; + conc = pNode->netConc / nie; + sign = SGN( conc ); + absConc = ABS( conc ); + if ( conc != 0.0 ) { + psi = sign * log( 0.5 * absConc + sqrt( 1.0 + 0.25*absConc*absConc )); + ni = nie * exp( psi ); + pi = nie * exp( - psi ); + } + else { + psi = 0.0; + ni = nie; + pi = nie; + } + pNode->psi = pElem->matlInfo->refPsi + psi; + pNode->nConc = ni; + pNode->pConc = pi; + } + pNode->psi += voltage; + } +} diff --git a/src/ciderlib/twod/twosetup.c b/src/ciderlib/twod/twosetup.c new file mode 100644 index 000000000..9cda8c41a --- /dev/null +++ b/src/ciderlib/twod/twosetup.c @@ -0,0 +1,318 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +/********** +Two-Dimensional Numerical Device Setup Routines +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numconst.h" +#include "numenum.h" +#include "twomesh.h" +#include "twodev.h" +#include "carddefs.h" /* XXX Not really modular if we need this. */ +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" + + +/* compute node parameters */ +void TWOsetup(TWOdevice *pDevice) +{ + double temp1, deltaEg, avgConc, totalConc, absNetConc; + double ncv0, dBand, dNie, psiBand[4]; + double *xScale = pDevice->xScale; + double *yScale = pDevice->yScale; + int index, eIndex; + int numContactNodes; + TWOnode *pNode; + TWOelem *pElem; + TWOedge *pEdge; + TWOcontact *pC; + TWOmaterial *info; + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + info = pElem->matlInfo; + + pElem->dx = xScale[ pElem->pTRNode->nodeI ]-xScale[ pElem->pTLNode->nodeI ]; + pElem->dy = yScale[ pElem->pBLNode->nodeJ ]-yScale[ pElem->pTLNode->nodeJ ]; + pElem->epsRel = info->eps; + + if ( pElem->elemType == INSULATOR ) { + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + if (pNode->nodeType == CONTACT) { + pNode->eaff = PHI_METAL; + pNode->eg = 0.0; + } else { + pNode->eaff = info->affin; + pNode->eg = info->eg0; + } + } + } + } else if ( pElem->elemType == SEMICON ) { + ncv0 = sqrt( info->nc0 ) * sqrt( info->nv0 ); + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + + /* Narrowing of Energy-Band Gap */ + if (BandGapNarrowing) { + absNetConc = ABS( pNode->netConc ); + if ( pNode->netConc > 0.0 ) { + temp1 = log( absNetConc / info->nrefBGN[ELEC] ); + deltaEg = - info->dEgDn[ELEC] * (temp1 + sqrt(temp1*temp1 + 0.5)); + pNode->eg = info->eg0 + deltaEg; + } else if ( pNode->netConc < 0.0 ) { + temp1 = log( absNetConc / info->nrefBGN[HOLE] ); + deltaEg = - info->dEgDn[HOLE] * (temp1 + sqrt(temp1*temp1 + 0.5)); + pNode->eg = info->eg0 + deltaEg; + } else { + pNode->eg = info->eg0; + } + } else { + pNode->eg = info->eg0; + } + pNode->nie = ncv0 * exp ( -0.5 * pNode->eg / Vt ); + pNode->eaff = info->affin; + /* Save band structure parameter. */ + psiBand[ index ] = - info->refPsi; + + /* Ionized-Impurity-Scattering Reduction of Carrier Lifetime */ + if (ConcDepLifetime) { + totalConc = pNode->totalConc; + temp1 = 1.0 / ( 1.0 + totalConc / info->nrefSRH[ELEC] ); + pNode->tn = info->tau0[ELEC] * temp1; + temp1 = 1.0 / ( 1.0 + totalConc / info->nrefSRH[HOLE] ); + pNode->tp = info->tau0[HOLE] * temp1; + } else { + pNode->tn = info->tau0[ELEC]; + pNode->tp = info->tau0[HOLE]; + } + } + } + + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + + /* Fixed Interface Charge */ + pEdge->qf = 0.0; + + /* Variable Band Built-In Potential */ + if ( index <= 1 ) { + dBand = psiBand[index+1] - psiBand[index]; + dNie = log( pElem->pNodes[index+1]->nie / + pElem->pNodes[index]->nie ); + pEdge->dCBand = dBand + dNie; + pEdge->dVBand = - dBand + dNie; + } else { + dBand = psiBand[index] - psiBand[(index+1)%4]; + dNie = log( pElem->pNodes[index]->nie / + pElem->pNodes[(index+1)%4]->nie ); + pEdge->dCBand = dBand + dNie; + pEdge->dVBand = - dBand + dNie; + } + } + } + + /* Evaluate conc.-dep. mobility. */ + /* Average conc of all four nodes. */ + avgConc = 0.25*(pElem->pTLNode->totalConc + pElem->pTRNode->totalConc + + pElem->pBRNode->totalConc + pElem->pBLNode->totalConc); + MOBconcDep( info, avgConc, &pElem->mun0, &pElem->mup0 ); + } + } + + for ( pC = pDevice->pFirstContact; pC != NIL(TWOcontact); pC = pC->next ) { + numContactNodes = pC->numNodes; + for ( index = 0; index < numContactNodes; index++ ) { + pNode = pC->pNodes[ index ]; + pNode->eaff = pC->workf; /* Affinity aka work function */ + } + } +} + +/* Transfer BC info from card to nodes and edges. */ +static void TWOcopyBCinfo( pDevice, pElem, card, index ) + TWOdevice *pDevice; + TWOelem *pElem; + BDRYcard *card; + int index; +{ + TWOnode *pNode; + TWOelem *pNElem; + TWOedge *pEdge; + TWOmaterial *info; + TWOchannel *newChannel; + int eIndex, nIndex; + int direction = index%2; + double length, area, width, layerWidth; + double dop, na = 0.0, nd = 0.0; + + /* First add fixed charge. */ + pEdge = pElem->pEdges[index]; + pEdge->qf += card->BDRYqf; + + /* Now add surface recombination. */ + if ( direction == 0 ) { /* Horizontal Edge */ + length = 0.5 * pElem->dx; + } else { + length = 0.5 * pElem->dy; + } + for (nIndex = 0; nIndex <= 1; nIndex++) { + pNode = pElem->pNodes[ (index+nIndex)%4 ]; + /* Compute semiconductor area around this node. */ + area = 0.0; + for (eIndex = 0; eIndex <= 3; eIndex++) { + pNElem = pNode->pElems[eIndex]; + if (pNElem != NIL(TWOelem) && pElem->elemType == SEMICON) { + area += 0.25 * pElem->dx * pElem->dy; + } + } + if (card->BDRYsnGiven) { + pNode->tn = pNode->tn / + (1.0 + ((card->BDRYsn * TNorm)*length*pNode->tn) / area); + } + if (card->BDRYspGiven) { + pNode->tp = pNode->tp / + (1.0 + ((card->BDRYsp * TNorm)*length*pNode->tp) / area); + } + /* Compute doping just in case we need it later. */ + na += 0.5 * pNode->na; + nd += 0.5 * pNode->nd; + } + + /* Finally do surface layer. */ + pNElem = pElem->pElems[index]; + if (card->BDRYlayerGiven && SurfaceMobility && pElem->elemType == SEMICON + && pElem->channel == 0 && pNElem && pNElem->elemType == INSULATOR + && pElem->pNodes[index]->nodeType != CONTACT && + pElem->pNodes[(index+1)%4]->nodeType != CONTACT ) { + /* Find the layer width. */ + layerWidth = card->BDRYlayer; + if (card->BDRYlayer <= 0.0) { /* Need to compute extrinsic Debye length */ + info = pElem->matlInfo; + dop = MAX(MAX(na,nd),info->ni0); + layerWidth = sqrt((Vt * info->eps) / (CHARGE * dop)); + } + + /* Add a channel to the list of channels. */ + XCALLOC( newChannel, TWOchannel, 1); + newChannel->pSeed = pElem; + newChannel->pNElem = pNElem; + newChannel->type = index; + if (pDevice->pChannel != NIL(TWOchannel)) { + newChannel->id = pDevice->pChannel->id + 1; + newChannel->next = pDevice->pChannel; + } else { + newChannel->id = 1; + newChannel->next = NIL(TWOchannel); + } + pDevice->pChannel = newChannel; + + /* Now add elements to channel until we're more than layerWidth away + * from the interface. If we encounter a missing element or an + * element that's already part of a different channel, quit. + * The seed element is at the surface. + */ + width = 0.0; + eIndex = (index+2)%4; + pElem->surface = TRUE; + while (width < layerWidth && pElem && pElem->channel == 0) { + pElem->channel = newChannel->id; + pElem->direction = direction; + /* + * Surface mobility is normally concentration-independent in + * the default model. Overwrite concentration-dependent value + * calculated earlier unless matching of low-field surface + * and bulk mobilities is requested. + */ + if (!MatchingMobility) { + pElem->mun0 = pElem->matlInfo->mus[ELEC]; + pElem->mup0 = pElem->matlInfo->mus[HOLE]; + } + if ( direction == 0 ) { + width += pElem->dy; + } else { + width += pElem->dx; + } + pElem = pElem->pElems[ eIndex ]; + } + } +} + +/* Compute boundary condition parameters. */ +void TWOsetBCparams(TWOdevice *pDevice, BDRYcard *cardList) +{ + int index, xIndex, yIndex; /* Need to access in X/Y order. */ + TWOnode *pNode; + TWOelem *pElem, *pNElem; + TWOedge *pEdge; + BDRYcard *card; + + for ( card = cardList; card != NIL(BDRYcard); card = card->BDRYnextCard ) { + for (xIndex = card->BDRYixLow; xIndex < card->BDRYixHigh; xIndex++) { + for (yIndex = card->BDRYiyLow; yIndex < card->BDRYiyHigh; yIndex++) { + pElem = pDevice->elemArray[ xIndex ][ yIndex ]; + if (pElem != NIL(TWOelem)) { + if (pElem->domain == card->BDRYdomain) { + for (index = 0; index <= 3; index++) { + if (pElem->evalEdges[index]) { + pNElem = pElem->pElems[index]; + if (card->BDRYneighborGiven) { + if (pNElem && pNElem->domain == card->BDRYneighbor) { + /* Found an interface edge. */ + TWOcopyBCinfo( pDevice, pElem, card, index ); + } + } else { + if (!pNElem || pNElem->domain != pElem->domain) { + /* Found a boundary edge. */ + TWOcopyBCinfo( pDevice, pElem, card, index ); + } + } + } + } + } + } + } + } + } +} + +void TWOnormalize(TWOdevice *pDevice) +{ + int index, eIndex; + TWOelem *pElem; + TWOnode *pNode; + TWOedge *pEdge; + + for ( eIndex = 1; eIndex <= pDevice->numElems; eIndex++ ) { + pElem = pDevice->elements[ eIndex ]; + + pElem->dx /= LNorm; + pElem->dy /= LNorm; + pElem->epsRel /= EpsNorm; + for ( index = 0; index <= 3; index++ ) { + if ( pElem->evalNodes[ index ] ) { + pNode = pElem->pNodes[ index ]; + pNode->netConc /= NNorm; + pNode->nd /= NNorm; + pNode->na /= NNorm; + pNode->nie /= NNorm; + pNode->eg /= VNorm; + pNode->eaff /= VNorm; + } + if ( pElem->evalEdges[ index ] ) { + pEdge = pElem->pEdges[ index ]; + pEdge->qf /= NNorm*LNorm; + } + } + } +} diff --git a/src/ciderlib/twod/twosolve.c b/src/ciderlib/twod/twosolve.c new file mode 100644 index 000000000..9b204f683 --- /dev/null +++ b/src/ciderlib/twod/twosolve.c @@ -0,0 +1,1197 @@ +/********** +Copyright 1991 Regents of the University of California. All rights reserved. +Author: 1987 Kartikeya Mayaram, U. C. Berkeley CAD Group +Author: 1991 David A. Gates, U. C. Berkeley CAD Group +**********/ + +#include "ngspice.h" +#include "numglobs.h" +#include "numenum.h" +#include "twodev.h" +#include "twomesh.h" +#include "spMatrix.h" +#include "bool.h" +#include "twoddefs.h" +#include "twodext.h" +#include "cidersupt.h" +#include "../../maths/misc/norm.h" + + +#include "ifsim.h" +extern IFfrontEnd *SPfrontEnd; + + +/* functions to calculate the 2D solutions */ + + +/* the iteration driving loop and convergence check */ +void +TWOdcSolve(TWOdevice *pDevice, int iterationLimit, BOOLEAN newSolver, + BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + TWOnode *pNode; + TWOelem *pElem; + int size = pDevice->numEqns; + int index, eIndex, error; + int timesConverged = 0; + BOOLEAN quitLoop; + BOOLEAN debug; + BOOLEAN negConc = FALSE; + double *rhs = pDevice->rhs; + double *solution = pDevice->dcSolution; + double *delta = pDevice->dcDeltaSolution; + double poissNorm, contNorm; + double startTime, totalStartTime; + double totalTime, loadTime, factorTime, solveTime, updateTime, checkTime; + double orderTime = 0.0; + + totalTime = loadTime = factorTime = solveTime = updateTime = checkTime = 0.0; + totalStartTime = SPfrontEnd->IFseconds(); + + quitLoop = FALSE; + debug = (!tranAnalysis && TWOdcDebug) || (tranAnalysis && TWOtranDebug); + pDevice->iterationNumber = 0; + pDevice->converged = FALSE; + + if (debug) { + if (pDevice->poissonOnly) { + fprintf(stdout, "Equilibrium Solution:\n"); + } else { + fprintf(stdout, "Bias Solution:\n"); + } + fprintf(stdout, "Iteration RHS Norm\n"); + } + while (!(pDevice->converged || (pDevice->iterationNumber > iterationLimit) + || quitLoop)) { + pDevice->iterationNumber++; + + if ((!pDevice->poissonOnly) && (iterationLimit > 0) + &&(!tranAnalysis)) { + TWOjacCheck(pDevice, tranAnalysis, info); + } + + /* LOAD */ + startTime = SPfrontEnd->IFseconds(); + if (pDevice->poissonOnly) { + TWOQsysLoad(pDevice); + } else if (!OneCarrier) { + TWO_sysLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONsysLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPsysLoad(pDevice, tranAnalysis, info); + } + pDevice->rhsNorm = maxNorm(rhs, size); + loadTime += SPfrontEnd->IFseconds() - startTime; + if (debug) { + fprintf(stdout, "%7d %11.4e%s\n", + pDevice->iterationNumber - 1, pDevice->rhsNorm, + negConc ? " negative conc encountered" : ""); + negConc = FALSE; + } + + /* FACTOR */ + startTime = SPfrontEnd->IFseconds(); + error = spFactor(pDevice->matrix); + factorTime += SPfrontEnd->IFseconds() - startTime; + if (newSolver) { + if (pDevice->iterationNumber == 1) { + orderTime = factorTime; + } else if (pDevice->iterationNumber == 2) { + orderTime -= factorTime - orderTime; + factorTime -= orderTime; + if (pDevice->poissonOnly) { + pDevice->pStats->orderTime[STAT_SETUP] += orderTime; + } else { + pDevice->pStats->orderTime[STAT_DC] += orderTime; + } + newSolver = FALSE; + } + } + if (foundError(error)) { + if (error == spSINGULAR) { + int badRow, badCol; + spWhereSingular(pDevice->matrix, &badRow, &badCol); + printf("***** singular at (%d,%d)\n", badRow, badCol); + } + pDevice->converged = FALSE; + quitLoop = TRUE; + continue; + } + + /* SOLVE */ + startTime = SPfrontEnd->IFseconds(); + spSolve(pDevice->matrix, rhs, delta, NIL(spREAL), NIL(spREAL)); + solveTime += SPfrontEnd->IFseconds() - startTime; + + /* UPDATE */ + startTime = SPfrontEnd->IFseconds(); + /* Use norm reducing Newton method for DC bias solutions only. */ + if ((!pDevice->poissonOnly) && (iterationLimit > 0) + && (!tranAnalysis) && (pDevice->rhsNorm > 1e-1)) { + error = TWOnewDelta(pDevice, tranAnalysis, info); + if (error) { + pDevice->converged = FALSE; + quitLoop = TRUE; + updateTime += SPfrontEnd->IFseconds() - startTime; + continue; + } + } + for (index = 1; index <= size; index++) { + solution[index] += delta[index]; + } + updateTime += SPfrontEnd->IFseconds() - startTime; + + /* CHECK CONVERGENCE */ + startTime = SPfrontEnd->IFseconds(); + /* Check if updates have gotten sufficiently small. */ + if (pDevice->iterationNumber != 1) { + pDevice->converged = TWOdeltaConverged(pDevice); + } + /* Check if the residual is smaller than abstol. */ + if (pDevice->converged && (!pDevice->poissonOnly) + && (!tranAnalysis)) { + if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + pDevice->rhsNorm = maxNorm(rhs, size); + if (pDevice->rhsNorm > pDevice->abstol) { + pDevice->converged = FALSE; + } + if ((++timesConverged >= 2) + && (pDevice->rhsNorm < 1e3 * pDevice->abstol)) { + pDevice->converged = TRUE; + } else if (timesConverged >= 5) { + pDevice->converged = FALSE; + quitLoop = TRUE; + continue; + } + } else if (pDevice->converged && pDevice->poissonOnly) { + TWOQrhsLoad(pDevice); + pDevice->rhsNorm = maxNorm(rhs, size); + if (pDevice->rhsNorm > pDevice->abstol) { + pDevice->converged = FALSE; + } + if (++timesConverged >= 5) { + pDevice->converged = TRUE; + } + } + /* Check if the carrier concentrations are negative. */ + if (pDevice->converged && (!pDevice->poissonOnly)) { + /* Clear garbage entry since carrier-free elements reference it. */ + solution[0] = 0.0; + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (solution[pNode->nEqn] < 0.0) { + pDevice->converged = FALSE; + negConc = TRUE; + if (tranAnalysis) { + quitLoop = TRUE; + } else { + solution[pNode->nEqn] = 0.0; + } + } + if (solution[pNode->pEqn] < 0.0) { + pDevice->converged = FALSE; + negConc = TRUE; + if (tranAnalysis) { + quitLoop = TRUE; + } else { + solution[pNode->pEqn] = 0.0; + } + } + } + } + } + if (!pDevice->converged) { + if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + pDevice->rhsNorm = maxNorm(rhs, size); + } + } + checkTime += SPfrontEnd->IFseconds() - startTime; + } + totalTime += SPfrontEnd->IFseconds() - totalStartTime; + + if (tranAnalysis) { + pDevice->pStats->loadTime[STAT_TRAN] += loadTime; + pDevice->pStats->factorTime[STAT_TRAN] += factorTime; + pDevice->pStats->solveTime[STAT_TRAN] += solveTime; + pDevice->pStats->updateTime[STAT_TRAN] += updateTime; + pDevice->pStats->checkTime[STAT_TRAN] += checkTime; + pDevice->pStats->numIters[STAT_TRAN] += pDevice->iterationNumber; + } else if (pDevice->poissonOnly) { + pDevice->pStats->loadTime[STAT_SETUP] += loadTime; + pDevice->pStats->factorTime[STAT_SETUP] += factorTime; + pDevice->pStats->solveTime[STAT_SETUP] += solveTime; + pDevice->pStats->updateTime[STAT_SETUP] += updateTime; + pDevice->pStats->checkTime[STAT_SETUP] += checkTime; + pDevice->pStats->numIters[STAT_SETUP] += pDevice->iterationNumber; + } else { + pDevice->pStats->loadTime[STAT_DC] += loadTime; + pDevice->pStats->factorTime[STAT_DC] += factorTime; + pDevice->pStats->solveTime[STAT_DC] += solveTime; + pDevice->pStats->updateTime[STAT_DC] += updateTime; + pDevice->pStats->checkTime[STAT_DC] += checkTime; + pDevice->pStats->numIters[STAT_DC] += pDevice->iterationNumber; + } + + if (debug) { + if (!tranAnalysis) { + pDevice->rhsNorm = maxNorm(rhs, size); + fprintf(stdout, "%7d %11.4e%s\n", + pDevice->iterationNumber, pDevice->rhsNorm, + negConc ? " negative conc in solution" : ""); + } + if (pDevice->converged) { + if (!pDevice->poissonOnly) { + poissNorm = contNorm = 0.0; + rhs[0] = 0.0; /* Make sure garbage entry is clear. */ + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + poissNorm = MAX(poissNorm,ABS(rhs[pNode->psiEqn])); + contNorm = MAX(contNorm,ABS(rhs[pNode->nEqn])); + contNorm = MAX(contNorm,ABS(rhs[pNode->pEqn])); + } + } + } + fprintf(stdout, + "Residual: %11.4e C/um poisson, %11.4e A/um continuity\n", + poissNorm * EpsNorm * VNorm * 1e-4, + contNorm * JNorm * LNorm * 1e-4); + } else { + fprintf(stdout, "Residual: %11.4e C/um poisson\n", + pDevice->rhsNorm * EpsNorm * VNorm * 1e-4); + } + } + } +} + +BOOLEAN +TWOdeltaConverged(TWOdevice *pDevice) +{ + /* This function returns a TRUE if converged else a FALSE. */ + int index; + BOOLEAN converged = TRUE; + double xOld, xNew, tol; + + for (index = 1; index <= pDevice->numEqns; index++) { + xOld = pDevice->dcSolution[index]; + xNew = xOld + pDevice->dcDeltaSolution[index]; + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xOld - xNew) > tol) { + converged = FALSE; + break; + } + } + return (converged); +} + +BOOLEAN +TWOdeviceConverged(TWOdevice *pDevice) +{ + int index, eIndex; + BOOLEAN converged = TRUE; + double *solution = pDevice->dcSolution; + TWOnode *pNode; + TWOelem *pElem; + double startTime; + + /* If the update is sufficiently small, and the carrier concentrations + * are all positive, then return TRUE, else return FALSE. + */ + + /* CHECK CONVERGENCE */ + startTime = SPfrontEnd->IFseconds(); + if ((converged = TWOdeltaConverged(pDevice)) == TRUE) { + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nEqn != 0 && solution[pNode->nEqn] < 0.0) { + converged = FALSE; + solution[pNode->nEqn] = pNode->nConc = 0.0; + } + if (pNode->pEqn != 0 && solution[pNode->pEqn] < 0.0) { + converged = FALSE; + solution[pNode->pEqn] = pNode->pConc = 0.0; + } + } + } + } + } + pDevice->pStats->checkTime[STAT_TRAN] += SPfrontEnd->IFseconds() - startTime; + + return (converged); +} + +void +TWOresetJacobian(TWOdevice *pDevice) +{ + int error; + + + if (!OneCarrier) { + TWO_jacLoad(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacLoad(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacLoad(pDevice); + } else { + printf("TWOresetJacobian: unknown carrier type\n"); + exit(-1); + } + error = spFactor(pDevice->matrix); + if (foundError(error)) { + exit(-1); + } +} + +/* + * Compute the device state assuming charge neutrality exists everywhere in + * the device. In insulators, where there is no charge, assign the potential + * at half the insulator band gap (refPsi). + */ +void +TWOstoreNeutralGuess(TWOdevice *pDevice) +{ + int nIndex, eIndex; + TWOelem *pElem; + TWOnode *pNode; + double nie, conc, absConc, refPsi, psi, ni, pi, sign; + + /* assign the initial guess for Poisson's equation */ + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + refPsi = pElem->matlInfo->refPsi; + if (pElem->elemType == INSULATOR) { + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType == CONTACT) { + /* + * a metal contact to insulator domain so use work function + * difference as the value of psi + */ + pNode->psi = RefPsi - pNode->eaff; + } else { + pNode->psi = refPsi; + } + } + } + } + if (pElem->elemType == SEMICON) { + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + /* silicon nodes */ + nie = pNode->nie; + conc = pNode->netConc / pNode->nie; + psi = 0.0; + ni = nie; + pi = nie; + sign = SGN(conc); + absConc = ABS(conc); + if (conc != 0.0) { + psi = sign * log(0.5 * absConc + + sqrt(1.0 + 0.25 * absConc * absConc)); + ni = nie * exp(psi); + pi = nie * exp(-psi); + } + pNode->psi = refPsi + psi; + pNode->nConc = ni; + pNode->pConc = pi; + /* store the initial guess in the dc solution vector */ + if (pNode->nodeType != CONTACT) { + pDevice->dcSolution[pNode->poiEqn] = pNode->psi; + } + } + } + } + } +} + +/* computing the equilibrium solution; solution of Poisson's eqn */ +/* the solution is used as an initial starting point for bias conditions */ + +void +TWOequilSolve(TWOdevice *pDevice) +{ + BOOLEAN newSolver = FALSE; + int error; + int nIndex, eIndex; + TWOelem *pElem; + TWOnode *pNode; + double startTime, setupTime, miscTime; + + setupTime = miscTime = 0.0; + + /* SETUP */ + startTime = SPfrontEnd->IFseconds(); + switch (pDevice->solverType) { + case SLV_SMSIG: + case SLV_BIAS: + /* free up memory allocated for the bias solution */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + FREE(pDevice->rhsImag); + spDestroy(pDevice->matrix); + case SLV_NONE: + pDevice->poissonOnly = TRUE; + pDevice->numEqns = pDevice->dimEquil - 1; + XCALLOC(pDevice->dcSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->dcDeltaSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->copiedSolution, double, pDevice->dimEquil); + XCALLOC(pDevice->rhs, double, pDevice->dimEquil); + pDevice->matrix = spCreate(pDevice->numEqns, 0, &error); + if (error == spNO_MEMORY) { + printf("TWOequilSolve: Out of Memory\n"); + exit(-1); + } + newSolver = TRUE; + spSetReal(pDevice->matrix); + TWOQjacBuild(pDevice); + pDevice->numOrigEquil = spElementCount(pDevice->matrix); + pDevice->numFillEquil = 0; + case SLV_EQUIL: + pDevice->solverType = SLV_EQUIL; + break; + default: + fprintf(stderr, "Panic: Unknown solver type in equil solution.\n"); + exit(-1); + break; + } + TWOstoreNeutralGuess(pDevice); + setupTime += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + TWOdcSolve(pDevice, MaxIterations, newSolver, FALSE, (TWOtranInfo *) NULL); + + /* MISCELLANEOUS */ + startTime = SPfrontEnd->IFseconds(); + if (newSolver) { + pDevice->numFillEquil = spFillinCount(pDevice->matrix); + } + if (pDevice->converged) { + TWOQcommonTerms(pDevice); + + /* save equilibrium potential */ + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi0 = pNode->psi; + } + } + } + } else { + printf("TWOequilSolve: No Convergence\n"); + } + miscTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->setupTime[STAT_SETUP] += setupTime; + pDevice->pStats->miscTime[STAT_SETUP] += miscTime; +} + +/* compute the solution under an applied bias */ +/* the equilibrium solution is taken as an initial guess */ +void +TWObiasSolve(TWOdevice *pDevice, int iterationLimit, BOOLEAN tranAnalysis, + TWOtranInfo *info) +{ + BOOLEAN newSolver = FALSE; + int error; + int index, eIndex; + TWOelem *pElem; + TWOnode *pNode; + double refPsi; + double startTime, setupTime, miscTime; + + setupTime = miscTime = 0.0; + + /* SETUP */ + startTime = SPfrontEnd->IFseconds(); + switch (pDevice->solverType) { + case SLV_EQUIL: + /* free up the vectors allocated in the equilibrium solution */ + FREE(pDevice->dcSolution); + FREE(pDevice->dcDeltaSolution); + FREE(pDevice->copiedSolution); + FREE(pDevice->rhs); + spDestroy(pDevice->matrix); + case SLV_NONE: + pDevice->poissonOnly = FALSE; + pDevice->numEqns = pDevice->dimBias - 1; + XCALLOC(pDevice->dcSolution, double, pDevice->dimBias); + XCALLOC(pDevice->dcDeltaSolution, double, pDevice->dimBias); + XCALLOC(pDevice->copiedSolution, double, pDevice->dimBias); + XCALLOC(pDevice->rhs, double, pDevice->dimBias); + XCALLOC(pDevice->rhsImag, double, pDevice->dimBias); + pDevice->matrix = spCreate(pDevice->numEqns, 1, &error); + if (error == spNO_MEMORY) { + printf("TWObiasSolve: Out of Memory\n"); + exit(-1); + } + newSolver = TRUE; + if (!OneCarrier) { + TWO_jacBuild(pDevice); + } else if (OneCarrier == N_TYPE) { + TWONjacBuild(pDevice); + } else if (OneCarrier == P_TYPE) { + TWOPjacBuild(pDevice); + } + pDevice->numOrigBias = spElementCount(pDevice->matrix); + pDevice->numFillBias = 0; + TWOstoreInitialGuess(pDevice); + case SLV_SMSIG: + spSetReal(pDevice->matrix); + case SLV_BIAS: + pDevice->solverType = SLV_BIAS; + break; + default: + fprintf(stderr, "Panic: Unknown solver type in bias solution.\n"); + exit(-1); + break; + } + setupTime += SPfrontEnd->IFseconds() - startTime; + + /* SOLVE */ + TWOdcSolve(pDevice, iterationLimit, newSolver, tranAnalysis, info); + + /* MISCELLANEOUS */ + startTime = SPfrontEnd->IFseconds(); + if (newSolver) { + pDevice->numFillBias = spFillinCount(pDevice->matrix); + } + if ((!pDevice->converged) && iterationLimit > 1) { + printf("TWObiasSolve: No Convergence\n"); + } else if (pDevice->converged) { + /* update the nodal quantities */ + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + refPsi = pElem->matlInfo->refPsi; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + pNode->psi = pDevice->dcSolution[pNode->psiEqn]; + if (pElem->elemType == SEMICON) { + if (!OneCarrier) { + pNode->nConc = pDevice->dcSolution[pNode->nEqn]; + pNode->pConc = pDevice->dcSolution[pNode->pEqn]; + } else if (OneCarrier == N_TYPE) { + pNode->nConc = pDevice->dcSolution[pNode->nEqn]; + pNode->pConc = pNode->nie * exp(-pNode->psi + refPsi); + } else if (OneCarrier == P_TYPE) { + pNode->pConc = pDevice->dcSolution[pNode->pEqn]; + pNode->nConc = pNode->nie * exp(pNode->psi - refPsi); + } + } + } + } + } + } + + /* update the current terms */ + if (!OneCarrier) { + TWO_commonTerms(pDevice, FALSE, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONcommonTerms(pDevice, FALSE, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPcommonTerms(pDevice, FALSE, tranAnalysis, info); + } + } else if (iterationLimit <= 1) { + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + refPsi = pElem->matlInfo->refPsi; + for (index = 0; index <= 3; index++) { + if (pElem->evalNodes[index]) { + pNode = pElem->pNodes[index]; + if (pNode->nodeType != CONTACT) { + pNode->psi = pDevice->dcSolution[pNode->psiEqn]; + *(pDevice->devState0 + pNode->nodePsi) = pNode->psi; + if (pElem->elemType == SEMICON) { + if (!OneCarrier) { + pNode->nConc = pDevice->dcSolution[pNode->nEqn]; + pNode->pConc = pDevice->dcSolution[pNode->pEqn]; + } else if (OneCarrier == N_TYPE) { + pNode->nConc = pDevice->dcSolution[pNode->nEqn]; + pNode->pConc = pNode->nie * exp(-pNode->psi + refPsi); + } else if (OneCarrier == P_TYPE) { + pNode->pConc = pDevice->dcSolution[pNode->pEqn]; + pNode->nConc = pNode->nie * exp(pNode->psi - refPsi); + } + *(pDevice->devState0 + pNode->nodeN) = pNode->nConc; + *(pDevice->devState0 + pNode->nodeP) = pNode->pConc; + } + } + } + } + } + } + miscTime += SPfrontEnd->IFseconds() - startTime; + if (tranAnalysis) { + pDevice->pStats->setupTime[STAT_TRAN] += setupTime; + pDevice->pStats->miscTime[STAT_TRAN] += miscTime; + } else { + pDevice->pStats->setupTime[STAT_DC] += setupTime; + pDevice->pStats->miscTime[STAT_DC] += miscTime; + } +} + +/* Copy the device's equilibrium state into the solution vector. */ +void +TWOstoreEquilibGuess(TWOdevice *pDevice) +{ + int nIndex, eIndex; + double *solution = pDevice->dcSolution; + double refPsi; + TWOelem *pElem; + TWOnode *pNode; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + refPsi = pElem->matlInfo->refPsi; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + pDevice->dcSolution[pNode->psiEqn] = pNode->psi0; + if (pElem->elemType == SEMICON) { + if (!OneCarrier) { + solution[pNode->nEqn] = pNode->nie * exp(pNode->psi0 - refPsi); + solution[pNode->pEqn] = pNode->nie * exp(-pNode->psi0 + refPsi); + } else if (OneCarrier == N_TYPE) { + solution[pNode->nEqn] = pNode->nie * exp(pNode->psi0 - refPsi); + } else if (OneCarrier == P_TYPE) { + solution[pNode->pEqn] = pNode->nie * exp(-pNode->psi0 + refPsi); + } + } + } + } + } + } +} + +/* Copy the device's internal state into the solution vector. */ +void +TWOstoreInitialGuess(TWOdevice *pDevice) +{ + int nIndex, eIndex; + double *solution = pDevice->dcSolution; + TWOelem *pElem; + TWOnode *pNode; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + solution[pNode->psiEqn] = pNode->psi; + if (pElem->elemType == SEMICON) { + if (!OneCarrier) { + solution[pNode->nEqn] = pNode->nConc; + solution[pNode->pEqn] = pNode->pConc; + } else if (OneCarrier == N_TYPE) { + solution[pNode->nEqn] = pNode->nConc; + } else if (OneCarrier == P_TYPE) { + solution[pNode->pEqn] = pNode->pConc; + } + } + } + } + } + } +} + + +/* + * function computeNewDelta computes an acceptable delta + */ + +void +oldTWOnewDelta(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + int index; + double newNorm, fib, lambda, fibn, fibp; + BOOLEAN acceptable = FALSE; + lambda = 1.0; + fibn = 1.0; + fibp = 1.0; + + /* + * copy the contents of dcSolution into copiedSolution and modify + * dcSolution by adding the deltaSolution + */ + + for (index = 1; index <= pDevice->numEqns; ++index) { + pDevice->copiedSolution[index] = pDevice->dcSolution[index]; + pDevice->dcSolution[index] += pDevice->dcDeltaSolution[index]; + } + + /* compute L2 norm of the deltaX vector */ + pDevice->rhsNorm = l2Norm(pDevice->dcDeltaSolution, pDevice->numEqns); + + if (pDevice->poissonOnly) { + TWOQrhsLoad(pDevice); + } else if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + newNorm = TWOnuNorm(pDevice); + if (newNorm <= pDevice->rhsNorm) { + acceptable = TRUE; + } else { + /* chop the step size so that deltax is acceptable */ + for (; !acceptable;) { + fib = fibp; + fibp = fibn; + fibn += fib; + lambda *= (fibp / fibn); + + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index] + + lambda * pDevice->dcDeltaSolution[index]; + } + if (pDevice->poissonOnly) { + TWOQrhsLoad(pDevice); + } else if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + newNorm = TWOnuNorm(pDevice); + if (newNorm <= pDevice->rhsNorm) { + acceptable = TRUE; + } + } + } + /* restore the previous dcSolution and the new deltaSolution */ + pDevice->rhsNorm = newNorm; + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index]; + pDevice->dcDeltaSolution[index] *= lambda; + } +} + + + +int +TWOnewDelta(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + int index, iterNum = 0; + double newNorm, origNorm; + double fib, lambda, fibn, fibp; + BOOLEAN acceptable = FALSE, error = FALSE; + + lambda = 1.0; + fibn = 1.0; + fibp = 1.0; + + /* + * copy the contents of dcSolution into copiedSolution and modify + * dcSolution by adding the deltaSolution + */ + + for (index = 1; index <= pDevice->numEqns; ++index) { + pDevice->copiedSolution[index] = pDevice->dcSolution[index]; + pDevice->dcSolution[index] += pDevice->dcDeltaSolution[index]; + } + + if (pDevice->poissonOnly) { + TWOQrhsLoad(pDevice); + } else if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + newNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + if (pDevice->rhsNorm <= pDevice->abstol) { + lambda = 0.0; + newNorm = pDevice->rhsNorm; + } else if (newNorm < pDevice->rhsNorm) { + acceptable = TRUE; + } else { + /* chop the step size so that deltax is acceptable */ + if (TWOdcDebug) { + fprintf(stdout, " %11.4e %11.4e\n", + newNorm, lambda); + } + while (!acceptable) { + iterNum++; + + if (iterNum > NORM_RED_MAXITERS) { + error = TRUE; + lambda = 0.0; + /* Don't break out until after we've reset the device. */ + } + fib = fibp; + fibp = fibn; + fibn += fib; + lambda *= (fibp / fibn); + + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index] + + lambda * pDevice->dcDeltaSolution[index]; + } + if (pDevice->poissonOnly) { + TWOQrhsLoad(pDevice); + } else if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + newNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + if (error) { + break; + } + if (newNorm <= pDevice->rhsNorm) { + acceptable = TRUE; + } + if (TWOdcDebug) { + fprintf(stdout, " %11.4e %11.4e\n", + newNorm, lambda); + } + } + } + /* restore the previous dcSolution and the new deltaSolution */ + pDevice->rhsNorm = newNorm; + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->dcSolution[index] = pDevice->copiedSolution[index]; + pDevice->dcDeltaSolution[index] *= lambda; + } + return (error); +} + + +/* Predict the values of the internal variables at the next timepoint. */ +/* Needed for Predictor-Corrector LTE estimation */ +void +TWOpredict(TWOdevice *pDevice, TWOtranInfo *info) +{ + int nIndex, eIndex; + TWOnode *pNode; + TWOelem *pElem; + double startTime, miscTime = 0.0; + + /* TRANSIENT MISC */ + startTime = SPfrontEnd->IFseconds(); + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi = *(pDevice->devState1 + pNode->nodePsi); + if ((pElem->elemType == SEMICON) && (pNode->nodeType != CONTACT)) { + if (!OneCarrier) { + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + } else if (OneCarrier == N_TYPE) { + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + pNode->pPred = *(pDevice->devState1 + pNode->nodeP); + } else if (OneCarrier == P_TYPE) { + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + pNode->nPred = *(pDevice->devState1 + pNode->nodeN); + } + pNode->nConc = pNode->nPred; + pNode->pConc = pNode->pPred; + } + } + } + } + miscTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->miscTime[STAT_TRAN] += miscTime; +} + + +/* Estimate the device's overall truncation error. */ +double +TWOtrunc(TWOdevice *pDevice, TWOtranInfo *info, double delta) +{ + int nIndex, eIndex; + TWOelem *pElem; + TWOnode *pNode; + double tolN, tolP, lte, relError, temp, relLTE; + double lteCoeff = info->lteCoeff; + double mult = 10.0; + double reltol; + double startTime, lteTime = 0.0; + + /* TRANSIENT LTE */ + startTime = SPfrontEnd->IFseconds(); + + relError = 0.0; + reltol = pDevice->reltol * mult; + + /* need to get the predictor for the current order */ + /* the scheme currently used is very dumb. need to fix later */ + computePredCoeff(info->method, info->order, info->predCoeff, info->delta); + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if ((pElem->elemType == SEMICON) && (pNode->nodeType != CONTACT)) { + if (!OneCarrier) { + tolN = pDevice->abstol + reltol * ABS(pNode->nConc); + tolP = pDevice->abstol + reltol * ABS(pNode->pConc); + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + lte = lteCoeff * (pNode->nConc - pNode->nPred); + temp = lte / tolN; + relError += temp * temp; + lte = lteCoeff * (pNode->pConc - pNode->pPred); + temp = lte / tolP; + relError += temp * temp; + } else if (OneCarrier == N_TYPE) { + tolN = pDevice->abstol + reltol * ABS(pNode->nConc); + pNode->nPred = predict(pDevice->devStates, info, pNode->nodeN); + lte = lteCoeff * (pNode->nConc - pNode->nPred); + temp = lte / tolN; + relError += temp * temp; + } else if (OneCarrier == P_TYPE) { + tolP = pDevice->abstol + reltol * ABS(pNode->pConc); + pNode->pPred = predict(pDevice->devStates, info, pNode->nodeP); + lte = lteCoeff * (pNode->pConc - pNode->pPred); + temp = lte / tolP; + relError += temp * temp; + } + } + } + } + } + + relError = MAX(pDevice->abstol, relError); /* make sure it is non zero */ + + /* the total relative error has been calculated norm-2 squared */ + relError = sqrt(relError / pDevice->numEqns); + + /* depending on the order of the integration method compute new delta */ + temp = delta / pow(relError, 1.0 / (info->order + 1)); + + lteTime += SPfrontEnd->IFseconds() - startTime; + pDevice->pStats->lteTime += lteTime; + + /* return the new delta (stored as temp) */ + return (temp); +} + +/* Save info from state table into the internal state. */ +void +TWOsaveState(TWOdevice *pDevice) +{ + int nIndex, eIndex; + TWOnode *pNode; + TWOelem *pElem; + + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + pNode->psi = *(pDevice->devState1 + pNode->nodePsi); + if ((pElem->elemType == SEMICON) && (pNode->nodeType != CONTACT)) { + pNode->nConc = *(pDevice->devState1 + pNode->nodeN); + pNode->pConc = *(pDevice->devState1 + pNode->nodeP); + } + } + } + } +} + +/* + * A function that checks convergence based on the convergence of the quasi + * Fermi levels. This should be better since we are looking at potentials in + * all (psi, phin, phip) + */ +BOOLEAN +TWOpsiDeltaConverged(TWOdevice *pDevice) +{ + int index, nIndex, eIndex; + TWOnode *pNode; + TWOelem *pElem; + double xOld, xNew, xDelta, tol; + double psi, newPsi, nConc, pConc, newN, newP; + double phiN, phiP, newPhiN, newPhiP; + BOOLEAN converged = TRUE; + + /* equilibrium solution */ + if (pDevice->poissonOnly) { + for (index = 1; index <= pDevice->numEqns; index++) { + xOld = pDevice->dcSolution[index]; + xDelta = pDevice->dcDeltaSolution[index]; + xNew = xOld + xDelta; + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xDelta) > tol) { + converged = FALSE; + goto done; + } + } + return (converged); + } + /* bias solution. check convergence on psi, phin, phip */ + for (eIndex = 1; eIndex <= pDevice->numElems; eIndex++) { + pElem = pDevice->elements[eIndex]; + for (nIndex = 0; nIndex <= 3; nIndex++) { + if (pElem->evalNodes[nIndex]) { + pNode = pElem->pNodes[nIndex]; + if (pNode->nodeType != CONTACT) { + /* check convergence on psi */ + xOld = pDevice->dcSolution[pNode->psiEqn]; + xDelta = pDevice->dcDeltaSolution[pNode->psiEqn]; + xNew = xOld + xDelta; + tol = pDevice->abstol + + pDevice->reltol * MAX(ABS(xOld), ABS(xNew)); + if (ABS(xDelta) > tol) { + converged = FALSE; + goto done; + } + } + /* check convergence on phin and phip */ + if ((pElem->elemType == SEMICON) && (pNode->nodeType != CONTACT)) { + psi = pDevice->dcSolution[pNode->psiEqn]; + nConc = pDevice->dcSolution[pNode->nEqn]; + pConc = pDevice->dcSolution[pNode->pEqn]; + newPsi = psi + pDevice->dcDeltaSolution[pNode->psiEqn]; + newN = nConc + pDevice->dcDeltaSolution[pNode->nEqn]; + newP = pConc + pDevice->dcDeltaSolution[pNode->pEqn]; + phiN = psi - log(nConc / pNode->nie); + phiP = psi + log(pConc / pNode->nie); + newPhiN = newPsi - log(newN / pNode->nie); + newPhiP = newPsi + log(newP / pNode->nie); + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(phiN), ABS(newPhiN)); + if (ABS(newPhiN - phiN) > tol) { + converged = FALSE; + goto done; + } + tol = pDevice->abstol + pDevice->reltol * MAX(ABS(phiP), ABS(newPhiP)); + if (ABS(newPhiP - phiP) > tol) { + converged = FALSE; + goto done; + } + } + } + } + } +done: + return (converged); +} + + +/* Function to compute Nu norm of the rhs vector. */ +/* nu-Norm calculation based upon work done at Stanford. */ +double +TWOnuNorm(TWOdevice *pDevice) +{ + double norm = 0.0; + double temp; + int index; + + /* the LU Decomposed matrix is available. use it to calculate x */ + + spSolve(pDevice->matrix, pDevice->rhs, pDevice->rhsImag, + NIL(spREAL), NIL(spREAL)); + + /* the solution is in the rhsImag vector */ + /* compute L2-norm of the rhsImag vector */ + + for (index = 1; index <= pDevice->numEqns; index++) { + temp = pDevice->rhsImag[index]; + norm += temp * temp; + } + norm = sqrt(norm); + return (norm); +} + +/* + * Check for numerical errors in the Jacobian. Useful debugging aid when new + * models are being incorporated. + */ +void +TWOjacCheck(TWOdevice *pDevice, BOOLEAN tranAnalysis, TWOtranInfo *info) +{ + int index, rIndex; + double del, diff, tol, *dptr; + + if (TWOjacDebug) { + if (!OneCarrier) { + TWO_sysLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONsysLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPsysLoad(pDevice, tranAnalysis, info); + } + /* + * spPrint( pDevice->matrix, 0, 1, 1 ); + */ + pDevice->rhsNorm = maxNorm(pDevice->rhs, pDevice->numEqns); + for (index = 1; index <= pDevice->numEqns; index++) { + if (1e3 * ABS(pDevice->rhs[index]) > pDevice->rhsNorm) { + fprintf(stderr, "eqn %d: res %11.4e, norm %11.4e\n", + index, pDevice->rhs[index], pDevice->rhsNorm); + } + } + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->rhsImag[index] = pDevice->rhs[index]; + } + for (index = 1; index <= pDevice->numEqns; index++) { + pDevice->copiedSolution[index] = pDevice->dcSolution[index]; + del = 1e-4 * pDevice->abstol + 1e-6 * ABS(pDevice->dcSolution[index]); + pDevice->dcSolution[index] += del; + if (!OneCarrier) { + TWO_rhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == N_TYPE) { + TWONrhsLoad(pDevice, tranAnalysis, info); + } else if (OneCarrier == P_TYPE) { + TWOPrhsLoad(pDevice, tranAnalysis, info); + } + pDevice->dcSolution[index] = pDevice->copiedSolution[index]; + for (rIndex = 1; rIndex <= pDevice->numEqns; rIndex++) { + diff = (pDevice->rhsImag[rIndex] - pDevice->rhs[rIndex]) / del; + dptr = spFindElement(pDevice->matrix, rIndex, index); + if (dptr != NIL(double)) { + tol = (1e-4 * pDevice->abstol) + (1e-2 * MAX(ABS(diff), ABS(*dptr))); + if ((diff != 0.0) && (ABS(diff - *dptr) > tol)) { + fprintf(stderr, "Mismatch[%d][%d]: FD = %11.4e, AJ = %11.4e\n\t FD-AJ = %11.4e vs. %11.4e\n", + rIndex, index, diff, *dptr, + ABS(diff - *dptr), tol); + } + } else { + if (diff != 0.0) { + fprintf(stderr, "Missing [%d][%d]: FD = %11.4e, AJ = 0.0\n", + rIndex, index, diff); + } + } + } + } + } +}