diff options
author | 2019-03-26 11:13:37 +1100 | |
---|---|---|
committer | 2019-03-26 11:13:37 +1100 | |
commit | 97b67170277437131adf6ed4d60139c172529e4f (patch) | |
tree | 7a5cbf608f191075f1612b1af15832c206a3fe2d /src/lib/netlist/analog/nld_mosfet.cpp | |
parent | b380514764cf857469bae61c11143a19f79a74c5 (diff) |
(nw) Clean up the mess on master
This effectively reverts b380514764cf857469bae61c11143a19f79a74c5 and
c24473ddff715ecec2e258a6eb38960cf8c8e98e, restoring the state at
598cd5227223c3b04ca31f0dbc1981256d9ea3ff.
Before pushing, please check that what you're about to push is sane.
Check your local commit log and ensure there isn't anything out-of-place
before pushing to mainline. When things like this happen, it wastes
everyone's time. I really don't need this in a week when real work™ is
busting my balls and I'm behind where I want to be with preparing for
MAME release.
Diffstat (limited to 'src/lib/netlist/analog/nld_mosfet.cpp')
-rw-r--r-- | src/lib/netlist/analog/nld_mosfet.cpp | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/src/lib/netlist/analog/nld_mosfet.cpp b/src/lib/netlist/analog/nld_mosfet.cpp new file mode 100644 index 00000000000..ea717fdefee --- /dev/null +++ b/src/lib/netlist/analog/nld_mosfet.cpp @@ -0,0 +1,488 @@ +// license:GPL-2.0+ +// copyright-holders:Couriersud +/* + * nld_mosfet.cpp + * + * Formulas in here based on the following Sources: + * + * https://www.imperial.ac.uk/pls/portallive/docs/1/7292573.PDF + * http://www3.imperial.ac.uk/pls/portallive/docs/1/56133736.PDF + * https://people.rit.edu/lffeee/SPICE_MOSFET_Model_Intro.pdf + * https://people.rit.edu/lffeee/SPICE.pdf + * http://web.mit.edu/course/6/6.012/SPR98/www/lectures/S98_Lecture10.pdf + * http://homepages.rpi.edu/~sawyes/Models_review.pdf + * http://jaco.ec.t.kanazawa-u.ac.jp/edu/mix/pdf/3.pdf + * + * Farid N. Naim, Circuit Simulation (Wiley-IEEE Press, 2010). + * Stefan Jahn, Michael Margraf, Vincent Habchi and Raimund Jacob, "Qucs Technical Papers" (2007) + * + */ + +#include "netlist/solver/nld_solver.h" +#include "netlist/nl_setup.h" +#include "nlid_twoterm.h" + +#include <cmath> + +#define BODY_CONNECTED_TO_SOURCE (1) + +namespace netlist +{ +namespace analog +{ + + using constants = plib::constants<nl_double>; + + // ----------------------------------------------------------------------------- + // nld_FET - Base classes + // ----------------------------------------------------------------------------- + + /*! Class representing the nmos model paramers. + * + * This is the model representation of the nmos model. Typically, SPICE uses + * the following parameters. A "Y" in the first column indicates that the + * parameter is actually used in netlist. + * + * | NL? |Name | Description|Units |Default |Example | + * |:---:|------|-----------------------------------------------------------------------|-------|---------:|----------------:| + * | Y |Vto | Zero-bias threshold voltage | V | 0 | 1 | + * | Y |Kp | Transconductance parameter | A/V² | 0.00002 | 0.00003 | + * | Y |Gamma | Bulk threshold parameter | V^½ | 0 | 0.37 | + * | Y |Phi | Surface inversion potential | V | 0.6 | 0.65 | + * | Y |Lambda| Channel-length modulation (level 1 and 2 only) | 1/V | 0 | 0.02 | + * | |Rd | Drain ohmic resistance |W|0|1| + * | |Rs | Source ohmic resistance |W|0|1| + * | |Cbd | Zero-bias B-D junction capacitance |F|0|20f| + * | |Cbs | Zero-bias B-S junction capacitance |F|0|20f| + * | Y |Is | Bulk junction saturation current |A|0.00000000000001|1E-015| + * | Y |N | Bulk diode emission coefficient |-|1|* + * | |Pb | Bulk junction potential |V|0.8|0.87|8| + * | |Cgso | Gate-source overlap capacitance per meter channel width |F/m|0|0.00000000004| + * | |Cgdo | Gate-drain overlap capacitance per meter channel width |F/m|0|0.00000000004|* + * | |Cgbo | Gate-bulk overlap capacitance per meter channel width |F/m|0|0.0000000002|* + * | |Rsh | Drain and source diffusion sheet resistance |W|0|10|* + * | |Cj | Zero-bias bulk junction bottom capacitance per square meter of junction area|F/m²|0|0.0002|* + * | |Mj | Bulk junction bottom grading coefficient |-|0.5|0.5|* + * | |Cjsw | Zero-bias bulk junction sidewall capacitance per meter of junction perimeter|F/m|0|1p|* + * | |Mjsw | Bulk junction sidewall grading coefficient |-|.50 level 1 .33 level 2,3|| + * | |Js | Bulk junction saturation current per square-meter of junction area|A/m|0|0.00000001| + * | Y |Tox | Oxide thickness |m|0.0000001|0.0000001| + * | Y |Nsub | Substrate doping |1/cm³|0|4000000000000000| + * | |Nss | Surface state density |1/cm²|0|10000000000| + * | |Nfs | Fast surface state |1/cm²|0|10000000000|* + * | |TPG | Type of gate material: +1 opp. to substrate -1 same as substrate 0 Al gate|-|1| + * | |Xj | Metallurgical junction depth |m|0|1µ|* + * | Y |Ld | Lateral diffusion |m|0|0.8µ| + * | Y |Uo | Surface mobility |cm²/V/s|600|700| + * | |Ucrit | Critical field for mobility degradation (level 2 only) |V/cm|10000|10000| + * | |Uexp | Critical field exponent in mobility degradation (level 2 only) |-|0|0.1| + * | |Utra | Transverse field coefficient (level 2 only) |-|0|0.3|* + * | |Vmax | Maximum carrier drift velocity (levels 2 & 3 only) |m/s|0|50000| + * | |Neff | Total channel-charge exponent (level 2 only) |-|1|5| + * | |Kf | Flicker noise coefficient |-|0|1E-026| + * | |Af | Flicker noise exponent |-|1|1.2| + * | |Fc | Coefficient for forward-bias depletion capacitance formula |-|0.5| + * | |Delta | Width effect on threshold voltage(levels 2 and 3) |-|0|1| + * | |Theta | Mobility modulation (level 3 only) |-|0|0.1| + * | |Eta | Static feedback (level 3 only) |-|0|1| + * | |Kappa | Saturation field (level 3 only) |0.2|0.5| + * | |Tnom | Parameter measurement temperature |ºC|27|50|| + * | Y |L | Length scaling |-|1.0|| + * | Y |W | Width scaling |-|1.0|| + * */ + + class fet_model_t : public param_model_t + { + public: + fet_model_t(device_t &device, const pstring &name, const pstring &val) + : param_model_t(device, name, val) + , m_VTO(*this, "VTO") + , m_N(*this, "N") + , m_ISS(*this, "IS") // Haven't seen a model using ISS / ISD + , m_ISD(*this, "IS") + , m_LD(*this, "LD") + , m_L(*this, "L") + , m_W(*this, "W") + , m_TOX(*this, "TOX") + , m_KP(*this, "KP") + , m_UO(*this, "UO") + , m_PHI(*this, "PHI") + , m_NSUB(*this, "NSUB") + , m_GAMMA(*this, "GAMMA") + , m_LAMBDA(*this, "LAMBDA") + , m_RD(*this, "RD") + , m_RS(*this, "RS") + {} + + value_t m_VTO; //!< Threshold voltage [V] + value_t m_N; //!< Bulk diode emission coefficient + value_t m_ISS; //!< Body diode saturation current + value_t m_ISD; //!< Body diode saturation current + value_t m_LD; //!< Lateral diffusion [m] + value_t m_L; //!< Length scaling + value_t m_W; //!< Width scaling + value_t m_TOX; //!< Oxide thickness + value_t m_KP; //!< Transconductance parameter [A/V²] + value_t m_UO; //!< Surface mobility [cm²/V/s] + value_t m_PHI; //!< Surface inversion potential [V] + value_t m_NSUB;//!< Substrate doping [1/cm³] + value_t m_GAMMA; //!< Bulk threshold parameter [V^½] + value_t m_LAMBDA; //!< Channel-length modulation [1/V] + value_t m_RD; //!< Drain ohmic resistance + value_t m_RS; //!< Source ohmic resistance + }; + + // Have a common start for mosfets + + NETLIB_OBJECT(FET) + { + public: + enum q_type { + FET_NMOS, + FET_PMOS + }; + + NETLIB_CONSTRUCTOR(FET) + , m_model(*this, "MODEL", "NMOS") + , m_qtype(FET_NMOS) + { + } + + NETLIB_IS_DYNAMIC(true) + + //NETLIB_RESETI(); + NETLIB_UPDATEI() { } + + q_type qtype() const { return m_qtype; } + bool is_qtype(q_type atype) const { return m_qtype == atype; } + void set_qtype(q_type atype) { m_qtype = atype; } + protected: + + fet_model_t m_model; + private: + q_type m_qtype; + }; + + // ----------------------------------------------------------------------------- + // nld_QBJT_EB + // ----------------------------------------------------------------------------- + + + NETLIB_OBJECT_DERIVED(MOSFET, FET) + { + public: + NETLIB_CONSTRUCTOR_DERIVED(MOSFET, FET) + , m_DG(*this, "m_DG", true) + , m_SG(*this, "m_SG", true) + , m_SD(*this, "m_SD", true) + , m_D_BD(*this, "m_D_BD") +#if (!BODY_CONNECTED_TO_SOURCE) + , m_D_BS(*this, "m_D_BS") +#endif + , m_phi(0.0) + , m_gamma(0.0) + , m_vto(0.0) + , m_beta(0.0) + , m_lambda(0.0) + , m_Leff(0.0) + , m_Cox(0.0) + { + register_subalias("S", m_SG.m_P); // Source + register_subalias("G", m_SG.m_N); // Gate + + register_subalias("D", m_DG.m_P); // Drain + + connect(m_SG.m_P, m_SD.m_P); + connect(m_SG.m_N, m_DG.m_N); + connect(m_DG.m_P, m_SD.m_N); + +#if 0 + if (m_model.m_CJE > 0.0) + { + create_and_register_subdevice("m_CJE", m_CJE); + connect("B", "m_CJE.1"); + connect("E", "m_CJE.2"); + } + if (m_model.m_CJC > 0.0) + { + create_and_register_subdevice("m_CJC", m_CJC); + connect("B", "m_CJC.1"); + connect("C", "m_CJC.2"); + } +#endif + } + + protected: + + NETLIB_RESETI(); + NETLIB_UPDATEI(); + NETLIB_UPDATE_PARAMI(); + NETLIB_UPDATE_TERMINALSI(); + + private: + + nld_twoterm m_DG; + nld_twoterm m_SG; + nld_twoterm m_SD; + + generic_diode<diode_e::MOS> m_D_BD; +#if (!BODY_CONNECTED_TO_SOURCE) + generic_diode<diode_e::MOS> m_D_BS; +#endif + + nl_double m_phi; + nl_double m_gamma; + nl_double m_vto; + nl_double m_beta; + nl_double m_lambda; + + /* used in capacitance calculation */ + nl_double m_Leff; + nl_double m_Cox; + + //NETLIB_SUBXX(analog, C) m_CJE; + //NETLIB_SUBXX(analog, C) m_CJC; + }; + + + + // ---------------------------------------------------------------------------------------- + // nld_Q - Ebers Moll + // ---------------------------------------------------------------------------------------- + + + NETLIB_UPDATE(MOSFET) + { + if (!m_SG.m_P.net().isRailNet()) + m_SG.m_P.solve_now(); // Basis + else if (!m_SG.m_N.net().isRailNet()) + m_SG.m_N.solve_now(); // Emitter + else + m_DG.m_N.solve_now(); // Collector + } + + NETLIB_RESET(MOSFET) + { + NETLIB_NAME(FET)::reset(); +#if 0 + if (m_CJE) + { + m_CJE->reset(); + m_CJE->m_C.setTo(m_model.m_CJE); + } + if (m_CJC) + { + m_CJC->reset(); + m_CJC->m_C.setTo(m_model.m_CJC); + } +#endif + } + + NETLIB_UPDATE_TERMINALS(MOSFET) + { + const nl_double polarity = (qtype() == FET_NMOS ? 1.0 : -1.0); + + const nl_double Ugd = -m_DG.deltaV() * polarity; // Gate - Drain + const nl_double Ugs = -m_SG.deltaV() * polarity; // Gate - Source + const nl_double Ubs = 0.0; // Bulk - Source == 0 if connected + const nl_double Ubd = m_SD.deltaV() * polarity; // Bulk - Drain = Source - Drain + const nl_double Uds = Ugs - Ugd; + +#if (!BODY_CONNECTED_TO_SOURCE) + m_D_BS.update_diode(Ubs); +#endif + m_D_BD.update_diode(Ubd); + + // Are we in forward mode ? + const bool is_forward = Uds >= 0; + + // calculate Vth + const nl_double Vbulk = is_forward ? Ubs : Ubd; + const nl_double phi_m_Vbulk = (m_phi > Vbulk) ? std::sqrt(m_phi - Vbulk) : 0.0; + const nl_double Vth = m_vto * polarity + m_gamma * (phi_m_Vbulk - std::sqrt(m_phi)); + + const nl_double Vctrl = (is_forward ? Ugs : Ugd) - Vth; + + nl_double Ids, gm, gds, gmb; + + if (Vctrl <= 0.0) + { + // cutoff region + Ids = 0.0; + gm = 0.0; + gds = 0.0; + gmb = 0.0; + } + else + { + const nl_double Vds = std::abs(Uds); + const nl_double b = m_beta * (1.0 + m_lambda * Vds); + if (Vctrl <= Vds) + { + // saturation region + Ids = b * Vctrl * Vctrl / 2.0; + gm = b * Vctrl; + gds = m_lambda * m_beta * Vctrl * Vctrl / 2.0; + } + else + { + // linear region + Ids = b * Vds * (Vctrl - Vds / 2); + gm = b * Vds; + gds = b * (Vctrl - Vds) + m_lambda * m_beta * Vds * (Vctrl - Vds / 2.0); + } + + // backgate transconductance + const nl_double bgtc = (phi_m_Vbulk != 0.0) ? (m_gamma / phi_m_Vbulk / 2.0) : 0.0; + gmb = gm * bgtc; + } + + // FIXME: these are needed to compute capacitance + // nl_double Udsat = pol * std::max (Utst, 0.0); + // Uon = pol * Vth; + + // compute bulk diode equivalent currents + + const nl_double IeqBD = m_D_BD.Ieq(); + const nl_double gbd = m_D_BD.G(); +#if 0 + const nl_double IeqBS = m_D_BS.Ieq(); + const nl_double gbs = m_D_BS.G(); +#else + const nl_double IeqBS = 0.0; + const nl_double gbs = 0.0; +#endif + // exchange controlling nodes if necessary + const nl_double gsource = is_forward ? (gm + gmb) : 0; + const nl_double gdrain = is_forward ? 0.0 : (gm + gmb); + + const nl_double IeqDS = (is_forward) ? + Ids - gm * Ugs - gmb * Ubs - gds * Uds + : -Ids - gm * Ugd - gmb * Ubd - gds * Uds; + + // IG = 0 + const nl_double IG = 0.0; + const nl_double ID = (+IeqBD - IeqDS) * polarity; + const nl_double IS = (+IeqBS + IeqDS) * polarity; + const nl_double IB = (-IeqBD - IeqBS) * polarity; + + const nl_double gGG = 0.0; // ok + const nl_double gGD = 0.0; // ok + const nl_double gGS = 0.0; // ok + const nl_double gGB = 0.0; // ok + + const nl_double gDG = gm; // ok + const nl_double gDD = gds + gbd - gdrain; // ok + const nl_double gDS = -gds - gsource; // ok + const nl_double gDB = gmb - gbd; // ok + + const nl_double gSG = -gm; // ok + const nl_double gSD = -gds + gdrain; // ok + const nl_double gSS = gbs + gds + gsource; // ok + const nl_double gSB = -gbs - gmb; + + const nl_double gBG = 0.0; // ok + const nl_double gBD = -gbd; // ok + const nl_double gBS = -gbs; + const nl_double gBB = gbs + gbd; // ok + + // Source connected to body, Diode S-B shorted! + const nl_double gSSBB = gSS + gBB + gBS + gSB; + + // S G + m_SG.set_mat( gSSBB, gSG + gBG, +(IS + IB), // S + gGS + gGB, gGG, IG ); // G + // D G + m_DG.set_mat( gDD, gDG, +ID, // D + gGD, 0.0, 0.0 ); // G + // S D + m_SD.set_mat( 0.0, gSD + gBD, 0.0, // S + gDS + gDB, 0.0, 0.0); // D + + } + + + NETLIB_UPDATE_PARAM(MOSFET) + { + set_qtype((m_model.model_type() == "NMOS") ? FET_NMOS : FET_PMOS); + + /* + * From http://ltwiki.org/LTspiceHelp/LTspiceHelp/M_MOSFET.htm : + * + * VTO, KP, LAMBDA, PHI and GAMMA. These parameters are computed + * if the process parameters(NSUB, TOX,...) are given, but + * user-specified values always override. + * + * But couldn't find a formula for lambda anywhere + * + */ + + m_lambda = m_model.m_LAMBDA; // FIXME: m_lambda only set once + + // calculate effective channel length + m_Leff = m_model.m_L - 2 * m_model.m_LD; + nl_assert_always(m_Leff > 0.0, "Effective Lateral diffusion would be negative for model " + m_model.name()); + if (m_model.m_TOX > 0.0) + m_Cox = (constants::eps_SiO2() * constants::eps_0() / m_model.m_TOX); + else + m_Cox = 0.0; + + // calculate DC transconductance coefficient + if (m_model.m_KP > 0) + m_beta = m_model.m_KP * m_model.m_W / m_Leff; + else if (m_Cox > 0 && m_model.m_UO > 0) + m_beta = m_model.m_UO * 1e-4 * m_Cox * m_model.m_W / m_Leff; + else + m_beta = 2e-5 * m_model.m_W / m_Leff; + + // Bulk diodes + + m_D_BD.set_param(m_model.m_ISD, m_model.m_N, exec().gmin(), constants::T0()); +#if (!BODY_CONNECTED_TO_SOURCE) + m_D_BS.set_param(m_model.m_ISS, m_model.m_N, exec().gmin(), constants::T0()); +#endif + + //FIXME::UT can disappear + const double Vt = constants::T0() * constants::k_b() / constants::Q_e(); + + // calculate surface potential if not given + + if (m_model.m_PHI > 0.0) + m_phi = m_model.m_PHI; + else if (m_model.m_NSUB > 0.0) + { + nl_assert_always(m_model.m_NSUB * 1e6 >= constants::NiSi(), "Error calculating phi for model " + m_model.name()); + m_phi = 2 * Vt * std::log (m_model.m_NSUB * 1e6 / constants::NiSi()); + } + else + m_phi = 0.6; + + // calculate bulk threshold if not given + if (m_model.m_GAMMA > 0.0) + m_gamma = m_model.m_GAMMA; + else + { + if (m_Cox > 0 && m_model.m_NSUB > 0) + m_gamma = std::sqrt (2.0 * constants::Q_e() * constants::eps_Si() * constants::eps_0() * m_model.m_NSUB * 1e6) / m_Cox; + else + m_gamma = 0.0; + } + + m_vto = m_model.m_VTO; + nl_assert_always(m_vto != 0.0, "Threshold voltage not specified for " + m_model.name()); + + /* FIXME: VTO if missing may be calculated from TPG, NSS and temperature. Usually models + * specify VTO so skip this here. + */ + + m_Cox = m_Cox * m_model.m_W * m_Leff; + + } + +} // namespace analog + +namespace devices { + NETLIB_DEVICE_IMPL_NS(analog, MOSFET, "MOSFET", "MODEL") +} // namespace devices + +} // namespace netlist |