From bc27086abb1b49eb800a0596965acf437a093779 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 13 Jun 2026 21:49:39 +0200 Subject: [PATCH] SPICE profiles: outgoing parameters --- src/db/db/dbNetlistDeviceClasses.cc | 8 +- src/db/db/dbNetlistSpiceReader.cc | 18 ++- src/db/db/dbNetlistSpiceReaderDelegate.cc | 15 +- src/db/db/dbNetlistSpiceWriter.cc | 180 +++++++++++++++++++--- src/db/db/dbNetlistSpiceWriter.h | 8 + src/db/db/gsiDeclDbNetlistSpiceWriter.cc | 35 ++++- 6 files changed, 224 insertions(+), 40 deletions(-) diff --git a/src/db/db/dbNetlistDeviceClasses.cc b/src/db/db/dbNetlistDeviceClasses.cc index 90993cadd..1faeff3bd 100644 --- a/src/db/db/dbNetlistDeviceClasses.cc +++ b/src/db/db/dbNetlistDeviceClasses.cc @@ -692,7 +692,9 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor () db::DeviceClass::SpiceProfile sp; sp.element = "M"; - sp.terminal_order = { "D", "G", "S" }; + // NOTE: we add another dummy terminal which is skipped on reader and + // duplicates the S terminal in the writer. + sp.terminal_order = { "D", "G", "S", "?S" }; sp.incoming_parameters = { { "M", "" }, // drop @@ -705,6 +707,8 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor () { "PD", "PD*M" } }; + sp.outgoing_parameters = { { "**", "_" }}; + set_spice_profile (std::string ("*"), sp); } @@ -948,7 +952,7 @@ DeviceClassMOS4Transistor::DeviceClassMOS4Transistor () // modify SPICE profile with B terminal db::DeviceClass::SpiceProfile sp = spice_profile ("*"); - sp.terminal_order.push_back ("B"); + sp.terminal_order = { "D", "G", "S", "B" }; set_spice_profile (std::string ("*"), sp); } diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 224839218..eb91d38d1 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -1189,6 +1189,17 @@ SpiceNetlistBuilder::subcircuit_captured (const std::string &nc_name) } } +static size_t min_terminals (const std::vector &to) +{ + for (size_t i = 0; i < to.size (); ++i) { + if (to[i].front () == '?') { + return i; + } + } + + return to.size (); +} + bool SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &prefix, const std::string &name) { @@ -1216,9 +1227,12 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref // 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")), + 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 max, but got %d")), element, model, mp_delegate->profile (), int (to.size ()), int (nn.size ()))); + } else if (nn.size () < min_terminals (to)) { + error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals min, but got %d")), + element, model, mp_delegate->profile (), int (min_terminals (to)), int (nn.size ()))); } // indicates that we must not use the element name further diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index 9164432f8..90b369e70 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -547,16 +547,15 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin 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 (to->front () == '?') { + break; + } 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)); @@ -569,6 +568,14 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin cls->name (), m_profile, int (td.size ()), int (terminal_order.size ()))); } + // check number of nets available + + if (nets.size () > sp.terminal_order.size ()) { + error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d max, but %d are given")), cn, int (sp.terminal_order.size ()), int (nets.size ()))); + } else if (nets.size () < terminal_order.size ()) { + error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d min, but %d are given")), cn, int (terminal_order.size ()), int (nets.size ()))); + } + // create the device db::Device *device = new db::Device (cls, name); diff --git a/src/db/db/dbNetlistSpiceWriter.cc b/src/db/db/dbNetlistSpiceWriter.cc index cb7eb95b8..171945acd 100644 --- a/src/db/db/dbNetlistSpiceWriter.cc +++ b/src/db/db/dbNetlistSpiceWriter.cc @@ -28,6 +28,7 @@ #include "tlUniqueName.h" #include "tlTimer.h" #include "tlLog.h" +#include "tlExpression.h" #include #include @@ -106,18 +107,160 @@ void NetlistSpiceWriterDelegate::write_device (const db::Device &dev) const } } + +static void +write_parameter_value (std::ostringstream &os, const tl::Variant &v, double sis) +{ + if (v.is_double ()) { + + // for compatibility + if (fabs (sis * 1e6 - 1.0) < db::epsilon) { + os << tl::to_string (v.to_double ()) << "U"; + } else if (fabs (sis * 1e12 - 1.0) < db::epsilon) { + os << tl::to_string (v.to_double ()) << "P"; + } else { + os << tl::to_string (v.to_double () * sis); + } + + } else if (v.is_long () || v.is_ulong ()) { + os << v.to_string (); + } else { + os << tl::to_word_or_quoted_string (v.to_string ()); + } +} + +namespace { + +class SPICEParameterEval + : public tl::Eval +{ +public: + SPICEParameterEval (const std::string &name, const db::Device &dev) + : m_name (name), mp_dev (&dev) + { + // .. nothing yet .. + } + +protected: + virtual void resolve_name (const std::string &name, const tl::EvalFunction *& /*function*/, const tl::Variant *&value, tl::Variant *& /*var*/) + { + std::string n = (name == "_" ? m_name : name); + if (mp_dev->device_class ()->has_parameter_with_name (n)) { + value = &mp_dev->parameter_value (mp_dev->device_class ()->parameter_id_for_name (n)); + return; + } + + static tl::Variant nil; + value = &nil; + } + +private: + const std::string m_name; + const db::Device *mp_dev; +}; + +} + +static tl::Variant +eval_parameter_expression (const std::string &name, const std::string &expr, const db::Device &dev) +{ + // shortcuts + if (expr.empty ()) { + + return tl::Variant (); + + } else if (expr == "_") { + + if (dev.device_class ()->has_parameter_with_name (name)) { + auto id = dev.device_class ()->parameter_id_for_name (name); + return dev.parameter_value (id); + } else { + return tl::Variant (); + } + + } else { + + SPICEParameterEval eval (name, dev); + return eval.eval (expr); + + } +} + void NetlistSpiceWriterDelegate::write_device_profile (const db::Device &dev, const db::DeviceClass::SpiceProfile &profile) const { std::ostringstream os; os << profile.element; os << format_name (dev.expanded_name ()); + os << format_terminals_with_order (dev, profile.terminal_order); - if (! dev.device_class ()->name ().empty ()) { - os << " "; - os << format_name (dev.device_class ()->name ()); + + // transfer parameters into device + + const std::map &dict = profile.outgoing_parameters; + + std::vector > all_params; + std::vector > > param_values; + + const db::DeviceClass *cls = dev.device_class (); + tl_assert (cls != 0); + + const auto &pdef = cls->parameter_definitions (); + for (auto p = pdef.begin (); p != pdef.end (); ++p) { + all_params.push_back (std::make_pair (p->name (), p.operator-> ())); + } + + for (auto d = dict.begin (); d != dict.end (); ++d) { + if (! d->first.empty () && d->first.front () != '*' && ! cls->has_parameter_with_name (d->first)) { + all_params.push_back (std::make_pair (d->first, (const db::DeviceParameterDefinition *) 0)); + } + } + + tl::Variant direct_value; + + for (auto p = all_params.begin (); p != all_params.end (); ++p) { + + auto id = dict.end (); + + if ((id = dict.find (p->first)) != dict.end () || + (p->second && p->second->is_primary () && (id = dict.find ("*!")) != dict.end ()) || + (p->second && ! p->second->is_primary () && (id = dict.find ("*?")) != dict.end ()) || + (p->second && (id = dict.find ("**")) != dict.end ()) || + ((id = dict.find ("*")) != dict.end ()) || + m_write_all_parameters) { + + tl::Variant pv; + if (id != dict.end ()) { + pv = eval_parameter_expression (p->first, id->second, dev); + } else if (p->second) { + pv = dev.parameter_value (p->second->id ()); + } + + if (! pv.is_nil ()) { + if (p->first == "$") { + direct_value = pv; + } else { + param_values.push_back (std::make_pair (p->first, std::make_pair (pv, p->second))); + } + } + + } + + } + + if (! direct_value.is_nil ()) { + write_parameter_value (os, direct_value, 1.0); + } + + if (! cls->name ().empty ()) { + os << " "; + os << format_name (cls->name ()); + } + + for (auto p = param_values.begin (); p != param_values.end (); ++p) { + os << " " << p->first << "="; + write_parameter_value (os, p->second.first, p->second.second ? p->second.second->si_scaling () : 1.0); } - os << format_params (dev); emit_line (os.str ()); } @@ -125,6 +268,7 @@ void NetlistSpiceWriterDelegate::write_device_profile (const db::Device &dev, co void NetlistSpiceWriterDelegate::write_device_default (const db::Device &dev) const { const db::DeviceClass *dc = dev.device_class (); + tl_assert (dc != 0); const db::DeviceClassCapacitor *cap = dynamic_cast (dc); const db::DeviceClassCapacitor *cap3 = dynamic_cast (dc); @@ -260,10 +404,14 @@ std::string NetlistSpiceWriterDelegate::format_terminals_with_order (const db::D std::ostringstream os; for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) { - if (! dev.device_class ()->has_terminal_with_name (*t)) { + std::string tn = *t; + if (tn.front () == '?') { + tn.erase (0, 1); + } + if (! dev.device_class ()->has_terminal_with_name (tn)) { throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid terminal name '%s' for device of class '%s' in SPICE profile '%s")), *t, dev.device_class ()->name (), mp_writer->profile ())); } - os << " " << net_to_string (dev.net_for_terminal (dev.device_class ()->terminal_id_for_name (*t))); + os << " " << net_to_string (dev.net_for_terminal (dev.device_class ()->terminal_id_for_name (tn))); } return os.str (); @@ -283,25 +431,7 @@ std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, si if (i->id () != without_id && (! only_primary || i->is_primary ())) { os << " " << i->name () << "="; - - const tl::Variant &v = dev.parameter_value (i->id ()); - if (v.is_double ()) { - - double sis = i->si_scaling (); - // for compatibility - if (fabs (sis * 1e6 - 1.0) < db::epsilon) { - os << tl::to_string (v.to_double ()) << "U"; - } else if (fabs (sis * 1e12 - 1.0) < db::epsilon) { - os << tl::to_string (v.to_double ()) << "P"; - } else { - os << tl::to_string (v.to_double () * sis); - } - - } else if (v.is_long () || v.is_ulong ()) { - os << v.to_string (); - } else { - os << tl::to_word_or_quoted_string (v.to_string ()); - } + write_parameter_value (os, dev.parameter_value (i->id ()), i->si_scaling ()); } diff --git a/src/db/db/dbNetlistSpiceWriter.h b/src/db/db/dbNetlistSpiceWriter.h index 03ccbbfbc..098b3cf63 100644 --- a/src/db/db/dbNetlistSpiceWriter.h +++ b/src/db/db/dbNetlistSpiceWriter.h @@ -163,6 +163,14 @@ public: return m_with_comments; } + /** + * @brief Gets the delegate + */ + NetlistSpiceWriterDelegate *delegate () + { + return mp_delegate.get (); + } + private: friend class NetlistSpiceWriterDelegate; diff --git a/src/db/db/gsiDeclDbNetlistSpiceWriter.cc b/src/db/db/gsiDeclDbNetlistSpiceWriter.cc index c2410c11d..c1f1237bb 100644 --- a/src/db/db/gsiDeclDbNetlistSpiceWriter.cc +++ b/src/db/db/gsiDeclDbNetlistSpiceWriter.cc @@ -133,13 +133,6 @@ 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, - "@hide" - ) + - gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters, gsi::arg ("f"), - "@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. " @@ -193,6 +186,19 @@ Class db_NetlistWriter ("db", "NetlistWriter", "This class has been introduced in version 0.26." ); + +void set_write_all_parameters (db::NetlistSpiceWriter *writer, bool f) +{ + if (writer->delegate ()) { + writer->delegate ()->set_write_all_parameters (f); + } +} + +bool write_all_parameters (db::NetlistSpiceWriter *writer) +{ + return writer->delegate () ? writer->delegate ()->write_all_parameters () : false; +} + Class db_NetlistSpiceWriter (db_NetlistWriter, "db", "NetlistSpiceWriter", gsi::constructor ("new", &new_spice_writer, gsi::arg ("profile", std::string ()), "@brief Creates a new writer without delegate.\n" @@ -208,6 +214,21 @@ Class db_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne "\n" "The profile argument has been added in version 0.31.0." ) + + gsi::method_ext ("write_all_parameters=", &set_write_all_parameters, + "@brief Sets a flag indicating whether to write all parameters\n" + "\n" + "With this flag all device parameters are written to the SPICE file, even ones which are normally not. " + "Note that a SPICE writer delegate or a SPICE profile may override the default behavior.\n" + "\n" + "This attribute has been introduced in version 0.31.0." + ) + + gsi::method_ext ("write_all_parameters=", &set_write_all_parameters, + "@brief Gets a flag indicating whether to write all parameters\n" + "\n" + "See \\write_all_parameters= for details about this attribute.\n" + "\n" + "This attribute has been introduced in version 0.31.0." + ) + gsi::method ("not_connect_prefix", &db::NetlistSpiceWriter::not_connect_prefix, "@brief Gets the prefix used for terminals or pins which are not connected.\n" "See \\not_connect_prefix= for details.\n"