SPICE profiles: outgoing parameters

This commit is contained in:
Matthias Koefferlein 2026-06-13 21:49:39 +02:00
parent d09eb21096
commit bc27086abb
6 changed files with 224 additions and 40 deletions

View File

@ -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);
}

View File

@ -1189,6 +1189,17 @@ SpiceNetlistBuilder::subcircuit_captured (const std::string &nc_name)
}
}
static size_t min_terminals (const std::vector<std::string> &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<std::string> &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

View File

@ -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<db::DeviceTerminalDefinition> &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<size_t> 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);

View File

@ -28,6 +28,7 @@
#include "tlUniqueName.h"
#include "tlTimer.h"
#include "tlLog.h"
#include "tlExpression.h"
#include <sstream>
#include <set>
@ -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<std::string, std::string> &dict = profile.outgoing_parameters;
std::vector<std::pair<std::string, const db::DeviceParameterDefinition *> > all_params;
std::vector<std::pair<std::string, std::pair<tl::Variant, const db::DeviceParameterDefinition *> > > 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<const db::DeviceClassCapacitor *> (dc);
const db::DeviceClassCapacitor *cap3 = dynamic_cast<const db::DeviceClassCapacitorWithBulk *> (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 ());
}

View File

@ -163,6 +163,14 @@ public:
return m_with_comments;
}
/**
* @brief Gets the delegate
*/
NetlistSpiceWriterDelegate *delegate ()
{
return mp_delegate.get ();
}
private:
friend class NetlistSpiceWriterDelegate;

View File

@ -133,13 +133,6 @@ Class<NetlistSpiceWriterDelegateImpl> 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 ("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_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_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"