This commit is contained in:
Matthias Koefferlein 2026-05-21 23:05:03 +02:00
parent 3ef0d593b1
commit 1d150441b6
7 changed files with 270 additions and 46 deletions

View File

@ -330,6 +330,46 @@ size_t DeviceClass::terminal_id_for_name (const std::string &name) const
throw tl::Exception (tl::to_string (tr ("Invalid terminal name")) + ": '" + name + "'");
}
// @@@ GSI bindings for SPICE profile
void DeviceClass::set_spice_profile (const std::string &name, const SpiceProfile &profile)
{
m_spice_profiles [name] = profile;
}
bool DeviceClass::has_spice_profile (const std::string &name) const
{
auto p = m_spice_profiles.find (name);
if (p == m_spice_profiles.end ()) {
p = m_spice_profiles.find ("*");
}
return p != m_spice_profiles.end ();
}
const DeviceClass::SpiceProfile &DeviceClass::spice_profile (const std::string &name) const
{
auto p = m_spice_profiles.find (name);
if (p == m_spice_profiles.end ()) {
p = m_spice_profiles.find ("*");
}
if (p != m_spice_profiles.end ()) {
return p->second;
} else {
static DeviceClass::SpiceProfile def_profile;
return def_profile;
}
}
void DeviceClass::clear_spice_profiles ()
{
m_spice_profiles.clear ();
}
void DeviceClass::remove_spice_profile (const std::string &name)
{
m_spice_profiles.erase (name);
}
// The default compare delegate
static EqualDeviceParameters default_compare;

View File

@ -408,6 +408,17 @@ class DB_PUBLIC DeviceClass
: public gsi::ObjectBase, public tl::Object, public tl::UniqueId
{
public:
/**
* @brief The SPICE profile
*
* See \set_spice_profile for a description
*/
struct SpiceProfile
{
std::string element;
std::vector<std::string> terminal_order;
};
typedef size_t terminal_id_type;
/**
@ -689,6 +700,45 @@ public:
}
}
/**
* @brief Sets a SPICE profile
*
* A Spice profile is a declaration, how a device is serialized to SPICE format
* by default. There can be multiple profiles for different flavors of SPICE
* files (e.g. for simulation etc.).
* A profile has a name and is specified in the SPICE reader or writer.
*
* If a device has an entry for a specific profile name, it can specify:
* * The SPICE element (e.g. "X" or "M")
* * The terminal order (a list of terminal names)
*
* Using "*" for the profile name installs a fallback profile which is
* used if no other profile applies.
*/
void set_spice_profile (const std::string &name, const SpiceProfile &profile);
/**
* @brief Gets a value indicating whether a profile is available for the given name
*/
bool has_spice_profile (const std::string &name) const;
/**
* @brief Gets a SPICE profile with a given name
*/
const SpiceProfile &spice_profile (const std::string &name) const;
/**
* @brief Clears the SPICE profiles
*/
void clear_spice_profiles ();
/**
* @brief Removes a SPICE profile with the given name
*
* The name can be "*" to remove the fallback profile.
*/
void remove_spice_profile (const std::string &name);
/**
* @brief Compares the parameters of the devices a and b
*
@ -805,6 +855,15 @@ public:
db::mem_stat (stat, purpose, cat, m_description, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_terminal_definitions, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_parameter_definitions, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_spice_profiles, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_equivalent_terminal_ids, true, (void *) this);
if (mp_pc_delegate.get ()) {
db::mem_stat (stat, purpose, cat, *mp_pc_delegate, true, (void *) this);
}
if (mp_device_combiner.get ()) {
db::mem_stat (stat, purpose, cat, *mp_device_combiner, true, (void *) this);
}
}
private:
@ -813,6 +872,7 @@ private:
std::string m_name, m_description;
std::vector<DeviceTerminalDefinition> m_terminal_definitions;
std::vector<DeviceParameterDefinition> m_parameter_definitions;
std::map<std::string, SpiceProfile> m_spice_profiles;
bool m_strict;
db::Netlist *mp_netlist;
tl::shared_ptr<db::DeviceParameterCompareDelegate> mp_pc_delegate;

View File

@ -794,19 +794,19 @@ SpiceCircuitDict::read_options (tl::Extractor &ex)
}
} else if (n == "defad") {
if (v > min_value) {
mp_delegate->options ().defad = v;
mp_delegate->options ().default_values ["M"]["AD"] = v;
}
} else if (n == "defas") {
if (v > min_value) {
mp_delegate->options ().defas = v;
mp_delegate->options ().default_values ["M"]["AS"] = v;
}
} else if (n == "defl") {
if (v > min_value) {
mp_delegate->options ().defl = v;
mp_delegate->options ().default_values ["M"]["L"] = v;
}
} else if (n == "defw") {
if (v > min_value) {
mp_delegate->options ().defw = v;
mp_delegate->options ().default_values ["M"]["W"] = v;
}
}
@ -1196,9 +1196,10 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref
std::vector<std::string> nn;
NetlistSpiceReader::parameters_type pv;
std::string model;
std::string element = prefix;
double value = 0.0;
mp_delegate->parse_element (ex.skip (), prefix, model, value, nn, pv, m_variables);
mp_delegate->parse_element (ex.skip (), element, model, value, nn, pv, m_variables);
model = mp_netlist->normalize_name (model);
@ -1207,7 +1208,7 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref
nets.push_back (make_net (mp_delegate->translate_net_name (*i)));
}
if (prefix == "X" && ! subcircuit_captured (model)) {
if (element == "X" && ! subcircuit_captured (model)) {
const db::SpiceCachedCircuit *cc = mp_dict->cached_circuit (model);
if (! cc) {
@ -1251,7 +1252,7 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref
return true;
} else {
return mp_delegate->element (mp_netlist_circuit, prefix, name, model, value, nets, pv);
return mp_delegate->element (mp_netlist_circuit, element, name, model, value, nets, pv);
}
}

View File

@ -26,6 +26,7 @@
#include "dbNetlist.h"
#include "dbCircuit.h"
#include "dbNetlistDeviceClasses.h"
#include "tlLog.h"
namespace db
{
@ -34,12 +35,14 @@ namespace db
NetlistSpiceReaderOptions::NetlistSpiceReaderOptions ()
{
scale = 1.0;
defad = 0.0;
defas = 0.0;
// ngspice defaults:
defw = 100e-6;
defl = 100e-6;
default_values["M"]["AD"] = 0.0;
default_values["M"]["AS"] = 0.0;
default_values["M"]["W"] = 100e-6;
default_values["M"]["L"] = 100e-6;
scale = 1.0;
all_parameters = false;
}
// ------------------------------------------------------------------------------------------------------
@ -63,6 +66,26 @@ void NetlistSpiceReaderDelegate::set_netlist (db::Netlist *netlist)
void NetlistSpiceReaderDelegate::do_start ()
{
// build the element/model to class map
m_spice_profiles.clear ();
for (auto dc = mp_netlist->begin_device_classes (); dc != mp_netlist->end_device_classes (); ++dc) {
const db::DeviceClass *dcc = dc.operator-> ();
if (dcc->has_spice_profile (m_options.profile)) {
const db::DeviceClass::SpiceProfile &pf = dcc->spice_profile (m_options.profile);
if (! pf.element.empty ()) {
if (m_spice_profiles.find (std::make_pair (pf.element, dcc->name ())) != m_spice_profiles.end ()) {
tl::warn << tl::sprintf (tl::to_string (tr ("Duplicate model name %s bound to element %s in profile %s")), dcc->name (), pf.element, m_options.profile);
}
m_spice_profiles.insert (std::make_pair (std::make_pair (pf.element, dcc->name ()), dcc));
}
}
}
start (mp_netlist);
}
@ -175,23 +198,64 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s,
void NetlistSpiceReaderDelegate::def_values_per_element (const std::string &element, std::map<std::string, tl::Variant> &pv)
{
if (element == "M") {
pv.insert (std::make_pair ("W", m_options.defw));
pv.insert (std::make_pair ("L", m_options.defl));
pv.insert (std::make_pair ("AD", m_options.defad));
pv.insert (std::make_pair ("AS", m_options.defas));
auto d = m_options.default_values.find (element);
if (d != m_options.default_values.end ()) {
for (auto i = d->second.begin (); i != d->second.end (); ++i) {
pv.insert (std::make_pair (i->first, i->second));
}
}
}
void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &variables)
void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &variables)
{
def_values_per_element (element, pv);
parse_element_components (s, nn, pv, variables);
// interpret the parameters according to the code
if (element == "X") {
auto dp = m_spice_profiles.end ();
if (! nn.empty ()) {
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<std::string> &to = dp->second->spice_profile (m_options.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_options.profile, int (to.size ()), int (nn.size ())));
}
// reorder the terminals according to the terminal order
std::vector<std::string> nn_ordered;
const std::vector<db::DeviceTerminalDefinition> &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_options.profile, tos, t->name ()));
}
nn_ordered.push_back (nn[ti]);
}
nn.swap (nn_ordered);
} else if (element == "X") {
// subcircuit call:
// Xname n1 n2 ... nn circuit [params]
@ -327,11 +391,8 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std:
}
}
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, tl::Variant> &pv)
double NetlistSpiceReaderDelegate::get_multiplier (const std::map<std::string, tl::Variant> &params)
{
std::map<std::string, tl::Variant> params = pv;
std::vector<size_t> terminal_order;
double mult = 1.0;
auto mp = params.find ("M");
if (mp != params.end ()) {
@ -342,10 +403,23 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
error (tl::sprintf (tl::to_string (tr ("Invalid multiplier value (M=%.12g) - must not be zero or negative")), mult));
}
return mult;
}
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, tl::Variant> &pv)
{
std::map<std::string, tl::Variant> params = pv;
std::vector<size_t> terminal_order;
std::string cn = model;
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
if (element == "R") {
if (element.empty ()) {
// obtained through SPICE profile.
tl_assert (cls != 0);
} else if (element == "R") {
if (nets.size () == 2) {
if (cls) {
@ -374,6 +448,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// Apply multiplier (divider, according to ngspice manual)
double mult = get_multiplier (params);
value /= mult;
params["R"] = tl::Variant (value);
@ -404,6 +479,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// Apply multiplier (divider, according to ngspice manual)
double mult = get_multiplier (params);
value /= mult;
params["L"] = tl::Variant (value);
@ -436,6 +512,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// Apply multiplier
double mult = get_multiplier (params);
value *= mult;
params["C"] = tl::Variant (value);
@ -462,6 +539,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// 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]);
@ -499,6 +577,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// 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]);
@ -525,6 +604,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
// 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]);
@ -539,8 +619,11 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_S);
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_B);
} else {
} 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));
}
const std::vector<db::DeviceTerminalDefinition> &td = cls->terminal_definitions ();
@ -561,17 +644,27 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
}
for (auto p = params.begin (); p != params.end (); ++p) {
if (cls->has_parameter_with_name (p->first)) {
device->set_parameter_value (p->first, p->second);
} else if (m_options.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 (""));
} else {
device->set_parameter_value_create (p->first, p->second, false, tl::Variant ());
}
std::vector<db::DeviceParameterDefinition> &pd = cls->parameter_definitions_non_const ();
for (std::vector<db::DeviceParameterDefinition>::iterator i = pd.begin (); i != pd.end (); ++i) {
auto v = params.find (i->name ());
double pv = 0.0;
if (v != params.end ()) {
pv = v->second.to_double ();
} else {
continue;
}
device->set_parameter_value (i->id (), pv);
}
apply_parameter_scaling (device);
@ -588,7 +681,6 @@ NetlistSpiceReaderDelegate::apply_parameter_scaling (db::Device *device) const
const std::vector<db::DeviceParameterDefinition> &pd = device->device_class ()->parameter_definitions ();
for (auto i = pd.begin (); i != pd.end (); ++i) {
const tl::Variant &pv = device->parameter_value (i->id ());
// @@@ only in case of double???
if (pv.is_double ()) {
device->set_parameter_value (i->id (), pv.to_double () / i->si_scaling () * pow (m_options.scale, i->geo_scaling_exponent ()));
}

View File

@ -44,8 +44,10 @@ struct DB_PUBLIC NetlistSpiceReaderOptions
{
NetlistSpiceReaderOptions ();
std::string profile; // @@@ GSI binding
double scale;
double defad, defas, defw, defl;
std::map<std::string, std::map<std::string, double> > default_values;
bool all_parameters; // @@@ GSI binding
};
/**
@ -137,7 +139,7 @@ public:
* @param nn Out parameter: the net names
* @param pv Out parameter: the parameter values (key/value pairs)
*/
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &params);
virtual void parse_element (const std::string &s, std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &params);
/**
* @brief Produces an error with the given message
@ -187,8 +189,10 @@ public:
private:
db::Netlist *mp_netlist;
NetlistSpiceReaderOptions m_options;
std::map<std::pair<std::string, std::string>, const db::DeviceClass *> m_spice_profiles;
void def_values_per_element (const std::string &element, std::map<std::string, tl::Variant> &pv);
double get_multiplier (const std::map<std::string, tl::Variant> &pv);
};
}

View File

@ -237,7 +237,6 @@ std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, si
double sis = i->si_scaling ();
// for compatibility
// @@@ TODO: only for double???
if (fabs (sis * 1e6 - 1.0) < db::epsilon) {
os << tl::to_string (v) << "U";
} else if (fabs (sis * 1e12 - 1.0) < db::epsilon) {
@ -246,6 +245,8 @@ std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, si
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 ());
}

View File

@ -442,10 +442,17 @@ Class<db::Device> decl_dbDevice (decl_dbNetlistObject, "db", "Device",
"@brief Gets the parameter value for the given parameter name.\n"
"If the parameter name is not valid, an exception is thrown."
) +
// @@@ set_parameter_create!!!
gsi::method ("set_parameter", (void (db::Device::*) (const std::string &, const tl::Variant &)) &db::Device::set_parameter_value, gsi::arg ("param_name"), gsi::arg ("value"),
"@brief Sets the parameter value for the given parameter name.\n"
"If the parameter name is not valid, an exception is thrown."
) +
gsi::method ("set_parameter_create", (void (db::Device::*) (const std::string &, const tl::Variant &, bool, const tl::Variant &)) &db::Device::set_parameter_value_create, gsi::arg ("param_name"), gsi::arg ("value"), gsi::arg ("primary", true), gsi::arg ("default_value", tl::Variant (0.0), "none"),
"@brief Sets the parameter value for the given parameter name and creates a new parameter if no parameter is present with this name.\n"
"If no parameter with the given name exists in the device class's parameter table, a new parameter is created. "
"\\primary indicates that this parameter will be a primary one and \\default_value is the default value for this parameter. "
"The default value also implicitly defines the type of the parameter. A float value will create a float-type parameter, a string value for default will create a string-type parameter.\n"
"\n"
"This method has been introduced in version 0.31.0."
),
"@brief A device inside a circuit.\n"
"Device object represent atomic devices such as resistors, diodes or transistors. "
@ -887,10 +894,12 @@ Class<db::DeviceParameterDefinition> decl_dbDeviceParameterDefinition ("db", "De
gsi::method ("si_scaling", &db::DeviceParameterDefinition::si_scaling,
"@brief Gets the scaling factor to SI units.\n"
"For parameters in micrometers - for example W and L of MOS devices - this factor can be set to 1e-6 to reflect "
"the unit."
"the unit. SI unit scaling is only applied to parameters of the 'float' type. It is not applied to integer-type "
"or string parameters."
) +
gsi::method ("si_scaling=", &db::DeviceParameterDefinition::set_si_scaling, gsi::arg ("flag"),
"@brief Sets the scaling factor to SI units.\n"
"See \\si_scaling for details.\n"
"\n"
"This setter has been added in version 0.28.6."
) +
@ -898,6 +907,8 @@ Class<db::DeviceParameterDefinition> decl_dbDeviceParameterDefinition ("db", "De
"@brief Gets the geometry scaling exponent.\n"
"This value is used when applying '.options scale' in the SPICE reader for example. "
"It is zero for 'no scaling', 1.0 for linear scaling and 2.0 for quadratic scaling.\n"
"Geometric scaling is only applied to parameters of the 'float' type. It is not applied to integer-type "
"or string parameters.\n"
"\n"
"This attribute has been added in version 0.28.6."
) +
@ -2545,6 +2556,9 @@ class ParseElementData
public:
ParseElementData () : m_value (0.0) { }
const std::string &element_name () const { return m_element; }
std::string &element_name_nc () { return m_element; }
void set_element_name (const std::string &element) { m_element = element; }
const std::string &model_name () const { return m_model; }
std::string &model_name_nc () { return m_model; }
void set_model_name (const std::string &model) { m_model = model; }
@ -2559,7 +2573,7 @@ public:
void set_parameters (const db::NetlistSpiceReader::parameters_type &parameters) { m_parameters = parameters; }
private:
std::string m_model;
std::string m_model, m_element;
double m_value;
std::vector<std::string> m_net_names;
db::NetlistSpiceReader::parameters_type m_parameters;
@ -2702,12 +2716,15 @@ public:
ParseElementData parse_element_helper (const std::string &s, const std::string &element)
{
// NOTE: this way of treating the element name is compatible with the old way and
// the new way in which the element name can be modified by "parse_element".
ParseElementData data;
db::NetlistSpiceReaderDelegate::parse_element (s, element, data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc (), variables ());
data.set_element_name (element);
db::NetlistSpiceReaderDelegate::parse_element (s, data.element_name_nc (), data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc (), variables ());
return data;
}
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, db::NetlistSpiceReader::parameters_type &pv, const db::NetlistSpiceReader::parameters_type &variables)
virtual void parse_element (const std::string &s, std::string &element, std::string &model, double &value, std::vector<std::string> &nn, db::NetlistSpiceReader::parameters_type &pv, const db::NetlistSpiceReader::parameters_type &variables)
{
try {
@ -2721,6 +2738,7 @@ public:
data = parse_element_helper (s, element);
}
element = data.element_name ();
model = data.model_name ();
value = data.value ();
nn = data.net_names ();
@ -2866,6 +2884,14 @@ Class<ParseElementData> db_ParseElementData ("db", "ParseElementData",
gsi::method ("model_name=", &ParseElementData::set_model_name, gsi::arg ("m"),
"@brief Sets the model name\n"
) +
gsi::method ("element_name", &ParseElementData::element_name,
"@brief Gets the element name\n"
"This attribute is available since version 0.31.0.\n"
) +
gsi::method ("element_name=", &ParseElementData::set_element_name, gsi::arg ("e"),
"@brief Sets the element name\n"
"This attribute is available since version 0.31.0.\n"
) +
gsi::method ("net_names", &ParseElementData::net_names,
"@brief Gets the net names\n"
) +