Debugging, cleanup.

This commit is contained in:
Matthias Koefferlein 2026-06-08 23:38:38 +02:00
parent be79d3fbcd
commit 2ad11c0a35
5 changed files with 173 additions and 44 deletions

View File

@ -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<std::string> 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<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")),
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<db::Net *> nets;
for (std::vector<std::string>::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);
}
}

View File

@ -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<std::string> &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<std::string, tl::Variant> &params, const std::map<std::string, const db::DeviceParameterDefinition *> &all_params, double value)
eval_parameter_expression (const std::string &name, const std::string &expr, const std::map<std::string, tl::Variant> &params, const std::map<std::string, const db::DeviceParameterDefinition *> &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<db::Net *> &nets, const std::map<std::string, tl::Variant> &params)
{
std::string cn = model;
@ -600,32 +599,45 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
std::map<std::string, std::string>::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) {

View File

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

View File

@ -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 ..
}

View File

@ -418,12 +418,60 @@ Class<NetlistSpiceReaderDelegateImpl> 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<NetlistSpiceReaderDelegateImpl> 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."
);