You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1234 lines
35 KiB
1234 lines
35 KiB
/**********
|
|
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/ngspice.h"
|
|
#include "ngspice/numglobs.h"
|
|
#include "ngspice/numenum.h"
|
|
#include "ngspice/onedev.h"
|
|
#include "ngspice/onemesh.h"
|
|
#include "ngspice/spmatrix.h"
|
|
#include "ngspice/bool.h"
|
|
#include "ngspice/macros.h"
|
|
#include "onedext.h"
|
|
#include "oneddefs.h"
|
|
#include "ngspice/cidersupt.h"
|
|
#include "../../maths/misc/norm.h"
|
|
|
|
#include "ngspice/ifsim.h"
|
|
#include "ngspice/cktdefs.h"
|
|
#include "ngspice/ftedefs.h"
|
|
|
|
extern IFfrontEnd *SPfrontEnd;
|
|
|
|
|
|
|
|
/* 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();
|
|
|
|
#ifdef KLU
|
|
error = SMPreorderKLUforCIDER (pDevice->matrix) ;
|
|
#else
|
|
error = SMPluFacForCIDER (pDevice->matrix) ;
|
|
#endif
|
|
|
|
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;
|
|
SMPgetError(pDevice->matrix, &badCol, &badRow);
|
|
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();
|
|
|
|
#ifdef KLU
|
|
SMPsolveKLUforCIDER (pDevice->matrix, rhs, delta, NULL, NULL) ;
|
|
#else
|
|
SMPsolveForCIDER (pDevice->matrix, rhs, delta) ;
|
|
#endif
|
|
|
|
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);
|
|
|
|
#ifdef KLU
|
|
error = SMPreorderKLUforCIDER (pDevice->matrix) ;
|
|
#else
|
|
error = SMPluFacForCIDER (pDevice->matrix) ;
|
|
#endif
|
|
|
|
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);
|
|
|
|
#ifdef KLU
|
|
SMPdestroyKLUforCIDER (pDevice->matrix) ;
|
|
#else
|
|
SMPdestroy (pDevice->matrix) ;
|
|
#endif
|
|
|
|
FREE (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 = TMALLOC (SMPmatrix, 1) ;
|
|
|
|
#ifdef KLU
|
|
pDevice->matrix->CKTkluMODE = ft_curckt->ci_ckt->CKTkluMODE ;
|
|
error = SMPnewMatrixKLUforCIDER (pDevice->matrix, pDevice->numEqns, KLUmatrixReal) ;
|
|
#else
|
|
error = SMPnewMatrixForCIDER (pDevice->matrix, pDevice->numEqns, 0) ;
|
|
#endif
|
|
|
|
if (error == spNO_MEMORY) {
|
|
printf("ONEequilSolve: Out of Memory\n");
|
|
exit(-1);
|
|
}
|
|
newSolver = TRUE;
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixIsComplex = KLUmatrixReal ;
|
|
} else {
|
|
#endif
|
|
|
|
spSetReal (pDevice->matrix->SPmatrix) ;
|
|
|
|
#ifdef KLU
|
|
}
|
|
#endif
|
|
|
|
ONEQjacBuild(pDevice);
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
|
|
/* Convert the COO Storage to CSC for KLU and Fill the Binding Table */
|
|
SMPconvertCOOtoCSCKLUforCIDER (pDevice->matrix) ;
|
|
|
|
/* Bind the KLU Pointers */
|
|
ONEQbindCSC (pDevice) ;
|
|
|
|
/* Perform KLU Matrix Analysis */
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixSymbolic = klu_analyze ((int)pDevice->matrix->SMPkluMatrix->KLUmatrixN, pDevice->matrix->SMPkluMatrix->KLUmatrixAp,
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixAi, pDevice->matrix->SMPkluMatrix->KLUmatrixCommon) ;
|
|
if (pDevice->matrix->SMPkluMatrix->KLUmatrixSymbolic == NULL) {
|
|
printf ("CIDER: KLU Failed\n") ;
|
|
if (pDevice->matrix->SMPkluMatrix->KLUmatrixCommon->status == KLU_EMPTY_MATRIX) {
|
|
return ; // Francesco Lannutti - Fix KLU return values
|
|
}
|
|
}
|
|
pDevice->numOrigEquil = (int)pDevice->matrix->SMPkluMatrix->KLUmatrixNZ ;
|
|
} else {
|
|
pDevice->numOrigEquil = spElementCount (pDevice->matrix->SPmatrix) ;
|
|
}
|
|
#else
|
|
pDevice->numOrigEquil = spElementCount (pDevice->matrix->SPmatrix) ;
|
|
#endif
|
|
|
|
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, NULL);
|
|
|
|
/* MISCELLANEOUS */
|
|
startTime = SPfrontEnd->IFseconds();
|
|
if (newSolver) {
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
pDevice->numFillEquil = pDevice->matrix->SMPkluMatrix->KLUmatrixNumeric->lnz + pDevice->matrix->SMPkluMatrix->KLUmatrixNumeric->unz
|
|
- (int)pDevice->matrix->SMPkluMatrix->KLUmatrixNZ ;
|
|
} else {
|
|
#endif
|
|
|
|
pDevice->numFillEquil = spFillinCount(pDevice->matrix->SPmatrix);
|
|
|
|
#ifdef KLU
|
|
}
|
|
#endif
|
|
|
|
}
|
|
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);
|
|
|
|
#ifdef KLU
|
|
SMPdestroyKLUforCIDER (pDevice->matrix) ;
|
|
#else
|
|
SMPdestroy (pDevice->matrix) ;
|
|
#endif
|
|
|
|
FREE (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 = TMALLOC (SMPmatrix, 1) ;
|
|
|
|
#ifdef KLU
|
|
pDevice->matrix->CKTkluMODE = ft_curckt->ci_ckt->CKTkluMODE ;
|
|
error = SMPnewMatrixKLUforCIDER (pDevice->matrix, pDevice->numEqns, KLUMatrixComplex) ;
|
|
#else
|
|
error = SMPnewMatrixForCIDER (pDevice->matrix, pDevice->numEqns, 1) ;
|
|
#endif
|
|
|
|
if (error == spNO_MEMORY) {
|
|
exit(-1);
|
|
}
|
|
newSolver = TRUE;
|
|
ONE_jacBuild(pDevice);
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
|
|
/* Convert the COO Storage to CSC for KLU and Fill the Binding Table */
|
|
SMPconvertCOOtoCSCKLUforCIDER (pDevice->matrix) ;
|
|
|
|
/* Bind the KLU Pointers */
|
|
ONEbindCSC (pDevice) ;
|
|
|
|
/* Perform KLU Matrix Analysis */
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixSymbolic = klu_analyze ((int)pDevice->matrix->SMPkluMatrix->KLUmatrixN, pDevice->matrix->SMPkluMatrix->KLUmatrixAp,
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixAi, pDevice->matrix->SMPkluMatrix->KLUmatrixCommon) ;
|
|
if (pDevice->matrix->SMPkluMatrix->KLUmatrixSymbolic == NULL) {
|
|
if (pDevice->matrix->SMPkluMatrix->KLUmatrixCommon->status == KLU_EMPTY_MATRIX) {
|
|
printf ("CIDER: KLU failed\n") ;
|
|
return ; // Francesco Lannutti - Fix KLU return values
|
|
}
|
|
}
|
|
pDevice->numOrigBias = (int)pDevice->matrix->SMPkluMatrix->KLUmatrixNZ ;
|
|
} else {
|
|
pDevice->numOrigBias = spElementCount(pDevice->matrix->SPmatrix);
|
|
}
|
|
#else
|
|
pDevice->numOrigBias = spElementCount (pDevice->matrix->SPmatrix) ;
|
|
#endif
|
|
|
|
pDevice->numFillBias = 0;
|
|
ONEstoreInitialGuess(pDevice);
|
|
/* FALLTHRU */
|
|
case SLV_SMSIG:
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
pDevice->matrix->SMPkluMatrix->KLUmatrixIsComplex = KLUmatrixReal ;
|
|
} else {
|
|
#endif
|
|
|
|
spSetReal (pDevice->matrix->SPmatrix) ;
|
|
|
|
#ifdef KLU
|
|
}
|
|
#endif
|
|
|
|
/* 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) {
|
|
|
|
#ifdef KLU
|
|
if (pDevice->matrix->CKTkluMODE) {
|
|
pDevice->numFillBias = pDevice->matrix->SMPkluMatrix->KLUmatrixNumeric->lnz + pDevice->matrix->SMPkluMatrix->KLUmatrixNumeric->unz
|
|
- (int)pDevice->matrix->SMPkluMatrix->KLUmatrixNZ ;
|
|
} else {
|
|
#endif
|
|
|
|
pDevice->numFillBias = spFillinCount (pDevice->matrix->SPmatrix) ; // Francesco Lannutti - Fix for KLU
|
|
|
|
#ifdef KLU
|
|
}
|
|
#endif
|
|
|
|
}
|
|
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;
|
|
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)
|
|
{
|
|
/* The LU Decomposed matrix is available. Use it to calculate x. */
|
|
#ifdef KLU
|
|
printf ("CIDER: KLU to be fixed ONEnuNorm\n") ;
|
|
SMPsolveKLUforCIDER (pDevice->matrix, pDevice->rhs, pDevice->rhsImag, NULL, NULL) ;
|
|
#else
|
|
SMPsolveForCIDER (pDevice->matrix, pDevice->rhs, pDevice->rhsImag) ;
|
|
#endif
|
|
|
|
/* 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;
|
|
|
|
#ifdef KLU
|
|
printf ("CIDER: KLU to be fixed: spFindElement") ;
|
|
dptr = NULL ;
|
|
#else
|
|
dptr = spFindElement(pDevice->matrix->SPmatrix, rIndex, index); // Francesco Lannutti - Fix for KLU
|
|
#endif
|
|
|
|
/*
|
|
* if ( dptr ISNOT NULL ) { 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 != NULL) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|