diff options
Diffstat (limited to 'src/lib/netlist/analog')
-rw-r--r-- | src/lib/netlist/analog/nld_bjt.cpp | 708 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_bjt.h | 25 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_fourterm.h | 30 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_generic_models.h | 348 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_mosfet.cpp | 865 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_mosfet.h | 21 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_opamps.cpp | 314 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_opamps.h | 24 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_switches.cpp | 118 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_switches.h | 25 | ||||
-rw-r--r-- | src/lib/netlist/analog/nld_twoterm.h | 67 | ||||
-rw-r--r-- | src/lib/netlist/analog/nlid_fourterm.cpp | 199 | ||||
-rw-r--r-- | src/lib/netlist/analog/nlid_fourterm.h | 282 | ||||
-rw-r--r-- | src/lib/netlist/analog/nlid_twoterm.cpp | 361 | ||||
-rw-r--r-- | src/lib/netlist/analog/nlid_twoterm.h | 735 |
15 files changed, 2282 insertions, 1840 deletions
diff --git a/src/lib/netlist/analog/nld_bjt.cpp b/src/lib/netlist/analog/nld_bjt.cpp index 3d954504264..2152f8f431a 100644 --- a/src/lib/netlist/analog/nld_bjt.cpp +++ b/src/lib/netlist/analog/nld_bjt.cpp @@ -1,446 +1,574 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_bjt.c - * - */ -#include "netlist/solver/nld_solver.h" -#include "netlist/nl_setup.h" +#include "nl_base.h" #include "nlid_twoterm.h" +#include "solver/nld_solver.h" -#include <cmath> +// Names +// spell-checker: words Ebers, Moll -namespace netlist -{ -namespace analog -{ - using constants = plib::constants<nl_double>; +// FIXME: Remove QBJT_switch - no more use +namespace netlist::analog +{ class diode { public: - diode() : m_Is(1e-15), m_VT(0.0258), m_VT_inv(1.0 / m_VT) {} - diode(const nl_double Is, const nl_double n) + diode() + : m_Is(nlconst::np_Is()) + , m_VT(nlconst::np_VT()) + , m_VT_inv(plib::reciprocal(m_VT)) + { + } + + diode(nl_fptype Is, nl_fptype n) + : m_Is(Is) + , m_VT(nlconst::np_VT(n)) + , m_VT_inv(plib::reciprocal(m_VT)) { - m_Is = Is; - m_VT = 0.0258 * n; - m_VT_inv = 1.0 / m_VT; } - void set(const nl_double Is, const nl_double n) + void set(nl_fptype Is, nl_fptype n) noexcept { m_Is = Is; - m_VT = 0.0258 * n; - m_VT_inv = 1.0 / m_VT; + m_VT = nlconst::np_VT(n); + m_VT_inv = plib::reciprocal(m_VT); + } + nl_fptype I(nl_fptype V) const noexcept + { + return m_Is * plib::exp(V * m_VT_inv) - m_Is; + } + nl_fptype g(nl_fptype V) const noexcept + { + return m_Is * m_VT_inv * plib::exp(V * m_VT_inv); + } + nl_fptype V(nl_fptype I) const noexcept + { + return plib::log1p(I / m_Is) * m_VT; + } // log1p(x)=log(1.0 + x) + nl_fptype gI(nl_fptype I) const noexcept + { + return m_VT_inv * (I + m_Is); } - nl_double I(const nl_double V) const { return m_Is * std::exp(V * m_VT_inv) - m_Is; } - nl_double g(const nl_double V) const { return m_Is * m_VT_inv * std::exp(V * m_VT_inv); } - nl_double V(const nl_double I) const { return std::log1p(I / m_Is) * m_VT; } // log1p(x)=log(1.0 + x) - nl_double gI(const nl_double I) const { return m_VT_inv * (I + m_Is); } private: - nl_double m_Is; - nl_double m_VT; - nl_double m_VT_inv; + nl_fptype m_Is; + nl_fptype m_VT; + nl_fptype m_VT_inv; }; // ----------------------------------------------------------------------------- // nld_Q - Base classes // ----------------------------------------------------------------------------- - /*! Class representing the bjt model parameters. - * - * This is the model representation of the bjt model. Typically, SPICE uses - * the following parameters. A "Y" in the first column indicates that the - * parameter is actually used in netlist. - * - * | NL? | name | parameter | units | default | example | area | - * |:---:|------|-----------------------------------------------------------------------|-------|---------:|----------------:|:----:| - * | Y | IS | transport saturation current | A | 1E-016 | 1E-015 | * | - * | Y | BF | ideal maximum forward beta | - | 100 | 100 | | - * | Y | NF | forward current emission coefficient | - | 1 | 1 | | - * | | VAF | forward Early voltage | V | infinite | 200 | | - * | | IKF | corner for forward beta high current roll-off | A | infinite | 0.01 | * | - * | | ISE | B-E leakage saturation current | A | 0 | 0.0000000000001 | * | - * | | NE | B-E leakage emission coefficient | - | 1.5 | 2 | | - * | Y | BR | ideal maximum reverse beta | - | 1 | 0.1 | | - * | Y | NR | reverse current emission coefficient | - | 1 | 1 | | - * | | VAR | reverse Early voltage | V | infinite | 200 | | - * | | IKR | corner for reverse beta high current roll-off | A | infinite | 0.01 | * | - * | | ISC | leakage saturation current | A | 0 | 8 | | - * | | NC | leakage emission coefficient | - | 2 | 1.5 | | - * | | RB | zero bias base resistance | | 0 | 100 | * | - * | | IRB | current where base resistance falls halfway to its min value | A | infinite | 0.1 | * | - * | | RBM | minimum base resistance at high currents | | RB | 10 | * | - * | | RE | emitter resistance | | 0 | 1 | * | - * | | RC | collector resistance | | 0 | 10 | * | - * | Y | CJE | B-E zero-bias depletion capacitance | F | 0 | 2pF | * | - * | | VJE | B-E built-in potential | V | 0.75 | 0.6 | | - * | | MJE | B-E junction exponential factor | - | 0.33 | 0.33 | | - * | | TF | ideal forward transit time | sec | 0 | 0.1ns | | - * | | XTF | coefficient for bias dependence of TF | - | 0 | | | - * | | VTF | voltage describing VBC dependence of TF | V | infinite | | | - * | | ITF | high-current parameter for effect on TF | A | 0 | | * | - * | | PTF | excess phase at freq=1.0/(TF*2PI) Hz | deg | 0 | | | - * | Y | CJC | B-C zero-bias depletion capacitance | F | 0 | 2pF | * | - * | | VJC | B-C built-in potential | V | 0.75 | 0.5 | | - * | | MJC | B-C junction exponential factor | - | 0.33 | 0.5 | | - * | | XCJC | fraction of B-C depletion capacitance connected to internal base node | - | 1 | | | - * | | TR | ideal reverse transit time | sec | 0 | 10ns | | - * | | CJS | zero-bias collector-substrate capacitance | F | 0 | 2pF | * | - * | | VJS | substrate junction built-in potential | V | 0.75 | | | - * | | MJS | substrate junction exponential factor | - | 0 | 0.5 | | - * | | XTB | forward and reverse beta temperature exponent | - | 0 | | | - * | | EG | energy gap for temperature effect on IS | eV | 1.11 | | | - * | | XTI | temperature exponent for effect on IS | - | 3 | | | - * | | KF | flicker-noise coefficient | - | 0 | | | - * | | AF | flicker-noise exponent | - | 1 | | | - * | | FC | coefficient for forward-bias depletion capacitance formula | - | 0.5 | | | - * | | TNOM | Parameter measurement temperature | C | 27 | 50 | | - * */ - - class bjt_model_t : public param_model_t + enum class bjt_type + { + BJT_NPN, + BJT_PNP + }; + + /// \brief Class representing the bjt model parameters + /// + /// This is the model representation of the bjt model. Typically, SPICE + /// uses the following parameters. A "Y" in the first column indicates that + /// the parameter is actually used in netlist. + /// + /// | NL? | name | parameter | units | default | example | area | xxx + /// |:---:|------|-----------------------------------------------------------------------|-------|---------:|----------------:|:----:| xxx + /// | Y | IS | transport saturation current | A | 1E-016 | 1E-015 | * | xxx + /// | Y | BF | ideal maximum forward beta | - | 100 | 100 | | + /// | Y | NF | forward current emission coefficient | - | 1 | 1 | | + /// | | VAF | forward Early voltage | V | infinite | 200 | | + /// | | IKF | corner for forward beta high current roll-off | A | infinite | 0.01 | * | + /// | | ISE | B-E leakage saturation current | A | 0 | 0.0000000000001 | * | + /// | | NE | B-E leakage emission coefficient | - | 1.5 | 2 | | + /// | Y | BR | ideal maximum reverse beta | - | 1 | 0.1 | | + /// | Y | NR | reverse current emission coefficient | - | 1 | 1 | | + /// | | VAR | reverse Early voltage | V | infinite | 200 | | + /// | | IKR | corner for reverse beta high current roll-off | A | infinite | 0.01 | * | + /// | | ISC | leakage saturation current | A | 0 | 8 | | + /// | | NC | leakage emission coefficient | - | 2 | 1.5 | | + /// | | RB | zero bias base resistance | | 0 | 100 | * | + /// | | IRB | current where base resistance falls halfway to its min value | A | infinite | 0.1 | * | + /// | | RBM | minimum base resistance at high currents | | RB | 10 | * | + /// | | RE | emitter resistance | | 0 | 1 | * | + /// | | RC | collector resistance | | 0 | 10 | * | + /// | Y | CJE | B-E zero-bias depletion capacitance | F | 0 | 2pF | * | + /// | | VJE | B-E built-in potential | V | 0.75 | 0.6 | | + /// | | MJE | B-E junction exponential factor | - | 0.33 | 0.33 | | + /// | | TF | ideal forward transit time | sec | 0 | 0.1ns | | + /// | | XTF | coefficient for bias dependence of TF | - | 0 | | | + /// | | VTF | voltage describing VBC dependence of TF | V | infinite | | | + /// | | ITF | high-current parameter for effect on TF | A | 0 | | * | + /// | | PTF | excess phase at freq=1.0/(TF*2PI) Hz | deg | 0 | | | + /// | Y | CJC | B-C zero-bias depletion capacitance | F | 0 | 2pF | * | + /// | | VJC | B-C built-in potential | V | 0.75 | 0.5 | | + /// | | MJC | B-C junction exponential factor | - | 0.33 | 0.5 | | + /// | | XCJC | fraction of B-C depletion capacitance connected to internal base node | - | 1 | | | + /// | | TR | ideal reverse transit time | sec | 0 | 10ns | | + /// | | CJS | zero-bias collector-substrate capacitance | F | 0 | 2pF | * | + /// | | VJS | substrate junction built-in potential | V | 0.75 | | | + /// | | MJS | substrate junction exponential factor | - | 0 | 0.5 | | + /// | | XTB | forward and reverse beta temperature exponent | - | 0 | | | + /// | | EG | energy gap for temperature effect on IS | eV | 1.11 | | | + /// | | XTI | temperature exponent for effect on IS | - | 3 | | | + /// | | KF | flicker-noise coefficient | - | 0 | | | + /// | | AF | flicker-noise exponent | - | 1 | | | + /// | | FC | coefficient for forward-bias depletion capacitance formula | - | 0.5 | | | + /// | | TNOM | Parameter measurement temperature | C | 27 | 50 | | + /// + + class bjt_model_t { public: - bjt_model_t(device_t &device, const pstring &name, const pstring &val) - : param_model_t(device, name, val) - , m_IS (*this, "IS") - , m_BF (*this, "BF") - , m_NF (*this, "NF") - , m_BR (*this, "BR") - , m_NR (*this, "NR") - , m_CJE(*this, "CJE") - , m_CJC(*this, "CJC") - {} - - value_t m_IS; //!< transport saturation current - value_t m_BF; //!< ideal maximum forward beta - value_t m_NF; //!< forward current emission coefficient - value_t m_BR; //!< ideal maximum reverse beta - value_t m_NR; //!< reverse current emission coefficient - value_t m_CJE; //!< B-E zero-bias depletion capacitance - value_t m_CJC; //!< B-C zero-bias depletion capacitance + bjt_model_t(param_model_t &model) + : m_type((model.type() == "NPN") ? bjt_type::BJT_NPN + : bjt_type::BJT_PNP) + , m_IS(model, "IS") + , m_BF(model, "BF") + , m_NF(model, "NF") + , m_BR(model, "BR") + , m_NR(model, "NR") + , m_CJE(model, "CJE") + , m_CJC(model, "CJC") + { + } + bjt_type m_type; + param_model_t::value_t m_IS; //!< transport saturation current + param_model_t::value_t m_BF; //!< ideal maximum forward beta + param_model_t::value_t m_NF; //!< forward current emission coefficient + param_model_t::value_t m_BR; //!< ideal maximum reverse beta + param_model_t::value_t m_NR; //!< reverse current emission coefficient + param_model_t::value_t m_CJE; //!< B-E zero-bias depletion capacitance + param_model_t::value_t m_CJC; //!< B-C zero-bias depletion capacitance }; - // Have a common start for transistors + // ----------------------------------------------------------------------------- + // nld_QBJT_switch + // ----------------------------------------------------------------------------- - NETLIB_OBJECT(QBJT) + // + // + - C + // B ----VVV----+ | + // | | + // Rb Rc + // Rb Rc + // Rb Rc + // | | + // +----+----+ + // | + // E + // + + class nld_QBJT_switch : public base_device_t { public: - enum q_type { - BJT_NPN, - BJT_PNP - }; + nld_QBJT_switch(constructor_param_t data) + : base_device_t(data) + , m_model(*this, "MODEL", "NPN") + , m_bjt_model(m_model) + , m_RB(*this, "m_RB", NETLIB_DELEGATE(terminal_handler)) + , m_RC(*this, "m_RC", NETLIB_DELEGATE(terminal_handler)) + , m_BC(*this, "m_BC", NETLIB_DELEGATE(terminal_handler)) + , m_gB(nlconst::cgmin()) + , m_gC(nlconst::cgmin()) + , m_V(nlconst::zero()) + , m_state_on(*this, "m_state_on", 0U) + { + register_sub_alias("B", m_RB.P()); + register_sub_alias("E", m_RB.N()); + register_sub_alias("C", m_RC.P()); - NETLIB_CONSTRUCTOR_EX(QBJT, pstring model = "NPN") - , m_model(*this, "MODEL", model) - , m_qtype(BJT_NPN) + connect(m_RB.N(), m_RC.N()); + connect(m_RB.P(), m_BC.P()); + connect(m_RC.P(), m_BC.N()); + } + + NETLIB_RESETI(); + NETLIB_HANDLERI(terminal_handler) { + auto *solver(m_RB.solver()); + if (solver != nullptr) + solver->solve_now(); + else + m_RC.solver()->solve_now(); } 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: + NETLIB_UPDATE_PARAMI(); + NETLIB_UPDATE_TERMINALSI(); - bjt_model_t m_model; private: - q_type m_qtype; + param_model_t m_model; + bjt_model_t m_bjt_model; + NETLIB_NAME(two_terminal) m_RB; + NETLIB_NAME(two_terminal) m_RC; + NETLIB_NAME(two_terminal) m_BC; + + nl_fptype m_gB; // base conductance / switch on + nl_fptype m_gC; // collector conductance / switch on + nl_fptype m_V; // internal voltage source + state_var<unsigned> m_state_on; }; // ----------------------------------------------------------------------------- - // nld_QBJT_switch + // nld_three_terminal // ----------------------------------------------------------------------------- + // + // PIN1 C + // P1_P2 | + // +----N 3T P----+ + // | | + // | N + // Pin2 --+ 3T P0_P1 + // B | P + // | | + // +----N 3T P----+ + // P0_P2 | + // Pin0 E + // + + struct mna2 + { + using row = std::array<nl_fptype, 3>; + std::array<row, 2> arr; + }; + + struct mna3 + { + using row = std::array<nl_fptype, 4>; + std::array<row, 3> arr; + const row &operator[](std::size_t i) const { return arr[i]; } + }; - /* - * + - C - * B ----VVV----+ | - * | | - * Rb Rc - * Rb Rc - * Rb Rc - * | | - * +----+----+ - * | - * E - */ - - NETLIB_OBJECT_DERIVED(QBJT_switch, QBJT) + class nld_three_terminal : public base_device_t { - NETLIB_CONSTRUCTOR_DERIVED(QBJT_switch, QBJT) - , m_RB(*this, "m_RB", true) - , m_RC(*this, "m_RC", true) - , m_BC(*this, "m_BC", true) - , m_gB(1e-9) - , m_gC(1e-9) - , m_V(0.0) - , m_state_on(*this, "m_state_on", 0) + public: + nld_three_terminal(constructor_param_t data, + std::array<pstring, 3> pins) + : base_device_t(data) + , m_P0_P2(*this, "m_P1_P3", NETLIB_DELEGATE(terminal_handler)) + , m_P1_P2(*this, "m_P2_P3", NETLIB_DELEGATE(terminal_handler)) + , m_P0_P1(*this, "m_P1_P2", NETLIB_DELEGATE(terminal_handler)) { - register_subalias("B", m_RB.m_P); - register_subalias("E", m_RB.m_N); - register_subalias("C", m_RC.m_P); + register_sub_alias(pins[0], m_P0_P2.P()); // Emitter - row 1 + register_sub_alias(pins[1], m_P1_P2.P()); // Collector- row 2 + register_sub_alias(pins[2], m_P0_P2.N()); // Base -row 3 - connect(m_RB.m_N, m_RC.m_N); - connect(m_RB.m_P, m_BC.m_P); - connect(m_RC.m_P, m_BC.m_N); + connect(m_P0_P2.P(), m_P0_P1.P()); + connect(m_P0_P2.N(), m_P1_P2.N()); + connect(m_P1_P2.P(), m_P0_P1.N()); } - NETLIB_RESETI(); - NETLIB_UPDATEI(); - NETLIB_UPDATE_PARAMI(); - NETLIB_UPDATE_TERMINALSI(); + NETLIB_RESETI() + { + if (m_P0_P2.solver() == nullptr && m_P1_P2.solver() == nullptr) + throw nl_exception(MF_DEVICE_FRY_1(this->name())); + } - private: - nld_twoterm m_RB; - nld_twoterm m_RC; - nld_twoterm m_BC; + NETLIB_HANDLERI(terminal_handler) + { + auto *solver(m_P0_P2.solver()); + if (solver != nullptr) + solver->solve_now(); + else + m_P1_P2.solver()->solve_now(); + } - nl_double m_gB; // base conductance / switch on - nl_double m_gC; // collector conductance / switch on - nl_double m_V; // internal voltage source - state_var<unsigned> m_state_on; + template <int PIN1, int PIN2> + nl_fptype delta_V() const noexcept + { + static_assert(PIN1 >= 0 && PIN2 >= 0 && PIN1 <= 2 && PIN2 <= 2, + "out of bounds pin number"); + static constexpr const int sel = PIN1 * 10 + PIN2; + if constexpr (sel == 0) + return 0.0; + else if constexpr (sel == 1) // P0 P1 + return m_P0_P1.deltaV(); + else if constexpr (sel == 2) // P0 P2 + return m_P0_P2.deltaV(); + else if constexpr (sel == 10) // P1 P0 + return -m_P0_P1.deltaV(); + else if constexpr (sel == 11) // P1 P1 + return 0.0; + else if constexpr (sel == 12) // P1 P2 + return m_P1_P2.deltaV(); + else if constexpr (sel == 20) // P2 P0 + return -m_P0_P2.deltaV(); + else if constexpr (sel == 21) // P2 P1 + return -m_P1_P2.deltaV(); + else if constexpr (sel == 22) // P2 P2 + return 0.0; + } + + void set_mat_ex(double xee, double xec, double xeb, double xIe, + double xce, double xcc, double xcb, double xIc, + double xbe, double xbc, double xbb, double xIb) + { + using row2 = std::array<nl_fptype, 3>; + // rows 0 and 2 + m_P0_P2.set_mat({ + row2{xee, xeb, xIe}, + row2{xbe, xbb, xIb} + }); + // rows 1 and 2 + m_P1_P2.set_mat({ + row2{xcc, xcb, xIc}, + row2{xbc, 0, 0 } + }); + // rows 0 and 1 + m_P0_P1.set_mat({ + row2{0, xec, 0}, + row2{xce, 0, 0} + }); + } + + void set_mat_ex(const mna3 &m) + { + using row2 = std::array<nl_fptype, 3>; + // rows 0 and 2 + m_P0_P2.set_mat({ + row2{m[0][0], m[0][2], m[0][3]}, + row2{m[2][0], m[2][2], m[2][3]} + }); + // rows 1 and 2 + m_P1_P2.set_mat({ + row2{m[1][1], m[1][2], m[1][3]}, + row2{m[2][1], 0, 0 } + }); + // rows 0 and 1 + m_P0_P1.set_mat({ + row2{0, m[0][1], 0}, + row2{m[1][0], 0, 0} + }); + } private: + nld_two_terminal m_P0_P2; // gee, gec - gee, gce - gee, gee - gec | Ie + nld_two_terminal m_P1_P2; // gcc, gce - gcc, gec - gcc, gcc - gce | Ic + nld_two_terminal m_P0_P1; // 0, -gec, -gcc, 0 | 0 }; // ----------------------------------------------------------------------------- // nld_QBJT_EB // ----------------------------------------------------------------------------- - - NETLIB_OBJECT_DERIVED(QBJT_EB, QBJT) + class nld_QBJT_EB : public nld_three_terminal { + enum pins + { + E = 0, + C = 1, + B = 2 + }; + public: - NETLIB_CONSTRUCTOR_DERIVED(QBJT_EB, QBJT) + nld_QBJT_EB(constructor_param_t data) + : nld_three_terminal(data, {"E", "C", "B"}) + , m_model(*this, "MODEL", "NPN") + , m_bjt_model(m_model) , m_gD_BC(*this, "m_D_BC") , m_gD_BE(*this, "m_D_BE") - , m_D_CB(*this, "m_D_CB", true) - , m_D_EB(*this, "m_D_EB", true) - , m_D_EC(*this, "m_D_EC", true) , m_alpha_f(0) , m_alpha_r(0) { - register_subalias("E", m_D_EB.m_P); // Cathode - register_subalias("B", m_D_EB.m_N); // Anode - - register_subalias("C", m_D_CB.m_P); // Cathode - - connect(m_D_EB.m_P, m_D_EC.m_P); - connect(m_D_EB.m_N, m_D_CB.m_N); - connect(m_D_CB.m_P, m_D_EC.m_N); - - if (m_model.m_CJE > 0.0) + if (m_bjt_model.m_CJE > nlconst::zero()) { - create_and_register_subdevice("m_CJE", m_CJE); + create_and_register_sub_device(*this, "m_CJE", m_CJE); connect("B", "m_CJE.1"); connect("E", "m_CJE.2"); } - if (m_model.m_CJC > 0.0) + if (m_bjt_model.m_CJC > nlconst::zero()) { - create_and_register_subdevice("m_CJC", m_CJC); + create_and_register_sub_device(*this, "m_CJC", m_CJC); connect("B", "m_CJC.1"); connect("C", "m_CJC.2"); } - } protected: - NETLIB_RESETI(); - NETLIB_UPDATEI(); + + NETLIB_IS_DYNAMIC(true) + NETLIB_UPDATE_PARAMI(); NETLIB_UPDATE_TERMINALSI(); private: + param_model_t m_model; + bjt_model_t m_bjt_model; generic_diode<diode_e::BIPOLAR> m_gD_BC; generic_diode<diode_e::BIPOLAR> m_gD_BE; + nl_fptype m_alpha_f; + nl_fptype m_alpha_r; - nld_twoterm m_D_CB; // gcc, gce - gcc, gec - gcc, gcc - gce | Ic - nld_twoterm m_D_EB; // gee, gec - gee, gce - gee, gee - gec | Ie - nld_twoterm m_D_EC; // 0, -gec, -gcc, 0 | 0 - - nl_double m_alpha_f; - nl_double m_alpha_r; - - NETLIB_SUBXX(analog, C) m_CJE; - NETLIB_SUBXX(analog, C) m_CJC; + NETLIB_SUB_UPTR(analog, C) m_CJE; + NETLIB_SUB_UPTR(analog, C) m_CJC; }; - - // ---------------------------------------------------------------------------------------- - // nld_Q - // ---------------------------------------------------------------------------------------- - - NETLIB_UPDATE(QBJT) - { - // netlist().solver()->schedule1(); - } - // ---------------------------------------------------------------------------------------- // nld_QBJT_switch // ---------------------------------------------------------------------------------------- - NETLIB_RESET(QBJT_switch) { - NETLIB_NAME(QBJT)::reset(); + if (m_RB.solver() == nullptr && m_RC.solver() == nullptr) + throw nl_exception(MF_DEVICE_FRY_1(this->name())); - m_state_on = 0; + static constexpr const auto zero(nlconst::zero()); - m_RB.set_G_V_I(exec().gmin(), 0.0, 0.0); - m_RC.set_G_V_I(exec().gmin(), 0.0, 0.0); + m_state_on = 0; - m_BC.set_G_V_I(exec().gmin() / 10.0, 0.0, 0.0); + m_RB.set_G_V_I(exec().gmin(), zero, zero); + m_RC.set_G_V_I(exec().gmin(), zero, zero); + m_BC.set_G_V_I(exec().gmin() / nlconst::magic(10.0), zero, zero); } - NETLIB_UPDATE(QBJT_switch) + NETLIB_UPDATE_PARAM(QBJT_switch) { - if (!m_RB.m_P.net().isRailNet()) - m_RB.m_P.solve_now(); // Basis - else if (!m_RB.m_N.net().isRailNet()) - m_RB.m_N.solve_now(); // Emitter - else if (!m_RC.m_P.net().isRailNet()) - m_RC.m_P.solve_now(); // Collector - } + nl_fptype IS = m_bjt_model.m_IS; + nl_fptype BF = m_bjt_model.m_BF; + nl_fptype NF = m_bjt_model.m_NF; + // nl_fptype VJE = m_bjt_model.dValue("VJE", 0.75); + nl_fptype alpha = BF / (nlconst::one() + BF); +#if 0 + diode d(IS, NF); - NETLIB_UPDATE_PARAM(QBJT_switch) - { - nl_double IS = m_model.m_IS; - nl_double BF = m_model.m_BF; - nl_double NF = m_model.m_NF; - //nl_double VJE = m_model.dValue("VJE", 0.75); + // Assume 5mA Collector current for switch operation - set_qtype((m_model.type() == "NPN") ? BJT_NPN : BJT_PNP); + const auto cc(nlconst::magic(0.005)); + m_V = d.V(cc / alpha); - nl_double alpha = BF / (1.0 + BF); + // Base current is 0.005 / beta + // as a rough estimate, we just scale the conductance down - diode d(IS, NF); + m_gB = plib::reciprocal((m_V / (cc / BF))); + + // m_gB = d.gI(0.005 / alpha); + + if (m_gB < exec().gmin()) + m_gB = exec().gmin(); + m_gC = d.gI(cc); // very rough estimate +#else + // diode d(IS, NF); // Assume 5mA Collector current for switch operation - m_V = d.V(0.005 / alpha); + const auto cc(nlconst::magic(0.005)); + // Get voltage across diode + // m_V = d.V(cc / alpha); + m_V = plib::log1p((cc / alpha) / IS) * nlconst::np_VT(NF); - /* Base current is 0.005 / beta - * as a rough estimate, we just scale the conductance down */ + // Base current is 0.005 / beta + // as a rough estimate, we just scale the conductance down - m_gB = 1.0 / (m_V/(0.005 / BF)); + m_gB = plib::reciprocal((m_V / (cc / BF))); - //m_gB = d.gI(0.005 / alpha); + // m_gB = d.gI(0.005 / alpha); if (m_gB < exec().gmin()) m_gB = exec().gmin(); - m_gC = d.gI(0.005); // very rough estimate + + // m_gC = d.gI(cc); // very rough estimate + m_gC = plib::reciprocal(nlconst::np_VT(NF)) * (cc + IS); +#endif } NETLIB_UPDATE_TERMINALS(QBJT_switch) { - const nl_double m = (is_qtype( BJT_NPN) ? 1 : -1); + const nl_fptype m = (m_bjt_model.m_type == bjt_type::BJT_NPN) + ? nlconst::one() + : -nlconst::one(); - const unsigned new_state = (m_RB.deltaV() * m > m_V ) ? 1 : 0; + const unsigned new_state = (m_RB.deltaV() * m > m_V) ? 1 : 0; if (m_state_on ^ new_state) { - const nl_double gb = new_state ? m_gB : exec().gmin(); - const nl_double gc = new_state ? m_gC : exec().gmin(); - const nl_double v = new_state ? m_V * m : 0; + const auto zero(nlconst::zero()); + const nl_fptype gb = new_state ? m_gB : exec().gmin(); + const nl_fptype gc = new_state ? m_gC : exec().gmin(); + const nl_fptype v = new_state ? m_V * m : zero; - m_RB.set_G_V_I(gb, v, 0.0); - m_RC.set_G_V_I(gc, 0.0, 0.0); + m_RB.set_G_V_I(gb, v, zero); + m_RC.set_G_V_I(gc, zero, zero); m_state_on = new_state; } } - // ---------------------------------------------------------------------------------------- // nld_Q - Ebers Moll // ---------------------------------------------------------------------------------------- - - NETLIB_UPDATE(QBJT_EB) - { - if (!m_D_EB.m_P.net().isRailNet()) - m_D_EB.m_P.solve_now(); // Basis - else if (!m_D_EB.m_N.net().isRailNet()) - m_D_EB.m_N.solve_now(); // Emitter - else - m_D_CB.m_N.solve_now(); // Collector - } - NETLIB_RESET(QBJT_EB) { - NETLIB_NAME(QBJT)::reset(); + nld_three_terminal::reset(); + if (m_CJE) { m_CJE->reset(); - m_CJE->m_C.setTo(m_model.m_CJE); + m_CJE->set_cap_embedded(m_bjt_model.m_CJE); } if (m_CJC) { m_CJC->reset(); - m_CJC->m_C.setTo(m_model.m_CJC); + m_CJC->set_cap_embedded(m_bjt_model.m_CJC); } - } NETLIB_UPDATE_TERMINALS(QBJT_EB) { - const nl_double polarity = (qtype() == BJT_NPN ? 1.0 : -1.0); - - m_gD_BE.update_diode(-m_D_EB.deltaV() * polarity); - m_gD_BC.update_diode(-m_D_CB.deltaV() * polarity); - - const nl_double gee = m_gD_BE.G(); - const nl_double gcc = m_gD_BC.G(); - const nl_double gec = m_alpha_r * gcc; - const nl_double gce = m_alpha_f * gee; - const nl_double sIe = -m_gD_BE.I() + m_alpha_r * m_gD_BC.I(); - const nl_double sIc = m_alpha_f * m_gD_BE.I() - m_gD_BC.I(); - const nl_double Ie = (sIe + gee * m_gD_BE.Vd() - gec * m_gD_BC.Vd()) * polarity; - const nl_double Ic = (sIc - gce * m_gD_BE.Vd() + gcc * m_gD_BC.Vd()) * polarity; + const nl_fptype polarity(m_bjt_model.m_type == bjt_type::BJT_NPN + ? nlconst::one() + : -nlconst::one()); + + m_gD_BE.update_diode(delta_V<pins::B, pins::E>() * polarity); + m_gD_BC.update_diode(delta_V<pins::B, pins::C>() * polarity); + + const nl_fptype gee = m_gD_BE.G(); + const nl_fptype gcc = m_gD_BC.G(); + const nl_fptype gec = m_alpha_r * gcc; + const nl_fptype gce = m_alpha_f * gee; + const nl_fptype sIe = -m_gD_BE.I() + m_alpha_r * m_gD_BC.I(); + const nl_fptype sIc = m_alpha_f * m_gD_BE.I() - m_gD_BC.I(); + const nl_fptype Ie = (sIe + gee * m_gD_BE.Vd() - gec * m_gD_BC.Vd()) + * polarity; + const nl_fptype Ic = (sIc - gce * m_gD_BE.Vd() + gcc * m_gD_BC.Vd()) + * polarity; // "Circuit Design", page 174 - - m_D_EB.set_mat( gee, gec - gee, -Ie, - gce - gee, gee - gec, Ie); - m_D_CB.set_mat( gcc, gce - gcc, -Ic, - gec - gcc, gcc - gce, Ic); - m_D_EC.set_mat( 0, -gec, 0, - -gce, 0, 0); + using r = mna3::row; + set_mat_ex(mna3{ + r{gee, -gec, gec - gee, -Ie }, + r{-gce, gcc, gce - gcc, -Ic }, + r{gce - gee, gec - gcc, gcc + gee - gce - gec, Ie + Ic} + }); } - NETLIB_UPDATE_PARAM(QBJT_EB) { - nl_double IS = m_model.m_IS; - nl_double BF = m_model.m_BF; - nl_double NF = m_model.m_NF; - nl_double BR = m_model.m_BR; - nl_double NR = m_model.m_NR; - //nl_double VJE = m_model.dValue("VJE", 0.75); - - set_qtype((m_model.type() == "NPN") ? BJT_NPN : BJT_PNP); - - m_alpha_f = BF / (1.0 + BF); - m_alpha_r = BR / (1.0 + BR); - - m_gD_BE.set_param(IS / m_alpha_f, NF, exec().gmin(), constants::T0()); - m_gD_BC.set_param(IS / m_alpha_r, NR, exec().gmin(), constants::T0()); + nl_fptype IS = m_bjt_model.m_IS; + nl_fptype BF = m_bjt_model.m_BF; + nl_fptype NF = m_bjt_model.m_NF; + nl_fptype BR = m_bjt_model.m_BR; + nl_fptype NR = m_bjt_model.m_NR; + // nl_fptype VJE = m_m_bjt_model.dValue("VJE", 0.75); + + m_alpha_f = BF / (nlconst::one() + BF); + m_alpha_r = BR / (nlconst::one() + BR); + + m_gD_BE.set_param(IS / m_alpha_f, NF, exec().gmin(), nlconst::T0()); + m_gD_BC.set_param(IS / m_alpha_r, NR, exec().gmin(), nlconst::T0()); } -} // namespace analog +} // namespace netlist::analog -namespace devices { +namespace netlist::devices +{ NETLIB_DEVICE_IMPL_NS(analog, QBJT_EB, "QBJT_EB", "MODEL") NETLIB_DEVICE_IMPL_NS(analog, QBJT_switch, "QBJT_SW", "MODEL") -} // namespace devices - -} // namespace netlist +} // namespace netlist::devices diff --git a/src/lib/netlist/analog/nld_bjt.h b/src/lib/netlist/analog/nld_bjt.h deleted file mode 100644 index 3a8478f19f7..00000000000 --- a/src/lib/netlist/analog/nld_bjt.h +++ /dev/null @@ -1,25 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud -/* - * nld_bjt.h - * - */ - -#ifndef NLD_BJT_H_ -#define NLD_BJT_H_ - -#include "netlist/nl_setup.h" - -// ----------------------------------------------------------------------------- -// Macros -// ----------------------------------------------------------------------------- - -#define QBJT_SW(name, model) \ - NET_REGISTER_DEV(QBJT_SW, name) \ - NETDEV_PARAMI(name, MODEL, model) - -#define QBJT_EB(name, model) \ - NET_REGISTER_DEV(QBJT_EB, name) \ - NETDEV_PARAMI(name, MODEL, model) - -#endif /* NLD_BJT_H_ */ diff --git a/src/lib/netlist/analog/nld_fourterm.h b/src/lib/netlist/analog/nld_fourterm.h deleted file mode 100644 index 66c47c0be29..00000000000 --- a/src/lib/netlist/analog/nld_fourterm.h +++ /dev/null @@ -1,30 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud -/* - * nld_fourterm.h - * - */ - -#ifndef NLD_FOURTERM_H_ -#define NLD_FOURTERM_H_ - - -#include "netlist/nl_setup.h" - -// ---------------------------------------------------------------------------------------- -// Macros -// ---------------------------------------------------------------------------------------- - -#define VCCS(name) \ - NET_REGISTER_DEV(VCCS, name) - -#define CCCS(name) \ - NET_REGISTER_DEV(CCCS, name) - -#define VCVS(name) \ - NET_REGISTER_DEV(VCVS, name) - -#define LVCCS(name) \ - NET_REGISTER_DEV(LVCCS, name) - -#endif /* NLD_FOURTERM_H_ */ diff --git a/src/lib/netlist/analog/nld_generic_models.h b/src/lib/netlist/analog/nld_generic_models.h index 1edc4abd320..d8df599a511 100644 --- a/src/lib/netlist/analog/nld_generic_models.h +++ b/src/lib/netlist/analog/nld_generic_models.h @@ -1,21 +1,27 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nl_generic_models.h - * - */ #ifndef NLD_GENERIC_MODELS_H_ #define NLD_GENERIC_MODELS_H_ -#include "netlist/nl_base.h" -#include "netlist/nl_setup.h" +/// +/// \file nld_generic_models.h +/// -#include <cmath> +#include "core/state_var.h" +#include "nl_base.h" -namespace netlist -{ -namespace analog +// +// Set to 0 to use a linearized diode model in the range exceeding +// maximum dissipation. The intention is to have a faster +// convergence. On selected circuits (LM3900 trapezoidal) this is +// observable and has a 10% impact. +// FIXME: More research needed +// + +#define USE_TEXTBOOK_DIODE (1) + +namespace netlist::analog { // ----------------------------------------------------------------------------- @@ -37,15 +43,15 @@ namespace analog class generic_capacitor<capacitor_e::VARIABLE_CAPACITY> { public: - generic_capacitor(device_t &dev, const pstring &name) - : m_h(dev, name + ".m_h", 0.0) - , m_c(dev, name + ".m_c", 0.0) - , m_v(dev, name + ".m_v", 0.0) - , m_gmin(0.0) + generic_capacitor(core_device_t &dev, const pstring &name) + : m_h(dev, name + ".m_h", nlconst::zero()) + , m_c(dev, name + ".m_c", nlconst::zero()) + , m_v(dev, name + ".m_v", nlconst::zero()) + , m_gmin(nlconst::zero()) { } - capacitor_e type() const { return capacitor_e::VARIABLE_CAPACITY; } + static capacitor_e type() noexcept { return capacitor_e::VARIABLE_CAPACITY; } // Circuit Simulation, page 284, 5.360 // q(un+1) - q(un) = int(un, un+1, C(U)) = (C0+C1)/2 * (un+1-un) @@ -54,35 +60,39 @@ namespace analog // so that G depends on un+1 only and Ieq on un only. // In both cases, i = G * un+1 + Ieq - nl_double G(nl_double cap) const + nl_fptype G(nl_fptype cap) const noexcept { //return m_h * cap + m_gmin; - return m_h * 0.5 * (cap + m_c) + m_gmin; + return m_h * nlconst::half() * (cap + m_c) + m_gmin; //return m_h * cap + m_gmin; } - nl_double Ieq(nl_double cap, nl_double v) const + nl_fptype Ieq(nl_fptype cap, [[maybe_unused]] nl_fptype v) const noexcept { - plib::unused_var(v); //return -m_h * 0.5 * ((cap + m_c) * m_v + (cap - m_c) * v) ; - return -m_h * 0.5 * (cap + m_c) * m_v; + return -m_h * nlconst::half() * (cap + m_c) * m_v; //return -m_h * cap * m_v; } - void timestep(nl_double cap, nl_double v, nl_double step) + void time_step(nl_fptype cap, nl_fptype v, nl_fptype step) noexcept { - m_h = 1.0 / step; + m_h = plib::reciprocal(step); m_c = cap; m_v = v; } - void setparams(nl_double gmin) { m_gmin = gmin; } + void restore_state() noexcept + { + // no state used + } + + void set_params(nl_fptype gmin) noexcept { m_gmin = gmin; } private: - state_var<double> m_h; - state_var<double> m_c; - state_var<double> m_v; - nl_double m_gmin; + state_var<nl_fptype> m_h; + state_var<nl_fptype> m_c; + state_var<nl_fptype> m_v; + nl_fptype m_gmin; }; // "Circuit simulation", page 274 @@ -91,33 +101,107 @@ namespace analog { public: generic_capacitor(device_t &dev, const pstring &name) - : m_h(dev, name + ".m_h", 0.0) - , m_v(dev, name + ".m_v", 0.0) - , m_gmin(0.0) + : m_h(dev, name + ".m_h", nlconst::zero()) + , m_v(dev, name + ".m_v", nlconst::zero()) + , m_gmin(nlconst::zero()) { } - capacitor_e type() const { return capacitor_e::CONSTANT_CAPACITY; } - nl_double G(nl_double cap) const { return cap * m_h + m_gmin; } - nl_double Ieq(nl_double cap, nl_double v) const + static capacitor_e type() noexcept { return capacitor_e::CONSTANT_CAPACITY; } + nl_fptype G(nl_fptype cap) const noexcept { return cap * m_h + m_gmin; } + nl_fptype Ieq(nl_fptype cap, [[maybe_unused]] nl_fptype v) const noexcept { - plib::unused_var(v); return - G(cap) * m_v; } - void timestep(nl_double cap, nl_double v, nl_double step) + void time_step([[maybe_unused]] nl_fptype cap, nl_fptype v, nl_fptype step) noexcept { - plib::unused_var(cap); - m_h = 1.0 / step; + m_h = plib::reciprocal(step); m_v = v; } - void setparams(nl_double gmin) { m_gmin = gmin; } + void setparams(nl_fptype gmin) noexcept { m_gmin = gmin; } + private: + state_var<nl_fptype> m_h; + state_var<nl_fptype> m_v; + nl_fptype m_gmin; + }; + +#if (NL_USE_BACKWARD_EULER) + // Constant model for constant capacitor model + // Backward Euler + // "Circuit simulation", page 274 + struct generic_capacitor_const + { + public: + generic_capacitor_const( /*[[maybe_unused]]*/ core_device_t &dev, /*[[maybe_unused]]*/ const pstring &name) + : m_gmin(nlconst::zero()) + { + // gcc 7.2 (mingw) and 7.5 (ubuntu) don't accept maybe_unused here + plib::unused_var(dev, name); + } + + // Returns { G, Ieq } + std::pair<nl_fptype, nl_fptype> time_step(nl_fptype cap, nl_fptype v, nl_fptype step) const noexcept + { + const nl_fptype h(plib::reciprocal(step)); + const nl_fptype G(cap * h + m_gmin); + return { G, - G * v }; + } + void restore_state() noexcept + { + // this one has no state + } + void set_parameters(nl_fptype gmin) noexcept { m_gmin = gmin; } private: - state_var<nl_double> m_h; - state_var<double> m_v; - nl_double m_gmin; + nl_fptype m_gmin; }; +#else + // Constant model for constant capacitor model + // Trapezoidal + // "Circuit simulation", page 278 + struct generic_capacitor_const + { + public: + generic_capacitor_const([[maybe_unused]] core_device_t &dev, [[maybe_unused]] const pstring &name) + : m_gmin(nlconst::zero()) + , m_vn(0) + , m_in(0) + , m_trn(0.0) + { + } + // Returns { G, Ieq } + std::pair<nl_fptype, nl_fptype> time_step(nl_fptype cap, nl_fptype v, nl_fptype step) noexcept + { + const nl_fptype h(plib::reciprocal(step)); + if (m_trn == 0.0) + { + const nl_fptype G(cap * h + m_gmin); + m_vn = v; + m_trn = h; + return { G, - G * v }; + } + const nl_fptype Gn = nlconst::two() * cap * m_trn; + const nl_fptype inp1 = Gn * v - (m_in + Gn * m_vn); + const nl_fptype G(nlconst::two() * cap * h); + const nl_fptype Ieq(inp1 + G * v); + m_in = inp1; + m_vn = v; + m_trn = h; + return { G + m_gmin, -Ieq }; + } + void restore_state() noexcept + { + // this one has no state + } + void set_parameters(nl_fptype gmin) noexcept { m_gmin = gmin; } + private: + nl_fptype m_gmin; + nl_fptype m_vn; + nl_fptype m_in; + nl_fptype m_trn; + }; +#endif // ----------------------------------------------------------------------------- // A generic diode model to be used in other devices (Diode, BJT ...) // ----------------------------------------------------------------------------- @@ -132,39 +216,58 @@ namespace analog class generic_diode { public: - generic_diode(device_t &dev, const pstring &name) - : m_Vd(dev, name + ".m_Vd", 0.7) - , m_Id(dev, name + ".m_Id", 0.0) - , m_G(dev, name + ".m_G", 1e-15) - , m_Vt(0.0) - , m_Vmin(0.0) // not used in MOS model - , m_Is(0.0) - , m_logIs(0.0) - , m_n(0.0) - , m_gmin(1e-15) - , m_VtInv(0.0) - , m_Vcrit(0.0) - , m_name(name) + generic_diode() + : m_Vd(nlconst::diode_start_voltage()) + , m_Id(nlconst::zero()) + , m_G(nlconst::cgminalt()) + , m_Vt(nlconst::zero()) + , m_Vmin(nlconst::zero()) // not used in MOS model + , m_Is(nlconst::zero()) + , m_logIs(nlconst::zero()) + , m_gmin(nlconst::cgminalt()) + , m_VtInv(nlconst::zero()) + , m_Vcrit(nlconst::zero()) { - set_param(1e-15, 1, 1e-15, 300.0); + set_param( + nlconst::np_Is() + , nlconst::one() + , nlconst::cgminalt() + , nlconst::T0()); } - void update_diode(const nl_double nVd) + generic_diode(core_device_t &dev, const pstring &name) + : generic_diode() + { + dev.state().save(dev, m_Vd, dev.name(), name + ".m_Vd"); + dev.state().save(dev, m_Id, dev.name(), name + ".m_Id"); + dev.state().save(dev, m_G, dev.name(), name + ".m_G"); + } + // Basic math + // + // I(V) = f(V) + // + // G(V) = df/dV(V) + // + // Ieq(V) = I(V) - V * G(V) + // + // + void update_diode(nl_fptype nVd) noexcept { - nl_double IseVDVt(0.0); - if (TYPE == diode_e::BIPOLAR) { - //printf("%s: %g %g\n", m_name.c_str(), nVd, (double) m_Vd); +#if USE_TEXTBOOK_DIODE if (nVd > m_Vcrit) { - const nl_double d = std::min(1e100, nVd - m_Vd); - const nl_double a = std::abs(d) * m_VtInv; - m_Vd = m_Vd + (d < 0 ? -1.0 : 1.0) * std::log1p(a) * m_Vt; + // if the old voltage is less than zero and new is above + // make sure we move enough so that matrix and current + // changes. + const nl_fptype old = std::max(nlconst::zero(), m_Vd); + const nl_fptype d = std::min(+fp_constants<nl_fptype>::DIODE_MAXDIFF(), nVd - old); + const nl_fptype a = plib::abs(d) * m_VtInv; + m_Vd = old + plib::signum(d) * plib::log1p(a) * m_Vt; } else - m_Vd = std::max(-1e100, nVd); - //m_Vd = nVd; + m_Vd = std::max(-fp_constants<nl_fptype>::DIODE_MAXDIFF(), nVd); if (m_Vd < m_Vmin) { @@ -173,73 +276,108 @@ namespace analog } else { - IseVDVt = std::exp(m_logIs + m_Vd * m_VtInv); + const auto IseVDVt = plib::exp(m_logIs + m_Vd * m_VtInv); m_Id = IseVDVt - m_Is; m_G = IseVDVt * m_VtInv + m_gmin; } +#else + //printf("%s: %g %g\n", m_name.c_str(), nVd, (nl_fptype) m_Vd); + m_Vd = nVd; + if (nVd > m_Vcrit) + { + m_Id = m_Icrit_p_Is - m_Is + (m_Vd - m_Vcrit) * m_Icrit_p_Is * m_VtInv; + m_G = m_Icrit_p_Is * m_VtInv + m_gmin; + } + else if (m_Vd < m_Vmin) + { + m_G = m_gmin; + //m_Id = m_Imin + (m_Vd - m_Vmin) * m_gmin; + //m_Imin = m_gmin * m_Vt - m_Is; + m_Id = (m_Vd - m_Vmin + m_Vt) * m_gmin - m_Is; + } + else + { + const auto IseVDVt = plib::exp(m_logIs + m_Vd * m_VtInv); + m_Id = IseVDVt - m_Is; + m_G = IseVDVt * m_VtInv + m_gmin; + } +#endif } else if (TYPE == diode_e::MOS) { - if (nVd < constants::zero()) + m_Vd = nVd; + if (nVd < nlconst::zero()) { - m_Vd = nVd; m_G = m_Is * m_VtInv + m_gmin; m_Id = m_G * m_Vd; } - else /* log stepping should already be done in mosfet */ + else // log stepping should already be done in mosfet { - m_Vd = nVd; - IseVDVt = std::exp(std::min(300.0, m_logIs + m_Vd * m_VtInv)); + const auto IseVDVt = plib::exp(std::min(+fp_constants<nl_fptype>::DIODE_MAXVOLT(), m_logIs + m_Vd * m_VtInv)); m_Id = IseVDVt - m_Is; m_G = IseVDVt * m_VtInv + m_gmin; } } } - void set_param(const nl_double Is, const nl_double n, nl_double gmin, nl_double temp) + void set_param(nl_fptype Is, nl_fptype n, nl_fptype gmin, nl_fptype temp) noexcept { m_Is = Is; - m_logIs = std::log(Is); - m_n = n; + m_logIs = plib::log(Is); m_gmin = gmin; - m_Vt = m_n * temp * constants::k_b() / constants::Q_e(); - - m_Vmin = -5.0 * m_Vt; + m_Vt = nlconst::np_VT(n, temp); + m_VtInv = plib::reciprocal(m_Vt); + +#if USE_TEXTBOOK_DIODE + m_Vmin = nlconst::diode_min_cutoff_mult() * m_Vt; + // Vcrit : f(V) has smallest radius of curvature rho(V) == min(rho(v)) + m_Vcrit = m_Vt * plib::log(m_Vt / m_Is / nlconst::sqrt2()); +#else + m_Vmin = plib::log(m_gmin * m_Vt / m_Is) * m_Vt; + //m_Imin = plib::exp(m_logIs + m_Vmin * m_VtInv) - m_Is; + //m_Imin = m_gmin * m_Vt - m_Is; + // Fixme: calculate max dissipation voltage - use use 0.5 (500mW) here for typical diode + // P = V * I = V * (Is*exp(V/Vt) - Is) + // P ~= V * I = V * Is*exp(V/Vt) + // ln(P/Is) = ln(V)+V/Vt ~= V - 1 + V/vt + // V = (1+ln(P/Is))/(1 + 1/Vt) + + m_Vcrit = (nlconst::one() + plib::log(nlconst::half() / m_Is)) / (nlconst::one() + m_VtInv); + //printf("Vcrit: %f\n", m_Vcrit); + m_Icrit_p_Is = plib::exp(m_logIs + m_Vcrit * m_VtInv); + //m_Icrit = plib::exp(m_logIs + m_Vcrit * m_VtInv) - m_Is; +#endif - m_Vcrit = m_Vt * std::log(m_Vt / m_Is / constants::sqrt2()); - m_VtInv = constants::one() / m_Vt; - //printf("%g %g\n", m_Vmin, m_Vcrit); } + nl_fptype I() const noexcept { return m_Id; } + nl_fptype G() const noexcept { return m_G; } + nl_fptype Ieq() const noexcept { return (m_Id - m_Vd * m_G); } + nl_fptype Vd() const noexcept { return m_Vd; } - nl_double I() const { return m_Id; } - nl_double G() const { return m_G; } - nl_double Ieq() const { return (m_Id - m_Vd * m_G); } - nl_double Vd() const { return m_Vd; } - - /* owning object must save those ... */ + // owning object must save those ... private: - state_var<nl_double> m_Vd; - state_var<nl_double> m_Id; - state_var<nl_double> m_G; - - nl_double m_Vt; - nl_double m_Vmin; - nl_double m_Is; - nl_double m_logIs; - nl_double m_n; - nl_double m_gmin; - - nl_double m_VtInv; - nl_double m_Vcrit; - - pstring m_name; + nl_fptype m_Vd; + nl_fptype m_Id; + nl_fptype m_G; + + nl_fptype m_Vt; + nl_fptype m_Vmin; + nl_fptype m_Is; + nl_fptype m_logIs; + nl_fptype m_gmin; + + nl_fptype m_VtInv; + nl_fptype m_Vcrit; +#if !USE_TEXTBOOK_DIODE + //nl_fptype m_Imin; + nl_fptype m_Icrit_p_Is; +#endif }; -} // namespace analog -} // namespace netlist +} // namespace netlist::analog -#endif /* NLD_GENERIC_MODELS_H_ */ +#endif // NLD_GENERIC_MODELS_H_ diff --git a/src/lib/netlist/analog/nld_mosfet.cpp b/src/lib/netlist/analog/nld_mosfet.cpp index c66cec291f6..63911ceb02f 100644 --- a/src/lib/netlist/analog/nld_mosfet.cpp +++ b/src/lib/netlist/analog/nld_mosfet.cpp @@ -1,193 +1,168 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // 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" + +// Names +// spell-checker: words Farid, Naim, Jahn, Margraf, Habchi, Raimund, Qucs +// +// Specific technical terms +// spell-checker: words Cgso, Cgdo, Cgbo, Cjsw, Mjsw, Ucrit, Uexp, Utra, Neff, Tnom, capval, Udsat, Utst + +/// +/// \file 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 "../nl_setup.h" #include "nlid_twoterm.h" -#include <cmath> +#include "solver/nld_solver.h" -#define BODY_CONNECTED_TO_SOURCE (1) +#define BODY_CONNECTED_TO_SOURCE (1) -namespace netlist -{ -namespace analog +namespace netlist::analog { - using constants = plib::constants<nl_double>; - - // ----------------------------------------------------------------------------- - // nld_FET - Base classes - // ----------------------------------------------------------------------------- - - /*! Class representing the nmos/pmos model paramers. - * - * This is the model representation of the nmos model. - * - * Netlist has an additional parameter caller CAPMOD: - * - * CAPMOD=0: Capacitance model disabled - * CAPMOD=2: Meyer capacitance 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| - * | Y |Cgso | Gate-source overlap capacitance per meter channel width |F/m|0|0.00000000004| - * | Y |Cgdo | Gate-drain overlap capacitance per meter channel width |F/m|0|0.00000000004|* - * | Y |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 |-|100e-6|| - * | Y |W | Width scaling |-|100e-6|| - * */ - - class fet_model_t : public param_model_t + using constants = plib::constants<nl_fptype>; + + /// \brief Class representing the nmos/pmos model parameters. + /// + /// This is the model representation of the nmos model. + /// + /// Netlist has an additional parameter caller CAPMOD: + /// + /// CAPMOD=0: Capacitance model disabled + /// CAPMOD=2: Meyer capacitance 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| + /// | Y |Cgso | Gate-source overlap capacitance per meter channel width |F/m|0|0.00000000004| + /// | Y |Cgdo | Gate-drain overlap capacitance per meter channel width |F/m|0|0.00000000004*| + /// | Y |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 |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 |-|100e-6|| + /// | Y |W | Width scaling |-|100e-6|| + /// + + class fet_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") - , m_CGSO(*this, "CGSO") - , m_CGDO(*this, "CGDO") - , m_CGBO(*this, "CGBO") - , m_CAPMOD(*this, "CAPMOD") - {} - - 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 - value_t m_CGSO; //!< Gate-source overlap capacitance per meter channel width - value_t m_CGDO; //!< Gate-drain overlap capacitance per meter channel width - value_t m_CGBO; //!< Gate-bulk overlap capacitance per meter channel width - value_base_t<int> m_CAPMOD; //!< Capacitance model (0=no model 2=Meyer) - }; - - // 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) + fet_model_t(param_model_t &model) + : m_VTO(model, "VTO") + , m_N(model, "N") + , m_ISS(model, "IS") // Haven't seen a model using ISS / ISD + , m_ISD(model, "IS") + , m_LD(model, "LD") + , m_L(model, "L") + , m_W(model, "W") + , m_TOX(model, "TOX") + , m_KP(model, "KP") + , m_UO(model, "UO") + , m_PHI(model, "PHI") + , m_NSUB(model, "NSUB") + , m_GAMMA(model, "GAMMA") + , m_LAMBDA(model, "LAMBDA") + , m_RD(model, "RD") + , m_RS(model, "RS") + , m_CGSO(model, "CGSO") + , m_CGDO(model, "CGDO") + , m_CGBO(model, "CGBO") + , m_CAPMOD(model, "CAPMOD") { } - 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; + param_model_t::value_t m_VTO; //!< Threshold voltage [V] + param_model_t::value_t m_N; //!< Bulk diode emission coefficient + param_model_t::value_t m_ISS; //!< Body diode saturation current + param_model_t::value_t m_ISD; //!< Body diode saturation current + param_model_t::value_t m_LD; //!< Lateral diffusion [m] + param_model_t::value_t m_L; //!< Length scaling + param_model_t::value_t m_W; //!< Width scaling + param_model_t::value_t m_TOX; //!< Oxide thickness + param_model_t::value_t m_KP; //!< Transconductance parameter [A/V²] + param_model_t::value_t m_UO; //!< Surface mobility [cm²/V/s] + param_model_t::value_t m_PHI; //!< Surface inversion potential [V] + param_model_t::value_t m_NSUB; //!< Substrate doping [1/cm³] + param_model_t::value_t m_GAMMA; //!< Bulk threshold parameter [V^½] + param_model_t::value_t m_LAMBDA; //!< Channel-length modulation [1/V] + param_model_t::value_t m_RD; //!< Drain ohmic resistance + param_model_t::value_t m_RS; //!< Source ohmic resistance + param_model_t::value_t m_CGSO; //!< Gate-source overlap capacitance per + //!< meter channel width + param_model_t::value_t m_CGDO; //!< Gate-drain overlap capacitance per + //!< meter channel width + param_model_t::value_t m_CGBO; //!< Gate-bulk overlap capacitance per + //!< meter channel width + param_model_t::value_base_t<int> m_CAPMOD; //!< Capacitance model (0=no + //!< model 2=Meyer) }; // ----------------------------------------------------------------------------- - // nld_QBJT_EB + // nld_MOSFET // ----------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(MOSFET, FET) + class nld_MOSFET : public base_device_t { public: - NETLIB_CONSTRUCTOR_DERIVED(MOSFET, FET) - , m_DG(*this, "m_DG", true) - , m_SG(*this, "m_SG", true) - , m_SD(*this, "m_SD", true) + nld_MOSFET(constructor_param_t data) + : base_device_t(data) + , m_model(*this, "MODEL", "NMOS") + , m_DG(*this, "m_DG", NETLIB_DELEGATE(terminal_handler)) + , m_SG(*this, "m_SG", NETLIB_DELEGATE(terminal_handler)) + , m_SD(*this, "m_SD", NETLIB_DELEGATE(terminal_handler)) , m_D_BD(*this, "m_D_BD") #if (!BODY_CONNECTED_TO_SOURCE) , m_D_BS(*this, "m_D_BS") @@ -195,144 +170,183 @@ namespace analog , m_cap_gb(*this, "m_cap_gb") , m_cap_gs(*this, "m_cap_gs") , m_cap_gd(*this, "m_cap_gd") - , 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_CoxWL(0.0) - , m_polarity(qtype() == FET_NMOS ? 1.0 : -1.0) - , m_Cgb(0.0) - , m_Cgs(0.0) - , m_Cgd(0.0) - , m_capmod(2) - , m_Vgs(*this, "m_Vgs", 0.0) - , m_Vgd(*this, "m_Vgd", 0.0) - { - register_subalias("S", m_SG.m_P); // Source - register_subalias("G", m_SG.m_N); // Gate + , m_phi(nlconst::zero()) + , m_gamma(nlconst::zero()) + , m_vto(nlconst::zero()) + , m_beta(nlconst::zero()) + , m_lambda(nlconst::zero()) + , m_Leff(nlconst::zero()) + , m_CoxWL(nlconst::zero()) + // S, m_polarity(qtype() == FET_NMOS ? nlconst::one() : -nlconst::one()) + , m_Cgb(nlconst::zero()) + , m_Cgs(nlconst::zero()) + , m_Cgd(nlconst::zero()) + , m_capacitor_model(2) + , m_Vgs(*this, "m_Vgs", nlconst::zero()) + , m_Vgd(*this, "m_Vgd", nlconst::zero()) + , m_model_acc(m_model) + { + register_sub_alias("S", m_SG.P()); // Source + register_sub_alias("G", m_SG.N()); // Gate - register_subalias("D", m_DG.m_P); // Drain + register_sub_alias("D", m_DG.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); + connect(m_SG.P(), m_SD.P()); + connect(m_SG.N(), m_DG.N()); + connect(m_DG.P(), m_SD.N()); - set_qtype((m_model.type() == "NMOS_DEFAULT") ? FET_NMOS : FET_PMOS); - m_polarity = qtype() == FET_NMOS ? 1.0 : -1.0; + m_polarity = (m_model.type() == "NMOS_DEFAULT" ? nlconst::one() + : -nlconst::one()); - m_capmod = m_model.m_CAPMOD; - // printf("capmod %d %g %g\n", m_capmod, (double)m_model.m_VTO, m_polarity); - nl_assert_always(m_capmod == 0 || m_capmod == 2, "Error: CAPMODEL invalid value for " + m_model.name()); + m_capacitor_model = m_model_acc.m_CAPMOD; + //# printf("capmod %d %g %g\n", m_capacitor_model, (nl_fptype)m_model_acc.m_VTO, m_polarity); + nl_assert_always(m_capacitor_model == 0 || m_capacitor_model == 2, + "Error: CAPMODEL invalid value"); - /* - * 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 - * - */ + // + // 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 + m_lambda = m_model_acc.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()); + m_Leff = m_model_acc.m_L - 2 * m_model_acc.m_LD; + nl_assert_always( + m_Leff > nlconst::zero(), + "Effective Lateral diffusion would be negative for model"); - nl_double Cox = (m_model.m_TOX > 0.0) ? (constants::eps_SiO2() * constants::eps_0() / m_model.m_TOX) : 0.0; + nl_fptype Cox = (m_model_acc.m_TOX > nlconst::zero()) + ? (constants::eps_SiO2() * constants::eps_0() + / m_model_acc.m_TOX) + : nlconst::zero(); // calculate DC transconductance coefficient - if (m_model.m_KP > 0) - m_beta = m_model.m_KP * m_model.m_W / m_Leff; - else if (Cox > 0 && m_model.m_UO > 0) - m_beta = m_model.m_UO * 1e-4 * Cox * m_model.m_W / m_Leff; + if (m_model_acc.m_KP > nlconst::zero()) + m_beta = m_model_acc.m_KP * m_model_acc.m_W / m_Leff; + else if (Cox > nlconst::zero() + && m_model_acc.m_UO > nlconst::zero()) + m_beta = m_model_acc.m_UO * nlconst::magic(1e-4) * Cox + * m_model_acc.m_W / m_Leff; else - m_beta = 2e-5 * m_model.m_W / m_Leff; + m_beta = nlconst::magic(2e-5) * m_model_acc.m_W / m_Leff; - //FIXME::UT can disappear - const double Vt = constants::T0() * constants::k_b() / constants::Q_e(); + // FIXME::UT can disappear + const nl_fptype 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) + if (m_model_acc.m_PHI > nlconst::zero()) + m_phi = m_model_acc.m_PHI; + else if (m_model_acc.m_NSUB > nlconst::zero()) { - 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()); + nl_assert_always(m_model_acc.m_NSUB * nlconst::magic(1e6) + >= constants::NiSi(), + "Error calculating phi for model"); + m_phi = nlconst::two() * Vt + * plib::log(m_model_acc.m_NSUB * nlconst::magic(1e6) + / constants::NiSi()); } else - m_phi = 0.6; + m_phi = nlconst::magic(0.6); // calculate bulk threshold if not given - if (m_model.m_GAMMA > 0.0) - m_gamma = m_model.m_GAMMA; + if (m_model_acc.m_GAMMA > nlconst::zero()) + m_gamma = m_model_acc.m_GAMMA; else { - if (Cox > 0.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) / Cox; + if (Cox > nlconst::zero() + && m_model_acc.m_NSUB > nlconst::zero()) + m_gamma = plib::sqrt( + nlconst::two() * constants::Q_e() + * constants::eps_Si() * constants::eps_0() + * m_model_acc.m_NSUB * nlconst::magic(1e6)) + / Cox; else - m_gamma = 0.0; + m_gamma = nlconst::zero(); } - m_vto = m_model.m_VTO; - if(m_vto != 0.0) + m_vto = m_model_acc.m_VTO; + // FIXME zero conversion + if (m_vto == nlconst::zero()) log().warning(MW_MOSFET_THRESHOLD_VOLTAGE(m_model.name())); - /* FIXME: VTO if missing may be calculated from TPG, NSS and temperature. Usually models - * specify VTO so skip this here. - */ + // FIXME: VTO if missing may be calculated from TPG, NSS and + // temperature. Usually models specify VTO so skip this here. - m_CoxWL = Cox * m_model.m_W * m_Leff; + m_CoxWL = Cox * m_model_acc.m_W * m_Leff; - //printf("Cox: %g\n", m_Cox); + //#printf("Cox: %g\n", m_Cox); } - NETLIB_IS_TIMESTEP(true || m_capmod != 0) + NETLIB_IS_DYNAMIC(true) + NETLIB_IS_TIMESTEP(true || m_capacitor_model != 0) NETLIB_TIMESTEPI() { - if (m_capmod != 0) + if (m_capacitor_model != 0) { - //const nl_double Ugd = -m_DG.deltaV() * m_polarity; // Gate - Drain - //const nl_double Ugs = -m_SG.deltaV() * m_polarity; // Gate - Source - const nl_double Ugd = m_Vgd; // Gate - Drain - const nl_double Ugs = m_Vgs; // Gate - Source - const nl_double Ubs = 0.0; // Bulk - Source == 0 if connected - const nl_double Ugb = Ugs - Ubs; - - m_cap_gb.timestep(m_Cgb, Ugb, step); - m_cap_gs.timestep(m_Cgs, Ugs, step); - m_cap_gd.timestep(m_Cgd, Ugd, step); + if (ts_type == detail::time_step_type::FORWARD) + { + //#const nl_nl_fptype Ugd = -m_DG.deltaV() * m_polarity; // Gate - Drain + //#const nl_nl_fptype Ugs = -m_SG.deltaV() * m_polarity; // Gate - Source + const nl_fptype Ugd = m_Vgd; // Gate - Drain + const nl_fptype Ugs = m_Vgs; // Gate - Source + const nl_fptype Ubs = nlconst::zero(); // Bulk - Source == 0 + // if connected + const nl_fptype Ugb = Ugs - Ubs; + + m_cap_gb.time_step(m_Cgb, Ugb, step); + m_cap_gs.time_step(m_Cgs, Ugs, step); + m_cap_gd.time_step(m_Cgd, Ugd, step); + } + else + { + m_cap_gb.restore_state(); + m_cap_gs.restore_state(); + m_cap_gd.restore_state(); + } } } protected: - NETLIB_RESETI() { - NETLIB_NAME(FET)::reset(); // 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 + m_D_BD.set_param(m_model_acc.m_ISD, m_model_acc.m_N, exec().gmin(), + constants::T0()); +#if (!BODY_CONNECTED_TO_SOURCE) + m_D_BS.set_param(m_model_acc.m_ISS, m_model_acc.m_N, exec().gmin(), + constants::T0()); +#endif } - NETLIB_UPDATEI(); + NETLIB_HANDLERI(terminal_handler) + { + // only called if connected to a rail net ==> notify the solver to + // recalculate + auto *solv(m_SG.solver()); + if (solv != nullptr) + solv->solve_now(); + else + m_DG.solver()->solve_now(); + } NETLIB_UPDATE_PARAMI(); NETLIB_UPDATE_TERMINALSI(); private: + param_model_t m_model; - nld_twoterm m_DG; - nld_twoterm m_SG; - nld_twoterm m_SD; + NETLIB_NAME(two_terminal) m_DG; + NETLIB_NAME(two_terminal) m_SG; + NETLIB_NAME(two_terminal) m_SD; generic_diode<diode_e::MOS> m_D_BD; #if (!BODY_CONNECTED_TO_SOURCE) @@ -343,83 +357,94 @@ namespace analog generic_capacitor<capacitor_e::VARIABLE_CAPACITY> m_cap_gs; generic_capacitor<capacitor_e::VARIABLE_CAPACITY> m_cap_gd; - nl_double m_phi; - nl_double m_gamma; - nl_double m_vto; - nl_double m_beta; - nl_double m_lambda; + nl_fptype m_phi; + nl_fptype m_gamma; + nl_fptype m_vto; + nl_fptype m_beta; + nl_fptype m_lambda; - /* used in capacitance calculation */ - nl_double m_Leff; - nl_double m_CoxWL; - nl_double m_polarity; + // used in capacitance calculation + nl_fptype m_Leff; + nl_fptype m_CoxWL; + nl_fptype m_polarity; - /* capacitance values */ + // capacitance values - nl_double m_Cgb; - nl_double m_Cgs; - nl_double m_Cgd; + nl_fptype m_Cgb; + nl_fptype m_Cgs; + nl_fptype m_Cgd; - int m_capmod; - state_var<nl_double> m_Vgs; - state_var<nl_double> m_Vgd; + int m_capacitor_model; + state_var<nl_fptype> m_Vgs; + state_var<nl_fptype> m_Vgd; + fet_model_t m_model_acc; - void set_cap(generic_capacitor<capacitor_e::VARIABLE_CAPACITY> cap, - nl_double capval, nl_double V, - nl_double &g11, nl_double &g12, nl_double &g21, nl_double &g22, - nl_double &I1, nl_double &I2) + void set_cap(generic_capacitor<capacitor_e::VARIABLE_CAPACITY> &cap, + nl_fptype capval, nl_fptype V, nl_fptype &g11, + nl_fptype &g12, nl_fptype &g21, nl_fptype &g22, + nl_fptype &I1, nl_fptype &I2) const { - const nl_double I = cap.Ieq(capval, V) * m_polarity; - const nl_double G = cap.G(capval); - g11 += G; g12 -= G; g21 -= G; g22 += G; - I1 -= I; I2 += I; - //printf("Cap: %g\n", capval); + const nl_fptype I = cap.Ieq(capval, V) * m_polarity; + const nl_fptype G = cap.G(capval); + g11 += G; + g12 -= G; + g21 -= G; + g22 += G; + I1 -= I; + I2 += I; + // printf("Cap: %g\n", capval); } - void calculate_caps(nl_double Vgs, nl_double Vgd, nl_double Vth, - nl_double &Cgs, nl_double &Cgd, nl_double &Cgb) + void + calculate_caps(nl_fptype Vgs, nl_fptype Vgd, nl_fptype Vth, + nl_fptype &Cgs, nl_fptype &Cgd, nl_fptype &Cgb) const { - nl_double Vctrl = Vgs - Vth * m_polarity; + nl_fptype Vctrl = Vgs - Vth * m_polarity; // Cut off - now further differentiated into 3 different formulas // Accumulation if (Vctrl <= -m_phi) { Cgb = m_CoxWL; - Cgs = 0.0; - Cgd = 0.0; + Cgs = nlconst::zero(); + Cgd = nlconst::zero(); } - else if (Vctrl <= -m_phi / 2.0) + else if (Vctrl <= -m_phi / nlconst::two()) { Cgb = -Vctrl * m_CoxWL / m_phi; - Cgs = 0.0; - Cgd = 0.0; + Cgs = nlconst::zero(); + Cgd = nlconst::zero(); } // Depletion else if (Vctrl <= 0) { Cgb = -Vctrl * m_CoxWL / m_phi; - Cgs = Vctrl * m_CoxWL * (4.0 / 3.0) / m_phi + (2.0 / 3.0) * m_CoxWL; - Cgd = 0.0; + Cgs = Vctrl * m_CoxWL * nlconst::fraction(4.0, 3.0) / m_phi + + nlconst::two_thirds() * m_CoxWL; + Cgd = nlconst::zero(); } else { - const nl_double Vdsat = Vctrl; - const nl_double Vds = Vgs - Vgd; + const nl_fptype Vdsat = Vctrl; + const nl_fptype Vds = Vgs - Vgd; // saturation if (Vdsat <= Vds) { - Cgb = 0; - Cgs = (2.0 / 3.0) * m_CoxWL; - Cgd = 0; + Cgb = nlconst::zero(); + Cgs = nlconst::two_thirds() * m_CoxWL; + Cgd = nlconst::zero(); } else { // linear - const nl_double Sqr1 = std::pow(Vdsat - Vds, 2); - const nl_double Sqr2 = std::pow(2.0 * Vdsat - Vds, 2); + const auto Sqr1(plib::narrow_cast<nl_fptype>( + plib::pow(Vdsat - Vds, 2))); + const auto Sqr2(plib::narrow_cast<nl_fptype>( + plib::pow(nlconst::two() * Vdsat - Vds, 2))); Cgb = 0; - Cgs = m_CoxWL * (1.0 - Sqr1 / Sqr2) * (2.0 / 3.0); - Cgd = m_CoxWL * (1.0 - Vdsat * Vdsat / Sqr2) * (2.0 / 3.0); + Cgs = m_CoxWL * (nlconst::one() - Sqr1 / Sqr2) + * nlconst::two_thirds(); + Cgd = m_CoxWL * (nlconst::one() - Vdsat * Vdsat / Sqr2) + * nlconst::two_thirds(); } } } @@ -429,36 +454,33 @@ namespace analog // MOSFET // ---------------------------------------------------------------------------------------- - 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_UPDATE_TERMINALS(MOSFET) { - nl_double Vgd = -m_DG.deltaV() * m_polarity; // Gate - Drain - nl_double Vgs = -m_SG.deltaV() * m_polarity; // Gate - Source + nl_fptype Vgd = -m_DG.deltaV() * m_polarity; // Gate - Drain + nl_fptype Vgs = -m_SG.deltaV() * m_polarity; // Gate - Source // limit step sizes - const nl_double k = 3.5; // see "Circuit Simulation", page 185 - nl_double d = (Vgs - m_Vgs); - Vgs = m_Vgs + 1.0/k * (d < 0 ? -1.0 : 1.0) * std::log1p(k * std::abs(d)); + const nl_fptype k = nlconst::magic(3.5); // see "Circuit Simulation", + // page 185 + nl_fptype d = (Vgs - m_Vgs); + Vgs = m_Vgs + + plib::reciprocal(k) * plib::signum(d) + * plib::log1p(k * plib::abs(d)); d = (Vgd - m_Vgd); - Vgd = m_Vgd + 1.0/k * (d < 0 ? -1.0 : 1.0) * std::log1p(k * std::abs(d)); + Vgd = m_Vgd + + plib::reciprocal(k) * plib::signum(d) + * plib::log1p(k * plib::abs(d)); m_Vgs = Vgs; m_Vgd = Vgd; - const nl_double Vbs = 0.0; // Bulk - Source == 0 if connected - //const nl_double Vbd = m_SD.deltaV() * m_polarity; // Bulk - Drain = Source - Drain - const nl_double Vds = Vgs - Vgd; - const nl_double Vbd = -Vds; // Bulk - Drain = Source - Drain + const nl_fptype Vbs = nlconst::zero(); // Bulk - Source == 0 if + // connected + // const nl_nl_fptype Vbd = m_SD.deltaV() * m_polarity; // Bulk - Drain + // = Source - Drain + const nl_fptype Vds = Vgs - Vgd; + const nl_fptype Vbd = -Vds; // Bulk - Drain = Source - Drain #if (!BODY_CONNECTED_TO_SOURCE) m_D_BS.update_diode(Vbs); @@ -467,135 +489,176 @@ namespace analog // Are we in forward mode ? // in backward mode, just swap source and drain - const bool is_forward = Vds >= 0; + const bool is_forward = Vds >= nlconst::zero(); // calculate Vth - const nl_double Vbulk = is_forward ? Vbs : Vbd; - const nl_double phi_m_Vbulk = (m_phi > Vbulk) ? std::sqrt(m_phi - Vbulk) : 0.0; - const nl_double Vth = m_vto * m_polarity + m_gamma * (phi_m_Vbulk - std::sqrt(m_phi)); + const nl_fptype Vbulk = is_forward ? Vbs : Vbd; + const nl_fptype phi_m_Vbulk = (m_phi > Vbulk) + ? plib::sqrt(m_phi - Vbulk) + : nlconst::zero(); + const nl_fptype Vth = m_vto * m_polarity + + m_gamma * (phi_m_Vbulk - plib::sqrt(m_phi)); + + const nl_fptype Vctrl = (is_forward ? Vgs : Vgd) - Vth; - const nl_double Vctrl = (is_forward ? Vgs : Vgd) - Vth; + nl_fptype Ids(0); + nl_fptype gm(0); + nl_fptype gds(0); + nl_fptype gmb(0); - nl_double Ids, gm, gds, gmb; - const nl_double absVds = std::abs(Vds); + const nl_fptype absVds = plib::abs(Vds); - if (Vctrl <= 0.0) + if (Vctrl <= nlconst::zero()) { // cutoff region - Ids = 0.0; - gm = 0.0; - gds = 0.0; - gmb = 0.0; + Ids = nlconst::zero(); + gm = nlconst::zero(); + gds = nlconst::zero(); + gmb = nlconst::zero(); } else { - const nl_double beta = m_beta * (1.0 + m_lambda * absVds); + const nl_fptype beta = m_beta + * (nlconst::one() + m_lambda * absVds); if (Vctrl <= absVds) { // saturation region - Ids = beta * Vctrl * Vctrl / 2.0; - gm = beta * Vctrl; - gds = m_lambda * m_beta * Vctrl * Vctrl / 2.0; + Ids = beta * Vctrl * Vctrl / nlconst::two(); + gm = beta * Vctrl; + gds = m_lambda * m_beta * Vctrl * Vctrl / nlconst::two(); } else { // linear region - Ids = beta * absVds * (Vctrl - absVds / 2); - gm = beta * absVds; - gds = beta * (Vctrl - absVds) + m_lambda * m_beta * absVds * (Vctrl - absVds / 2.0); + Ids = beta * absVds * (Vctrl - absVds / nlconst::two()); + gm = beta * absVds; + gds = beta * (Vctrl - absVds) + + m_lambda * m_beta * absVds + * (Vctrl - absVds / nlconst::two()); } - // backgate transconductance - const nl_double bgtc = (phi_m_Vbulk != 0.0) ? (m_gamma / phi_m_Vbulk / 2.0) : 0.0; + // back gate transconductance + const nl_fptype bgtc = (phi_m_Vbulk != nlconst::zero()) + ? (m_gamma / phi_m_Vbulk + / nlconst::two()) + : nlconst::zero(); gmb = gm * bgtc; } // FIXME: these are needed to compute capacitance - // nl_double Udsat = pol * std::max (Utst, 0.0); + // nl_fptype 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(); + const nl_fptype IeqBD = m_D_BD.Ieq(); + const nl_fptype gbd = m_D_BD.G(); #if (!BODY_CONNECTED_TO_SOURCE) - const nl_double IeqBS = m_D_BS.Ieq(); - const nl_double gbs = m_D_BS.G(); + const nl_fptype IeqBS = m_D_BS.Ieq(); + const nl_fptype gbs = m_D_BS.G(); #else - const nl_double IeqBS = 0.0; - const nl_double gbs = 0.0; + const nl_fptype IeqBS = nlconst::zero(); + const nl_fptype gbs = nlconst::zero(); #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_fptype gate_source = is_forward ? (gm + gmb) : nlconst::zero(); + const nl_fptype gate_drain = is_forward ? nlconst::zero() : (gm + gmb); - const nl_double IeqDS = (is_forward) ? - Ids - gm * Vgs - gmb * Vbs - gds * Vds - : -Ids - gm * Vgd - gmb * Vbd - gds * Vds; + const nl_fptype IeqDS = (is_forward) + ? Ids - gm * Vgs - gmb * Vbs - gds * Vds + : -Ids - gm * Vgd - gmb * Vbd - gds * Vds; // IG = 0 - nl_double IG = 0.0; - nl_double ID = (+IeqBD - IeqDS) * m_polarity; - nl_double IS = (+IeqBS + IeqDS) * m_polarity; - nl_double IB = (-IeqBD - IeqBS) * m_polarity; - - nl_double gGG = 0.0; - nl_double gGD = 0.0; - nl_double gGS = 0.0; - nl_double gGB = 0.0; - - nl_double gDG = gm; - nl_double gDD = gds + gbd - gdrain; - const nl_double gDS = -gds - gsource; - const nl_double gDB = gmb - gbd; - - nl_double gSG = -gm; - const nl_double gSD = -gds + gdrain; - nl_double gSS = gbs + gds + gsource; - const nl_double gSB = -gbs - gmb; - - nl_double gBG = 0.0; - const nl_double gBD = -gbd; - const nl_double gBS = -gbs; - nl_double gBB = gbs + gbd; - - if (m_capmod != 0) + nl_fptype IG = nlconst::zero(); + nl_fptype ID = (+IeqBD - IeqDS) * m_polarity; + nl_fptype IS = (+IeqBS + IeqDS) * m_polarity; + nl_fptype IB = (-IeqBD - IeqBS) * m_polarity; + + nl_fptype gGG = nlconst::zero(); + nl_fptype gGD = nlconst::zero(); + nl_fptype gGS = nlconst::zero(); + nl_fptype gGB = nlconst::zero(); + + nl_fptype gDG = gm; + nl_fptype gDD = gds + gbd - gate_drain; + const nl_fptype gDS = -gds - gate_source; + const nl_fptype gDB = gmb - gbd; + + nl_fptype gSG = -gm; + const nl_fptype gSD = -gds + gate_drain; + nl_fptype gSS = gbs + gds + gate_source; + const nl_fptype gSB = -gbs - gmb; + + nl_fptype gBG = nlconst::zero(); + const nl_fptype gBD = -gbd; + const nl_fptype gBS = -gbs; + nl_fptype gBB = gbs + gbd; + + if (m_capacitor_model != 0) { - const nl_double Vgb = Vgs - Vbs; + const nl_fptype Vgb = Vgs - Vbs; if (is_forward) calculate_caps(Vgs, Vgd, Vth, m_Cgs, m_Cgd, m_Cgb); else calculate_caps(Vgd, Vgs, Vth, m_Cgd, m_Cgs, m_Cgb); - set_cap(m_cap_gb, m_Cgb + m_model.m_CGBO * m_Leff, Vgb, gGG, gGB, gBG, gBB, IG, IB); - set_cap(m_cap_gs, m_Cgs + m_model.m_CGSO * m_model.m_W, Vgs, gGG, gGS, gSG, gSS, IG, IS); - set_cap(m_cap_gd, m_Cgd + m_model.m_CGDO * m_model.m_W, Vgd, gGG, gGD, gDG, gDD, IG, ID); + set_cap(m_cap_gb, m_Cgb + m_model_acc.m_CGBO * m_Leff, Vgb, gGG, + gGB, gBG, gBB, IG, IB); + set_cap(m_cap_gs, m_Cgs + m_model_acc.m_CGSO * m_model_acc.m_W, Vgs, + gGG, gGS, gSG, gSS, IG, IS); + set_cap(m_cap_gd, m_Cgd + m_model_acc.m_CGDO * m_model_acc.m_W, Vgd, + gGG, gGD, gDG, gDD, IG, ID); } // Source connected to body, Diode S-B shorted! - const nl_double gSSBB = gSS + gBB + gBS + gSB; - + const nl_fptype gSSBB = gSS + gBB + gBS + gSB; + const auto zero(nlconst::zero()); // S G - m_SG.set_mat( gSSBB, gSG + gBG, +(IS + IB), // S - gGS + gGB, gGG, IG ); // 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 + m_DG.set_mat(gDD, gDG, +ID, // D + gGD, zero, zero); // G // S D - m_SD.set_mat( 0.0, gSD + gBD, 0.0, // S - gDS + gDB, 0.0, 0.0); // D + m_SD.set_mat(zero, gSD + gBD, zero, // S + gDS + gDB, zero, zero); // D + + /// | + /// | D S G I + /// | + /// | D gDD gDS + gDB gDG ID + /// + /// | S gSD + gBD gSSBB gSG + gBG IS+IB + /// + /// | G gGD gGS + gGB gGG IG + /// | + /// | forward=yes, bulk diode=no, backgate transconductance=no + /// | IG = 0, gGG = 0, gGS, gGB =0, gGD=0l + /// gDD=gds + gbd=gds + /// gSD+gBD=-gds + gmb - gbd = -gds + /// + /// | D S G I + /// | + /// | D gDD gDS + gDB gDG ID + /// + /// | S gSD + gBD gSSBB gSG + gBG IS+IB + /// + /// | G 0 0 0 0 + /// | + /// | + /// | + /// | + /// | + /// | } - NETLIB_UPDATE_PARAM(MOSFET) - { - } + NETLIB_UPDATE_PARAM(MOSFET) {} -} // namespace analog +} // namespace netlist::analog -namespace devices { +namespace netlist::devices +{ NETLIB_DEVICE_IMPL_NS(analog, MOSFET, "MOSFET", "MODEL") -} // namespace devices - -} // namespace netlist +} // namespace netlist::devices diff --git a/src/lib/netlist/analog/nld_mosfet.h b/src/lib/netlist/analog/nld_mosfet.h deleted file mode 100644 index 32011d709d8..00000000000 --- a/src/lib/netlist/analog/nld_mosfet.h +++ /dev/null @@ -1,21 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud -/* - * nld_mosfet.h - * - */ - -#ifndef NLD_MOSFET_H_ -#define NLD_MOSFET_H_ - -#include "netlist/nl_setup.h" - -// ----------------------------------------------------------------------------- -// Macros -// ----------------------------------------------------------------------------- - -#define MOSFET(name, model) \ - NET_REGISTER_DEV(MOSFET, name) \ - NETDEV_PARAMI(name, MODEL, model) - -#endif /* NLD_MOSFET_H_ */ diff --git a/src/lib/netlist/analog/nld_opamps.cpp b/src/lib/netlist/analog/nld_opamps.cpp index 24f239e9455..b7e4ebfa762 100644 --- a/src/lib/netlist/analog/nld_opamps.cpp +++ b/src/lib/netlist/analog/nld_opamps.cpp @@ -1,128 +1,133 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_opamps.c - * - */ - -#include "nld_opamps.h" -#include "netlist/nl_base.h" -#include "netlist/nl_errstr.h" + +#include "nl_base.h" +#include "nl_errstr.h" #include "nlid_fourterm.h" #include "nlid_twoterm.h" -#include <cmath> +// +// Set to 1 to model output impedance as a series resistor. +// The default is that the VCVS already has an internal impedance. +// This needs more investigation. +// +#define TEST_ALT_OUTPUT (0) namespace netlist { namespace analog { - /* - * Type = 0: Impedance changer - * 1; Idealized opamp - * 2; opamp with first pole - * 3: opamp with first pole + output limit - * 4: opamp with input stage, first pole + output limit - * - * Type 1 parameters: - * FPF = frequency of first pole in Hz (ony used for open-loop gain) - * UGF = unity gain frequency in Hz (only used for open-loop gain) - * RI = input resistance in Ohms - * RO = output resistance in Ohms - * - * Type 3 parameters: - * VLH = high supply rail minus high output swing in V - * VLL = low output swing minus low supply rail in V - * FPF = frequency of first pole in Hz - * UGF = unity gain frequency (transition frequency) in Hz - * SLEW = unity gain slew rate in V/s - * RI = input resistance in Ohms - * RO = output resistance in Ohms - * DAB = Differential Amp Bias ~ op amp's total quiescent current. - * - * .model abc OPAMP(VLH=2.0 VLL=0.2 FPF=5 UGF=10k SLEW=0.6u RI=1000k RO=50 DAB=0.002) - * - * http://www.ecircuitcenter.com/Circuits/opmodel1/opmodel1.htm - * - * */ - - /*! Class representing the opamp model parameters. - * The opamp model was designed based on designs from - * http://www.ecircuitcenter.com/Circuits/opmodel1/opmodel1.htm. - * Currently 2 different types are supported: Type 1 and Type 3. Type 1 - * is less complex and should run faster than Type 3. - * - * This is an extension to the traditional SPICE approach which - * assumes that you will be using an manufacturer model. These models may - * have copyrights incompatible with the netlist license. Thus they may not - * be suitable for certain implementations of netlist. - * - * For the typical use cases in low frequency (< 100 KHz) applications at - * which netlist is targeted, this model is certainly suitable. All parameters - * can be determined from a typical opamp datasheet. - * - * |Type|name |parameter |units|default| example| - * |:--:|:-----|:----------------------------------------------|:----|------:|-------:| - * | 3 |TYPE |Model Type, 1 and 3 are supported | | | | - * |1,3 |FPF |frequency of first pole |Hz | |100 | - * | 3 |SLEW |unity gain slew rate |V/s | | 1| - * |1,3 |RI |input resistance |Ohm | |1M | - * |1,3 |RO |output resistance |Ohm | |50 | - * |1,3 |UGF |unity gain frequency (transition frequency) |Hz | |1000 | - * | 3 |VLL |low output swing minus low supply rail |V | |1.5 | - * | 3 |VLH |high supply rail minus high output swing |V | |1.5 | - * | 3 |DAB |Differential Amp Bias - total quiescent current|A | |0.001 | - */ - - class opamp_model_t : public param_model_t + + /// \brief Class representing the opamp model parameters. + /// + /// The opamp model was designed based on designs from + /// http://www.ecircuitcenter.com/Circuits/opmodel1/opmodel1.htm. + /// Currently 2 different types are supported: Type 1 and Type 3. Type 1 + /// is less complex and should run faster than Type 3. + /// + /// This is an extension to the traditional SPICE approach which + /// assumes that you will be using an manufacturer model. These models may + /// have copyrights incompatible with the netlist license. Thus they may not + /// be suitable for certain implementations of netlist. + /// + /// For the typical use cases in low frequency (< 100 KHz) applications at + /// which netlist is targeted, this model is certainly suitable. All parameters + /// can be determined from a typical opamp datasheet. + /// + /// |Type|name |parameter |units|default| example| + /// |:--:|:-----|:----------------------------------------------|:----|------:|-------:| + /// | 3 |TYPE |Model Type, 1 and 3 are supported | | | | + /// |1,3 |FPF |frequency of first pole |Hz | |100 | + /// | 3 |SLEW |unity gain slew rate |V/s | | 1| + /// |1,3 |RI |input resistance |Ohm | |1M | + /// |1,3 |RO |output resistance |Ohm | |50 | + /// |1,3 |UGF |unity gain frequency (transition frequency) |Hz | |1000 | + /// | 3 |VLL |low output swing minus low supply rail |V | |1.5 | + /// | 3 |VLH |high supply rail minus high output swing |V | |1.5 | + /// | 3 |DAB |Differential Amp Bias - total quiescent current|A | |0.001 | + /// + /// + /// Type = 0: Impedance changer + /// 1; Idealized opamp + /// 2; opamp with first pole + /// 3: opamp with first pole + output limit + /// 4: opamp with input stage, first pole + output limit + /// + /// Type 1 parameters: + /// FPF = frequency of first pole in Hz (ony used for open-loop gain) + /// UGF = unity gain frequency in Hz (only used for open-loop gain) + /// RI = input resistance in Ohms + /// RO = output resistance in Ohms + /// + /// Type 3 parameters: + /// VLH = high supply rail minus high output swing in V + /// VLL = low output swing minus low supply rail in V + /// FPF = frequency of first pole in Hz + /// UGF = unity gain frequency (transition frequency) in Hz + /// SLEW = unity gain slew rate in V/s + /// RI = input resistance in Ohms + /// RO = output resistance in Ohms + /// DAB = Differential Amp Bias ~ op amp's total quiescent current. + /// + /// .model abc OPAMP(VLH=2.0 VLL=0.2 FPF=5 UGF=10k SLEW=0.6u RI=1000k RO=50 DAB=0.002) + /// + /// http://www.ecircuitcenter.com/Circuits/opmodel1/opmodel1.htm + /// + /// + class opamp_model_t { public: - opamp_model_t(device_t &device, const pstring &name, const pstring &val) - : param_model_t(device, name, val) - , m_TYPE(*this, "TYPE") - , m_FPF(*this, "FPF") - , m_SLEW(*this, "SLEW") - , m_RI(*this, "RI") - , m_RO(*this, "RO") - , m_UGF(*this, "UGF") - , m_VLL(*this, "VLL") - , m_VLH(*this, "VLH") - , m_DAB(*this, "DAB") + opamp_model_t(param_model_t &model) + : m_TYPE(model, "TYPE") + , m_FPF(model, "FPF") + , m_SLEW(model, "SLEW") + , m_RI(model, "RI") + , m_RO(model, "RO") + , m_UGF(model, "UGF") + , m_VLL(model, "VLL") + , m_VLH(model, "VLH") + , m_DAB(model, "DAB") {} - value_t m_TYPE; //!< Model Type, 1 and 3 are supported - value_t m_FPF; //!< frequency of first pole - value_t m_SLEW; //!< unity gain slew rate - value_t m_RI; //!< input resistance - value_t m_RO; //!< output resistance - value_t m_UGF; //!< unity gain frequency (transition frequency) - value_t m_VLL; //!< low output swing minus low supply rail - value_t m_VLH; //!< high supply rail minus high output swing - value_t m_DAB; //!< Differential Amp Bias - total quiescent current + param_model_t::value_t m_TYPE; //!< Model Type, 1 and 3 are supported + param_model_t::value_t m_FPF; //!< frequency of first pole + param_model_t::value_t m_SLEW; //!< unity gain slew rate + param_model_t::value_t m_RI; //!< input resistance + param_model_t::value_t m_RO; //!< output resistance + param_model_t::value_t m_UGF; //!< unity gain frequency (transition frequency) + param_model_t::value_t m_VLL; //!< low output swing minus low supply rail + param_model_t::value_t m_VLH; //!< high supply rail minus high output swing + param_model_t::value_t m_DAB; //!< Differential Amp Bias - total quiescent current }; - NETLIB_OBJECT(opamp) + class nld_opamp : public base_device_t { - NETLIB_CONSTRUCTOR(opamp) + public: + nld_opamp(constructor_param_t data) + : base_device_t(data) , m_RP(*this, "RP1") , m_G1(*this, "G1") - , m_VCC(*this, "VCC") - , m_GND(*this, "GND") + , m_VCC(*this, "VCC", NETLIB_DELEGATE(supply)) + , m_GND(*this, "GND", NETLIB_DELEGATE(supply)) , m_model(*this, "MODEL", "LM324") + , m_modacc(m_model) , m_VH(*this, "VH") , m_VL(*this, "VL") , m_VREF(*this, "VREF") + , m_type(plib::narrow_cast<int>(m_modacc.m_TYPE)) { - m_type = static_cast<int>(m_model.m_TYPE); if (m_type < 1 || m_type > 3) + { log().fatal(MF_OPAMP_UNKNOWN_TYPE(m_type)); + throw nl_exception(MF_OPAMP_UNKNOWN_TYPE(m_type)); + } if (m_type == 1) { - register_subalias("PLUS", "G1.IP"); - register_subalias("MINUS", "G1.IN"); - register_subalias("OUT", "G1.OP"); + register_sub_alias("PLUS", "G1.IP"); + register_sub_alias("MINUS", "G1.IN"); + register_sub_alias("OUT", "G1.OP"); connect("G1.ON", "VREF"); connect("RP1.2", "VREF"); @@ -131,11 +136,13 @@ namespace netlist } if (m_type == 2 || m_type == 3) { - create_and_register_subdevice("CP1", m_CP); - create_and_register_subdevice("EBUF", m_EBUF); - - register_subalias("PLUS", "G1.IP"); - register_subalias("MINUS", "G1.IN"); + create_and_register_sub_device(*this, "CP1", m_CP); + create_and_register_sub_device(*this, "EBUF", m_EBUF); +#if TEST_ALT_OUTPUT + create_and_register_sub_device("RO", m_RO); +#endif + register_sub_alias("PLUS", "G1.IP"); + register_sub_alias("MINUS", "G1.IN"); connect("G1.ON", "VREF"); connect("RP1.2", "VREF"); @@ -150,97 +157,120 @@ namespace netlist } if (m_type == 2) { - register_subalias("OUT", "EBUF.OP"); +#if TEST_ALT_OUTPUT + connect("EBUF.OP", "RO.1"); + register_sub_alias("OUT", "RO.2"); +#else + register_sub_alias("OUT", "EBUF.OP"); +#endif } if (m_type == 3) { - create_and_register_subdevice("DN", m_DN, "D(IS=1e-15 N=1)"); - create_and_register_subdevice("DP", m_DP, "D(IS=1e-15 N=1)"); + create_and_register_sub_device(*this, "DN", m_DN, "D(IS=1e-15 N=1)"); + create_and_register_sub_device(*this, "DP", m_DP, "D(IS=1e-15 N=1)"); connect("DP.K", "VH"); connect("VL", "DN.A"); connect("DP.A", "DN.K"); connect("DN.K", "RP1.1"); - - register_subalias("OUT", "EBUF.OP"); +#if TEST_ALT_OUTPUT + connect("EBUF.OP", "RO.1"); + register_sub_alias("OUT", "RO.2"); +#else + register_sub_alias("OUT", "EBUF.OP"); +#endif } } - NETLIB_UPDATEI(); + NETLIB_HANDLERI(supply) + { + const nl_fptype cVt = nlconst::np_VT(nlconst::one()); // * m_n; + const nl_fptype cId = m_modacc.m_DAB; // 3 mA + const nl_fptype cVd = cVt * plib::log(cId / nlconst::np_Is() + nlconst::one()); + + m_VH.push(m_VCC() - m_modacc.m_VLH - cVd); + m_VL.push(m_GND() + m_modacc.m_VLL + cVd); + m_VREF.push((m_VCC() + m_GND()) / nlconst::two()); + } + NETLIB_RESETI() { } + NETLIB_UPDATE_PARAMI(); private: - analog::NETLIB_SUB(R_base) m_RP; - analog::NETLIB_SUB(VCCS) m_G1; - NETLIB_SUBXX(analog, C) m_CP; - NETLIB_SUBXX(analog, VCVS) m_EBUF; - NETLIB_SUBXX(analog, D) m_DP; - NETLIB_SUBXX(analog, D) m_DN; + NETLIB_SUB_NS(analog, R_base) m_RP; + NETLIB_SUB_NS(analog, VCCS) m_G1; + NETLIB_SUB_UPTR(analog, C) m_CP; +#if TEST_ALT_OUTPUT + NETLIB_SUB_UPTR(analog, R_base) m_RO; +#endif + NETLIB_SUB_UPTR(analog, VCVS) m_EBUF; + NETLIB_SUB_UPTR(analog, D) m_DP; + NETLIB_SUB_UPTR(analog, D) m_DN; analog_input_t m_VCC; analog_input_t m_GND; - opamp_model_t m_model; + param_model_t m_model; + opamp_model_t m_modacc; analog_output_t m_VH; analog_output_t m_VL; analog_output_t m_VREF; - /* state */ + // state int m_type; }; - NETLIB_UPDATE(opamp) - { - const double cVt = 0.0258 * 1.0; // * m_n; - const double cId = m_model.m_DAB; // 3 mA - const double cVd = cVt * std::log(cId / 1e-15 + 1.0); - - m_VH.push(m_VCC() - m_model.m_VLH - cVd); - m_VL.push(m_GND() + m_model.m_VLL + cVd); - m_VREF.push((m_VCC() + m_GND()) / 2.0); - } - NETLIB_UPDATE_PARAM(opamp) { - m_G1.m_RI.setTo(m_model.m_RI); + m_G1().m_RI.set(m_modacc.m_RI); if (m_type == 1) { - double RO = m_model.m_RO; - double G = m_model.m_UGF / m_model.m_FPF / RO; - m_RP.set_R(RO); - m_G1.m_G.setTo(G); + nl_fptype RO = m_modacc.m_RO; + nl_fptype G = m_modacc.m_UGF / m_modacc.m_FPF / RO; + m_RP().set_R(RO); + m_G1().m_G.set(G); } if (m_type == 3 || m_type == 2) { - double CP = m_model.m_DAB / m_model.m_SLEW; - double RP = 0.5 / constants::pi() / CP / m_model.m_FPF; - double G = m_model.m_UGF / m_model.m_FPF / RP; + nl_fptype CP = m_modacc.m_DAB / m_modacc.m_SLEW; + nl_fptype RP = nlconst::half() / nlconst::pi() / CP / m_modacc.m_FPF; + nl_fptype G = m_modacc.m_UGF / m_modacc.m_FPF / RP; //printf("OPAMP %s: %g %g %g\n", name().c_str(), CP, RP, G); - if (m_model.m_SLEW / (4.0 * constants::pi() * 0.0258) < m_model.m_UGF) + if (m_modacc.m_SLEW / (nlconst::four() * nlconst::pi() * nlconst::np_VT()) < m_modacc.m_UGF) log().warning(MW_OPAMP_FAIL_CONVERGENCE(this->name())); - m_CP->m_C.setTo(CP); - m_RP.set_R(RP); - m_G1.m_G.setTo(G); + m_CP->set_cap_embedded(CP); + m_RP().set_R(RP); + m_G1().m_G.set(G); } if (m_type == 2) { - m_EBUF->m_G.setTo(1.0); - m_EBUF->m_RO.setTo(m_model.m_RO); + m_EBUF->m_G.set(nlconst::one()); +#if TEST_ALT_OUTPUT + m_EBUF->m_RO.set(0.001); + m_RO->set_R(m_modacc.m_RO); +#else + m_EBUF->m_RO.set(m_modacc.m_RO); +#endif } if (m_type == 3) { - m_EBUF->m_G.setTo(1.0); - m_EBUF->m_RO.setTo(m_model.m_RO); + m_EBUF->m_G.set(nlconst::one()); +#if TEST_ALT_OUTPUT + m_EBUF->m_RO.set(0.001); + m_RO->set_R(m_modacc.m_RO); +#else + m_EBUF->m_RO.set(m_modacc.m_RO); +#endif } } diff --git a/src/lib/netlist/analog/nld_opamps.h b/src/lib/netlist/analog/nld_opamps.h deleted file mode 100644 index 4a07f646587..00000000000 --- a/src/lib/netlist/analog/nld_opamps.h +++ /dev/null @@ -1,24 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud -/* - * nld_opamps.h - * - */ - -//#pragma once - -#ifndef NLD_OPAMPS_H_ -#define NLD_OPAMPS_H_ - -#include "netlist/nl_setup.h" - -// ---------------------------------------------------------------------------------------- -// Macros -// ---------------------------------------------------------------------------------------- - -#define OPAMP(name, model) \ - NET_REGISTER_DEV(OPAMP, name) \ - NETDEV_PARAMI(name, MODEL, model) - - -#endif /* NLD_OPAMPS_H_ */ diff --git a/src/lib/netlist/analog/nld_switches.cpp b/src/lib/netlist/analog/nld_switches.cpp index 0e62fe13952..6b87279e398 100644 --- a/src/lib/netlist/analog/nld_switches.cpp +++ b/src/lib/netlist/analog/nld_switches.cpp @@ -1,19 +1,15 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_legacy.c - * - */ #include "nlid_twoterm.h" -#include "netlist/nl_base.h" -#include "netlist/nl_factory.h" -#include "netlist/solver/nld_solver.h" +#include "nl_base.h" +#include "nl_factory.h" +#include "solver/nld_solver.h" -/* FIXME : convert to parameters */ +// FIXME : convert to parameters -#define R_OFF (1.0 / exec().gmin()) -#define R_ON 0.01 +#define R_OFF (plib::reciprocal(exec().gmin())) +#define R_ON nlconst::magic(0.01) namespace netlist { @@ -23,83 +19,72 @@ namespace netlist // SWITCH // ---------------------------------------------------------------------------------------- - NETLIB_OBJECT(switch1) + class nld_switch1 : public base_device_t { - NETLIB_CONSTRUCTOR(switch1) + public: + nld_switch1(constructor_param_t data) + : base_device_t(data) , m_R(*this, "R") , m_POS(*this, "POS", false) { - register_subalias("1", m_R.m_P); - register_subalias("2", m_R.m_N); + register_sub_alias("1", m_R().P()); + register_sub_alias("2", m_R().N()); } - NETLIB_RESETI(); - NETLIB_UPDATEI(); - NETLIB_UPDATE_PARAMI(); - - analog::NETLIB_SUB(R_base) m_R; - param_logic_t m_POS; - }; - - - NETLIB_RESET(switch1) - { - m_R.set_R(R_OFF); - } - - NETLIB_UPDATE(switch1) - { - } - - NETLIB_UPDATE_PARAM(switch1) - { - m_R.solve_now(); - if (!m_POS()) + NETLIB_RESETI() { - m_R.set_R(R_OFF); + m_R().set_R(R_OFF); } - else + NETLIB_UPDATE_PARAMI() { - m_R.set_R(R_ON); + m_R().change_state([this]() + { + m_R().set_R(m_POS() ? R_ON : R_OFF); + }); } - m_R.solve_later(); - } + private: + NETLIB_SUB_NS(analog, R_base) m_R; + param_logic_t m_POS; + }; // ---------------------------------------------------------------------------------------- // SWITCH2 // ---------------------------------------------------------------------------------------- - NETLIB_OBJECT(switch2) + class nld_switch2 : public base_device_t { - NETLIB_CONSTRUCTOR(switch2) + public: + nld_switch2(constructor_param_t data) + : base_device_t(data) , m_R1(*this, "R1") , m_R2(*this, "R2") , m_POS(*this, "POS", false) { - connect(m_R1.m_N, m_R2.m_N); + connect(m_R1().N(), m_R2().N()); - register_subalias("1", m_R1.m_P); - register_subalias("2", m_R2.m_P); + register_sub_alias("1", m_R1().P()); + register_sub_alias("2", m_R2().P()); - register_subalias("Q", m_R1.m_N); + register_sub_alias("Q", m_R1().N()); } NETLIB_RESETI(); - NETLIB_UPDATEI(); NETLIB_UPDATE_PARAMI(); - analog::NETLIB_SUB(R_base) m_R1; - analog::NETLIB_SUB(R_base) m_R2; - param_logic_t m_POS; + private: + NETLIB_SUB_NS(analog, R_base) m_R1; + NETLIB_SUB_NS(analog, R_base) m_R2; + param_logic_t m_POS; }; NETLIB_RESET(switch2) { - m_R1.set_R(R_ON); - m_R2.set_R(R_OFF); + m_R1().set_R(R_ON); + m_R2().set_R(R_OFF); } +#ifdef FIXMELATER NETLIB_UPDATE(switch2) { if (!m_POS()) @@ -112,26 +97,23 @@ namespace netlist m_R1.set_R(R_OFF); m_R2.set_R(R_ON); } - - //m_R1.update_dev(time); - //m_R2.update_dev(time); } - +#endif NETLIB_UPDATE_PARAM(switch2) { - if (!m_POS()) - { - m_R1.set_R(R_ON); - m_R2.set_R(R_OFF); - } + // R1 and R2 are connected. However this net may be a rail net. + // The code here thus is a bit more complex. + + nl_fptype r1 = m_POS() ? R_OFF : R_ON; + nl_fptype r2 = m_POS() ? R_ON : R_OFF; + + if (m_R1().solver() == m_R2().solver()) + m_R1().change_state([this, &r1, &r2]() { m_R1().set_R(r1); m_R2().set_R(r2); }); else { - m_R1.set_R(R_OFF); - m_R2.set_R(R_ON); + m_R1().change_state([this, &r1]() { m_R1().set_R(r1); }); + m_R2().change_state([this, &r2]() { m_R2().set_R(r2); }); } - - m_R1.solve_now(); - m_R2.solve_now(); } } //namespace analog diff --git a/src/lib/netlist/analog/nld_switches.h b/src/lib/netlist/analog/nld_switches.h deleted file mode 100644 index 7aa31c925ae..00000000000 --- a/src/lib/netlist/analog/nld_switches.h +++ /dev/null @@ -1,25 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud -/* - * nld_switches.h - * - */ - -#pragma once - -#ifndef NLD_SWITCHES_H_ -#define NLD_SWITCHES_H_ - -#include "netlist/nl_setup.h" - -// ---------------------------------------------------------------------------------------- -// Macros -// ---------------------------------------------------------------------------------------- - -#define SWITCH(name) \ - NET_REGISTER_DEV(SWITCH, name) - -#define SWITCH2(name) \ - NET_REGISTER_DEV(SWITCH2, name) - -#endif /* NLD_SWITCHES_H_ */ diff --git a/src/lib/netlist/analog/nld_twoterm.h b/src/lib/netlist/analog/nld_twoterm.h deleted file mode 100644 index b175f40af97..00000000000 --- a/src/lib/netlist/analog/nld_twoterm.h +++ /dev/null @@ -1,67 +0,0 @@ -// license:GPL-2.0+ -// copyright-holders:Couriersud - -#ifndef NLD_TWOTERM_H_ -#define NLD_TWOTERM_H_ - -#include "netlist/nl_setup.h" - -// ----------------------------------------------------------------------------- -// Macros -// ----------------------------------------------------------------------------- - -#define RES(name, p_R) \ - NET_REGISTER_DEV(RES, name) \ - NETDEV_PARAMI(name, R, p_R) - -#define POT(name, p_R) \ - NET_REGISTER_DEV(POT, name) \ - NETDEV_PARAMI(name, R, p_R) - -/* Does not have pin 3 connected */ -#define POT2(name, p_R) \ - NET_REGISTER_DEV(POT2, name) \ - NETDEV_PARAMI(name, R, p_R) - - -#define CAP(name, p_C) \ - NET_REGISTER_DEV(CAP, name) \ - NETDEV_PARAMI(name, C, p_C) - -#define IND(name, p_L) \ - NET_REGISTER_DEV(IND, name) \ - NETDEV_PARAMI(name, L, p_L) - -/* Generic Diode */ -#define DIODE(name, model) \ - NET_REGISTER_DEV(DIODE, name) \ - NETDEV_PARAMI(name, MODEL, model) - -#define VS(name, pV) \ - NET_REGISTER_DEV(VS, name) \ - NETDEV_PARAMI(name, V, pV) - -#define CS(name, pI) \ - NET_REGISTER_DEV(CS, name) \ - NETDEV_PARAMI(name, I, pI) - -// ----------------------------------------------------------------------------- -// Generic macros -// ----------------------------------------------------------------------------- - -#ifdef RES_R -#warning "Do not include rescap.h in a netlist environment" -#endif -#ifndef RES_R -#define RES_R(res) (static_cast<double>(res)) -#define RES_K(res) (static_cast<double>(res) * 1e3) -#define RES_M(res) (static_cast<double>(res) * 1e6) -#define CAP_U(cap) (static_cast<double>(cap) * 1e-6) -#define CAP_N(cap) (static_cast<double>(cap) * 1e-9) -#define CAP_P(cap) (static_cast<double>(cap) * 1e-12) -#define IND_U(ind) (static_cast<double>(ind) * 1e-6) -#define IND_N(ind) (static_cast<double>(ind) * 1e-9) -#define IND_P(ind) (static_cast<double>(ind) * 1e-12) -#endif - -#endif /* NLD_TWOTERM_H_ */ diff --git a/src/lib/netlist/analog/nlid_fourterm.cpp b/src/lib/netlist/analog/nlid_fourterm.cpp index 7fa6a2f3809..e12a931c142 100644 --- a/src/lib/netlist/analog/nlid_fourterm.cpp +++ b/src/lib/netlist/analog/nlid_fourterm.cpp @@ -1,124 +1,129 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_fourterm.c - * - */ -#include "netlist/solver/nld_solver.h" -#include "netlist/nl_factory.h" #include "nlid_fourterm.h" -#include <cmath> +#include "nl_factory.h" -namespace netlist +#include "solver/nld_solver.h" + +namespace netlist::analog { - namespace analog + + // ------------------------------------------------------------------------- + // nld_VCCS + // ------------------------------------------------------------------------- + + NETLIB_RESET(VCCS) { + const nl_fptype m_mult = m_G() * m_gfac; // 1.0 ==> 1V ==> 1A + const nl_fptype GI = plib::reciprocal(m_RI()); + m_IP.set_conductivity(GI); + m_IN.set_conductivity(GI); -// ---------------------------------------------------------------------------------------- -// nld_VCCS -// ---------------------------------------------------------------------------------------- + m_OP.set_go_gt(-m_mult, nlconst::zero()); + m_OP1.set_go_gt(m_mult, nlconst::zero()); -NETLIB_RESET(VCCS) -{ - const nl_double m_mult = m_G() * m_gfac; // 1.0 ==> 1V ==> 1A - const nl_double GI = plib::constants<nl_double>::one() / m_RI(); + m_ON.set_go_gt(m_mult, nlconst::zero()); + m_ON1.set_go_gt(-m_mult, nlconst::zero()); + } - m_IP.set_conductivity(GI); - m_IN.set_conductivity(GI); + NETLIB_HANDLER(VCCS, terminal_handler) + { + solver::matrix_solver_t *solv = nullptr; + // only called if connected to a rail net ==> notify the solver to + // recalculate + // NOLINTNEXTLINE(bugprone-branch-clone) + if ((solv = m_IP.solver()) != nullptr) + solv->solve_now(); + else if ((solv = m_IN.solver()) != nullptr) + solv->solve_now(); + else if ((solv = m_OP.solver()) != nullptr) + solv->solve_now(); + else if ((solv = m_ON.solver()) != nullptr) + solv->solve_now(); + } + + // ------------------------------------------------------------------------- + // nld_LVCCS + // ------------------------------------------------------------------------- + + NETLIB_RESET(LVCCS) { NETLIB_NAME(VCCS)::reset(); } + + NETLIB_UPDATE_PARAM(LVCCS) { NETLIB_NAME(VCCS)::update_param(); } + + NETLIB_UPDATE_TERMINALS(LVCCS) + { + const nl_fptype m_mult = m_G() * get_gfac(); // 1.0 ==> 1V ==> 1A + const nl_fptype vi = m_IP.net().Q_Analog() - m_IN.net().Q_Analog(); + const auto c1(nlconst::magic(0.2)); - m_OP.set_go_gt(-m_mult, plib::constants<nl_double>::zero()); - m_OP1.set_go_gt(m_mult, plib::constants<nl_double>::zero()); + if (plib::abs(m_mult / m_cur_limit() * vi) > nlconst::half()) + m_vi = m_vi + c1 * plib::tanh((vi - m_vi) / c1); + else + m_vi = vi; - m_ON.set_go_gt(m_mult, plib::constants<nl_double>::zero()); - m_ON1.set_go_gt(-m_mult, plib::constants<nl_double>::zero()); -} + const nl_fptype x = m_mult / m_cur_limit() * m_vi; + const nl_fptype tanhx = plib::tanh(x); -NETLIB_UPDATE(VCCS) -{ - /* only called if connected to a rail net ==> notify the solver to recalculate */ - if (!m_IP.net().isRailNet()) - m_IP.solve_now(); - else if (!m_IN.net().isRailNet()) - m_IN.solve_now(); - else if (!m_OP.net().isRailNet()) - m_OP.solve_now(); - else if (!m_ON.net().isRailNet()) - m_ON.solve_now(); -} - -// ---------------------------------------------------------------------------------------- -// nld_LVCCS -// ---------------------------------------------------------------------------------------- - -NETLIB_RESET(LVCCS) -{ - NETLIB_NAME(VCCS)::reset(); -} + const nl_fptype beta = m_mult * (nlconst::one() - tanhx * tanhx); + const nl_fptype I = m_cur_limit() * tanhx - beta * m_vi; -NETLIB_UPDATE_PARAM(LVCCS) -{ - NETLIB_NAME(VCCS)::update_param(); -} + m_OP.set_go_gt_I(-beta, nlconst::zero(), I); + m_OP1.set_go_gt(beta, nlconst::zero()); -NETLIB_UPDATE_TERMINALS(LVCCS) -{ - const nl_double m_mult = m_G() * m_gfac; // 1.0 ==> 1V ==> 1A - const nl_double vi = m_IP.net().Q_Analog() - m_IN.net().Q_Analog(); + m_ON.set_go_gt_I(beta, nlconst::zero(), -I); + m_ON1.set_go_gt(-beta, nlconst::zero()); + } - if (std::abs(m_mult / m_cur_limit() * vi) > 0.5) - m_vi = m_vi + 0.2*std::tanh((vi - m_vi)/0.2); - else - m_vi = vi; + // ------------------------------------------------------------------------- + // nld_CCCS + // ------------------------------------------------------------------------- - const nl_double x = m_mult / m_cur_limit() * m_vi; - const nl_double X = std::tanh(x); + NETLIB_RESET(CCCS) { NETLIB_NAME(VCCS)::reset(); } - const nl_double beta = m_mult * (1.0 - X*X); - const nl_double I = m_cur_limit() * X - beta * m_vi; + NETLIB_UPDATE_PARAM(CCCS) { NETLIB_NAME(VCCS)::update_param(); } - m_OP.set_go_gt_I(-beta, plib::constants<nl_double>::zero(), I); - m_OP1.set_go_gt(beta, plib::constants<nl_double>::zero()); + // ------------------------------------------------------------------------- + // nld_VCVS + // ------------------------------------------------------------------------- - m_ON.set_go_gt_I(beta, plib::constants<nl_double>::zero(), -I); - m_ON1.set_go_gt(-beta, plib::constants<nl_double>::zero()); -} + NETLIB_RESET(VCVS) + { + const auto gfac(plib::reciprocal(m_RO())); + set_gfac(gfac); -// ---------------------------------------------------------------------------------------- -// nld_CCCS -// ---------------------------------------------------------------------------------------- + NETLIB_NAME(VCCS)::reset(); -NETLIB_RESET(CCCS) -{ - NETLIB_NAME(VCCS)::reset(); -} + m_OP2.set_conductivity(gfac); + m_ON2.set_conductivity(gfac); + } -NETLIB_UPDATE_PARAM(CCCS) -{ - NETLIB_NAME(VCCS)::update_param(); -} + // ------------------------------------------------------------------------- + // nld_CCVS + // ------------------------------------------------------------------------- + + NETLIB_RESET(CCVS) + { + const auto gfac(plib::reciprocal(m_RO())); + set_gfac(gfac); + + NETLIB_NAME(VCCS)::reset(); + + m_OP2.set_conductivity(gfac); + m_ON2.set_conductivity(gfac); + } -// ---------------------------------------------------------------------------------------- -// nld_VCVS -// ---------------------------------------------------------------------------------------- +} // namespace netlist::analog -NETLIB_RESET(VCVS) +namespace netlist::devices { - m_gfac = plib::constants<nl_double>::one() / m_RO(); - NETLIB_NAME(VCCS)::reset(); - - m_OP2.set_conductivity(plib::constants<nl_double>::one() / m_RO()); - m_ON2.set_conductivity(plib::constants<nl_double>::one() / m_RO()); -} - - } //namespace analog - - namespace devices { - NETLIB_DEVICE_IMPL_NS(analog, VCVS, "VCVS", "") - NETLIB_DEVICE_IMPL_NS(analog, VCCS, "VCCS", "") - NETLIB_DEVICE_IMPL_NS(analog, CCCS, "CCCS", "") - NETLIB_DEVICE_IMPL_NS(analog, LVCCS, "LVCCS", "") - } // namespace devices -} // namespace netlist + // clang-format off + NETLIB_DEVICE_IMPL_NS(analog, VCVS, "VCVS", "G") + NETLIB_DEVICE_IMPL_NS(analog, VCCS, "VCCS", "G") + NETLIB_DEVICE_IMPL_NS(analog, CCCS, "CCCS", "G") + NETLIB_DEVICE_IMPL_NS(analog, CCVS, "CCVS", "G") + NETLIB_DEVICE_IMPL_NS(analog, LVCCS, "LVCCS", "") + // clang-format on +} // namespace netlist::devices diff --git a/src/lib/netlist/analog/nlid_fourterm.h b/src/lib/netlist/analog/nlid_fourterm.h index 5ca6a747116..1320f97984b 100644 --- a/src/lib/netlist/analog/nlid_fourterm.h +++ b/src/lib/netlist/analog/nlid_fourterm.h @@ -1,71 +1,74 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nlid_fourterm.h - * - */ #ifndef NLID_FOURTERM_H_ #define NLID_FOURTERM_H_ -#include "netlist/nl_base.h" +/// +/// \file nlid_fourterm.h +/// + +#include "nl_base.h" + #include "plib/putil.h" -namespace netlist { - namespace analog { +namespace netlist::analog +{ // ---------------------------------------------------------------------------------------- // nld_VCCS // ---------------------------------------------------------------------------------------- - /* - * Voltage controlled current source - * - * IP ---+ +------> OP - * | | - * RI I - * RI => G => I IOut = (V(IP)-V(IN)) * G - * RI I - * | | - * IN ---+ +------< ON - * - * G=1 ==> 1V ==> 1A - * - * RI = 1 / NETLIST_GMIN - * - */ - - NETLIB_OBJECT(VCCS) + // + // Voltage controlled current source + // + // IP ---+ +------> OP + // | | + // RI I + // RI => G => I IOut = (V(IP)-V(IN)) * G + // RI I + // | | + // IN ---+ +------< ON + // + // G=1 ==> 1V ==> 1A + // + // RI = 1 / NETLIST_GMIN + // + class nld_VCCS : public base_device_t { public: - NETLIB_CONSTRUCTOR(VCCS) - , m_G(*this, "G", 1.0) - , m_RI(*this, "RI", 1e9) - , m_OP(*this, "OP", &m_IP) - , m_ON(*this, "ON", &m_IP) - , m_IP(*this, "IP", &m_IN) // <= this should be NULL and terminal be filtered out prior to solving... - , m_IN(*this, "IN", &m_IP) // <= this should be NULL and terminal be filtered out prior to solving... - , m_OP1(*this, "_OP1", &m_IN) - , m_ON1(*this, "_ON1", &m_IN) - , m_gfac(1.0) + nld_VCCS(constructor_param_t data, nl_fptype ri = nlconst::magic(1e9)) + : base_device_t(data) + , m_G(*this, "G", nlconst::one()) + , m_RI(*this, "RI", ri) + , m_OP(*this, "OP", &m_IP, {&m_ON, &m_IN}, + NETLIB_DELEGATE(terminal_handler)) + , m_ON(*this, "ON", &m_IP, {&m_OP, &m_IN}, + NETLIB_DELEGATE(terminal_handler)) + , m_IP(*this, "IP", &m_IN, {&m_OP, &m_ON}, + NETLIB_DELEGATE(terminal_handler)) + , m_IN(*this, "IN", &m_IP, {&m_OP, &m_ON}, + NETLIB_DELEGATE(terminal_handler)) + , m_OP1(*this, "_OP1", &m_IN, NETLIB_DELEGATE(terminal_handler)) + , m_ON1(*this, "_ON1", &m_IN, NETLIB_DELEGATE(terminal_handler)) + , m_gfac(nlconst::one()) { connect(m_OP, m_OP1); connect(m_ON, m_ON1); - m_gfac = plib::constants<nl_double>::one(); } NETLIB_RESETI(); - param_double_t m_G; - param_double_t m_RI; + param_fp_t m_G; + param_fp_t m_RI; protected: - NETLIB_UPDATEI(); - NETLIB_UPDATE_PARAMI() - { - NETLIB_NAME(VCCS)::reset(); - } + NETLIB_HANDLERI(terminal_handler); + NETLIB_UPDATE_PARAMI() { NETLIB_NAME(VCCS)::reset(); } + void set_gfac(nl_fptype g) noexcept { m_gfac = g; } + + nl_fptype get_gfac() const noexcept { return m_gfac; } terminal_t m_OP; terminal_t m_ON; @@ -76,107 +79,107 @@ namespace netlist { terminal_t m_OP1; terminal_t m_ON1; - nl_double m_gfac; + private: + nl_fptype m_gfac; }; - /* Limited Current source*/ + // Limited Current source - NETLIB_OBJECT_DERIVED(LVCCS, VCCS) + class nld_LVCCS : public nld_VCCS { public: - NETLIB_CONSTRUCTOR_DERIVED(LVCCS, VCCS) - , m_cur_limit(*this, "CURLIM", 1000.0) - , m_vi(0.0) + nld_LVCCS(constructor_param_t data) + : nld_VCCS(data) + , m_cur_limit(*this, "CURLIM", nlconst::magic(1000.0)) + , m_vi(nlconst::zero()) { } NETLIB_IS_DYNAMIC(true) protected: - //NETLIB_UPDATEI(); NETLIB_RESETI(); NETLIB_UPDATE_PARAMI(); NETLIB_UPDATE_TERMINALSI(); private: - param_double_t m_cur_limit; /* current limit */ - nl_double m_vi; + param_fp_t m_cur_limit; // current limit + nl_fptype m_vi; }; // ---------------------------------------------------------------------------------------- // nld_CCCS // ---------------------------------------------------------------------------------------- - /* - * Current controlled current source - * - * IP ---+ +------> OP - * | | - * RI I - * RI => G => I IOut = (V(IP)-V(IN)) / RI * G - * RI I - * | | - * IN ---+ +------< ON - * - * G=1 ==> 1A ==> 1A - * - * RI = 1 - * - * This needs high levels of accuracy to work with 1 Ohm RI. - * - */ - - NETLIB_OBJECT_DERIVED(CCCS, VCCS) + // + // Current controlled current source + // + // IP ---+ +------> OP + // | | + // RI I + // RI => G => I IOut = -(V(IP)-V(IN)) / RI * G + // RI I + // | | + // IN ---+ +------< ON + // + // G=1 ==> 1A ==> 1A + // + // RI = 1 + // + // If current flows from IP to IN than output current flows from OP to ON + // + // This needs high levels of accuracy to work with 1 Ohm RI. + // + + class nld_CCCS : public nld_VCCS { public: - NETLIB_CONSTRUCTOR_DERIVED(CCCS, VCCS) + nld_CCCS(constructor_param_t data) + : nld_VCCS(data, nlconst::one()) { - m_gfac = plib::constants<nl_double>::one() / m_RI(); + set_gfac(-plib::reciprocal(m_RI())); } NETLIB_RESETI(); protected: - //NETLIB_UPDATEI(); NETLIB_UPDATE_PARAMI(); }; - // ---------------------------------------------------------------------------------------- // nld_VCVS // ---------------------------------------------------------------------------------------- - /* - * Voltage controlled voltage source - * - * Parameters: - * G Default: 1 - * RO Default: 1 (would be typically 50 for an op-amp - * - * IP ---+ +--+---- OP - * | | | - * RI I RO - * RI => G => I RO V(OP) - V(ON) = (V(IP)-V(IN)) * G - * RI I RO - * | | | - * IN ---+ +--+---- ON - * - * G=1 ==> 1V ==> 1V - * - * RI = 1 / NETLIST_GMIN - * - * Internal GI = G / RO - * - */ - - - NETLIB_OBJECT_DERIVED(VCVS, VCCS) + // + // Voltage controlled voltage source + // + // Parameters: + // G Default: 1 + // RO Default: 1 (would be typically 50 for an op-amp + // + // IP ---+ +--+---- OP + // | | | + // RI I RO + // RI => G => I RO V(OP) - V(ON) = (V(IP)-V(IN)) * G + // RI I RO + // | | | + // IN ---+ +--+---- ON + // + // G=1 ==> 1V ==> 1V + // + // RI = 1 / NETLIST_GMIN + // + // Internal GI = G / RO + // + + class nld_VCVS : public nld_VCCS { public: - NETLIB_CONSTRUCTOR_DERIVED(VCVS, VCCS) - , m_RO(*this, "RO", 1.0) - , m_OP2(*this, "_OP2", &m_ON2) - , m_ON2(*this, "_ON2", &m_OP2) + nld_VCVS(constructor_param_t data) + : nld_VCCS(data) + , m_RO(*this, "RO", nlconst::one()) + , m_OP2(*this, "_OP2", &m_ON2, NETLIB_DELEGATE(terminal_handler)) + , m_ON2(*this, "_ON2", &m_OP2, NETLIB_DELEGATE(terminal_handler)) { connect(m_OP2, m_OP1); connect(m_ON2, m_ON1); @@ -184,19 +187,74 @@ namespace netlist { NETLIB_RESETI(); - param_double_t m_RO; + param_fp_t m_RO; private: - //NETLIB_UPDATEI(); - //NETLIB_UPDATE_PARAMI(); + // NETLIB_UPDATE_PARAMI(); + NETLIB_HANDLERI(terminal_handler) + { + NETLIB_NAME(VCCS)::terminal_handler(); + } terminal_t m_OP2; terminal_t m_ON2; + }; + // ---------------------------------------------------------------------------------------- + // nld_CCVS + // ---------------------------------------------------------------------------------------- + // + // Voltage controlled voltage source + // + // Parameters: + // G Default: 1 + // RO Default: 1 (would be typically 50 for an op-amp + // + // IP ---+ +--+---- OP + // | | | + // RI I RO + // RI => G => I RO V(OP) - V(ON) = (V(IP)-V(IN)) / RI * G + // RI I RO + // | | | + // IN ---+ +--+---- ON + // + // G=1 ==> 1A ==> 1V + // + // RI = 1 + // + // Internal GI = G / RO + // + + class nld_CCVS : public nld_VCCS + { + public: + nld_CCVS(constructor_param_t data) + : nld_VCCS(data, nlconst::one()) + , m_RO(*this, "RO", nlconst::one()) + , m_OP2(*this, "_OP2", &m_ON2, NETLIB_DELEGATE(terminal_handler)) + , m_ON2(*this, "_ON2", &m_OP2, NETLIB_DELEGATE(terminal_handler)) + { + connect(m_OP2, m_OP1); + connect(m_ON2, m_ON1); + } + + NETLIB_RESETI(); + + param_fp_t m_RO; + + private: + // NETLIB_UPDATE_PARAMI(); + + NETLIB_HANDLERI(terminal_handler) + { + NETLIB_NAME(VCCS)::terminal_handler(); + } + + terminal_t m_OP2; + terminal_t m_ON2; }; - } // namespace analog -} // namespace netlist +} // namespace netlist::analog -#endif /* NLD_FOURTERM_H_ */ +#endif // NLD_FOURTERM_H_ diff --git a/src/lib/netlist/analog/nlid_twoterm.cpp b/src/lib/netlist/analog/nlid_twoterm.cpp index aaddb65387a..1a3c79e713f 100644 --- a/src/lib/netlist/analog/nlid_twoterm.cpp +++ b/src/lib/netlist/analog/nlid_twoterm.cpp @@ -1,190 +1,231 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_twoterm.c - * - */ -#include "netlist/solver/nld_solver.h" - -#include "netlist/nl_factory.h" #include "nlid_twoterm.h" -#include <cmath> +#include "nl_factory.h" -namespace netlist -{ - namespace analog - { +#include "solver/nld_solver.h" -// ---------------------------------------------------------------------------------------- -// nld_twoterm -// ---------------------------------------------------------------------------------------- - -void NETLIB_NAME(twoterm)::solve_now() -{ - /* we only need to call the non-rail terminal */ - if (m_P.has_net() && !m_P.net().isRailNet()) - m_P.solve_now(); - else if (m_N.has_net() && !m_N.net().isRailNet()) - m_N.solve_now(); -} - -void NETLIB_NAME(twoterm)::solve_later(netlist_time delay) +namespace netlist::analog { - /* we only need to call the non-rail terminal */ - if (m_P.has_net() && !m_P.net().isRailNet()) - m_P.schedule_solve_after(delay); - else if (m_N.has_net() && !m_N.net().isRailNet()) - m_N.schedule_solve_after(delay); -} + // ------------------------------------------------------------------------- + // nld_twoterm + // ------------------------------------------------------------------------- -NETLIB_UPDATE(twoterm) -{ - /* only called if connected to a rail net ==> notify the solver to recalculate */ - solve_now(); -} - -// ---------------------------------------------------------------------------------------- -// nld_R_base -// ---------------------------------------------------------------------------------------- - -NETLIB_RESET(R_base) -{ - NETLIB_NAME(twoterm)::reset(); - set_R(1.0 / exec().gmin()); -} + solver::matrix_solver_t *nld_two_terminal::solver() const noexcept + { + auto *solv(m_P.solver()); + if (solv != nullptr) + return solv; + return m_N.solver(); + } -// ---------------------------------------------------------------------------------------- -// nld_POT -// ---------------------------------------------------------------------------------------- + void nld_two_terminal::solve_now() const + { + auto *solv(solver()); + if (solv != nullptr) + solv->solve_now(); + } -NETLIB_RESET(POT) -{ - nl_double v = m_Dial(); - if (m_DialIsLog()) - v = (std::exp(v) - 1.0) / (std::exp(1.0) - 1.0); + NETLIB_HANDLER(two_terminal, terminal_handler) + { + // only called if connected to a rail net ==> notify the solver to + // recalculate + // printf("%s update\n", this->name().c_str()); + solve_now(); + } - m_R1.set_R(std::max(m_R() * v, exec().gmin())); - m_R2.set_R(std::max(m_R() * (plib::constants<nl_double>::one() - v), exec().gmin())); -} + // ------------------------------------------------------------------------- + // nld_POT + // ------------------------------------------------------------------------- -NETLIB_UPDATE_PARAM(POT) -{ - m_R1.solve_now(); - m_R2.solve_now(); + NETLIB_RESET(POT) + { + nl_fptype v = m_Dial(); + if (m_DialIsLog()) + v = (plib::exp(v) - nlconst::one()) + / (plib::exp(nlconst::one()) - nlconst::one()); - nl_double v = m_Dial(); - if (m_DialIsLog()) - v = (std::exp(v) - 1.0) / (std::exp(1.0) - 1.0); - if (m_Reverse()) - v = 1.0 - v; - m_R1.set_R(std::max(m_R() * v, exec().gmin())); - m_R2.set_R(std::max(m_R() * (plib::constants<nl_double>::one() - v), exec().gmin())); + m_R1().set_R(std::max(m_R() * v, exec().gmin())); + m_R2().set_R(std::max(m_R() * (nlconst::one() - v), exec().gmin())); + } -} + NETLIB_UPDATE_PARAM(POT) + { + nl_fptype v = m_Dial(); + if (m_DialIsLog()) + v = (plib::exp(v) - nlconst::one()) + / (plib::exp(nlconst::one()) - nlconst::one()); + if (m_Reverse()) + v = nlconst::one() - v; + + nl_fptype r1(std::max(m_R() * v, exec().gmin())); + nl_fptype r2(std::max(m_R() * (nlconst::one() - v), exec().gmin())); + + if (m_R1().solver() == m_R2().solver()) + m_R1().change_state( + [this, &r1, &r2]() + { + m_R1().set_R(r1); + m_R2().set_R(r2); + }); + else + { + m_R1().change_state([this, &r1]() { m_R1().set_R(r1); }); + m_R2().change_state([this, &r2]() { m_R2().set_R(r2); }); + } + } + + // ------------------------------------------------------------------------- + // nld_POT2 + // ------------------------------------------------------------------------- + + NETLIB_RESET(POT2) + { + nl_fptype v = m_Dial(); -// ---------------------------------------------------------------------------------------- -// nld_POT2 -// ---------------------------------------------------------------------------------------- + if (m_DialIsLog()) + v = (plib::exp(v) - nlconst::one()) + / (plib::exp(nlconst::one()) - nlconst::one()); + if (m_Reverse()) + v = nlconst::one() - v; + m_R1().set_R(std::max(m_R() * v, exec().gmin())); + } -NETLIB_RESET(POT2) -{ - nl_double v = m_Dial(); + NETLIB_UPDATE_PARAM(POT2) + { + nl_fptype v = m_Dial(); - if (m_DialIsLog()) - v = (std::exp(v) - 1.0) / (std::exp(1.0) - 1.0); - if (m_Reverse()) - v = 1.0 - v; - m_R1.set_R(std::max(m_R() * v, exec().gmin())); -} + if (m_DialIsLog()) + v = (plib::exp(v) - nlconst::one()) + / (plib::exp(nlconst::one()) - nlconst::one()); + if (m_Reverse()) + v = nlconst::one() - v; + m_R1().change_state( + [this, &v]() { m_R1().set_R(std::max(m_R() * v, exec().gmin())); }); + } -NETLIB_UPDATE_PARAM(POT2) -{ - m_R1.solve_now(); + // ------------------------------------------------------------------------- + // nld_L + // ------------------------------------------------------------------------- - nl_double v = m_Dial(); + NETLIB_RESET(L) + { + m_gmin = exec().gmin(); + m_I = nlconst::zero(); + m_G = m_gmin; + set_mat(m_G, -m_G, -m_I, // + -m_G, m_G, m_I); + } - if (m_DialIsLog()) - v = (std::exp(v) - 1.0) / (std::exp(1.0) - 1.0); - if (m_Reverse()) - v = 1.0 - v; - m_R1.set_R(std::max(m_R() * v, exec().gmin())); -} + NETLIB_UPDATE_PARAM(L) {} -// ---------------------------------------------------------------------------------------- -// nld_L -// ---------------------------------------------------------------------------------------- + NETLIB_TIMESTEP(L) + { + if (ts_type == detail::time_step_type::FORWARD) + { + m_last_I = m_I; + m_last_G = m_G; + // Gpar should support convergence + m_I += m_G * deltaV(); + m_G = step / m_L() + m_gmin; + set_mat(m_G, -m_G, -m_I, // + -m_G, m_G, m_I); + } + else + { + m_I = m_last_I; + m_G = m_last_G; + } + } + + // ------------------------------------------------------------------------- + // nld_D + // ------------------------------------------------------------------------- + + NETLIB_RESET(D) + { + nl_fptype Is = m_modacc.m_IS; + nl_fptype n = m_modacc.m_N; -NETLIB_RESET(L) -{ - m_gmin = exec().gmin(); - m_I = 0.0; - m_G = m_gmin; - set_mat( m_G, -m_G, -m_I, - -m_G, m_G, m_I); - //set(1.0/NETLIST_GMIN, 0.0, -5.0 * NETLIST_GMIN); -} - -NETLIB_UPDATE_PARAM(L) -{ -} + m_D.set_param(Is, n, exec().gmin(), nlconst::T0()); + set_G_V_I(m_D.G(), nlconst::zero(), m_D.Ieq()); + } -NETLIB_TIMESTEP(L) -{ - /* Gpar should support convergence */ - m_I += m_I + m_G * deltaV(); - m_G = step / m_L() + m_gmin; - set_mat( m_G, -m_G, -m_I, - -m_G, m_G, m_I); - //set(m_G, 0.0, m_I); -} - -// ---------------------------------------------------------------------------------------- -// nld_D -// ---------------------------------------------------------------------------------------- - -NETLIB_RESET(D) -{ - nl_double Is = m_model.m_IS; - nl_double n = m_model.m_N; + NETLIB_UPDATE_PARAM(D) + { + nl_fptype Is = m_modacc.m_IS; + nl_fptype n = m_modacc.m_N; - m_D.set_param(Is, n, exec().gmin(), constants::T0()); - set_G_V_I(m_D.G(), 0.0, m_D.Ieq()); -} + m_D.set_param(Is, n, exec().gmin(), nlconst::T0()); + } -NETLIB_UPDATE_PARAM(D) -{ - nl_double Is = m_model.m_IS; - nl_double n = m_model.m_N; + NETLIB_UPDATE_TERMINALS(D) + { + m_D.update_diode(deltaV()); + const nl_fptype G(m_D.G()); + const nl_fptype I(m_D.Ieq()); + set_mat(G, -G, -I, // + -G, G, I); + // set(m_D.G(), 0.0, m_D.Ieq()); + } + + // ------------------------------------------------------------------------- + // nld_Z + // ------------------------------------------------------------------------- + + NETLIB_RESET(Z) + { + nl_fptype IsBV = m_modacc.m_IBV + / (plib::exp(m_modacc.m_BV + / nlconst::np_VT(m_modacc.m_NBV)) + - nlconst::one()); + + m_D.set_param(m_modacc.m_IS, m_modacc.m_N, exec().gmin(), + nlconst::T0()); + m_R.set_param(IsBV, m_modacc.m_NBV, exec().gmin(), nlconst::T0()); + set_G_V_I(m_D.G(), nlconst::zero(), m_D.Ieq()); + } + + NETLIB_UPDATE_PARAM(Z) + { + nl_fptype IsBV = m_modacc.m_IBV + / (plib::exp(m_modacc.m_BV + / nlconst::np_VT(m_modacc.m_NBV)) + - nlconst::one()); + + m_D.set_param(m_modacc.m_IS, m_modacc.m_N, exec().gmin(), + nlconst::T0()); + m_R.set_param(IsBV, m_modacc.m_NBV, exec().gmin(), nlconst::T0()); + set_G_V_I(m_D.G(), nlconst::zero(), m_D.Ieq()); + } + + NETLIB_UPDATE_TERMINALS(Z) + { + m_D.update_diode(deltaV()); + m_R.update_diode(-deltaV()); + const nl_fptype G(m_D.G() + m_R.G()); + const nl_fptype I(m_D.Ieq() - m_R.Ieq()); + set_mat(G, -G, -I, // + -G, G, I); + } - m_D.set_param(Is, n, exec().gmin(), constants::T0()); -} +} // namespace netlist::analog -NETLIB_UPDATE_TERMINALS(D) +namespace netlist::devices { - m_D.update_diode(deltaV()); - const nl_double G = m_D.G(); - const nl_double I = m_D.Ieq(); - set_mat( G, -G, -I, - -G, G, I); - //set(m_D.G(), 0.0, m_D.Ieq()); -} - - - } //namespace analog - - namespace devices { - NETLIB_DEVICE_IMPL_NS(analog, R, "RES", "R") - NETLIB_DEVICE_IMPL_NS(analog, POT, "POT", "R") - NETLIB_DEVICE_IMPL_NS(analog, POT2, "POT2", "R") - NETLIB_DEVICE_IMPL_NS(analog, C, "CAP", "C") - NETLIB_DEVICE_IMPL_NS(analog, L, "IND", "L") - NETLIB_DEVICE_IMPL_NS(analog, D, "DIODE", "MODEL") - NETLIB_DEVICE_IMPL_NS(analog, VS, "VS", "V") - NETLIB_DEVICE_IMPL_NS(analog, CS, "CS", "I") - } // namespace devices - -} // namespace netlist + // clang-format off + NETLIB_DEVICE_IMPL_NS(analog, R, "RES", "R") + NETLIB_DEVICE_IMPL_NS(analog, POT, "POT", "R") + NETLIB_DEVICE_IMPL_NS(analog, POT2, "POT2", "R") + NETLIB_DEVICE_IMPL_NS(analog, C, "CAP", "C") + NETLIB_DEVICE_IMPL_NS(analog, L, "IND", "L") + NETLIB_DEVICE_IMPL_NS(analog, D, "DIODE", "MODEL") + NETLIB_DEVICE_IMPL_NS(analog, Z, "ZDIODE", "MODEL") + NETLIB_DEVICE_IMPL_NS(analog, VS, "VS", "V") + NETLIB_DEVICE_IMPL_NS(analog, CS, "CS", "I") + // clang-format on +} // namespace netlist::devices diff --git a/src/lib/netlist/analog/nlid_twoterm.h b/src/lib/netlist/analog/nlid_twoterm.h index 1dc639008c4..d84356fe82d 100644 --- a/src/lib/netlist/analog/nlid_twoterm.h +++ b/src/lib/netlist/analog/nlid_twoterm.h @@ -1,245 +1,360 @@ -// license:GPL-2.0+ +// license:BSD-3-Clause // copyright-holders:Couriersud -/* - * nld_twoterm.h - * - * Devices with two terminals ... - * - * - * (k) - * +-----T-----+ - * | | | - * | +--+--+ | - * | | | | - * | R | | - * | R | | - * | R I | - * | | I | Device n - * | V+ I | - * | V | | - * | V- | | - * | | | | - * | +--+--+ | - * | | | - * +-----T-----+ - * (l) - * - * This is a resistance in series to a voltage source and paralleled by a - * current source. This is suitable to model voltage sources, current sources, - * resistors, capacitors, inductances and diodes. - * - */ #ifndef NLID_TWOTERM_H_ #define NLID_TWOTERM_H_ -#include "netlist/nl_base.h" -#include "netlist/nl_setup.h" -#include "netlist/solver/nld_solver.h" +/// +/// \file nlid_twoterm.h +/// +/// Devices with two terminals ... +/// +/// +/// (k) +/// +-----T-----+ +/// | | | +/// | +--+--+ | +/// | | | | +/// | R | | +/// | R | | +/// | R I | +/// | | I | Device n +/// | V+ I | +/// | V | | +/// | V- | | +/// | | | | +/// | +--+--+ | +/// | | | +/// +-----T-----+ +/// (l) +/// +/// This is a resistance in series to a voltage source and paralleled by a +/// current source. This is suitable to model voltage sources, current sources, +/// resistors, capacitors, inductances and diodes. +/// +// + +#include "../nl_setup.h" +#include "nl_base.h" #include "nld_generic_models.h" -#include "plib/pfunction.h" -#include <cmath> +#include "solver/nld_solver.h" + +#include "plib/pfunction.h" // ----------------------------------------------------------------------------- // Implementation // ----------------------------------------------------------------------------- -namespace netlist -{ -namespace analog +namespace netlist::analog { - // ----------------------------------------------------------------------------- - // nld_twoterm - // ----------------------------------------------------------------------------- - - template <class C> - inline core_device_t &bselect(bool b, C &d1, core_device_t &d2) - { - auto *h = dynamic_cast<core_device_t *>(&d1); - return b ? *h : d2; - } - template<> - inline core_device_t &bselect(bool b, netlist_state_t &d1, core_device_t &d2) - { - plib::unused_var(d1); - if (b) - throw nl_exception("bselect with netlist and b==true"); - return d2; - } + // ------------------------------------------------------------------------- + // nld_two_terminal + // ------------------------------------------------------------------------- - NETLIB_OBJECT(twoterm) + class nld_two_terminal : public base_device_t { - NETLIB_CONSTRUCTOR_EX(twoterm, bool terminals_owned = false) - , m_P(bselect(terminals_owned, owner, *this), (terminals_owned ? name + "." : "") + "1", &m_N) - , m_N(bselect(terminals_owned, owner, *this), (terminals_owned ? name + "." : "") + "2", &m_P) + public: + nld_two_terminal(constructor_param_t data) + : base_device_t(data) + , m_P(*this, "1", &m_N, NETLIB_DELEGATE(terminal_handler)) + , m_N(*this, "2", &m_P, NETLIB_DELEGATE(terminal_handler)) { } - terminal_t m_P; - terminal_t m_N; + // This constructor covers the case in which the terminals are "owned" + // by the device using a two_terminal. In this case it passes + // the terminal handler on to the terminals. + + nld_two_terminal(base_device_t &owner, const pstring &name, + nl_delegate owner_delegate) + : base_device_t( + constructor_data_t{owner.state(), owner.name() + "." + name}) + , m_P(owner, name + ".1", &m_N, owner_delegate) + , m_N(owner, name + ".2", &m_P, owner_delegate) + { + } - //NETLIB_UPDATE_TERMINALSI() { } - //NETLIB_RESETI() { } + // NETLIB_UPDATE_TERMINALSI() { } + // NETLIB_RESETI() {} public: + NETLIB_HANDLERI(terminal_handler); - NETLIB_UPDATEI(); + solver::matrix_solver_t *solver() const noexcept; - void solve_now(); + void solve_now() const; - void solve_later(netlist_time delay = netlist_time::quantum()); + template <typename F> + void change_state(F f) const + { + auto *solv(solver()); + if (solv) + solv->change_state(f); + } - void set_G_V_I(const nl_double G, const nl_double V, const nl_double I) + void set_G_V_I(nl_fptype G, nl_fptype V, nl_fptype I) const noexcept { - /* GO, GT, I */ + // GO, GT, I m_P.set_go_gt_I( -G, G, ( V) * G - I); m_N.set_go_gt_I( -G, G, ( -V) * G + I); } - nl_double deltaV() const + nl_fptype deltaV() const noexcept { return m_P.net().Q_Analog() - m_N.net().Q_Analog(); } - void set_mat(const nl_double a11, const nl_double a12, const nl_double rhs1, - const nl_double a21, const nl_double a22, const nl_double rhs2) + nl_fptype V1P() const noexcept { return m_P.net().Q_Analog(); } + + nl_fptype V2N() const noexcept { return m_N.net().Q_Analog(); } + + void set_mat(nl_fptype a11, nl_fptype a12, nl_fptype rhs1, // + nl_fptype a21, nl_fptype a22, nl_fptype rhs2 // + ) const noexcept { - /* GO, GT, I */ + // GO, GT, I m_P.set_go_gt_I(a12, a11, rhs1); m_N.set_go_gt_I(a21, a22, rhs2); } + void set_mat(const std::array<std::array<nl_fptype,3>,2> &a) const noexcept + { + // GO, GT, I + m_P.set_go_gt_I(a[0][1], a[0][0], a[0][2]); + m_N.set_go_gt_I(a[1][0], a[1][1], a[1][2]); + } + + void clear_mat() const noexcept + { + const auto z = nlconst::zero(); + // GO, GT, I + m_P.set_go_gt_I(z, z, z); + m_N.set_go_gt_I(z, z, z); + } + + /// \brief Get a const reference to the m_P terminal + /// + /// This is typically called during initialization to connect + /// terminals. + /// + /// \returns Reference to m_P terminal. + const terminal_t &P() const noexcept { return m_P; } + + /// \brief Get a const reference to the m_N terminal + /// + /// This is typically called during initialization to connect + /// terminals. + /// + /// \returns Reference to m_N terminal. + const terminal_t &N() const noexcept { return m_N; } + + /// \brief Get a reference to the m_P terminal + /// + /// This call is only allowed from the core. Device code should never + /// need to call this. + /// + /// \returns Reference to m_P terminal. + terminal_t &setup_P() noexcept { return m_P; } + + /// \brief Get a reference to the m_N terminal + /// + /// This call is only allowed from the core. Device code should never + /// need to call this. + /// + /// \returns Reference to m_P terminal. + terminal_t &setup_N() noexcept { return m_N; } + private: + terminal_t m_P; + terminal_t m_N; }; - - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // nld_R - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(R_base, twoterm) + class nld_R_base : public nld_two_terminal { - NETLIB_CONSTRUCTOR_DERIVED(R_base, twoterm) + public: + nld_R_base(constructor_param_t data) + : nld_two_terminal(data) { } - void set_R(const nl_double R) + void set_R(nl_fptype R) const noexcept { - const nl_double G = plib::constants<nl_double>::one() / R; - set_mat( G, -G, 0.0, - -G, G, 0.0); + const nl_fptype G = plib::reciprocal(R); + set_mat(G, -G, nlconst::zero(), // + -G, G, nlconst::zero()); } - NETLIB_RESETI(); + void set_G(nl_fptype G) const noexcept + { + set_mat(G, -G, nlconst::zero(), // + -G, G, nlconst::zero()); + } - protected: - //NETLIB_UPDATEI(); + // NETLIB_RESETI(); + protected: + // NETLIB_UPDATEI(); }; - NETLIB_OBJECT_DERIVED(R, R_base) + class nld_R : public nld_R_base { - NETLIB_CONSTRUCTOR_DERIVED(R, R_base) - , m_R(*this, "R", 1e9) + public: + nld_R(constructor_param_t data) + : nld_R_base(data) + , m_R(*this, "R", nlconst::magic(1e9)) { } - protected: - - //NETLIB_UPDATEI() { } - NETLIB_RESETI() - { - NETLIB_NAME(twoterm)::reset(); - set_R(std::max(m_R(), exec().gmin())); - } + NETLIB_RESETI() { set_R(std::max(m_R(), exec().gmin())); } NETLIB_UPDATE_PARAMI() { - solve_now(); - set_R(std::max(m_R(), exec().gmin())); + // FIXME: We only need to update the net first if this is a time + // stepping net + change_state([this]() { set_R(std::max(m_R(), exec().gmin())); }); } private: - param_double_t m_R; - /* protect set_R ... it's a recipe to desaster when used to bypass the parameter */ - using NETLIB_NAME(R_base)::set_R; + param_fp_t m_R; + // protect set_R ... it's a recipe to disaster when used to bypass the + // parameter + using nld_R_base::set_G; + using nld_R_base::set_R; }; - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // nld_POT - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT(POT) + class nld_POT : public base_device_t { - NETLIB_CONSTRUCTOR(POT) + public: + nld_POT(constructor_param_t data) + : base_device_t(data) , m_R1(*this, "_R1") , m_R2(*this, "_R2") , m_R(*this, "R", 10000) - , m_Dial(*this, "DIAL", 0.5) + , m_Dial(*this, "DIAL", nlconst::half()) , m_DialIsLog(*this, "DIALLOG", false) , m_Reverse(*this, "REVERSE", false) { - register_subalias("1", m_R1.m_P); - register_subalias("2", m_R1.m_N); - register_subalias("3", m_R2.m_N); - - connect(m_R2.m_P, m_R1.m_N); + register_sub_alias("1", m_R1().P()); + register_sub_alias("2", m_R1().N()); + register_sub_alias("3", m_R2().N()); + connect(m_R2().P(), m_R1().N()); } - //NETLIB_UPDATEI(); + // NETLIB_UPDATEI(); NETLIB_RESETI(); NETLIB_UPDATE_PARAMI(); private: - NETLIB_SUB(R_base) m_R1; - NETLIB_SUB(R_base) m_R2; + NETLIB_SUB_NS(analog, R_base) m_R1; + NETLIB_SUB_NS(analog, R_base) m_R2; - param_double_t m_R; - param_double_t m_Dial; + param_fp_t m_R; + param_fp_t m_Dial; param_logic_t m_DialIsLog; param_logic_t m_Reverse; }; - NETLIB_OBJECT(POT2) + class nld_POT2 : public base_device_t { - NETLIB_CONSTRUCTOR(POT2) + public: + nld_POT2(constructor_param_t data) + : base_device_t(data) , m_R1(*this, "_R1") - , m_R(*this, "R", 10000) - , m_Dial(*this, "DIAL", 0.5) + , m_R(*this, "R", nlconst::magic(10000.0)) + , m_Dial(*this, "DIAL", nlconst::half()) , m_DialIsLog(*this, "DIALLOG", false) , m_Reverse(*this, "REVERSE", false) { - register_subalias("1", m_R1.m_P); - register_subalias("2", m_R1.m_N); - + register_sub_alias("1", m_R1().P()); + register_sub_alias("2", m_R1().N()); } - //NETLIB_UPDATEI(); + // NETLIB_UPDATEI(); NETLIB_RESETI(); NETLIB_UPDATE_PARAMI(); private: - NETLIB_SUB(R_base) m_R1; + NETLIB_SUB_NS(analog, R_base) m_R1; - param_double_t m_R; - param_double_t m_Dial; + param_fp_t m_R; + param_fp_t m_Dial; param_logic_t m_DialIsLog; param_logic_t m_Reverse; }; - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // nld_C - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + +#if 1 + class nld_C : public nld_two_terminal + { + public: + nld_C(constructor_param_t data) + : nld_two_terminal(data) + , m_C(*this, "C", nlconst::magic(1e-6)) + , m_cap(*this, "m_cap") + { + } + + NETLIB_IS_TIMESTEP(true) + NETLIB_TIMESTEPI() + { + if (ts_type == detail::time_step_type::FORWARD) + { + // G, Ieq + const auto res(m_cap.time_step(m_C(), deltaV(), step)); + const nl_fptype G = res.first; + const nl_fptype I = res.second; + set_mat(G, -G, -I, // + -G, G, I); + } + else + m_cap.restore_state(); + } + + NETLIB_RESETI() { m_cap.set_parameters(exec().gmin()); } + + /// \brief Set capacitance + /// + /// This call will set the capacitance. The typical use case are + /// are components like BJTs which use this component to model + /// internal capacitances. Typically called during initialization. + /// + /// \param val Capacitance value + /// + void set_cap_embedded(nl_fptype val) { m_C.set(val); } + + protected: + // NETLIB_UPDATEI(); + // FIXME: should be able to change + NETLIB_UPDATE_PARAMI() {} + + private: + param_fp_t m_C; + generic_capacitor_const m_cap; + }; - NETLIB_OBJECT_DERIVED(C, twoterm) +#else + // Code preserved as a basis for a current/voltage controlled capacitor + class nld_C : public nld_two_terminal) { public: - NETLIB_CONSTRUCTOR_DERIVED(C, twoterm) - , m_C(*this, "C", 1e-6) + nld_C(constructor_param_t data) + : nld_two_terminal(data) + , m_C(*this, "C", nlconst::magic(1e-6)) , m_cap(*this, "m_cap") { } @@ -247,55 +362,57 @@ namespace analog NETLIB_IS_TIMESTEP(true) NETLIB_TIMESTEPI() { - m_cap.timestep(m_C(), deltaV(), step); + m_cap.time_step(m_C(), deltaV(), step); if (m_cap.type() == capacitor_e::CONSTANT_CAPACITY) { - const nl_double I = m_cap.Ieq(m_C(), deltaV()); - const nl_double G = m_cap.G(m_C()); - set_mat( G, -G, -I, - -G, G, I); + const nl_fptype I = m_cap.Ieq(m_C(), deltaV()); + const nl_fptype G = m_cap.G(m_C()); + set_mat(G, -G, -I, // + -G, G, I); } } NETLIB_IS_DYNAMIC(m_cap.type() == capacitor_e::VARIABLE_CAPACITY) NETLIB_UPDATE_TERMINALSI() { - const nl_double I = m_cap.Ieq(m_C(), deltaV()); - const nl_double G = m_cap.G(m_C()); - set_mat( G, -G, -I, - -G, G, I); + const nl_fptype I = m_cap.Ieq(m_C(), deltaV()); + const nl_fptype G = m_cap.G(m_C()); + set_mat(G, -G, -I, // + -G, G, I); } - param_double_t m_C; - NETLIB_RESETI() - { - m_cap.setparams(exec().gmin()); - } + param_fp_t m_C; + NETLIB_RESETI() { m_cap.set_parameters(exec().gmin()); } protected: - //NETLIB_UPDATEI(); - NETLIB_UPDATE_PARAMI() { } + // NETLIB_UPDATEI(); + // FIXME: should be able to change + NETLIB_UPDATE_PARAMI() {} private: - //generic_capacitor<capacitor_e::VARIABLE_CAPACITY> m_cap; + // generic_capacitor<capacitor_e::VARIABLE_CAPACITY> m_cap; generic_capacitor<capacitor_e::CONSTANT_CAPACITY> m_cap; }; +#endif - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // nld_L - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(L, twoterm) + class nld_L : public nld_two_terminal { public: - NETLIB_CONSTRUCTOR_DERIVED(L, twoterm) - , m_L(*this, "L", 1e-6) - , m_gmin(0.0) - , m_G(0.0) - , m_I(0.0) + nld_L(constructor_param_t data) + : nld_two_terminal(data) + , m_L(*this, "L", nlconst::magic(1e-6)) + , m_gmin(nlconst::zero()) + , m_G(*this, "m_G", nlconst::zero()) + , m_I(*this, "m_I", nlconst::zero()) + , m_last_I(*this, "m_last_I", nlconst::zero()) + , m_last_G(*this, "m_last_G", nlconst::zero()) { - //register_term("1", m_P); - //register_term("2", m_N); + // register_term("1", m_P); + // register_term("2", m_N); } NETLIB_IS_TIMESTEP(true) @@ -303,69 +420,91 @@ namespace analog NETLIB_RESETI(); protected: - //NETLIB_UPDATEI(); + // NETLIB_UPDATEI(); NETLIB_UPDATE_PARAMI(); private: - param_double_t m_L; + param_fp_t m_L; - nl_double m_gmin; - nl_double m_G; - nl_double m_I; + nl_fptype m_gmin; + state_var<nl_fptype> m_G; + state_var<nl_fptype> m_I; + state_var<nl_fptype> m_last_I; + state_var<nl_fptype> m_last_G; }; - /*! Class representing the diode model paramers. - * This is the model representation of the diode model. Typically, SPICE uses - * the following parameters. A "Y" in the first column indicates that the - * parameter is actually used in netlist. - * - * |NL? |name |parameter |units|default| example|area | - * |:--:|:-----|:--------------------------------|:----|------:|-------:|:----:| - * | Y |IS |saturation current |A |1.0e-14| 1.0e-14| * | - * | |RS |ohmic resistance |Ohm | 0| 10| * | - * | Y |N |emission coefficient |- | 1| 1| | - * | |TT |transit-time |sec | 0| 0.1ns| | - * | |CJO |zero-bias junction capacitance |F | 0| 2pF| * | - * | |VJ |junction potential |V | 1| 0.6| | - * | |M |grading coefficient |- | 0.5| 0.5| | - * | |EG |band-gap energy |eV | 1.11| 1.11 Si| | - * | |XTI |saturation-current temp.exp |- | 3|3.0 pn. 2.0 Schottky| | - * | |KF |flicker noise coefficient |- | 0| | | - * | |AF |flicker noise exponent |- | 1| | | - * | |FC |coefficient for forward-bias depletion capacitance formula|-|0.5|| | - * | |BV |reverse breakdown voltage |V |infinite| 40| | - * | |IBV |current at breakdown voltage |V | 0.001| | | - * | |TNOM |parameter measurement temperature|deg C| 27| 50| | - * - */ - - class diode_model_t : public param_model_t + /// \brief Class representing the diode model parameters. + /// + /// This is the model representation of the diode model. Typically, SPICE + /// uses the following parameters. A "Y" in the first column indicates that + /// the parameter is actually used in netlist. + /// + /// NBV, BV and IBV are only used in the ZDIODE model. It is assumed + /// that DIODEs are not modeled up to their breakdown voltage. + /// + /// |NL? |name |parameter |units|default| example|area | + /// |:--:|:-----|:--------------------------------|:----|------:|-------:|:----:| + /// | Y |IS |saturation current |A |1.0e-14| 1.0e-14| * | + /// | |RS |ohmic resistance |Ohm | 0| 10| * | + /// | Y |N |emission coefficient |- | 1| 1| | + /// | |TT |transit-time |sec | 0| 0.1ns| | + /// | |CJO |zero-bias junction capacitance |F | 0| 2pF| * | + /// | |VJ |junction potential |V | 1| 0.6| | + /// | |M |grading coefficient |- | 0.5| 0.5| | + /// | |EG |band-gap energy |eV | 1.11| 1.11 Si| | + /// | |XTI |saturation-current temp.exp |- | 3|3.0 pn. 2.0 Schottky| | + /// | |KF |flicker noise coefficient |- | 0| | | + /// | |AF |flicker noise exponent |- | 1| | | + /// | |FC |coefficient for forward-bias depletion capacitance formula|-|0.5|| | + /// | Y |NBV |reverse emission coefficient |- | 3| 1| | + /// | Y |BV |reverse breakdown voltage |V |infinite| 40| | + /// | Y |IBV |current at breakdown voltage |A | 0.001| | | + /// | |TNOM |parameter measurement temperature|deg C| 27| 50| | + /// + class diode_model_t { public: - diode_model_t(device_t &device, const pstring &name, const pstring &val) - : param_model_t(device, name, val) - , m_IS(*this, "IS") - , m_N(*this, "N") - {} - - value_t m_IS; //!< saturation current. - value_t m_N; //!< emission coefficient. + diode_model_t(param_model_t &model) + : m_IS(model, "IS") + , m_N(model, "N") + { + } + + param_model_t::value_t m_IS; //!< saturation current. + param_model_t::value_t m_N; //!< emission coefficient. }; + class zdiode_model_t : public diode_model_t + { + public: + zdiode_model_t(param_model_t &model) + : diode_model_t(model) + , m_NBV(model, "NBV") + , m_BV(model, "BV") + , m_IBV(model, "IBV") + { + } - // ----------------------------------------------------------------------------- + param_model_t::value_t m_NBV; //!< reverse emission coefficient. + param_model_t::value_t m_BV; //!< reverse breakdown voltage. + param_model_t::value_t m_IBV; //!< current at breakdown voltage. + }; + + // ------------------------------------------------------------------------- // nld_D - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(D, twoterm) + class nld_D : public nld_two_terminal { public: - NETLIB_CONSTRUCTOR_DERIVED_EX(D, twoterm, pstring model = "D") + nld_D(constructor_param_t data, const pstring &model = "D") + : nld_two_terminal(data) , m_model(*this, "MODEL", model) + , m_modacc(m_model) , m_D(*this, "m_D") { - register_subalias("A", m_P); - register_subalias("K", m_N); + register_sub_alias("A", P()); + register_sub_alias("K", N()); } NETLIB_IS_DYNAMIC(true) @@ -373,125 +512,175 @@ namespace analog NETLIB_RESETI(); protected: - //NETLIB_UPDATEI(); + // NETLIB_UPDATEI(); NETLIB_UPDATE_PARAMI(); private: - diode_model_t m_model; + param_model_t m_model; + diode_model_t m_modacc; generic_diode<diode_e::BIPOLAR> m_D; }; + // ------------------------------------------------------------------------- + // nld_Z - Zener Diode + // ------------------------------------------------------------------------- - // ----------------------------------------------------------------------------- + class nld_Z : public nld_two_terminal + { + public: + nld_Z(constructor_param_t data, const pstring &model = "D") + : nld_two_terminal(data) + , m_model(*this, "MODEL", model) + , m_modacc(m_model) + , m_D(*this, "m_D") + , m_R(*this, "m_R") + { + register_sub_alias("A", P()); + register_sub_alias("K", N()); + } + + NETLIB_IS_DYNAMIC(true) + NETLIB_UPDATE_TERMINALSI(); + NETLIB_RESETI(); + + protected: + // NETLIB_UPDATEI(); + NETLIB_UPDATE_PARAMI(); + + private: + param_model_t m_model; + zdiode_model_t m_modacc; + generic_diode<diode_e::BIPOLAR> m_D; + // REVERSE diode + generic_diode<diode_e::BIPOLAR> m_R; + }; + + // ------------------------------------------------------------------------- // nld_VS - Voltage source // // netlist voltage source must have inner resistance - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(VS, twoterm) + class nld_VS : public nld_two_terminal { public: - NETLIB_CONSTRUCTOR_DERIVED(VS, twoterm) - , m_t(*this, "m_t", 0.0) - , m_R(*this, "R", 0.1) - , m_V(*this, "V", 0.0) - , m_func(*this,"FUNC", "") - , m_compiled(this->name() + ".FUNCC", this, this->state().run_state_manager()) - , m_funcparam({0.0}) + nld_VS(constructor_param_t data) + : nld_two_terminal(data) + , m_t(*this, "m_t", nlconst::zero()) + , m_R(*this, "RI", nlconst::magic(0.1)) + , m_V(*this, "V", nlconst::zero()) + , m_func(*this, "FUNC", "") + , m_compiled(*this, "m_compiled") + , m_funcparam({nlconst::zero()}) { - register_subalias("P", m_P); - register_subalias("N", m_N); - if (m_func() != "") - m_compiled.compile(std::vector<pstring>({{"T"}}), m_func()); + register_sub_alias("P", P()); + register_sub_alias("N", N()); + if (!m_func().empty()) + m_compiled->compile(m_func(), + std::vector<pstring>({{pstring("T")}})); } - NETLIB_IS_TIMESTEP(m_func() != "") + NETLIB_IS_TIMESTEP(!m_func().empty()) NETLIB_TIMESTEPI() { - m_t += step; - m_funcparam[0] = m_t; - this->set_G_V_I(1.0 / m_R(), - m_compiled.evaluate(m_funcparam), - 0.0); + if (ts_type == detail::time_step_type::FORWARD) + { + m_t += step; + m_funcparam[0] = m_t; + this->set_G_V_I(plib::reciprocal(m_R()), + m_compiled->evaluate(m_funcparam), + nlconst::zero()); + } + else + m_t -= step; // only need to restore state, will be called again } protected: - // NETLIB_UPDATEI() { NETLIB_NAME(twoterm)::update(time); } - NETLIB_RESETI() { - NETLIB_NAME(twoterm)::reset(); - this->set_G_V_I(1.0 / m_R(), m_V(), 0.0); + nld_two_terminal::reset(); + this->set_G_V_I(plib::reciprocal(m_R()), m_V(), nlconst::zero()); } private: - state_var<double> m_t; - param_double_t m_R; - param_double_t m_V; - param_str_t m_func; - plib::pfunction m_compiled; - std::vector<double> m_funcparam; + state_var<nl_fptype> m_t; + param_fp_t m_R; + param_fp_t m_V; + param_str_t m_func; + state_var<plib::pfunction<nl_fptype>> m_compiled; + std::vector<nl_fptype> m_funcparam; }; - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- // nld_CS - Current source - // ----------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - NETLIB_OBJECT_DERIVED(CS, twoterm) + class nld_CS : public nld_two_terminal { public: - NETLIB_CONSTRUCTOR_DERIVED(CS, twoterm) - , m_t(*this, "m_t", 0.0) - , m_I(*this, "I", 1.0) - , m_func(*this,"FUNC", "") - , m_compiled(this->name() + ".FUNCC", this, this->state().run_state_manager()) - , m_funcparam({0.0}) + nld_CS(constructor_param_t data) + : nld_two_terminal(data) + , m_t(*this, "m_t", nlconst::zero()) + , m_I(*this, "I", nlconst::one()) + , m_func(*this, "FUNC", "") + , m_compiled(*this, "m_compiled") + , m_funcparam({nlconst::zero()}) { - register_subalias("P", m_P); - register_subalias("N", m_N); - if (m_func() != "") - m_compiled.compile(std::vector<pstring>({{"T"}}), m_func()); + register_sub_alias("P", "1"); + register_sub_alias("N", "2"); + if (!m_func().empty()) + m_compiled->compile(m_func(), + std::vector<pstring>({{pstring("T")}})); } - NETLIB_IS_TIMESTEP(m_func() != "") + NETLIB_IS_TIMESTEP(!m_func().empty()) NETLIB_TIMESTEPI() { - m_t += step; - m_funcparam[0] = m_t; - const double I = m_compiled.evaluate(m_funcparam); - set_mat(0.0, 0.0, -I, - 0.0, 0.0, I); + if (ts_type == detail::time_step_type::FORWARD) + { + m_t += step; + m_funcparam[0] = m_t; + const nl_fptype I = m_compiled->evaluate(m_funcparam); + const auto zero(nlconst::zero()); + set_mat(zero, zero, -I, // + zero, zero, I); + } + else + m_t -= step; } protected: - - //NETLIB_UPDATEI() { NETLIB_NAME(twoterm)::update(time); } NETLIB_RESETI() { - NETLIB_NAME(twoterm)::reset(); - set_mat(0.0, 0.0, -m_I(), - 0.0, 0.0, m_I()); + nld_two_terminal::reset(); + const auto zero(nlconst::zero()); + set_mat(zero, zero, -m_I(), // + zero, zero, m_I()); } NETLIB_UPDATE_PARAMI() { - solve_now(); - set_mat(0.0, 0.0, -m_I(), - 0.0, 0.0, m_I()); + // FIXME: We only need to update the net first if this is a time + // stepping net + // FIXME: works only for CS without function + change_state( + [this]() + { + const auto zero(nlconst::zero()); + set_mat(zero, zero, -m_I(), // + zero, zero, m_I()); + }); } - private: - state_var<double> m_t; - param_double_t m_I; - param_str_t m_func; - plib::pfunction m_compiled; - std::vector<double> m_funcparam; + state_var<nl_fptype> m_t; + param_fp_t m_I; + param_str_t m_func; + state_var<plib::pfunction<nl_fptype>> m_compiled; + std::vector<nl_fptype> m_funcparam; }; +} // namespace netlist::analog -} // namespace analog -} // namespace netlist - -#endif /* NLD_TWOTERM_H_ */ +#endif // NLD_TWOTERM_H_ |