diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index c135c8af7..a33f59de9 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -415,8 +415,69 @@ public: */ struct SpiceProfile { + /** + * @brief The SPICE element name to use for this device (i.e. "M", "R" etc.) + */ std::string element; + + /** + * @brief The order the terminals are written to or read from SPICE + * + * This is a list of valid terminal names + */ std::vector terminal_order; + + /** + * @brief A mapping of parameters when reading + * + * The key is the parameter name to be produced in the device + * and the value is a formula that references parameters from the SPICE card. + * For caseless netlists, the parameter names need to be upper case. + * + * Special directives apply for the key: + * * "*": all pre-defined parameters plus the ones from the SPICE card + * * "**": all pre-defined parameters + * * "*!": all pre-defined primary parameters + * * "*?": all pre-defined secondary parameters + * Specific names have precendence over wildcard names. + * + * The value is a formula in KLayout expression notation that specifies + * how the value of the parameter is computed from SPICE card parameters. + * SPICE parameters are referenced by name. If a parameter is not given, + * the value will be the default from the parameter declaration or "nil". + * So you can implement a default using "P||0.0" for example. + * "_" is the value of the same SPICE parameter. This is useful for generating + * catch-all rules, such as '"*": "_"' (copy all parameters). + * + * "$" represents the direct value in expressions. This is used for + * elements like "R", "L" or "C", when the component value is not given + * as a named parameter, but as an explicit value. + * + * If the formula returns a nil value, the parameter is not generated in the + * device or the default value is used if the parameter is a known one. + */ + std::map incoming_parameters; + + /** + * @brief A mapping of parameter when writing + * + * The key is the parameter name produced in the SPICE file + * and the value is a formula that references parameters from the + * device. + * For caseless netlists, the parameter names need to be upper case. + * + * Special directives apply for the key: + * * "*" or "**": all parameters from the device + * * "*!": all primary parameters + * * "*?": all secondary parameters + * Specific names have precendence over wildcard names. + * + * The value is a formula in KLayout expression notation that specifies + * how the value of the parameter is computed from device parameters. + * "_" is the value of the same SPICE parameter. This is useful for generating + * catch-all rules, such as '"*": "_"' (copy all parameters). + */ + std::map outgoing_parameters; }; typedef size_t terminal_id_type; @@ -927,6 +988,8 @@ public: virtual bool is_of (const db::DeviceClass *) const = 0; virtual DeviceClass *create () const = 0; + virtual size_t spice_num_nets () const = 0; + virtual const std::string &spice_element () const = 0; static DeviceClassTemplateBase *template_by_name (const std::string &name); static DeviceClassTemplateBase *is_a (const db::DeviceClass *dc); @@ -946,7 +1009,10 @@ public: device_class_template (const std::string &name) : DeviceClassTemplateBase (name) { - // .. nothing yet .. + T dc; + const db::DeviceClass::SpiceProfile &profile = dc.spice_profile ("*"); + m_num_nets = profile.terminal_order.size (); + m_element = profile.element; } virtual bool is_of (const db::DeviceClass *dc) const @@ -959,7 +1025,20 @@ public: return new T (); } + virtual size_t spice_num_nets () const + { + return m_num_nets; + } + + virtual const std::string &spice_element () const + { + return m_element; + } + private: + size_t m_num_nets; + std::string m_element; + device_class_template (const device_class_template &); device_class_template &operator= (const device_class_template &); }; diff --git a/src/db/db/dbLayoutToNetlistWriter.cc b/src/db/db/dbLayoutToNetlistWriter.cc index 33a353e3e..e82f04705 100644 --- a/src/db/db/dbLayoutToNetlistWriter.cc +++ b/src/db/db/dbLayoutToNetlistWriter.cc @@ -128,6 +128,13 @@ TokenizedOutput &TokenizedOutput::operator<< (const std::string &s) return *this; } +TokenizedOutput &TokenizedOutput::operator<< (double d) +{ + emit_sep (); + stream () << tl::sprintf ("%.12g", d); + return *this; +} + // ------------------------------------------------------------------------------------------- static void write_point (TokenizedOutput &out, const db::Point &pt, db::Point &ref, bool relative) @@ -326,7 +333,7 @@ void std_writer_impl::write_device_class (TokenizedOutput &stream, const d } const tl::Variant &def = p->default_value (); if (def.is_double ()) { - TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string (); + TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_double (); } else if (def.is_long () || def.is_ulong ()) { TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string (); } else if (def.is_a_string ()) { @@ -920,7 +927,7 @@ void std_writer_impl::write (TokenizedOutput &stream, const db::Device &de const tl::Variant &value = device.parameter_value (i->id ()); if (value.is_double ()) { - TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string (); + TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_double (); } else if (value.is_long () || value.is_ulong ()) { TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string (); } else if (value.is_a_string ()) { diff --git a/src/db/db/dbLayoutToNetlistWriter.h b/src/db/db/dbLayoutToNetlistWriter.h index 2f3d2d283..e010788d5 100644 --- a/src/db/db/dbLayoutToNetlistWriter.h +++ b/src/db/db/dbLayoutToNetlistWriter.h @@ -62,6 +62,7 @@ public: ~TokenizedOutput (); TokenizedOutput &operator<< (const std::string &s); + TokenizedOutput &operator<< (double d); tl::OutputStream &stream () { return *mp_stream; } diff --git a/src/db/db/dbNetlistDeviceClasses.cc b/src/db/db/dbNetlistDeviceClasses.cc index 437aa4e32..d0eac99a7 100644 --- a/src/db/db/dbNetlistDeviceClasses.cc +++ b/src/db/db/dbNetlistDeviceClasses.cc @@ -491,6 +491,23 @@ DeviceClassResistor::DeviceClassResistor () add_parameter_definition (db::DeviceParameterDefinition ("W", "Width (micrometer)", 0.0, false, 1e-6, 1.0)); add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "R"; + sp.terminal_order = { "A", "B" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "R", "(R||$)/(M||1.0)" }, + { "A", "A*(M||1.0)" }, + { "W", "W*(M||1.0)" }, + { "P", "P*(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -503,6 +520,11 @@ DeviceClassResistorWithBulk::DeviceClassResistorWithBulk () { set_device_combiner (new ResistorWithBulkDeviceCombiner ()); add_terminal_definition (db::DeviceTerminalDefinition ("W", "Terminal W (well, bulk)")); + + // modify SPICE profile with W terminal + db::DeviceClass::SpiceProfile sp = spice_profile ("*"); + sp.terminal_order.push_back ("W"); + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -528,6 +550,22 @@ DeviceClassCapacitor::DeviceClassCapacitor () add_parameter_definition (db::DeviceParameterDefinition ("C", "Capacitance (Farad)", 0.0)); add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "C"; + sp.terminal_order = { "A", "B" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "C", "(C||$)*(M||1.0)" }, + { "A", "A*(M||1.0)" }, + { "P", "P*(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -540,6 +578,11 @@ DeviceClassCapacitorWithBulk::DeviceClassCapacitorWithBulk () { set_device_combiner (new CapacitorWithBulkDeviceCombiner ()); add_terminal_definition (db::DeviceTerminalDefinition ("W", "Terminal W (well, bulk)")); + + // modify SPICE profile with W terminal + db::DeviceClass::SpiceProfile sp = spice_profile ("*"); + sp.terminal_order.push_back ("W"); + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -561,6 +604,20 @@ DeviceClassInductor::DeviceClassInductor () equivalent_terminal_id (terminal_id_A, terminal_id_B); add_parameter_definition (db::DeviceParameterDefinition ("L", "Inductance (Henry)", 0.0)); + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "L"; + sp.terminal_order = { "A", "B" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "L", "(L||$)/(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -582,6 +639,21 @@ DeviceClassDiode::DeviceClassDiode () add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "D"; + sp.terminal_order = { "A", "C" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "A", "A*(M||1.0)" }, + { "P", "P*(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -614,6 +686,25 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor () add_parameter_definition (db::DeviceParameterDefinition ("AD", "Drain area (square micrometer)", 0.0, false, 1e-12, 2.0)); add_parameter_definition (db::DeviceParameterDefinition ("PS", "Source perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); add_parameter_definition (db::DeviceParameterDefinition ("PD", "Drain perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "M"; + sp.terminal_order = { "D", "G", "S" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "L", "_" }, + { "W", "W*(M||1.0)" }, + { "AS", "AS*(M||1.0)" }, + { "PS", "PS*(M||1.0)" }, + { "AD", "AD*(M||1.0)" }, + { "PD", "PD*(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } bool @@ -853,6 +944,11 @@ DeviceClassMOS4Transistor::DeviceClassMOS4Transistor () { set_device_combiner (new MOS4DeviceCombiner ()); add_terminal_definition (db::DeviceTerminalDefinition ("B", "Bulk")); + + // modify SPICE profile with B terminal + db::DeviceClass::SpiceProfile sp = spice_profile ("*"); + sp.terminal_order.push_back ("B"); + set_spice_profile (std::string ("*"), sp); } bool @@ -893,6 +989,27 @@ DeviceClassBJT3Transistor::DeviceClassBJT3Transistor () add_parameter_definition (db::DeviceParameterDefinition ("AC", "Collector area (square micrometer)", 0.0, false, 1e-12, 2.0)); add_parameter_definition (db::DeviceParameterDefinition ("PC", "Collector perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); add_parameter_definition (db::DeviceParameterDefinition ("NE", "Emitter count", 1.0, true)); + + + // declare the default way of persisting in SPICE + + db::DeviceClass::SpiceProfile sp; + sp.element = "Q"; + sp.terminal_order = { "C", "B", "E" }; + + sp.incoming_parameters = { + { "M", "" }, // drop + // apply scaling if available + { "NE", "_" }, + { "AE", "AE*(M||1.0)" }, + { "PE", "PE*(M||1.0)" }, + { "AB", "AB*(M||1.0)" }, + { "PB", "PB*(M||1.0)" }, + { "AC", "AC*(M||1.0)" }, + { "PC", "PC*(M||1.0)" } + }; + + set_spice_profile (std::string ("*"), sp); } // ------------------------------------------------------------------------------------ @@ -904,6 +1021,11 @@ DeviceClassBJT4Transistor::DeviceClassBJT4Transistor () { set_device_combiner (new BJT4DeviceCombiner ()); add_terminal_definition (db::DeviceTerminalDefinition ("S", "Substrate")); + + // modify SPICE profile with B terminal + db::DeviceClass::SpiceProfile sp = spice_profile ("*"); + sp.terminal_order.push_back ("S"); + set_spice_profile (std::string ("*"), sp); } } diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index fc707a2cf..fbf101f86 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -27,6 +27,8 @@ #include "dbCircuit.h" #include "dbNetlistDeviceClasses.h" #include "tlLog.h" +#include "tlExpression.h" +#include "tlClassRegistry.h" namespace db { @@ -226,7 +228,7 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin auto dp = m_spice_profiles.end (); if (! nn.empty ()) { - m_spice_profiles.find (std::make_pair (element, nn.back ())); + dp = m_spice_profiles.find (std::make_pair (element, nn.back ())); } if (dp != m_spice_profiles.end ()) { @@ -246,27 +248,6 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin element, model, m_profile, int (to.size ()), int (nn.size ()))); } - // reorder the terminals according to the terminal order - std::vector nn_ordered; - - const std::vector &td = dp->second->terminal_definitions (); - for (auto t = td.begin (); t != td.end (); ++t) { - int ti = -1; - for (auto i = to.begin (); i != to.end () && ti < 0; ++i) { - if (*i == t->name ()) { - ti = int (i - to.begin ()); - } - } - if (ti < 0) { - std::string tos = tl::join (to, ","); - error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' terminal order (%s) does not provide a binding for terminal '%s'")), - element, model, m_profile, tos, t->name ())); - } - nn_ordered.push_back (nn[ti]); - } - - nn.swap (nn_ordered); - } else if (element == "X") { // subcircuit call: @@ -403,27 +384,136 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin } } -double NetlistSpiceReaderDelegate::get_multiplier (const std::map ¶ms) +namespace { + +class SPICEParameterEval + : public tl::Eval { - double mult = 1.0; - auto mp = params.find ("M"); - if (mp != params.end ()) { - mult = mp->second.to_double (); +public: + SPICEParameterEval (const std::string &name, const std::map ¶ms, const std::map &all_params, double value) + : m_name (name), m_params (params), m_all_params (all_params), m_value (value) + { + // .. nothing yet .. } - if (mult < 1e-10) { - error (tl::sprintf (tl::to_string (tr ("Invalid multiplier value (M=%.12g) - must not be zero or negative")), mult)); +protected: + virtual void resolve_name (const std::string &name, const tl::EvalFunction *& /*function*/, const tl::Variant *&value, tl::Variant *& /*var*/) + { + if (name == "$") { + value = &m_value; + return; + } + + auto p = m_params.find (name == "_" ? m_name : name); + if (p != m_params.end ()) { + value = &p->second; + return; + } + + auto pp = m_all_params.find (name == "_" ? m_name : name); + if (pp != m_all_params.end ()) { + if (pp->second) { + value = &pp->second->default_value (); + return; + } + } + + static tl::Variant nil; + value = &nil; } - return mult; +private: + const std::string m_name; + const std::map &m_params; + const std::map &m_all_params; + tl::Variant m_value; +}; + } -bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector &nets, const std::map &pv) +static tl::Variant +eval_parameter_expression (const std::string &expr, const std::string &name, const std::map ¶ms, const std::map &all_params, double value) { - std::map params = pv; - std::vector terminal_order; + // shortcuts + if (expr.empty ()) { + return tl::Variant (); + + } else if (expr == "_") { + + auto p = params.find (name); + return p != params.end () ? p->second : tl::Variant (); + + } else if (expr == "$") { + + return tl::Variant (value); + + } else { + + // real evaluation + SPICEParameterEval eval (name, params, all_params, value); + return eval.eval (expr); + + } +} + +static tl::Variant +default_from_value (const tl::Variant &v) +{ + if (v.is_long () || v.is_ulong ()) { + return tl::Variant (long (0)); + } else if (v.is_double ()) { + return tl::Variant (0.0); + } else if (v.is_a_string ()) { + return tl::Variant (""); + } else { + return tl::Variant (); + } +} + +static std::string default_model_name (const std::string &element, size_t nets) +{ + for (tl::Registrar::iterator i = tl::Registrar::begin (); i != tl::Registrar::end (); ++i) { + if (i->spice_element () == element && i->spice_num_nets () == nets) { + return i->name (); + } + } + + // TODO: raise an error maybe? + return "UNK"; +} + +static db::DeviceClass *bootstrap_device_class (db::Netlist *netlist, const std::string &name, const std::string &element, size_t nets) +{ + for (tl::Registrar::iterator i = tl::Registrar::begin (); i != tl::Registrar::end (); ++i) { + + if (i->spice_element () == element && i->spice_num_nets () == nets) { + + db::DeviceClass *cls = netlist->device_class_by_name (name); + if (! cls) { + cls = i->create (); + cls->set_name (name); + netlist->add_device_class (cls); + } + + return cls; + + } + + } + + return 0; +} + +bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector &nets, const std::map ¶ms) +{ std::string cn = model; + + // use a default model name if none is given + if (cn.empty ()) { + cn = default_model_name (element, nets.size ()); + } + db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn); if (element.empty ()) { @@ -431,248 +521,122 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin // obtained through SPICE profile. tl_assert (cls != 0); - } else if (element == "R") { - - if (nets.size () == 2) { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a resistor device class as required by 'R' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "RES"; - } - cls = make_device_class (circuit, cn); - } - } else if (nets.size () == 3) { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal resistor device class as required by 'R' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "RES3"; - } - cls = make_device_class (circuit, cn); - } - } else { - error (tl::to_string (tr ("A 'R' element requires two or three nets"))); - } - - // Apply multiplier (divider, according to ngspice manual) - double mult = get_multiplier (params); - value /= mult; - params["R"] = tl::Variant (value); - - // Apply multiplier to other parameters - static const char *scale_params[] = { "A", "P", "W" }; - for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { - auto p = params.find (scale_params [i]); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); - } - } - - } else if (element == "L") { - - if (nets.size () == 2) { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a inductor device class as required by 'L' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "IND"; - } - cls = make_device_class (circuit, cn); - } - } else { - error (tl::to_string (tr ("A 'L' element requires two nets"))); - } - - // Apply multiplier (divider, according to ngspice manual) - double mult = get_multiplier (params); - value /= mult; - params["L"] = tl::Variant (value); - - } else if (element == "C") { - - if (nets.size () == 2) { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a capacitor device class as required by 'C' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "CAP"; - } - cls = make_device_class (circuit, cn); - } - } else if (nets.size () == 3) { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal capacitor device class as required by 'C' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "CAP3"; - } - cls = make_device_class (circuit, cn); - } - } else { - error (tl::to_string (tr ("A 'C' element requires two or three nets"))); - } - - // Apply multiplier - double mult = get_multiplier (params); - value *= mult; - params["C"] = tl::Variant (value); - - // Apply multiplier to other parameters - static const char *scale_params[] = { "A", "P" }; - for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { - auto p = params.find (scale_params [i]); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); - } - } - - } else if (element == "D") { - - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a diode device class as required by 'D' element")), cn)); - } - } else { - if (cn.empty ()) { - cn = "DIODE"; - } - cls = make_device_class (circuit, cn); - } - - // Apply multiplier - double mult = get_multiplier (params); - static const char *scale_params[] = { "A", "P" }; - for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { - auto p = params.find (scale_params [i]); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); - } - } - - } else if (element == "Q") { - - if (nets.size () != 3 && nets.size () != 4) { - error (tl::to_string (tr ("'Q' element needs to have 3 or 4 terminals"))); - } else if (cls) { - if (nets.size () == 3) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a 3-terminal BJT device class as required by 'Q' element")), cn)); - } - } else { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal BJT device class as required by 'Q' element")), cn)); - } - } - } else { - if (nets.size () == 3) { - if (cn.empty ()) { - cn = "BJT3"; - } - cls = make_device_class (circuit, cn); - } else { - if (cn.empty ()) { - cn = "BJT4"; - } - cls = make_device_class (circuit, cn); - } - } - - // Apply multiplier - double mult = get_multiplier (params); - static const char *scale_params[] = { "AE", "PE", "AB", "PB", "AC", "PC" }; - for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { - auto p = params.find (scale_params [i]); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); - } - } - - } else if (element == "M") { - - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal MOS device class as required by 'M' element")), cn)); - } - } else { - if (nets.size () == 4) { - if (cn.empty ()) { - cn = "MOS4"; - } - cls = make_device_class (circuit, cn); - } else { - error (tl::to_string (tr ("'M' element needs to have 4 terminals"))); - } - } - - // Apply multiplier - double mult = get_multiplier (params); - static const char *scale_params[] = { "W", "AD", "AS", "PD", "PS" }; - for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { - auto p = params.find (scale_params [i]); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); - } - } - - // issue #1304 - terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_D); - terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_G); - terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_S); - terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_B); - } else if (! cls) { - // if the element class cannot be deduced from the "X" element name, raise an error - error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element)); + // create a device class from the device templates + cls = bootstrap_device_class (circuit->netlist (), cn, element, nets.size ()); + + if (! cls) { + + std::vector candidates; + for (tl::Registrar::iterator i = tl::Registrar::begin (); i != tl::Registrar::end (); ++i) { + if (i->spice_element () == element) { + candidates.push_back (tl::sprintf (tl::to_string (tr ("%s (%d terminals)")), i->name (), int (nets.size ()))); + } + } + + if (! candidates.empty ()) { + error (tl::sprintf (tl::to_string (tr ("No matching device found on element '%s' with '%d' terminals. Candidates are: ")), element, int (nets.size ())) + tl::join (candidates, ", ")); + } else { + error (tl::sprintf (tl::to_string (tr ("Element '%s' is not understood by SPICE parser")), element, int (nets.size ()))); + } + + } } + const db::DeviceClass::SpiceProfile &sp = cls->spice_profile (m_profile); const std::vector &td = cls->terminal_definitions (); + if (td.size () != nets.size ()) { error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d, but %d are given")), cn, int (td.size ()), int (nets.size ()))); } + // derive the terminal order from the SPICE profile + + std::vector terminal_order; + terminal_order.reserve (sp.terminal_order.size ()); + + for (auto to = sp.terminal_order.begin (); to != sp.terminal_order.end (); ++to) { + if (! cls->has_terminal_with_name (*to)) { + error (tl::sprintf (tl::to_string (tr ("Device class '%s' and SPICE profile '%s': inconsistent terminal order - '%s' is not a valid terminal name")), + cls->name (), m_profile, *to)); + } + terminal_order.push_back (cls->terminal_id_for_name (*to)); + } + + if (terminal_order.size () != td.size ()) { + error (tl::sprintf (tl::to_string (tr ("Device class '%s' and SPICE profile '%s': inconsistent terminal order - '%d' terminals are defined, '%d' given in terminal order list")), + cls->name (), m_profile, int (td.size ()), int (terminal_order.size ()))); + } + + // create the device + db::Device *device = new db::Device (cls, name); circuit->add_device (device); - if (terminal_order.empty ()) { - for (auto t = td.begin (); t != td.end (); ++t) { - device->connect_terminal (t->id (), nets [t - td.begin ()]); - } - } else { - for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) { - device->connect_terminal (*t, nets [t - terminal_order.begin ()]); + // make the device connections + + for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) { + device->connect_terminal (*t, nets [t - terminal_order.begin ()]); + } + + // transfer parameters into device + + const std::map &dict = sp.incoming_parameters; + + std::map all_params; + + for (auto p = params.begin (); p != params.end (); ++p) { + if (cls->has_parameter_with_name (p->first)) { + all_params.insert (std::make_pair (p->first, cls->parameter_definition (cls->parameter_id_for_name (p->first)))); + } else { + all_params.insert (std::make_pair (p->first, (const db::DeviceParameterDefinition *) 0)); } } - for (auto p = params.begin (); p != params.end (); ++p) { + for (auto p = all_params.begin (); p != all_params.end (); ++p) { - if (cls->has_parameter_with_name (p->first)) { + std::map::const_iterator id; + if ((id = dict.find (p->first)) != dict.end ()) { - device->set_parameter_value (p->first, p->second); + if (p->second) { + device->set_parameter_value (p->second->id (), eval_parameter_expression (p->first, id->second, params, all_params, value)); + } else { + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + device->set_parameter_value_create (p->first, pv, false, default_from_value (pv)); + } + + } else if (p->second && p->second->is_primary () && (id = dict.find ("*!")) != dict.end ()) { + + device->set_parameter_value (p->second->id (), eval_parameter_expression (p->first, id->second, params, all_params, value)); + + } else if (p->second && ! p->second->is_primary () && (id = dict.find ("*?")) != dict.end ()) { + + device->set_parameter_value (p->second->id (), eval_parameter_expression (p->first, id->second, params, all_params, value)); + + } else if (p->second && (id = dict.find ("**")) != dict.end ()) { + + device->set_parameter_value (p->second->id (), eval_parameter_expression (p->first, id->second, params, all_params, value)); + + } else if ((id = dict.find ("*")) != dict.end ()) { + + if (p->second) { + device->set_parameter_value (p->second->id (), eval_parameter_expression (p->first, id->second, params, all_params, value)); + } else { + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + device->set_parameter_value_create (p->first, pv, false, default_from_value (pv)); + } } else if (m_read_all_parameters) { - // if requested, create the parameter - if (p->second.is_long () || p->second.is_ulong ()) { - device->set_parameter_value_create (p->first, p->second, false, tl::Variant (long (0))); - } else if (p->second.is_double ()) { - device->set_parameter_value_create (p->first, p->second, false, tl::Variant (0.0)); - } else if (p->second.is_a_string ()) { - device->set_parameter_value_create (p->first, p->second, false, tl::Variant ("")); + auto pp = params.find (p->first); + tl_assert (pp != params.end ()); + + if (p->second) { + device->set_parameter_value (p->second->id (), pp->second); } else { - device->set_parameter_value_create (p->first, p->second, false, tl::Variant ()); + device->set_parameter_value_create (p->first, pp->second, false, default_from_value (pp->second)); } } @@ -680,6 +644,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } apply_parameter_scaling (device); + return true; } diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.h b/src/db/db/dbNetlistSpiceReaderDelegate.h index 3744d89bf..e0860fd9c 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.h +++ b/src/db/db/dbNetlistSpiceReaderDelegate.h @@ -219,7 +219,6 @@ private: std::map, const db::DeviceClass *> m_spice_profiles; void def_values_per_element (const std::string &element, std::map &pv); - double get_multiplier (const std::map &pv); }; } diff --git a/src/db/db/dbNetlistSpiceWriter.cc b/src/db/db/dbNetlistSpiceWriter.cc index f91c2c962..0271c61e8 100644 --- a/src/db/db/dbNetlistSpiceWriter.cc +++ b/src/db/db/dbNetlistSpiceWriter.cc @@ -44,7 +44,7 @@ static const char *s_not_connect_prefix = "nc_"; // -------------------------------------------------------------------------------- NetlistSpiceWriterDelegate::NetlistSpiceWriterDelegate () - : mp_writer (0), mp_netlist (0), m_write_all_parameters (false) + : mp_writer (0), mp_netlist (0), m_write_all_parameters (true) { // .. nothing yet .. } diff --git a/src/db/db/gsiDeclDbNetlistSpiceReader.cc b/src/db/db/gsiDeclDbNetlistSpiceReader.cc index 2b9ac667a..39d6b2c4c 100644 --- a/src/db/db/gsiDeclDbNetlistSpiceReader.cc +++ b/src/db/db/gsiDeclDbNetlistSpiceReader.cc @@ -418,21 +418,12 @@ Class db_NetlistSpiceReaderDelegate ("db", "Netl gsi::method_ext ("parse_element", &parse_element_fb, "@hide") + gsi::method_ext ("control_statement", &control_statement_fb, "@hide") + gsi::method_ext ("translate_net_name", &translate_net_name_fb, "@hide") + + // provided for test purposes gsi::method ("read_all_parameters=", &NetlistSpiceReaderDelegateImpl::read_all_parameters, - "@brief Sets a flag indicating whether to read all device parameters.\n" - "If this flag is set to true, all parameters of the devices are read in the default implementation. " - "If set to false (the default), only known parameters (i.e. only parameters declared by the device classes in the netlist) are read.\n" - "\n" - "Note, that you can customize the reader's device input also by reimplementing \\parse_element or \\element. This reimplementation may " - "chose to ignore the \\read_all_parameters attribute.\n" - "\n" - "This attribute has been introduced in version 0.31.0." + "@hide\n" ) + gsi::method ("read_all_parameters=", &NetlistSpiceReaderDelegateImpl::set_read_all_parameters, gsi::arg ("f"), - "@brief Gets a flag indicating whether to read all device parameters.\n" - "See \\read_all_parameters= for a description of this attribute.\n" - "\n" - "This attribute has been introduced in version 0.31.0." + "@hide\n" ) + gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"), "@brief This method is called when the reader starts reading a netlist\n" diff --git a/src/db/db/gsiDeclDbNetlistSpiceWriter.cc b/src/db/db/gsiDeclDbNetlistSpiceWriter.cc index 774692fde..c2410c11d 100644 --- a/src/db/db/gsiDeclDbNetlistSpiceWriter.cc +++ b/src/db/db/gsiDeclDbNetlistSpiceWriter.cc @@ -134,21 +134,12 @@ Class db_NetlistSpiceWriterDelegate ("db", "Netl gsi::method ("format_name", &NetlistSpiceWriterDelegateImpl::format_name, gsi::arg ("name"), "@brief Formats the given name in a SPICE-compatible way" ) + + // provided for test purposes gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters, - "@brief Sets a flag indicating whether to write all device parameters.\n" - "If this flag is set to true, all parameters of the devices are written in the default implementation. " - "If set to false (the default), only primary parameters are written.\n" - "\n" - "Note, that you can customize the writer's device output also by reimplementing \\write_device. This reimplementation may " - "chose to ignore the \\write_all_parameters attribute.\n" - "\n" - "This attribute has been introduced in version 0.31.0." + "@hide" ) + gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters, gsi::arg ("f"), - "@brief Gets a flag indicating whether to write all device parameters.\n" - "See \\write_all_parameters= for a description of this attribute.\n" - "\n" - "This attribute has been introduced in version 0.31.0." + "@hide" ), "@brief Provides a delegate for the SPICE writer for doing special formatting for devices\n" "Supply a customized class to provide a specialized writing scheme for devices. "