diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index b4b484729..224839218 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -1192,7 +1192,13 @@ SpiceNetlistBuilder::subcircuit_captured (const std::string &nc_name) bool SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &prefix, const std::string &name) { - // generic parse + // low-level SPICE line parsing + // - "element" will be the element characters ("R", "M", ...) + // - "model" will be the model name if given or empty if no model name is there + // - "value" will be the direct value (for "R", "C", "L"), usually also available as parameter "R", "C" or "L" + // - "nn" will be a list of node (net) names + // - "pv" will be a list of key/value pairs of the parameters + std::vector nn; NetlistSpiceReader::parameters_type pv; std::string model; @@ -1203,6 +1209,25 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref model = mp_netlist->normalize_name (model); + // checks if the element is associated with a SPICE profile + + const db::DeviceClass *dc = mp_delegate->device_class (element, model); + if (dc) { + + // check the number of terminals + const std::vector &to = dc->spice_profile (mp_delegate->profile ()).terminal_order; + if (nn.size () != to.size ()) { + error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals, but got %d")), + element, model, mp_delegate->profile (), int (to.size ()), int (nn.size ()))); + } + + // indicates that we must not use the element name further + element.clear (); + + } + + // obtains the db::Net objects from the net names + std::vector nets; for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { nets.push_back (make_net (mp_delegate->translate_net_name (*i))); @@ -1252,7 +1277,10 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref return true; } else { + + // element to device translation return mp_delegate->element (mp_netlist_circuit, element, name, model, value, nets, pv); + } } diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index fbf101f86..9156b3f15 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -71,6 +71,18 @@ NetlistSpiceReaderDelegate::set_read_all_parameters (bool f) m_read_all_parameters = f; } +bool +NetlistSpiceReaderDelegate::legacy_mode () const +{ + return m_legacy_mode; +} + +void +NetlistSpiceReaderDelegate::set_legacy_mode (bool f) +{ + m_legacy_mode = f; +} + void NetlistSpiceReaderDelegate::set_netlist (db::Netlist *netlist, const std::string &profile) { m_options = NetlistSpiceReaderOptions (); @@ -225,30 +237,7 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin def_values_per_element (element, pv); parse_element_components (s, nn, pv, variables); - auto dp = m_spice_profiles.end (); - - if (! nn.empty ()) { - dp = m_spice_profiles.find (std::make_pair (element, nn.back ())); - } - - if (dp != m_spice_profiles.end ()) { - - // element bound to a SPICE element through the device class - - model = dp->first.second; - nn.pop_back (); - - // indicates that we must not use the element name further - element.clear (); - - const std::vector &to = dp->second->spice_profile (m_profile).terminal_order; - - if (nn.size () != to.size ()) { - error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals, but got %d")), - element, model, m_profile, int (to.size ()), int (nn.size ()))); - } - - } else if (element == "X") { + if (! m_legacy_mode && element == "X") { // subcircuit call: // Xname n1 n2 ... nn circuit [params] @@ -432,7 +421,7 @@ private: } 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) +eval_parameter_expression (const std::string &name, const std::string &expr, const std::map ¶ms, const std::map &all_params, double value) { // shortcuts if (expr.empty ()) { @@ -505,6 +494,16 @@ static db::DeviceClass *bootstrap_device_class (db::Netlist *netlist, const std: return 0; } +const db::DeviceClass *NetlistSpiceReaderDelegate::device_class (const std::string &element, const std::string &model) const +{ + auto dp = m_spice_profiles.find (std::make_pair (element, model)); + if (dp != m_spice_profiles.end ()) { + return dp->second; + } else { + 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; @@ -600,32 +599,45 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin std::map::const_iterator id; if ((id = dict.find (p->first)) != 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)); + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + if (! pv.is_nil ()) { + if (p->second) { + device->set_parameter_value (p->second->id (), pv); + } else { + 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)); + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + if (! pv.is_nil ()) { + device->set_parameter_value (p->second->id (), 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)); + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + if (! pv.is_nil ()) { + device->set_parameter_value (p->second->id (), pv); + } } 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)); + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + if (! pv.is_nil ()) { + device->set_parameter_value (p->second->id (), pv); + } } 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)); + tl::Variant pv = eval_parameter_expression (p->first, id->second, params, all_params, value); + if (! pv.is_nil ()) { + if (p->second) { + device->set_parameter_value (p->second->id (), pv); + } else { + device->set_parameter_value_create (p->first, pv, false, default_from_value (pv)); + } } } else if (m_read_all_parameters) { diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.h b/src/db/db/dbNetlistSpiceReaderDelegate.h index e0860fd9c..b00ae069b 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.h +++ b/src/db/db/dbNetlistSpiceReaderDelegate.h @@ -106,6 +106,26 @@ public: */ void set_read_all_parameters (bool f); + /** + * @brief Gets a flag indicating legacy mode + * + * This flag controls the parsing of SPICE lines in the default + * implementation of "parse_element". With this flag set to true (default), + * the elements are restricted to their original meaning, i.e. "R" + * can have two or three nets and a direct value. With this flag set to + * false, all elements can have any number of terminals, the last entry + * is the model and a direct value is not allowed. + * + * This flag allows configuring the SPICE reading without + * need to reimplementing a delegate. + */ + bool legacy_mode () const; + + /** + * @brief Sets a flag indicating legacy mode + */ + void set_legacy_mode (bool f); + /** * @brief Called when the netlist reading starts */ @@ -211,11 +231,27 @@ public: */ void apply_parameter_scaling (db::Device *device) const; + /** + * @brief Gets the device class registered for an element/model combination + * + * Returns a null pointer if no such combination is registered. + */ + const db::DeviceClass *device_class (const std::string &element, const std::string &model) const; + + /** + * @brief Gets the SPICE profile + */ + const std::string &profile () const + { + return m_profile; + } + private: db::Netlist *mp_netlist; NetlistSpiceReaderOptions m_options; std::string m_profile; bool m_read_all_parameters; + bool m_legacy_mode; std::map, const db::DeviceClass *> m_spice_profiles; void def_values_per_element (const std::string &element, std::map &pv); diff --git a/src/db/db/dbNetlistSpiceWriter.cc b/src/db/db/dbNetlistSpiceWriter.cc index 0271c61e8..f91c2c962 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 (true) + : mp_writer (0), mp_netlist (0), m_write_all_parameters (false) { // .. nothing yet .. } diff --git a/src/db/db/gsiDeclDbNetlistSpiceReader.cc b/src/db/db/gsiDeclDbNetlistSpiceReader.cc index 39d6b2c4c..e2ab326ae 100644 --- a/src/db/db/gsiDeclDbNetlistSpiceReader.cc +++ b/src/db/db/gsiDeclDbNetlistSpiceReader.cc @@ -418,12 +418,60 @@ 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, - "@hide\n" + gsi::method ("read_all_parameters", &NetlistSpiceReaderDelegateImpl::read_all_parameters, + "@brief Gets a flag indicating whether to read all SPICE parameters\n" + "\n" + "With this flag set to false, unknown device parameters are ignored. Parameters are declared by the " + "device classes present in the netlist, before the reader is used. SPICE profiles can be used to " + "configure the parameters accepted by the SPICE reader, in addition to fixed and pre-defined parameters. " + "With 'read_all_parameters' set to true, all parameters are read, even if they are not declared as fixed " + "parameters by the device classes or as incoming parameters in their SPICE profiles.\n" + "\n" + "Note, that reimplementing \"element\" may change that behavior.\n" + "\n" + "This attribute has been introduced in version 0.31.0." ) + gsi::method ("read_all_parameters=", &NetlistSpiceReaderDelegateImpl::set_read_all_parameters, gsi::arg ("f"), - "@hide\n" + "@brief Sets a flag indicating whether to read all SPICE parameters\n" + "See \\read_all_parameters for details about this attribute.\n" + "\n" + "This attribute has been introduced in version 0.31.0." + ) + + gsi::method ("legacy_mode=", &NetlistSpiceReaderDelegateImpl::legacy_mode, + "@brief Gets a flag indicating legacy mode\n" + "\n" + "This flag controls the parsing of SPICE lines in the default\n" + "implementation of \"parse_element\". With this flag set to true (default),\n" + "the elements are restricted to their original SPICE meaning, i.e. \"R\"\n" + "can have two or three nets and a direct value. With this flag set to\n" + "false, all elements can have any number of terminals, the last entry\n" + "is the model and a direct value is not allowed. In non-legacy mode, all\n" + "elements share the same notation and can be bound to devices through\n" + "SPICE profiles without restrictions.\n" + "\n" + "@code\n" + "* Allowed always:\n" + "R A B MODEL R=1k\n" + "\n" + "* Allowed with legacy_mode=true (default):\n" + "R A B 1k\n" + "\n" + "* Allowed with legacy_mode=false with a device class named 'MODEL' and\n" + "* accepting 4 terminals for the 'R' element in its SPICE profile:\n" + "R A B C D MODEL A=17.5 P=105\n" + "@/code\n" + "\n" + "This flag allows configuring the SPICE reading without\n" + "need for reimplementing a delegate. Note, that reimplementing\n" + "\"parse_element\" may change that behavior.\n" + "\n" + "This attribute has been introduced in version 0.31.0." + ) + + gsi::method ("legacy_mode=", &NetlistSpiceReaderDelegateImpl::set_legacy_mode, gsi::arg ("f"), + "@brief Sets a flag indicating legacy mode\n" + "See \\legacy_mode for details about this attribute.\n" + "\n" + "This attribute has been introduced in version 0.31.0." ) + gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"), "@brief This method is called when the reader starts reading a netlist\n" @@ -537,6 +585,11 @@ Class db_NetlistSpiceReaderDelegate ("db", "Netl "\n" "See \\NetlistSpiceReader for more details.\n" "\n" + "'SPICE profiles' significantly reduce the need for reimplementing a SPICE reader delegate. " + "These profiles are descriptors attached to device classes. By pre-registering device classes " + "in the netlist before reading SPICE files, these classes will describe themselves and how they " + "like to be read from and written to SPICE files.\n" + "\n" "This class has been introduced in version 0.26. Profiles have been added to the device classes in version 0.31.0." );