/********** 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); } } } } } }