/* $Id: d_mos.model,v 26.19 2007/03/21 01:30:28 al Exp $ -*- C++ -*- * Copyright (C) 2001 Albert Davis * Author: Albert Davis * * This file is part of "Gnucap", the Gnu Circuit Analysis Package * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. *------------------------------------------------------------------ * data structures and defaults for mos model. * internal units all mks (meters) * but some user input parameters are in cm. * * netlist syntax: * device: mxxxx d g s b mname * model: .model mname NMOS * or .model mname PMOS */ h_headers { #include "d_diode.h" } cc_headers { #include "u_limit.h" #include "e_storag.h" #include "d_mos_base.h" } /*--------------------------------------------------------------------------*/ device MOS { parse_name mosfet; model_type MOS_BASE; id_letter M; circuit { sync; ports {drain gate source bulk}; local_nodes { idrain short_to=drain short_if="!OPT::rstray || s->rd == 0."; isource short_to=source short_if="!OPT::rstray || s->rs == 0."; } args db DIODE { m = c->m; area = s->ad; perim = c->pd; is_raw = s->idsat; cj_raw = m->cbd; cjsw_raw = NA; off = true; set_modelname(modelname()); attach(model()); } args sb DIODE { m = c->m; area = s->as; perim = c->ps; is_raw = s->issat; cj_raw = m->cbs; cjsw_raw = NA; off = true; set_modelname(modelname()); attach(model()); } resistor Rs {source isource} value="s->rs/c->m" omit="!OPT::rstray || s->rs == 0."; resistor Rd {drain idrain} value="s->rd/c->m" omit="!OPT::rstray || s->rd == 0."; diode Ddb {bulk idrain} args="db" reverse="m->polarity==pP" omit="_n[n_bulk].n_() == _n[n_drain].n_() || s->idsat == 0."; diode Dsb {bulk isource} args="sb" reverse="m->polarity==pP" omit="_n[n_bulk].n_() == _n[n_source].n_() || s->issat == 0."; capacitor Cgs {gate isource} value="s->cgso" eval=Cgs omit="!OPT::cstray || _n[n_gate].n_() == _n[n_source].n_()"; capacitor Cgd {gate idrain} value="s->cgdo" eval=Cgd omit="!OPT::cstray || _n[n_gate].n_() == _n[n_drain].n_()"; capacitor Cgb {gate bulk} value="s->cgbo" eval=Cgb omit="!OPT::cstray || _n[n_bulk].n_() == _n[n_gate].n_()"; /* fpoly_cap Cqgs {gate isource gate bulk isource bulk idrain bulk} state=qgs // qgate, cgs, cggb, cgsb, cgdb omit="m->cmodel != 0 || !OPT::cstray || _n[n_gate].n_() == _n[n_isource].n_()"; fpoly_cap Cqgd {gate idrain gate bulk idrain bulk isource bulk} state=qgd // qgate, cgs, cggb, cgsb, cgdb omit="m->cmodel != 0 || !OPT::cstray || _n[n_gate].n_() == _n[n_idrain].n_()"; fpoly_cap Cqds {idrain isource gate bulk isource bulk idrain bulk} state=qdrn // qdrn, cds, cdgb, cdsb, cddb omit="m->cmodel != 0 || !OPT::cstray || _n[n_idrain].n_() == _n[n_isource].n_()"; fpoly_cap Cqbs {bulk isource gate bulk isource bulk idrain bulk} state=qbs omit="m->cmodel != 0 || !OPT::cstray || _n[n_bulk].n_() == _n[n_isource].n_()"; fpoly_cap Cqbd {bulk idrain gate bulk idrain bulk isource bulk} state=qbd omit="m->cmodel != 0 || !OPT::cstray || _n[n_bulk].n_() == _n[n_idrain].n_()"; */ cpoly_g Ids {idrain isource gate isource idrain gate bulk isource idrain bulk} state=idsxxx; cpoly_g Idb {idrain bulk idrain isource gate isource bulk isource} state=idbxxx omit="!(m->needs_isub) || _n[n_drain].n_() == _n[n_bulk].n_()"; cpoly_g Isb {isource bulk isource idrain gate idrain bulk idrain} state=isbxxx omit="!(m->needs_isub) || _n[n_source].n_() == _n[n_bulk].n_()"; } tr_probe { VDS = "@n_drain[V] - @n_source[V]"; VGS = "@n_gate[V] - @n_source[V]"; VBS = "@n_bulk[V] - @n_source[V]"; VDSInt = "vds"; VGSInt = "vgs"; VBSInt = "vbs"; VGD = "@n_gate[V] - @n_drain[V]"; VBD = "@n_bulk[V] - @n_drain[V]"; VSD = "@n_source[V] - @n_drain[V]"; VDM = "(@n_drain[V] - @n_source[V] + @n_drain[V] - @n_drain[V]) / 2."; VGM = "(@n_gate[V] - @n_source[V] + @n_gate[V] - @n_drain[V]) / 2."; VBM = "(@n_bulk[V] - @n_source[V] + @n_bulk[V] - @n_drain[V]) / 2."; VSM = "(@n_source[V] - @n_source[V] + @n_source[V] - @n_drain[V]) / 2."; VDG = "@n_drain[V] - @n_gate[V]"; VBG = "@n_bulk[V] - @n_gate[V]"; VSG = "@n_source[V] - @n_gate[V]"; VDB = "@n_drain[V] - @n_bulk[V]"; VGB = "@n_gate[V] - @n_bulk[V]"; VSB = "@n_source[V] - @n_bulk[V]"; VD = "@n_drain[V]"; VG = "@n_gate[V]"; VB = "@n_bulk[V]"; VS = "@n_source[V]"; Id = "(_Rd) ? @Rd[I] : @Ids[I] - @Cgd[I] + @Ddb[I] * m->polarity"; IS = "(_Rs) ? @Rs[I] : -@Ids[I] - @Cgs[I] + @Dsb[I] * m->polarity"; IG = "@Cgs[I] + @Cgd[I] + @Cgb[I]"; IB = "- @Ddb[I] * m->polarity - @Dsb[I] * m->polarity - @Cgb[I]"; IBD = "@Ddb[I]"; IBS = "@Dsb[I]"; CGSOvl = "@Cgs[NV]"; CGDOvl = "@Cgd[NV]"; CGBOvl = "@Cgb[NV]"; CGST = "@Cgs[EV]"; CGDT = "@Cgd[EV]"; CGBT = "@Cgb[EV]"; CGSm = "@Cgs[EV] - @Cgs[NV]"; CGDm = "@Cgd[EV] - @Cgd[NV]"; CGBm = "@Cgb[EV] - @Cgb[NV]"; CBD = "@Ddb[Cap]"; CBS = "@Dsb[Cap]"; CGATE = "s->cgate"; GM = "(reversed) ? gmr : gmf"; GMBs = "(reversed) ? gmbr : gmbf"; GBD = "@Ddb[G]"; GBS = "@Dsb[G]"; VDSAT = "vdsat * m->polarity"; VTH = "von * m->polarity"; IDS = "m->polarity * ((reversed) ? -ids : ids)"; IDSTray = "- @Cgd[I] + @Ddb[I] * m->polarity"; P ="@Rs[P] +@Rd[P] +@Ddb[P] +@Dsb[P] +@Cgs[P] +@Cgd[P] +@Cgb[P] +@Ids[P]"; PD="@Rs[PD]+@Rd[PD]+@Ddb[PD]+@Dsb[PD]+@Cgs[PD]+@Cgd[PD]+@Cgb[PD]+@Ids[PD]"; PS="@Rs[PS]+@Rd[PS]+@Ddb[PS]+@Dsb[PS]+@Cgs[PS]+@Cgd[PS]+@Cgb[PS]+@Ids[PS]"; REgion = "static_cast((!cutoff) + (!subthreshold * 2) + (saturated * 4) + (sbfwd * 10) + ((vbs > vds) * 20) + (punchthru * 40)) * ((reversed)? -1 : 1)"; SUBthreshold = "static_cast(subthreshold)"; CUToff = "static_cast(cutoff)"; SATurated= "static_cast(saturated)"; TRIode = "static_cast(!saturated && !subthreshold)"; SBFwd = "static_cast(sbfwd)"; DBFwd = "static_cast(vbs > vds)"; REVersed = "static_cast(reversed)"; Status = "static_cast(converged() * 2)"; } device { calculated_parameters { // ordinary drain current double ids "" default=0.; double idsxxx; double gds "dids/dvds" default=0.; double gmf "dids/dvgs" default=0.; double gmr "dids/dvgd" default=0.; double gmbf "dids/dvbs" default=0.; double gmbr "dids/dvbd" default=0.; // drain-bulk double idb "" default=0.; double idbxxx "" default=0.; double gdbdb "placeholder" default=0.; double gdbds "disub/dvds" default=0.; double gdbgs "disub/dvgs" default=0.; double gdbbs "disub/dvbs" default=0.; // source-bulk double isb "" default=0.; double isbxxx "" default=0.; double gsbsb "placeholder" default=0.; double gsbsd "disub/dvds" default=0.; double gsbgd "disub/dvgs" default=0.; double gsbbd "disub/dvbs" default=0.; // charge double qgate "raw" default=0.; double cgs "dqgate_vgs placeholder" default=0.; double cggb "dqgate_vgb" default=0.; double cgsb "dqgate_vsb" default=0.; double cgdb "dqgate_vdb" default=0.; double qgs "forward mode" default=0.; double cgsgs "dqgs_vgs placeholder" default=0.; double cgsgb "dqgs_vgb" default=0.; double cgssb "dqgs_vsb" default=0.; double cgsdb "dqgs_vdb" default=0.; double qgd "reverse mode" default=0.; double cgdgd "dqgd_vgs placeholder" default=0.; double cgdgb "dqgd_vgb" default=0.; double cgdsb "dqgd_vsb" default=0.; double cgddb "dqgd_vdb" default=0.; double qdrn "Qds" default=0.; double cdsds "dqds_vds placeholder" default=0.; double cdgb "dqds_vgb" default=0.; double cdsb "dqds_vsb" default=0.; double cddb "dqds_vdb" default=0.; double qbulk "raw" default=0.; double cbs "dqbs_vbs placeholder" default=0.; double cbgb "dqbs_vbg" default=0.; double cbsb "dqbs_vsb" default=0.; double cbdb "dqbs_vdb" default=0.; double qbs "Qbs forward" default=0.; double cbsbs "dqbs_vbs placeholder" default=0.; double cbsgb "dqbs_vbg" default=0.; double cbssb "dqbs_vsb" default=0.; double cbsdb "dqbs_vdb" default=0.; double qbd "Qbd reverse" default=0.; double cbdbd "dqbd_vbd placeholder" default=0.; double cbdgb "dqbd_vbg" default=0.; double cbdsb "dqbd_vsb" default=0.; double cbddb "dqbd_vdb" default=0.; double gtau "" default=0.; double cqgb "" default=0.; double cqsb "" default=0.; double cqdb "" default=0.; double cqbb "" default=0.; /* double tconst "" default=0.; double cgb "placeholder" default=0.; // capacitors and charges double qgb "placeholder" default=0.; double qgd "" default=0.; double cgd "" default=0.; double qgs "" default=0.; //double cgs "" default=0.; */ double vgs "terminal voltages" default=0.; double vds "" default=0.; double vbs "" default=0.; double vdsat "saturation voltage" default=0.; double vgst "vgs - von." default=0.; double von "actual threshold voltage" default=0.; bool reversed "flag: Vgs < 0, reverse s & d" default=false; bool cutoff "flag: in cut off region" default=false; bool subthreshold "flag: subthreshold region" default=false; bool saturated "flag: in saturation region" default=false; bool sbfwd "flag: sb diode fwd biased" default=false; bool punchthru "flag: punch thru region" default=false; } } common { raw_parameters { double m "device multiplier" name=M default=1.0 positive print_test="m != 1."; double l_in "drawn (optical) channel length" name=L positive default="OPT::defl"; double w_in "channel width (drawn)" name=W positive default="OPT::defw"; double ad_in "drain area, drawn" name=AD positive default="OPT::defad" print_test="has_hard_value(ad_in)"; double as_in "source area, drawn" name=AS positive default="OPT::defas" print_test="has_hard_value(as_in)"; double pd "drain perimeter" name=PD positive default=0.0 print_test="has_hard_value(pd)"; double ps "source perimeter" name=PS positive default=0.0 print_test="has_hard_value(ps)"; double nrd "drain # squares" name=NRD positive default=1.0 print_test="has_hard_value(nrd)"; double nrs "source # squares" name=NRS positive default=1.0 print_test="has_hard_value(nrs)"; } } tr_eval { int foo=3; } /*--------------------------------------------------------------------*/ eval Cgb { STORAGE* brh = prechecked_cast(d); assert(brh); double cap = brh->value(); if (m->cmodel != 0) { if (p->vgst < - s->phi) { /* accumulation */ cap += s->cgate; }else if (p->vgst < 0.) { /* depletion */ cap += s->cgate * (-p->vgst) / s->phi; }else{ /* active, overlap only */ } } brh->_y0.f1 = cap; if (d->analysis_is_tran_dynamic()) { cap = (brh->_y0.f1 + brh->_q[1].f1) / 2; brh->_y0.f0 = (brh->_y0.x - brh->_q[1].x) * cap + brh->_q[1].f0; }else{ assert(d->analysis_is_static() || d->analysis_is_restore()); brh->_y0.f0 = brh->_y0.x * brh->_y0.f1; } brh->_y0 *= c->m; trace3(brh->long_label().c_str(), brh->_y0.x, brh->_y0.f0, brh->_y0.f1); } /*--------------------------------------------------------------------*/ eval Cgd { STORAGE* brh = prechecked_cast(d); assert(brh); double cap = 0; if (m->cmodel != 0) { assert(p->vdsat >= 0.); assert(p->vds >= 0.); double vbs = (m->cmodel == 3) ? 0. : p->vbs; double vdbsat = p->vdsat - vbs; double vdb = p->vds - vbs; double ddif = 2. * vdbsat - vdb; if (!p->reversed) { // treat as Cgs if (p->vgst >= 0.) { if (p->vdsat > p->vds) { /* linear */ cap = (2./3.) * s->cgate * (1. - (vdbsat*vdbsat)/(ddif*ddif)); if (p->vgst <= .1) { cap *= 10. * p->vgst; // smooth discontinuity } } } }else{ // treat as Cgs if (p->vgst >= -s->phi/2.) { /* depletion or active */ cap = (2./3.) * s->cgate; if (p->vdsat > p->vds) { /* linear */ double ndif = p->vdsat - p->vds; cap *= 1. - (ndif*ndif)/(ddif*ddif); } if (p->vgst <= 0) { cap *= 1. + p->vgst / (s->phi); cap *= 1. + p->vgst / (s->phi); } } } } cap += brh->value(); /* else overlap only */ brh->_y0.f1 = cap; if (d->analysis_is_tran_dynamic()) { cap = (brh->_y0.f1 + brh->_q[1].f1) / 2; brh->_y0.f0 = (brh->_y0.x - brh->_q[1].x) * cap + brh->_q[1].f0; }else{ assert(d->analysis_is_static() || d->analysis_is_restore()); brh->_y0.f0 = brh->_y0.x * brh->_y0.f1; } brh->_y0 *= c->m; trace3(brh->long_label().c_str(), brh->_y0.x, brh->_y0.f0, brh->_y0.f1); } /*--------------------------------------------------------------------*/ eval Cgs { STORAGE* brh = prechecked_cast(d); assert(brh); double cap = 0; if (m->cmodel != 0) { assert(p->vdsat >= 0.); assert(p->vds >= 0.); double vbs = (m->cmodel == 3) ? 0. : p->vbs; double vdbsat = p->vdsat - vbs; double vdb = p->vds - vbs; double ddif = 2. * vdbsat - vdb; if (p->reversed) { // treat as Cgd if (p->vgst >= 0.) { if (p->vdsat > p->vds) { /* linear */ cap = (2./3.) * s->cgate * (1. - (vdbsat*vdbsat)/(ddif*ddif)); if (p->vgst <= .1) { cap *= 10. * p->vgst; // smooth discontinuity } } } }else{ // treat as Cgs if (p->vgst >= -s->phi/2.) { /* depletion or active */ cap = (2./3.) * s->cgate; if (p->vdsat > p->vds) { /* linear */ double ndif = p->vdsat - p->vds; cap *= 1. - (ndif*ndif)/(ddif*ddif); } if (p->vgst <= 0) { cap *= 1. + p->vgst / (s->phi); cap *= 1. + p->vgst / (s->phi); } } } } cap += brh->value(); /* else overlap only */ brh->_y0.f1 = cap; if (d->analysis_is_tran_dynamic()) { cap = (brh->_y0.f1 + brh->_q[1].f1) / 2; brh->_y0.f0 = (brh->_y0.x - brh->_q[1].x) * cap + brh->_q[1].f0; }else{ assert(d->analysis_is_static() || d->analysis_is_restore()); brh->_y0.f0 = brh->_y0.x * brh->_y0.f1; } brh->_y0 *= c->m; trace3(brh->long_label().c_str(), brh->_y0.x, brh->_y0.f0, brh->_y0.f1); } /*--------------------------------------------------------------------*/ function reverse_if_needed() { if (vds < 0) { error(bTRACE, long_label() + ": reversing\n"); error(bTRACE, "before: vds=%g vgs=%g vbs=%g\n", vds, vgs, vbs); reversed = !reversed; vgs -= vds; vbs -= vds; vds = -vds; error(bTRACE, "after: vds=%g vgs=%g vbs=%g\n", vds, vgs, vbs); if (OPT::dampstrategy & dsREVERSE) { SIM::fulldamp = true; untested(); error(bTRACE, long_label() + ":reverse damp\n"); } if (!(OPT::mosflags & 0040)) { vbs = std::min(vbs,0.); }else{ untested(); } } } /*--------------------------------------------------------------------*/ } /*--------------------------------------------------------------------------*/ cc_direct { /*--------------------------------------------------------------------------*/ bool DEV_MOS::tr_needs_eval()const { if (is_q_for_eval()) { untested(); return false; }else if (!converged()) { return true; }else{ const COMMON_MOS* c = prechecked_cast(common()); assert(c); const MODEL_MOS_BASE* m=prechecked_cast(c->model()); assert(m); polarity_t polarity = m->polarity; node_t& eff_source((reversed) ? _n[n_idrain] : _n[n_isource]); node_t& eff_drain((reversed) ? _n[n_isource] : _n[n_idrain]); return !(conchk(vds,polarity*(eff_drain.v0()-eff_source.v0()),OPT::vntol) && conchk(vgs, polarity*(_n[n_gate].v0()-eff_source.v0()), OPT::vntol) && conchk(vbs, polarity*(_n[n_bulk].v0()-eff_source.v0()), OPT::vntol)); } } /*--------------------------------------------------------------------------*/ bool DEV_MOS::do_tr() { const COMMON_MOS* c = prechecked_cast(common()); assert(c); const MODEL_MOS_BASE* m = prechecked_cast(c->model()); assert(m); bool was_cutoff = cutoff; bool was_subthreshold = subthreshold; bool was_saturated = saturated; bool was_reversed = reversed; bool was_sbfwd = sbfwd; polarity_t polarity = m->polarity; if (initial_step()) { reversed = false; vds = vgs = vbs = 0.; }else{ double Vds, Vgs, Vbs; if (reversed) { Vds = polarity * volts_limited(_n[n_isource],_n[n_idrain]); Vgs = polarity * volts_limited(_n[n_gate],_n[n_idrain]); Vbs = polarity * volts_limited(_n[n_bulk],_n[n_idrain]); }else{ Vds = polarity * volts_limited(_n[n_idrain],_n[n_isource]); Vgs = polarity * volts_limited(_n[n_gate],_n[n_isource]); Vbs = polarity * volts_limited(_n[n_bulk],_n[n_isource]); } vgs = fet_limit_vgs(Vgs, vgs, von); if (_n[n_drain].n_() == _n[n_gate].n_()) { vds = Vds + (vgs - Vgs); }else{ // Spice hacks Vds here, but my tests show that it often makes // convergence worse, and never improves it. // I am guessing that it does help when drain and gate are connected, // and Spice does it here in case they are and cannot be determined // whether they are or not. // The hack maintains Vdg after Vgs limiting. //Vds = Vds + (vgs - Vgs); vds = fet_limit_vds(Vds, vds); } vbs = std::min(Vbs, 0.); //vbs = pnj_limit(double Vbs, double vbs, double vt, double vcrit); //vds = Vds; //vgs = Vgs; //vbs = Vbs; } assert(qgate == qgate); assert(qgs == qgs); assert(qgd == qgd); assert(qdrn == qdrn); assert(qbulk == qbulk); assert(qbs == qbs); assert(qbd == qbd); m->tr_eval(this); assert(qgate == qgate); assert(qgs == qgs); assert(qgd == qgd); assert(qdrn == qdrn); assert(qbulk == qbulk); assert(qbs == qbs); assert(qbd == qbd); if (reversed) { idsxxx = ids + vds*gds + vgs*gmr + vbs*gmbr; isbxxx = isb - vds*gsbsd - vgs*gsbgd - vbs*gsbbd; idbxxx = 0.; }else{ idsxxx = ids - vds*gds - vgs*gmf - vbs*gmbf; idbxxx = idb - vds*gdbds - vgs*gdbgs - vbs*gdbbs; isbxxx = 0.; } ids *= polarity; idsxxx *= polarity; if (c->m != 1.) { ids *= c->m; idsxxx *= c->m; gds *= c->m; gmf *= c->m; gmr *= c->m; gmbf *= c->m; gmbr *= c->m; idb *= c->m; idbxxx *= c->m; gdbds *= c->m; gdbgs *= c->m; gdbbs *= c->m; isb *= c->m; isbxxx *= c->m; gsbsd *= c->m; gsbgd *= c->m; gsbbd *= c->m; qgate *= c->m; cggb *= c->m; cgsb *= c->m; cgdb *= c->m; qdrn *= c->m; cddb *= c->m; cdgb *= c->m; cdsb *= c->m; } assert(subckt()); set_converged(subckt()->do_tr()); trace3(long_label().c_str(), vds, vgs, vbs); trace4("", ids, gmf, gds, gmbf); trace4("", ids, gmr, gds, gmbr); if (was_cutoff != cutoff || was_subthreshold != subthreshold || was_saturated != saturated || was_reversed != reversed || was_sbfwd != sbfwd) { if (OPT::dampstrategy & dsDEVREGION) { SIM::fulldamp = true; } #if defined(DO_TRACE) error(bTRACE,"%s:%d: region change\n", long_label().c_str(), evaliter()); #endif } return converged(); } } /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/