------------------------------------------------------------ | Vsource - instance parameters (input-only) | |-----------------------------------------------------------+ | pulse Pulse description | | sine Sinusoidal source description | | sin Sinusoidal source description | | exp Exponential source description | ------------------------------------------------------------ | pwl Piecewise linear description | | sffm Single freq. FM description | | ac AC magnitude, phase vector | | distof1 f1 input for distortion | | distof2 f2 input for distortion | ------------------------------------------------------------ ------------------------------------------------------------ | Vsource - instance parameters (input-output) | |-----------------------------------------------------------+ | dc D.C. source value | | acmag A.C. Magnitude | | acphase A.C. Phase | ------------------------------------------------------------ ------------------------------------------------------------ | Vsource - instance parameters (output-only) | |-----------------------------------------------------------+ | pos_node Positive node of source | | neg_node Negative node of source | | function Function of the source | | order Order of the source function | ------------------------------------------------------------ | coeffs Coefficients for the function | | acreal AC real part | | acimag AC imaginary part | | i Voltage source current | | p Instantaneous power | ------------------------------------------------------------
NGSPICE is the result of many hours of work spent in front of a screen trying to fix and enhance the original Spice3 code. Most of the work done affects simulation results in some way, so many users ask why the results obtained with Spice3 differ with the ones they get from NGSPICE.
This chapter collects all the enhancements introduced into NGSPICE during its development, ordered by categories. For each improvement described here, the file(s) and function(s) affected are reported into a table, letting the experienced user to understand at what extent such improvement affects his or her simulation.
This section collects most of the enhancements made to the code that builds up device models and device support routines. If you are concerned about discrepancies between the results you get with NGSPICE and the ones you got with a clean Spice3f, look here to see if they depend on a bug that has been fixed (or a new on introduced)
The NGSPICE resistor model has been enhanced adding some useful features already present in other simulators:
In CAPask()
, the power stored in a capacitor and current
through it were not showed correctly during transient analysis.
CAPask()
In the original implementation of the diode model, the parameter
DIOtBrkdwnV
was used instead of the flag DIObreakdownVoltageGiven
,
in the DIOload()
function. In the same function the
DIOjunctionPot
was used instead of the temperature corrected
version DIOtJctPot
.
In the DIOtemp()
function, the DIOtVcrit
was calculated without
taking into account the device area. This could cause floating point
overflows, especially in device models designed to be scaled by a small area,
e.g. 2u by 2u diodes (area=4e-12).
The flawed code was:
here->DIOtVcrit=vte*log(vte/(CONSTroot2*here->DIOtSatCur));
here->DIOtVcrit=vte* log(vte/(CONSTroot2*here->DIOtSatCur*here->DIOarea));
Enhancement Data:
DIOload(), DIOtemp()
The Level 1 MOS model (MOS1)now accepts the "M" device parameter (multiplicity), to simulate "M" paralleled identical devices. The "M" parameter affects the following quantities:
MOS1acLoad()
), the value assigned
to the "M" (MOS1m
) parameter multiplies the overlap capacitances:
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
.
MOS1ask()
),
the value of gate-source, gate-drain and gate-bulk capacitances,
corresponding to the parameters: MOS1_CAPGS
, MOS1_CAPGD
,
MOS1_CAPGB
, are multiplied by the value of "M".
MOS1dset()
),
the overlap capcitances (GateSourceOverlapCap
,
GateDrainOverlapCap
, GateBulkOverlapCap
), the saturation
currents (DrainSatCurr
, SourceSatCurr
), the "beta"
(Beta
) and the oxide capacitance (OxideCap
) are all
mutiplied by the value of "M".
MOS1load()
), the value of "M"
paramter multiplies:DrainSatCurr
, SourceSatCurr
,
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
, Beta
, OxideCap
. The meaning of
the variables have been already explained above.
MOS1noi()
), the noise
densities, contained in the noizDEns[]
vector are multplied by
the value of "M".
GateSourceOverlapCap
,
GateDrainOverlapCap
, GateBulkOverlapCap
.
MOS1temp()
) the source and
drain critical voltages (MOS1sourceVcrit
, MOS1drainVcrit
)
are multiplied by the value of "M", like the zero-voltage bulk-drain and
bulk-source capacitances: czbd
, czbdsw
, czbs
,
czbssw
, where "sw" suffix stands for "sidewall" and means perimetral
capacitsnces and the drain and sources conductances (
MOS1drainConductance
. MOS1sourceConductance
)
Other minor changes for "M" parameter support includes: MOS1sprt()
prints the value of "M", MOS1param()
sets the value of "M" and
MOS1ask()
returns that value.
The "Gmin" implementation across the substrate diodes of MOS1 was incorrect,
correcting this dramatically improved DC convergence. The original code in
MOS1load()
was:
... ... next1: if(vbs <= 0) { here->MOS1gbs = SourceSatCur/vt; here->MOS1cbs = here->MOS1gbs*vbs; here->MOS1gbs += ckt->CKTgmin; } else { evbs = exp(MIN(MAX_EXP_ARG,vbs/vt)); here->MOS1gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS1cbs = SourceSatCur * (evbs-1); } if(vbd <= 0) { here->MOS1gbd = DrainSatCur/vt; here->MOS1cbd = here->MOS1gbd *vbd; here->MOS1gbd += ckt->CKTgmin; } else { evbd = exp(MIN(MAX_EXP_ARG,vbd/vt)); here->MOS1gbd = DrainSatCur*evbd/vt +ckt->CKTgmin; here->MOS1cbd = DrainSatCur *(evbd-1); } ... ...
and the new one is:
... ... next1: if(vbs <= -3*vt) { here->MOS1gbs = ckt->CKTgmin; here->MOS1cbs = here->MOS1gbs*vbs-SourceSatCur; } else { evbs = exp(MIN(MAX_EXP_ARG,vbs/vt)); here->MOS1gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS1cbs = SourceSatCur*(evbs-1) + ckt->CKTgmin*vbs; } if(vbd <= -3*vt) { here->MOS1gbd = ckt->CKTgmin; here->MOS1cbd = here->MOS1gbd*vbd-DrainSatCur; } else { evbd = exp(MIN(MAX_EXP_ARG,vbd/vt)); here->MOS1gbd = DrainSatCur*evbd/vt + ckt->CKTgmin; here->MOS1cbd = DrainSatCur*(evbd-1) + ckt->CKTgmin*vbd; } ... ...
In the "load current vector" section of the MOS1load()
routine,
"Gmin" appeared in the calculation of ceqbd
and ceqbs
:
/* * load current vector */ ceqbs = model->MOS1type * (here->MOS1cbs-(here->MOS1gbs-ckt->CKTgmin)*vbs); ceqbd = model->MOS1type * (here->MOS1cbd-(here->MOS1gbd-ckt->CKTgmin)*vbd);
The code has been corrected as follows:
/* * load current vector */ ceqbs = model->MOS1type * (here->MOS1cbs-(here->MOS1gbs)*vbs); ceqbd = model->MOS1type * (here->MOS1cbd-(here->MOS1gbd)*vbd);
MOS1 device reported only half of the Meyer capcitance without adding the
overlap capacitance contribution, when reporting to the .OP
printout
or in the rawfile. The routine MOS1ask()
was responsible for this:
... ... case MOS1_CGS: value->rValue = *(ckt->CKTstate0 + here->MOS1capgs); return(OK); case MOS1_CGD: value->rValue = *(ckt->CKTstate0 + here->MOS1capgd); return(OK); ... ... case MOS1_CAPGS: value->rValue = *(ckt->CKTstate0 + here->MOS1capgs); return(OK); ... ... case MOS1_CAPGD: value->rValue = *(ckt->CKTstate0 + here->MOS1capgd); return(OK); ... ... case MOS1_CAPGB: value->rValue = *(ckt->CKTstate0 + here->MOS1capgb); return(OK);
The new code is:
... ... case MOS1_CGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS1capgs); return(OK); case MOS1_CGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS1capgd); return(OK); ... ... case MOS1_CAPGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS1capgs); /* add overlap capacitance */ value->rValue += (here->sMOS1modPtr->MOS1gateSourceOverlapCapFactor) * here->MOS1m * (here->MOS1w); return(OK); ... ... case MOS1_CAPGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS1capgd); /* add overlap capacitance */ value->rValue += (here->sMOS1modPtr->MOS1gateSourceOverlapCapFactor) * here->MOS1m * (here->MOS1w); return(OK); ... ... case MOS1_CAPGB: value->rValue = 2* *(ckt->CKTstate0 + here->MOS1capgb); /* add overlap capacitance */ value->rValue += (here->sMOS1modPtr->MOS1gateBulkOverlapCapFactor) * here->MOS1m * (here->MOS1l -2*(here->sMOS1modPtr->MOS1latDiff)); return(OK);
Level 2 mosfet model now accetps the "M" instance parameter (multiplicity) to simulate M paralleled identical devices. The affected quantities are:
MOS2acLoad()
, the value assigned
to the "M" (MOS2m
) parameter, multiplies the overlap
capacitance: GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
.
MOS2ask()
routine (instance parameters reporting) the
gate-source, gate-drain and gate-bulk capacitances, corresponding
to MOS2_CAPGS
, MOS2_CAPGD
, MOS2_CAPGB
parameters, are multiplied by the value of "M".
MOS2dset()
function (distortion analysis setup)
the overlap capcitances (GateSourceOverlapCap
,
GateDrainOverlapCap
, GateBulkOverlapCap
),
the saturation currents (DrainSatCurr
, SourceSatCurr
),
the "beta" (Beta
), the oxide capacitance (OxideCap
)
and and the xn
quantity are all mutiplied by the value
of "M".
MOS2load()
, the value of "M"
parameter multiplies:DrainSatCurr
, SourceSatCurr
,
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
, Beta
, OxideCap
,
xn
. The meaning of the variables have been already
explained above.
MOS2noi()
, the noise
densities, contained in the noizDEns[]
vector are
multplied by the value of "M".
MOS2pzLoad()
),
the following quantities are multiplied by the value of "M":
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
.
MOS2temp()
routine the source and drain critical
voltages (MOS2sourceVcrit
, MOS2drainVcrit
)
are multiplied by the value of "M", like the zero-voltage
bulk-drain and bulk-source capacitances: czbd
,
czbdsw
, czbs
, czbssw
, where "sw" suffix
stands for "sidewall" and means perimetral capacitances and
the drain and sources conductances (MOS2drainConductance
,
MOS2sourceConductance
).
Other minor changes for "M" parameter support includes: MOS2sprt()
prints the value of "M", MOS2param()
sets the value of "M" and
MOS2ask()
returns that value.
The "Gmin" implementation across the substrate diodes of MOS2 was incorrect,
correcting this dramatically improved DC convergence. The original code in
MOS2load()
was:
... ... /* bulk-source and bulk-drain diodes * here we just evaluate the ideal diode current and the * corresponding derivative (conductance). */ next1: if(vbs <= 0) { here->MOS2gbs = SourceSatCur/vt; here->MOS2cbs = here->MOS2gbs*vbs; here->MOS2gbs += ckt->CKTgmin; } else { evbs = exp(vbs/vt); here->MOS2gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS2cbs = SourceSatCur * (evbs-1); } if(vbd <= 0) { here->MOS2gbd = DrainSatCur/vt; here->MOS2cbd = here->MOS2gbd *vbd; here->MOS2gbd += ckt->CKTgmin; } else { evbd = exp(vbd/vt); here->MOS2gbd = DrainSatCur*evbd/vt +ckt->CKTgmin; here->MOS2cbd = DrainSatCur *(evbd-1); } ... ...
Then new code is:
... ... /* bulk-source and bulk-drain diodes * here we just evaluate the ideal diode current and the * corresponding derivative (conductance). */ next1: if(vbs <= -3*vt) { here->MOS2gbs = ckt->CKTgmin; here->MOS2cbs = here->MOS2gbs*vbs-SourceSatCur; } else { evbs = exp(MIN(MAX_EXP_ARG,vbs/vt)); here->MOS2gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS2cbs = SourceSatCur*(evbs-1) + ckt->CKTgmin*vbs; } if(vbd <= -3*vt) { here->MOS2gbd = ckt->CKTgmin; here->MOS2cbd = here->MOS2gbd*vbd-DrainSatCur; } else { evbd = exp(MIN(MAX_EXP_ARG,vbd/vt)); here->MOS2gbd = DrainSatCur*evbd/vt + ckt->CKTgmin; here->MOS2cbd = DrainSatCur*(evbd-1) + ckt->CKTgmin*vbd; }
In the "load current vector" section of the MOS2load()
routine,
"Gmin" appeared in the calculation of ceqbd
and ceqbs
:
... ... /* * load current vector */ ceqbs = model->MOS2type * (here->MOS2cbs-(here->MOS2gbs-ckt->CKTgmin)*vbs); ceqbd = model->MOS2type * (here->MOS2cbd-(here->MOS2gbd-ckt->CKTgmin)*vbd); ... ...
The correct code is:
... ... /* * load current vector */ ceqbs = model->MOS2type * (here->MOS2cbs-(here->MOS2gbs)*vbs); ceqbd = model->MOS2type * (here->MOS2cbd-(here->MOS2gbd)*vbd); ... ...
MOS2 device reported only half of the Meyer capacitance without adding the
overlap capacitance contribution, when reporting to the .OP
printout
or in the rawfile. The routine MOS2ask()
was responsible for this:
... ... case MOS2_CGS: value->rValue = *(ckt->CKTstate0 + here->MOS2capgs); return(OK); case MOS2_CGD: value->rValue = *(ckt->CKTstate0 + here->MOS2capgd); return(OK); ... ... case MOS2_CAPGS: value->rValue = *(ckt->CKTstate0 + here->MOS2capgs); return(OK); ... ... case MOS2_CAPGD: value->rValue = *(ckt->CKTstate0 + here->MOS2capgd); return(OK); ... ... case MOS2_CAPGB: value->rValue = *(ckt->CKTstate0 + here->MOS2capgb); return(OK); ... ...
The new code is:
... ... case MOS2_CGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS2capgs); return(OK); case MOS2_CGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS2capgd); return(OK); ... ... case MOS2_CAPGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS2capgs); /* add overlap capacitance */ value->rValue += (here->MOS2modPtr->MOS2gateSourceOverlapCapFactor) * here->MOS2m * (here->MOS2w); return(OK); ... ... case MOS2_CAPGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS2capgd); /* add overlap capacitance */ value->rValue += (here->MOS2modPtr->MOS2gateSourceOverlapCapFactor) * here->MOS2m * (here->MOS2w); return(OK); ... ... case MOS2_CAPGB: value->rValue = 2* *(ckt->CKTstate0 + here->MOS2capgb); /* add overlap capacitance */ value->rValue += (here->MOS2modPtr->MOS2gateBulkOverlapCapFactor) * here->MOS2m * (here->MOS2l -2*(here->MOS2modPtr->MOS2latDiff)); return(OK); ... ...
The level 2 MOSFET model seems to calculate Von and Vth values for the
threshold and subthreshold values respectively, but then uses Vbin to
calculate the Vdsat voltage used to find the drain current. However, a
jump statement uses Von to decide that the device is in the "cutoff"
region, which means that when this jump allows the drain current to be
calculated, Vdsat can already be well above zero. This leads to a
discontinuity of drain current with respect to gate voltage. The code
is now modified to use Vbin for the jump decision. It looks like the
code should actually use Vth as the threshold voltage, but since
PSPICE and HSPICE both follow the original Berkeley code, this was
left alone. The affected code can be found in MOS2load()
:
... ... if ((lvds-lvbs) >= 0) { barg = sqrt(phiMinVbs+lvds); dbrgdb = -0.5/barg; ... ... vgst = lvgs-von; if (lvgs <= von) { /* * cutoff region */ ... ... if (lvgs > von) goto line900; /* * subthreshold region */ ... ...
and the corrected code is:
... ... if ((lvbs-lvds) <= 0) { barg = sqrt(phiMinVbs+lvds); dbrgdb = -0.5/barg; ... ... vgst = lvgs-von; if (lvgs <= vbin) { /* * cutoff region */ ... ... if (model->MOS2fastSurfaceStateDensity != 0 && OxideCap != 0) { if (lvgs > von) goto line900; } else { if (lvgs > vbin) goto line900; goto doneval; } /* * subthreshold region */ ... ...
The level 3 model has been extensively corrected since it is a de-facto standard for circuit simulation.
The level 3 model supports the "M" parameter (multiplicity), which can be used to simulate M identical paralleled devices. The "M" parameter affects the quantities described in the following list:
MOS3acld()
, the value assigned
to the "M" (MOS3m
) parameter, multiplies the overlap
capacitance: GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
.
MOS3ask()
function (instance parameters reporting) the
gate-source, gate-drain and gate-bulk capacitances, corresponding
to MOS3_CAPGS
, MOS3_CAPGD
, MOS3_CAPGB
parameters, are multiplied by the value of "M".
MOS3dset()
function (distorsion analysis setup)
the overlap capcitances (GateSourceOverlapCap
,
GateDrainOverlapCap
, GateBulkOverlapCap
),
the saturation currents (DrainSatCurr
, SourceSatCurr
),
the "beta" (Beta
), the oxide capacitance (OxideCap
)
and and the csonco
quantity are all mutiplied by the value
of "M".
MOS3load()
, the value of "M"
parameter multiplies:DrainSatCurr
, SourceSatCurr
,
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
, Beta
, OxideCap
,
csonco
. The meaning of the variables have been already
explained above.
MOS3noi()
, the noise
densities, contained in the noizDEns[]
vector are
multplied by the value of "M".
MOS3pzLoad()
),
the following quantities are multiplied by the value of "M":
GateSourceOverlapCap
, GateDrainOverlapCap
,
GateBulkOverlapCap
.
MOS3temp()
routine the source and drain critical
voltages (MOS3sourceVcrit
, MOS3drainVcrit
)
are multiplied by the value of "M", like the zero-voltage
bulk-drain and bulk-source capacitances: czbd
,
czbdsw
, czbs
, czbssw
, where "sw" suffix
stands for "sidewall" and means perimetral capacitances and
the drain and sources conductances (MOS3drainConductance
,
MOS3sourceConductance
).
Other minor changes for "M" parameter support includes: MOS3sprt()
prints the value of "M", MOS3param()
sets the value of "M" and
MOS3ask()
returns that value.
Another important improvement over the original Spice3 code is the
support for process narrowing over drawn dimensions. The three model
parameters added to level 3 model are: xl
, wd
, xw
.
The changes in the code are described in depth as a reference for
future model development.
Adding new model parameters usually need the introduction of new
variables (one for each parameter) in the model structure:
sMOS3model
, which can be found in mos3defs.h. In this
case the new variables are:
... ... double MOS3lengthAdjust; /* New param: mask adjustment to length */ double MOS3widthNarrow; /* New param to reduce effective width */ double MOS3widthAdjust; /* New param: mask adjustment to width */ ... ... unsigned MOS3lengthAdjustGiven :1; unsigned MOS3widthNarrowGiven :1; unsigned MOS3widthAdjustGiven :1; ... ... #define MOS3_MOD_XL 145 #define MOS3_MOD_WD 146 #define MOS3_MOD_XW 147 ... ...
The single bit field that ends in "Given" are used to indicate
whether the parameter has been supplied by the user or must be
defaulted.
The last three #define
are needed as a mean to identify the
parameters throughout the model, since comparing integers is faster
than comparing stings. As you may have already imagined, those
numbers must be unique. The association between parameter name and
numerical code appears in MOS3mPTable[]
in mos3.c:
... ... IFparm MOS3mPTable[] = { /* model parameters */ OP("type", MOS3_MOD_TYPE, IF_STRING ,"N-channel or P-channel MOS"), IP("nmos", MOS3_MOD_NMOS, IF_FLAG ,"N type MOSfet model"), IP("pmos", MOS3_MOD_PMOS, IF_FLAG ,"P type MOSfet model"), ... ... IOP("xl", MOS3_MOD_XL, IF_REAL ,"Length mask adjustment"), IOP("wd", MOS3_MOD_WD, IF_REAL ,"Width Narrowing (Diffusion)"), IOP("xw", MOS3_MOD_XW, IF_REAL ,"Width mask adjustment"), ... ...
The keyword IOP
before the three parameters sets them as
input/output parameters (that can be set and queried). The function
used to set parameters values is MOS3mParam()
, which contains
the following code:
... ... case MOS3_MOD_XL: model->MOS3lengthAdjust = value->rValue; model->MOS3lengthAdjustGiven = TRUE; break; case MOS3_MOD_WD: model->MOS3widthNarrow = value->rValue; model->MOS3widthNarrowGiven = TRUE; break; case MOS3_MOD_XW: model->MOS3widthAdjust = value->rValue; model->MOS3widthAdjustGiven = TRUE; break; ... ...
The function used to query those parameters is MOS3mAsk()
and
the specific code is:
... ... case MOS3_MOD_XL: value->rValue = here->MOS3lengthAdjust; return(OK); case MOS3_MOD_WD: value->rValue = here->MOS3widthNarrow; return(OK); case MOS3_MOD_XW: value->rValue = here->MOS3widthAdjust; return(OK); ... ...
The code above describes the interface to the new parameters, their
influence on the model behaviour is contained in the following
functions: MOS3acLoad()
, MOS3load()
, MOS3noise()
,
MOS3pzLoad()
, MOS3setup()
, MOS3sLoad()
and
MOS3temp()
.
The MOS3acLoad()
function contains the code used to represent
the model for AC (small signal) analysis. The original code was:
... ... EffectiveLength=here->MOS3l - 2*model->MOS3latDiff; GateSourceOverlapCap = model->MOS3gateSourceOverlapCapFactor * here->MOS3w; GateDrainOverlapCap = model->MOS3gateDrainOverlapCapFactor * here->MOS3w; GateBulkOverlapCap = model->MOS3gateBulkOverlapCapFactor * EffectiveLength; ... ...
And the new one:
... ... double EffectiveWidth; ... ... EffectiveWidth = here->MOS3w - 2*model->MOS3widthNarrow + model->MOS3widthAdjust; EffectiveLength = here->MOS3l - 2*model->MOS3latDiff + model->MOS3lengthAdjust; GateSourceOverlapCap = model->MOS3gateSourceOverlapCapFactor * here->MOS3m * EffectiveWidth; GateDrainOverlapCap = model->MOS3gateDrainOverlapCapFactor * here->MOS3m * EffectiveWidth; GateBulkOverlapCap = model->MOS3gateBulkOverlapCapFactor * here->MOS3m * EffectiveLength; ... ...
A brief look at the new code shows that a new variable
EffectiveWidth
appears and its value depends on the
newly introduced parameters wd
and xw
, through
MOS3widthNarrow
and MOS3widthAdjust
, respectively.
The values of EffectiveLength
is trimmed with the value of
xl
through MOS3lengthAdjust
. The overlap capacitances
are multiplied by EffectiveWidth
instead of MOS3w
. The
MOS3m
value has been discussed above.
The MOS3pzLoad()
function is very similar to MOS3acLoad()
and the code affected is almost identical to the one above.
The MOS3load()
function describes the model for large signals
analyses. The old code is:
... ... EffectiveLength = here->MOS3l - 2*model->MOS3latDiff; if( (here->MOS3tSatCurDens == 0) || (here->MOS3drainArea == 0) || (here->MOS3sourceArea == 0)) { DrainSatCur = here->MOS3tSatCur; SourceSatCur = here->MOS3tSatCur; } else { DrainSatCur = here->MOS3tSatCurDens * here->MOS3drainArea; SourceSatCur = here->MOS3tSatCurDens * here->MOS3sourceArea; } GateSourceOverlapCap = model->MOS3gateSourceOverlapCapFactor * here->MOS3w; GateDrainOverlapCap = model->MOS3gateDrainOverlapCapFactor * here->MOS3w; GateBulkOverlapCap = model->MOS3gateBulkOverlapCapFactor * EffectiveLength; Beta = here->MOS3tTransconductance * here->MOS3w/EffectiveLength; OxideCap = model->MOS3oxideCapFactor * EffectiveLength * here->MOS3w; ... ... /* *.....body effect */ gammas = model->MOS3gamma*fshort; fbodys = 0.5*gammas/(sqphbs+sqphbs); fbody = fbodys+model->MOS3narrowFactor/here->MOS3w; onfbdy = 1.0/(1.0+fbody); dfbdvb = -fbodys*dsqdvb/sqphbs+fbodys*dfsdvb/fshort; qbonco =gammas*sqphbs+model->MOS3narrowFactor*phibs/here->MOS3w; dqbdvb = gammas*dsqdvb+model->MOS3gamma*dfsdvb*sqphbs- model->MOS3narrowFactor/here->MOS3w; ... ... /* *.....joint weak inversion and strong inversion */ von = vth; if ( model->MOS3fastSurfaceStateDensity != 0.0 ) { csonco = CHARGE*model->MOS3fastSurfaceStateDensity * 1e4 /*(cm**2/m**2)*/ * EffectiveLength*here->MOS3w/OxideCap; ... ...
And the new code is:
... ... double EffectiveWidth; ... ... EffectiveWidth = here->MOS3w - 2*model->MOS3widthNarrow + model->MOS3widthAdjust; EffectiveLength = here->MOS3l - 2*model->MOS3latDiff + model->MOS3lengthAdjust; if( (here->MOS3tSatCurDens == 0) || (here->MOS3drainArea == 0) || (here->MOS3sourceArea == 0)) { DrainSatCur = here->MOS3m * here->MOS3tSatCur; SourceSatCur = here->MOS3m * here->MOS3tSatCur; } else { DrainSatCur = here->MOS3m * here->MOS3tSatCurDens * here->MOS3drainArea; SourceSatCur = here->MOS3m * here->MOS3tSatCurDens * here->MOS3sourceArea; } GateSourceOverlapCap = model->MOS3gateSourceOverlapCapFactor * here->MOS3m * EffectiveWidth; GateDrainOverlapCap = model->MOS3gateDrainOverlapCapFactor * here->MOS3m * EffectiveWidth; GateBulkOverlapCap = model->MOS3gateBulkOverlapCapFactor * here->MOS3m * EffectiveLength; Beta = here->MOS3tTransconductance * here->MOS3m * EffectiveWidth/EffectiveLength; OxideCap = model->MOS3oxideCapFactor * EffectiveLength * here->MOS3m * EffectiveWidth; ... ... /* *.....body effect */ gammas = model->MOS3gamma*fshort; fbodys = 0.5*gammas/(sqphbs+sqphbs); fbody = fbodys+model->MOS3narrowFactor/EffectiveWidth; onfbdy = 1.0/(1.0+fbody); dfbdvb = -fbodys*dsqdvb/sqphbs+fbodys*dfsdvb/fshort; qbonco = gammas*sqphbs+model->MOS3narrowFactor * phibs/EffectiveWidth; dqbdvb = gammas*dsqdvb+model->MOS3gamma*dfsdvb*sqphbs - model->MOS3narrowFactor/EffectiveWidth; ... ... /* *.....joint weak inversion and strong inversion */ von = vth; if ( model->MOS3fastSurfaceStateDensity != 0.0 ) { csonco = CHARGE * model->MOS3fastSurfaceStateDensity * 1e4 /*(cm**2/m**2)*/ * EffectiveLength*EffectiveWidth * here->MOS3m/OxideCap; ... ...
The "trick" is to substitute the MOS3w
with the effective
width taking into account device multiplicity. Another point
where device width matters is the noise routine:MOS3noise()
.
The oginal code computes noise densities as follows:
... ... noizDens[MOS3FLNOIZ] *= model->MOS3fNcoef * exp(model->MOS3fNexp * log(MAX(FABS(inst->MOS3cd),N_MINLOG))) / (data->freq * inst->MOS3w * (inst->MOS3l - 2*model->MOS3latDiff) * model->MOS3oxideCapFactor * model->MOS3oxideCapFactor); ... ...
The new code adds width narrowing and and multiplicity:
... ... noizDens[MOS3FLNOIZ] *= model->MOS3fNcoef * exp(model->MOS3fNexp * log(MAX(FABS(inst->MOS3cd),N_MINLOG))) / (data->freq * (inst->MOS3w - 2*model->MOS3widthNarrow) * inst->MOS3m * (inst->MOS3l - 2*model->MOS3latDiff) * model->MOS3oxideCapFactor * model->MOS3oxideCapFactor); ... ...
Another place in the code that needs changes is the device setup
routine MOS3setup()
. The followig code adds support for
the new parameters:
... ... if(!model->MOS3lengthAdjustGiven) { model->MOS3lengthAdjust = 0; } if(!model->MOS3widthNarrowGiven) { model->MOS3widthNarrow = 0; } if(!model->MOS3widthAdjustGiven) { model->MOS3widthAdjust = 0; } ... ...
This code sets up the default values when the parameters are not supplied by the user (since they are optional).
Another function modified to support the new parameters is the
MOS3temp()
. The old code is:
if(here->MOS3l - 2 * model->MOS3latDiff <=0) { (*(SPfrontEnd->IFerror))(ERR_FATAL, "%s: effective channel length less than zero", &(here->MOS3name)); return(E_BADPARM); }
And the new one:
... ... if(here->MOS3l - 2 * model->MOS3latDiff + model->MOS3lengthAdjust <1e-6) { (*(SPfrontEnd->IFerror))(ERR_FATAL, "%s: effective channel length less than zero", &(here->MOS3name)); return(E_PARMVAL); } if(here->MOS3w - 2 * model->MOS3widthNarrow + model->MOS3widthAdjust <1e-6) { (*(SPfrontEnd->IFerror))(ERR_FATAL, "%s: effective channel width less than zero", &(here->MOS3name)); return(E_PARMVAL); } ... ...
The changes add a check over device width that was not present in the original code and rise an error if the result is less than one micrometer, while the old code checked against zero.
The last (but not least) function that needed some care is the
sensitivity load routine: MOS3sLoad()
. The original code was:
... ... EffectiveLength = here->MOS3l - 2*model->MOS3latDiff; if(EffectiveLength == 0) { DqgsDp = 0; DqgdDp = 0; DqgbDp = 0; } else { DqgsDp = model->MOS3type * qgs0 / EffectiveLength; DqgdDp = model->MOS3type * qgd0 / EffectiveLength; DqgbDp = model->MOS3type * qgb0 / EffectiveLength; } } else { DqgsDp = model->MOS3type * qgs0 / here->MOS3w; DqgdDp = model->MOS3type * qgd0 / here->MOS3w; DqgbDp = model->MOS3type * qgb0 / here->MOS3w; } ... ...
And the modified code is:
... ... double EffectiveWidth; ... ... EffectiveLength = here->MOS3l - 2*model->MOS3latDiff + model->MOS3lengthAdjust; if(EffectiveLength == 0) { DqgsDp = 0; DqgdDp = 0; DqgbDp = 0; } else { DqgsDp = model->MOS3type * qgs0 / EffectiveLength; DqgdDp = model->MOS3type * qgd0 / EffectiveLength; DqgbDp = model->MOS3type * qgb0 / EffectiveLength; } } else { EffectiveWidth = here->MOS3w - 2*model->MOS3widthNarrow + model->MOS3widthAdjust; DqgsDp = model->MOS3type * qgs0 / EffectiveWidth; DqgdDp = model->MOS3type * qgd0 / EffectiveWidth; DqgbDp = model->MOS3type * qgb0 / EffectiveWidth; } ... ...
There was an error in the original implementation that did not take into
account lateral diffusion MOS3LatDiff
. The other changes take
into account the effective (against drawn) device width.
That's all folks! The changes needed to support the new parameters are (shortly and badly) described. This section on MOS3 continues with other fixes.
MOS3 device reported only half of the Meyer capacitance without adding the
overlap capacitance contribution, when reporting to the .OP
printout
or in the rawfile. The routine MOS3ask()
was responsible for this:
... ... case MOS3_CGS: value->rValue = *(ckt->CKTstate0 + here->MOS3capgs); return(OK); case MOS3_CGD: value->rValue = *(ckt->CKTstate0 + here->MOS3capgd); return(OK); ... ... case MOS3_CAPGS: value->rValue = *(ckt->CKTstate0 + here->MOS3capgs); return(OK); ... ... case MOS3_CAPGD: value->rValue = *(ckt->CKTstate0 + here->MOS3capgd); return(OK); ... ... case MOS3_CAPGB: value->rValue = *(ckt->CKTstate0 + here->MOS3capgb); return(OK); ... ...
The new code is:
... ... case MOS3_CGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS3capgs); return(OK); case MOS3_CGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS3capgd); return(OK); ... ... case MOS3_CAPGS: value->rValue = 2* *(ckt->CKTstate0 + here->MOS3capgs); /* add overlap capacitance */ value->rValue += (here->MOS3modPtr->MOS3gateSourceOverlapCapFactor) * here->MOS3m * (here->MOS3w +here->MOS3modPtr->MOS3widthAdjust -2*(here->MOS3modPtr->MOS3widthNarrow)); return(OK); ... ... case MOS3_CAPGD: value->rValue = 2* *(ckt->CKTstate0 + here->MOS3capgd); /* add overlap capacitance */ value->rValue += (here->MOS3modPtr->MOS3gateDrainOverlapCapFactor) * here->MOS3m * (here->MOS3w +here->MOS3modPtr->MOS3widthAdjust -2*(here->MOS3modPtr->MOS3widthNarrow)); return(OK); ... ... case MOS3_CAPGB: value->rValue = 2* *(ckt->CKTstate0 + here->MOS3capgb); /* add overlap capacitance */ value->rValue += (here->MOS3modPtr->MOS3gateBulkOverlapCapFactor) * here->MOS3m * (here->MOS3l +here->MOS3modPtr->MOS3lengthAdjust -2*(here->MOS3modPtr->MOS3latDiff)); return(OK); ... ...
The "Gmin" implementation across the substrate diodes of MOS3 was incorrect,
correcting this dramatically improved DC convergence. The original code in
MOS3load()
was:
/* * bulk-source and bulk-drain diodes * here we just evaluate the ideal diode current and the * corresponding derivative (conductance). */ next1: if(vbs <= 0) { here->MOS3gbs = SourceSatCur/vt; here->MOS3cbs = here->MOS3gbs*vbs; here->MOS3gbs += ckt->CKTgmin; } else { evbs = exp(MIN(MAX_EXP_ARG,vbs/vt)); here->MOS3gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS3cbs = SourceSatCur * (evbs-1); } if(vbd <= 0) { here->MOS3gbd = DrainSatCur/vt; here->MOS3cbd = here->MOS3gbd *vbd; here->MOS3gbd += ckt->CKTgmin; } else { evbd = exp(MIN(MAX_EXP_ARG,vbd/vt)); here->MOS3gbd = DrainSatCur*evbd/vt +ckt->CKTgmin; here->MOS3cbd = DrainSatCur *(evbd-1); }
The new corrected code is:
/* * bulk-source and bulk-drain diodes * here we just evaluate the ideal diode current and the * corresponding derivative (conductance). */ next1: if(vbs <= -3*vt) { here->MOS3gbs = ckt->CKTgmin; here->MOS3cbs = here->MOS3gbs*vbs-SourceSatCur; } else { evbs = exp(MIN(MAX_EXP_ARG,vbs/vt)); here->MOS3gbs = SourceSatCur*evbs/vt + ckt->CKTgmin; here->MOS3cbs = SourceSatCur*(evbs-1) + ckt->CKTgmin*vbs; } if(vbd <= -3*vt) { here->MOS3gbd = ckt->CKTgmin; here->MOS3cbd = here->MOS3gbd*vbd-DrainSatCur; } else { evbd = exp(MIN(MAX_EXP_ARG,vbd/vt)); here->MOS3gbd = DrainSatCur*evbd/vt + ckt->CKTgmin; here->MOS3cbd = DrainSatCur*(evbd-1) + ckt->CKTgmin*vbd; }
In the "load current vector" section of the MOS3load()
routine,
"Gmin" appeared in the calculation of ceqbd
and ceqbs
:
/* * load current vector */ ceqbs = model->MOS3type * (here->MOS3cbs-(here->MOS3gbs-ckt->CKTgmin)*vbs); ceqbd = model->MOS3type * (here->MOS3cbd-(here->MOS3gbd-ckt->CKTgmin)*vbd);
The new code is:
/* * load current vector */ ceqbs = model->MOS3type * (here->MOS3cbs-(here->MOS3gbs)*vbs); ceqbd = model->MOS3type * (here->MOS3cbd-(here->MOS3gbd)*vbd);
The gm, gmb and gs calculations in them MOS3 model (in MOD3load()
were all wrong:
... ... /* *.....normalized drain current */ cdnorm = cdo*vdsx; here->MOS3gm = vdsx; here->MOS3gds = vgsx-vth-(1.0+fbody+dvtdvd)*vdsx; here->MOS3gmbs = dcodvb*vdsx; /* *.....drain current without velocity saturation effect */ cd1 = Beta*cdnorm; Beta = Beta*fgate; cdrain = Beta*cdnorm; here->MOS3gm = Beta*here->MOS3gm+dfgdvg*cd1; here->MOS3gds = Beta*here->MOS3gds+dfgdvd*cd1; here->MOS3gmbs = Beta*here->MOS3gmbs; ... ... cdrain = cdrain*xlfact; diddl = cdrain/(EffectiveLength-delxl); here->MOS3gm = here->MOS3gm*xlfact+diddl*ddldvg; gds0 = here->MOS3gds*xlfact+diddl*ddldvd; here->MOS3gmbs = here->MOS3gmbs*xlfact+diddl*ddldvb; here->MOS3gm = here->MOS3gm+gds0*dvsdvg; here->MOS3gmbs = here->MOS3gmbs+gds0*dvsdvb; here->MOS3gds = gds0*dvsdvd+diddl*dldvd; ... ...
The code has been corrected as follows leading to much improved convergence:
... ... /* *.....normalized drain current */ cdnorm = cdo*vdsx; here->MOS3gm = vdsx; if ((here->MOS3mode*vds) > vdsat) here->MOS3gds = -dvtdvd*vdsx; else here->MOS3gds = vgsx-vth-(1.0+fbody+dvtdvd)*vdsx; here->MOS3gmbs = dcodvb*vdsx; /* *.....drain current without velocity saturation effect */ cd1 = Beta*cdnorm; Beta = Beta*fgate; cdrain = Beta*cdnorm; here->MOS3gm = Beta*here->MOS3gm+dfgdvg*cd1; here->MOS3gds = Beta*here->MOS3gds+dfgdvd*cd1; here->MOS3gmbs = Beta*here->MOS3gmbs+dfgdvb*cd1; ... ... cd1 = cdrain; cdrain = cdrain*xlfact; diddl = cdrain/(EffectiveLength-delxl); here->MOS3gm = here->MOS3gm*xlfact+diddl*ddldvg; here->MOS3gmbs = here->MOS3gmbs*xlfact+diddl*ddldvb; gds0 = diddl*ddldvd; here->MOS3gm = here->MOS3gm+gds0*dvsdvg; here->MOS3gmbs = here->MOS3gmbs+gds0*dvsdvb; here->MOS3gds = here->MOS3gds*xlfact+diddl*dldvd+gds0*dvsdvd; /* here->MOS3gds = (here->MOS3gds*xlfact)+gds0*dvsdvd- (cd1*ddldvd/(EffectiveLength*(1-2*dlonxl+dlonxl*dlonxl))); */ ... ...
The Spice3 "fix" for the MOS3 gds discontinuity between the linear
and saturated regions works only if VMAX parameter is set to a
non-zero value. A tweak has been added for the zero case. The Spice3
code (in MOS3load()
) was:
... ... /* *.....velocity saturation factor */ if ( model->MOS3maxDriftVel != 0.0 ) { fdrain = 1.0/(1.0+vdsx*onvdsc); fd2 = fdrain*fdrain; arga = fd2*vdsx*onvdsc*onfg; dfddvg = -dfgdvg*arga; dfddvd = -dfgdvd*arga-fd2*onvdsc; dfddvb = -dfgdvb*arga; ... ...
The code in NGSPICE is:
... ... /* *.....velocity saturation factor */ if ( model->MOS3maxDriftVel > 0.0 ) { fdrain = 1.0/(1.0+vdsx*onvdsc); fd2 = fdrain*fdrain; arga = fd2*vdsx*onvdsc*onfg; dfddvg = -dfgdvg*arga; if ((here->MOS3mode*vds) > vdsat) dfddvd = -dfgdvd*arga; else dfddvd = -dfgdvd*arga-fd2*onvdsc; dfddvb = -dfgdvb*arga; ... ...
The critical voltages in MOS3Temp()
were calculated without
using temperature corrected saturation current:
... ... vt*log(vt/(CONSTroot2*model->MOS3jctSatCur)); } else { here->MOS3drainVcrit = vt * log( vt / (CONSTroot2 * model->MOS3jctSatCurDensity * here->MOS3drainArea)); here->MOS3sourceVcrit = vt * log( vt / (CONSTroot2 * model->MOS3jctSatCurDensity * here->MOS3sourceArea)); } ... ...
This have been fixed as follows:
... ... vt*log(vt/(CONSTroot2*here->MOS3m*here->MOS3tSatCur)); } else { here->MOS3drainVcrit = vt * log( vt / (CONSTroot2 * here->MOS3m * here->MOS3tSatCurDens * here->MOS3drainArea)); here->MOS3sourceVcrit = vt * log( vt / (CONSTroot2 * here->MOS3m * here->MOS3tSatCurDens * here->MOS3sourceArea)); } ... ...
In MOS3temp()
some parameters were computed without taking
into account temperature corrected parameters:
... ... here->MOS3f3d = czbd * model->MOS3bulkJctBotGradingCoeff * sarg/ arg / model->MOS3bulkJctPotential + czbdsw * model->MOS3bulkJctSideGradingCoeff * sargsw/ arg /model->MOS3bulkJctPotential; here->MOS3f4d = czbd*model->MOS3bulkJctPotential*(1-arg*sarg)/ (1-model->MOS3bulkJctBotGradingCoeff) + czbdsw*model->MOS3bulkJctPotential*(1-arg*sargsw)/ (1-model->MOS3bulkJctSideGradingCoeff) -here->MOS3f3d/2* (here->MOS3tDepCap*here->MOS3tDepCap) -here->MOS3tDepCap * here->MOS3f2d; if(model->MOS3capBSGiven) { czbs=here->MOS3tCbs; } else { if(model->MOS3bulkCapFactorGiven) { czbs=here->MOS3tCj*here->MOS3sourceArea; } else { czbs=0; } } ... ... here->MOS3f3s = czbs * model->MOS3bulkJctBotGradingCoeff * sarg/ arg / model->MOS3bulkJctPotential + czbssw * model->MOS3bulkJctSideGradingCoeff * sargsw/ arg / model->MOS3bulkJctPotential; here->MOS3f4s = czbs*model->MOS3bulkJctPotential*(1-arg*sarg)/ (1-model->MOS3bulkJctBotGradingCoeff) + czbssw*model->MOS3bulkJctPotential*(1-arg*sargsw)/ (1-model->MOS3bulkJctSideGradingCoeff) -here->MOS3f3s/2* (here->MOS3tBulkPot*here->MOS3tBulkPot) -here->MOS3tBulkPot * here->MOS3f2s; } } ... ...
The corrected code is:
... ... here->MOS3f3d = czbd * model->MOS3bulkJctBotGradingCoeff * sarg/ arg / here->MOS3tBulkPot + czbdsw * model->MOS3bulkJctSideGradingCoeff * sargsw/ arg / here->MOS3tBulkPot; here->MOS3f4d = czbd*here->MOS3tBulkPot*(1-arg*sarg)/ (1-model->MOS3bulkJctBotGradingCoeff) + czbdsw*here->MOS3tBulkPot*(1-arg*sargsw)/ (1-model->MOS3bulkJctSideGradingCoeff) -here->MOS3f3d/2* (here->MOS3tDepCap*here->MOS3tDepCap) -here->MOS3tDepCap * here->MOS3f2d; if(model->MOS3capBSGiven) { czbs = here->MOS3tCbs * here->MOS3m; } else { if(model->MOS3bulkCapFactorGiven) { czbs=here->MOS3tCj*here->MOS3sourceArea * here->MOS3m; } else { czbs=0; } } ... ... here->MOS3f3s = czbs * model->MOS3bulkJctBotGradingCoeff * sarg/ arg / here->MOS3tBulkPot + czbssw * model->MOS3bulkJctSideGradingCoeff * sargsw/ arg /here->MOS3tBulkPot; here->MOS3f4s = czbs*here->MOS3tBulkPot*(1-arg*sarg)/ (1-model->MOS3bulkJctBotGradingCoeff) + czbssw*here->MOS3tBulkPot*(1-arg*sargsw)/ (1-model->MOS3bulkJctSideGradingCoeff) -here->MOS3f3s/2* (here->MOS3tDepCap*here->MOS3tDepCap) -here->MOS3tDepCap * here->MOS3f2s; } }
Spice3f voltage limiting across PN junctions did not perform limiting on negative voltages, resulting in convergence problems. In NGSPICE voltage limiting for PN diodes have been modified to work for negative voltages too, improving convergence on calculations that rely on this code.
Enhancement Data:
DEVpnjlim()
BJT1-2
, BSIM3-4
,
DIO
, EKV
, HFET2
, JFET1-2
, MES
,
MESA
, MOS1-9
.
In NGSPICE the calculation of vtstlo is done according to the formula:
vtstlo = fabs(vold - vto) + 1;
While in spice3f the formula was:
vtstlo = vtsthi/2 + 2;
Enhancement Data:
DEVfetlim()
BSIM1-4
,HFET1-2
,
JFET1-2
, MES
, MESA
, MOS1-9
, STAG
, EKV
.
The calculation of active gate capacitance in devsup.c has been improved to achieve better convergence.
Enhacement Data:
DEVqmeyer()
MOS1-9
.
BSIM3 compact model is a de-facto standard in the circuit simulation world and is extensively supported in the NGSPICE simulator. The original model dates back to the end of 1995. After almost ten years, three major revisions have been released by the Berkeley Device Group. NGSPICE supports all BSIM3 model revisions.
We dedicated an entire chapter to the BSIM3 model since the procedure followed to integrate it into NGSPICE equally applies to other models from Berkeley's Device Group like BSIM4 and BSIMSOI. Most of the content of this chapter is devoted to the latest release of BSIM3 available at the time of writing: version 3.2 and its minor revisions 3.2.2, 3.2.3 and 3.2.4. It is trivial to apply the same concepts to the older ones (3.0 and 3.1).
The BSIM3 integration into NGSPICE is the result of the merging of three different sources: the original Berkeley's code and two enhanced version, one supplied by Alan Gillespie and the other by Serban Popescu. Both Alan and Serban enhanced the basic model adding multiplicity support, though using two different approaches. Serban added another enhancement, the "hdif" parameter.
NGSPICE provides Berkeley's, Alan's and Serban's models, using Alan's approach fot multiplicity support in Berkeley's code.
As previously said, due to the importance of BSIM3, NGSPICE includes all its revisions. We have decide to assign different levels only to major revisions and use the "version" model parameter to switch among minors. The only exceptions to this rule are Serban's and Alan's model, they are a special kind of BSIM3 version 3.1 and they are kept separate from the Berkeley's model.
NGSPICE has five different levels for BSIM3:
Major Revision | Minor Revisions | Level | Notes
|
BSIM3v3.2 | 3.2, 3.2.2, 3.2.3, 3.2.4 | 8 | The latest release
|
BSIM3v1S | 3.1 | 49 | Serban's code
|
BSIM3v1 | 3.1 | 50 | Berkeley's code
|
BSIM3v1A | 3.1 | 51 | Alan's code
|
BSIM3v0 | 3.0 | 52 | Berkeley's code
|
As you may see from the table above, level 8, the one officially assigned by Berkeley Device Group,is reserved to the most recent major revision of the code. All BSIM3 models support the "m" parameter (multiplicity) but only Serban's one has the support for "hdif".
This section briefly describes how we integrated the BSIM3 model into NGSPICE. The integration process of BSIM3 model started with the download of the original code from Berkeley web site (device group). We devoted much of the work on release 3.2 and backported changes to the older ones. Our work on the BSIM model can be summarized in the following points:
The first item is a necessary step for older models, since the device
interface changed between spice3e (the original interface for BSIM3 model)
and spice3f and NGSPICE device interface is based on spice3f. Changes consists
is a shift in the position of BSIM3states
in bsim3def.h and in
the addition of the BSIM3unsetup()
function in b3set.c.
The next step is the inclusion of runtime switches to select code for the different minor revisions that a release can have (well, this is necessary only for release 3.2).
The result of the two steps is a multirevision BSIM3 model with a spice3f4/5 interface. Now we are ready to make the necessary enhancements and the changes for integrating the model into NGSPICE.
The third and fourth items in the list are the model enhancements: the "parallel simulation support" is a feature inherited from the CIDER simulator (built on top of NGSPICE) that allow the simulator to use multiple processor elements to evaluate device code (making the simulation faster). The parallel simulation is not yet enabled in NGSPICE, but it will probably in the future. The "parallelization" code basically consists in a series of switches that skip device evaluation code if local processor (machine) has not been assigned to that particular device instance.
Device "multiplicity" is a feature often found in commercial simulators, used to simulate many identical devices connected in parallel. This feature is important because reduces the number of nodes and equations in a circuit and makes the netlist more readable.
Now we are ready to restructure the code to make it compatible with the NGSPICE device interface, which is an extended version of the spice3f4/5 one.
BSIM3 release 3.2 has three minor revisions: 3.2.2, 3.2.3 and 3.2.4. Reserving a
different level for each is not a good solution, it will be a waste of space and
memory. The four (including 3.2) revisions differs for a few lines of code only,
so the ideai is to merge the code and isolate revision dependent parts with
runtime switches (the switch
statement). The modifications needed are minimal,
but some work is needed to avoid slow comparison between strings.
As written before, the magic of revision selection is done via the "version" model
parameter, which appears on the model card. In BSIM3 version 3.2 this parameter
is a floating point value, since earlier releases were 3.0 and 3.1 and was easy to
code revision into a real number. With the release of 3.2.2 it changed to a string,
for obvious reasons (3.2.2 is not a real number). Since the "version" parameter was
used only for model checking, that was not a problem. In the multirevision model,
comparisons based on it appears in the device evaluation code, where speed is critical,
so we added a parameter BSIM3intVersion
, defined in bsim3def.h as follows:
... ... char *BSIM3version; /* The following field is an integer coding * of BSIM3version. */ int BSIM3intVersion; #define BSIM3V324 324 /* BSIM3 V3.2.4 */ #define BSIM3V323 323 /* BSIM3 V3.2.3 */ #define BSIM3V322 322 /* BSIM3 V3.2.2 */ #define BSIM3V32 32 /* BSIM3 V3.2 */ #define BSIM3V3OLD 0 /* Old model */ double BSIM3tox; ... ...
The BSIM3intVersion
will be used as argument to the switch
statement,
because integer comparison is faster than string's one. The correct value of the
parameter is assigned in BSIM3setup()
by the code below:
... ... /* If the user does not provide the model revision, * we always choose the most recent. */ if (!model->BSIM3versionGiven) model->BSIM3version = "3.2.4"; /* I have added below the code that translate model string * into an integer. This trick is meant to speed up the * revision testing instruction, since comparing integer * is faster than comparing strings. * Paolo Nenzi 2002 */ if (!strcmp (model->BSIM3version, "3.2.4")) model->BSIM3intVersion = BSIM3V324; else if (!strcmp (model->BSIM3version, "3.2.3")) model->BSIM3intVersion = BSIM3V323; else if (!strcmp (model->BSIM3version, "3.2.2")) model->BSIM3intVersion = BSIM3V322; else if (!strcmp (model->BSIM3version, "3.2")) model->BSIM3intVersion = BSIM3V32; else model->BSIM3intVersion = BSIM3V3OLD; /* BSIM3V3OLD is a placeholder for pre 3.2 revision * This model should not be used for pre 3.2 models. */ ... ...
When we need to switch the code we use a switch
statement
like the following:
/* Added revision dependent code */ switch (model->BSIM3intVersion) { case BSIM3V324: /* BSIM3v3.2.4 specific code */ break; case BSIM3V323: /* BSIM3v3.2.3 specific code */ break; case BSIM3V322: /* BSIM3v3.2.2 specific code */ break; case BSIM3V32: /* BSIM3v3.2 specific code */ break; default: /* Old code */ }
The differences between minor revision fall in two categories: modification in the evaluation code and bug fixes. The idea followed in the merging was to leave out of the revision dependent code what was a mere bug fix.
Spice3 (and thus NGSPICE) uses an approach called MNA (Modified Nodal Analysis) to solve electrical circuits. MNA represents an electrical circuits using a matrix containing devices' conductances and constraint equations (if you need to know more, get a good book on circuit theory). This matrix is built by summing the contribution of each instance (another name for "element") in the circuit. The contribution of each instance to the circuit matrix is called a "matrix stamp", which is itself a matrix containing non zero elements only at positions occupied by the device. It is very important to understand that row and column index of the stamp must be consistent with the overall circuit matrix, the elements of the stamp have the same indexes they will have in the circuit matrix. Note that all stamps are each other independent, and this property can be exploited to add parallelization to the simulator (this is not new, CIDER uses this method).
All the code in the model (every spice3 model) is needed to build the matrix stamp for that particular instance of the device and for the analysis type requested.
If we add many (let's say "m") device in parallel, "m" identical stamp are added to the circuit matrix. When we write "identical" we mean that the stamps contain identical values but at different locations. Each device has "external" and "internal" nodes, the former are connected to devices' terminals and the latter are internal to the device. It should be clear that nodes corresponding to terminals must have the same node number and the same indexes in the matrix, since devices appear in parallel, while other must be different because those nodes are private to each instance.
If we want to simulate "m" identical devices in parallel with a single instance, its stamp must have an impact to the overall circuit matrix equal to the "superposition" of the single instances' "m" stamps. The term "superposition" is used instead of "sum" since we are interested in the external behaviour of the parallel, not in its internals. The matrix stamp of the "multiple" device is a stamp of a single device whose conductances are "adjusted" to represent the parallel situation. Doing this we discard the internal complexity of the single instance while leaving intact the influence of the parallel to the circuit. Reducing the complexity affects simulation speed, which is increased because there are less equations (not useful to our purposes) to solve and imposes less stress to simulator algorithms, resulting in less convergence problems.
We may say that a "multiple" device is an external representation of many identical device connected in parallel.
There are basically two approaches to build the equivalent stamp, both based on scaling the conductances of the model by the multiplicity value (the number of device to connect in parallel). The first approach (used in NGSPICE BSIM3) scales the conductances in the matrix stamp, when it is loaded into the overall matrix, and the other one scales the conductances in the entire model code. Both approaches have advantages and drawbacks, let's examine some of them.
The first approach, i.e. scaling the conductances at loading time, is advantageous because the stamp "loading" code is easy to spot and because the modification consists simply in multiplying each line by the multiplicity factor "m". Another advantage is that this approach does not require a full analysis of the device code to isolate the variables (they may be currents, resistances, etc., not only conductances) that need to be scaled. The most important drawback is that the evaluation code does not know anything about "multiplicity" and then all internal computations are made for the "single" device. To correct this, without interfering with the evaluation code, you need to correct the routine that exports internal values (it is the so called DEVask, where DEV is a placeholder for device's name).
The second approach, i.e. scaling the variables in the evaluation code, does not have the drawback of the previous one but require a full code analysis which, in turn, means more time to complete.
The multiplicity parameter is an instance parameter. The first step is to
modify the code adding space for it in the various structures. The structure
BSIM3instance
in bsim3def.h
needs an entry for the new parameter,
its "given" flag and a numerical ID for the entry in the BSIM3pTable
:
... ... double BSIM3w; double BSIM3m; double BSIM3drainArea; ... ... unsigned BSIM3wGiven :1; unsigned BSIM3mGiven :1; unsigned BSIM3drainAreaGiven :1; ... ... #define BSIM3_L 2 #define BSIM3_M 15 #define BSIM3_AS 3 ... ...
The BSIM3pTable
structure in b3.c needs an entry too:
... ... IOP( "w", BSIM3_W, IF_REAL , "Width"), IOP( "m", BSIM3_M, IF_REAL , "Parallel multiplier"), IOP( "ad", BSIM3_AD, IF_REAL , "Drain area"), ... ...
Once the entries are created, it is necessary to set up the bureaucracy needed
to set and query the new parameter. In the setup routine BSIM3setup()
the
following code should be added:
if (!here->BSIM3mGiven) here->BSIM3m = 1;
This states that if multiplicity is not given in the netlist, it must be defaulted
to one. To set the "given" flag the following code should be added to BSIM3param()
:
case BSIM3_M: here->BSIM3m = value->rValue; here->BSIM3mGiven = TRUE; break;
The last modification nedeed by model bureaucracy must be done in BSIM3ask()
.
The following code should be added:
case BSIM3_M: value->rValue = here->BSIM3m; return(OK);
Now the model is ready to set, query, default and use the new parameter. As discussed
before, this model use the "first" approach, thus only matrix loading code is affected
by the new parameter. In the BSIM3load
routine the "Load Current Vector" and
"Load Y Matrix" section should become:
... ... (*(ckt->CKTrhs + here->BSIM3gNode) -= m*ceqqg); (*(ckt->CKTrhs + here->BSIM3bNode) -= m*(ceqbs + ceqbd + ceqqb)); (*(ckt->CKTrhs + here->BSIM3dNodePrime) +=m*(ceqbd - cdreq - ceqqd)); (*(ckt->CKTrhs + here->BSIM3sNodePrime) += m*(cdreq + ceqbs + ceqqg + ceqqb + ceqqd)); if (here->BSIM3nqsMod) *(ckt->CKTrhs + here->BSIM3qNode) += m*(cqcheq - cqdef); /* * load y matrix */ T1 = qdef * here->BSIM3gtau; (*(here->BSIM3DdPtr) += m*here->BSIM3drainConductance); (*(here->BSIM3GgPtr) += m*(gcggb - ggtg)); ... ... *(here->BSIM3QspPtr) += m*(ggts - gcqsb); *(here->BSIM3QbPtr) += m*(ggtb - gcqbb); ... ...
The "load y matrix" is not completely shown, each line is multiplied by "m". The
same method has been applied to BSIM3acLoad()
routine:
... ... m = here->BSIM3m; *(here->BSIM3GgPtr +1) += m * xcggb; *(here->BSIM3BbPtr +1) -= M * (xcbgb + xcbdb + xcbsb); ... ... *(here->BSIM3QspPtr) += m * xgts; *(here->BSIM3QbPtr) += m * xgtb; } ... ...
The pole-zero analysis uses a load routine (BSIM3pzLoad()
) very similar to the
one used by the AC analysis:
... ... m = here->BSIM3m; *(here->BSIM3GgPtr) += m * (xcggb * s->real); *(here->BSIM3GgPtr + 1) += m * (xcggb * s->imag); ... ... *(here->BSIM3QbPtr) += m * xgtb; *(here->BSIM3QspPtr) += m * xgts; } ... ...
The noise analysis needs special attention, since it does not directly uses a
matrix load routine. In this case was necessary to study the noise models
used by BSIM3 and to scale the parameters affected by multiplicity. The following
parameters were multiplied by the value of BSIM3m
: BSIM3cd
(drain
current), BSIM3weff
(effective width), BSIM3drainConductance
,
BSIM3sourceConductance
, BSIM3gm
(transconductance), BSIM3gds
(drain to source conductance), BSIM3gmbs
(body source transconductance),
BSIM3qinv
(charge in the channel).
In 3.2.4 BSIM3 revision, a bugfix introduced the BSIM3rds
(drain to source
resistance) parameter. Since this is a resistance it has been divided by the
value of BSIM3m
.
Now that we dealt with analyses code, the last piece of code to modify is
the BSIM3ask()
routine. Again we have to identify what parameters need
to be scaled and multiply them by the multiplicity. If you look at the code
you will clearly see the affected parameters.
All BSIM3 models, when implemented into Spice3f (NGSPICE), shows a bug that
affects simulations when the modelcard contains the TNOM keyword. If you
run consecutive times a netlist without reloading the deck, you will get
different answers with each run. Mike Smith discovered that the bug
hides in the functions BSIM3mparam()
and BSIM3setup()
. The
solutions (as extracted from Mike's post to comp.lsi.cad):
In b3mpar.c look for the following code:- case BSIM3_MOD_TNOM : mod->BSIM3tnom = value->rValue; mod->BSIM3tnomGiven = TRUE; break; Change the second line so the code reads:- case BSIM3_MOD_TNOM : mod->BSIM3tnom = value->rValue + 273.15; mod->BSIM3tnomGiven = TRUE; break; In b3set.c, look for the following code:- if (!model->BSIM3tnomGiven) model->BSIM3tnom = ckt->CKTnomTemp; else model->BSIM3tnom = model->BSIM3tnom + 273.15; Delete the second and third lines to read:- if (!model->BSIM3tnomGiven) model->BSIM3tnom = ckt->CKTnomTemp;
BSIM3 model is developed by the UC Berkeley Device Group, which maintains a web site at the URL: http://www-device.eecs.berkeley.edu/ for all the model they develop.
da scrivere