This commit is contained in:
Matthias Koefferlein 2026-06-05 01:08:22 +02:00
parent 8b6d6cef52
commit be79d3fbcd
9 changed files with 437 additions and 282 deletions

View File

@ -415,8 +415,69 @@ public:
*/
struct SpiceProfile
{
/**
* @brief The SPICE element name to use for this device (i.e. "M", "R" etc.)
*/
std::string element;
/**
* @brief The order the terminals are written to or read from SPICE
*
* This is a list of valid terminal names
*/
std::vector<std::string> terminal_order;
/**
* @brief A mapping of parameters when reading
*
* The key is the parameter name to be produced in the device
* and the value is a formula that references parameters from the SPICE card.
* For caseless netlists, the parameter names need to be upper case.
*
* Special directives apply for the key:
* * "*": all pre-defined parameters plus the ones from the SPICE card
* * "**": all pre-defined parameters
* * "*!": all pre-defined primary parameters
* * "*?": all pre-defined secondary parameters
* Specific names have precendence over wildcard names.
*
* The value is a formula in KLayout expression notation that specifies
* how the value of the parameter is computed from SPICE card parameters.
* SPICE parameters are referenced by name. If a parameter is not given,
* the value will be the default from the parameter declaration or "nil".
* So you can implement a default using "P||0.0" for example.
* "_" is the value of the same SPICE parameter. This is useful for generating
* catch-all rules, such as '"*": "_"' (copy all parameters).
*
* "$" represents the direct value in expressions. This is used for
* elements like "R", "L" or "C", when the component value is not given
* as a named parameter, but as an explicit value.
*
* If the formula returns a nil value, the parameter is not generated in the
* device or the default value is used if the parameter is a known one.
*/
std::map<std::string, std::string> incoming_parameters;
/**
* @brief A mapping of parameter when writing
*
* The key is the parameter name produced in the SPICE file
* and the value is a formula that references parameters from the
* device.
* For caseless netlists, the parameter names need to be upper case.
*
* Special directives apply for the key:
* * "*" or "**": all parameters from the device
* * "*!": all primary parameters
* * "*?": all secondary parameters
* Specific names have precendence over wildcard names.
*
* The value is a formula in KLayout expression notation that specifies
* how the value of the parameter is computed from device parameters.
* "_" is the value of the same SPICE parameter. This is useful for generating
* catch-all rules, such as '"*": "_"' (copy all parameters).
*/
std::map<std::string, std::string> outgoing_parameters;
};
typedef size_t terminal_id_type;
@ -927,6 +988,8 @@ public:
virtual bool is_of (const db::DeviceClass *) const = 0;
virtual DeviceClass *create () const = 0;
virtual size_t spice_num_nets () const = 0;
virtual const std::string &spice_element () const = 0;
static DeviceClassTemplateBase *template_by_name (const std::string &name);
static DeviceClassTemplateBase *is_a (const db::DeviceClass *dc);
@ -946,7 +1009,10 @@ public:
device_class_template (const std::string &name)
: DeviceClassTemplateBase (name)
{
// .. nothing yet ..
T dc;
const db::DeviceClass::SpiceProfile &profile = dc.spice_profile ("*");
m_num_nets = profile.terminal_order.size ();
m_element = profile.element;
}
virtual bool is_of (const db::DeviceClass *dc) const
@ -959,7 +1025,20 @@ public:
return new T ();
}
virtual size_t spice_num_nets () const
{
return m_num_nets;
}
virtual const std::string &spice_element () const
{
return m_element;
}
private:
size_t m_num_nets;
std::string m_element;
device_class_template (const device_class_template &);
device_class_template &operator= (const device_class_template &);
};

View File

@ -128,6 +128,13 @@ TokenizedOutput &TokenizedOutput::operator<< (const std::string &s)
return *this;
}
TokenizedOutput &TokenizedOutput::operator<< (double d)
{
emit_sep ();
stream () << tl::sprintf ("%.12g", d);
return *this;
}
// -------------------------------------------------------------------------------------------
static void write_point (TokenizedOutput &out, const db::Point &pt, db::Point &ref, bool relative)
@ -326,7 +333,7 @@ void std_writer_impl<Keys>::write_device_class (TokenizedOutput &stream, const d
}
const tl::Variant &def = p->default_value ();
if (def.is_double ()) {
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string ();
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_double ();
} else if (def.is_long () || def.is_ulong ()) {
TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string ();
} else if (def.is_a_string ()) {
@ -920,7 +927,7 @@ void std_writer_impl<Keys>::write (TokenizedOutput &stream, const db::Device &de
const tl::Variant &value = device.parameter_value (i->id ());
if (value.is_double ()) {
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string ();
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_double ();
} else if (value.is_long () || value.is_ulong ()) {
TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string ();
} else if (value.is_a_string ()) {

View File

@ -62,6 +62,7 @@ public:
~TokenizedOutput ();
TokenizedOutput &operator<< (const std::string &s);
TokenizedOutput &operator<< (double d);
tl::OutputStream &stream () { return *mp_stream; }

View File

@ -491,6 +491,23 @@ DeviceClassResistor::DeviceClassResistor ()
add_parameter_definition (db::DeviceParameterDefinition ("W", "Width (micrometer)", 0.0, false, 1e-6, 1.0));
add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0));
add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "R";
sp.terminal_order = { "A", "B" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "R", "(R||$)/(M||1.0)" },
{ "A", "A*(M||1.0)" },
{ "W", "W*(M||1.0)" },
{ "P", "P*(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -503,6 +520,11 @@ DeviceClassResistorWithBulk::DeviceClassResistorWithBulk ()
{
set_device_combiner (new ResistorWithBulkDeviceCombiner ());
add_terminal_definition (db::DeviceTerminalDefinition ("W", "Terminal W (well, bulk)"));
// modify SPICE profile with W terminal
db::DeviceClass::SpiceProfile sp = spice_profile ("*");
sp.terminal_order.push_back ("W");
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -528,6 +550,22 @@ DeviceClassCapacitor::DeviceClassCapacitor ()
add_parameter_definition (db::DeviceParameterDefinition ("C", "Capacitance (Farad)", 0.0));
add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0));
add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "C";
sp.terminal_order = { "A", "B" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "C", "(C||$)*(M||1.0)" },
{ "A", "A*(M||1.0)" },
{ "P", "P*(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -540,6 +578,11 @@ DeviceClassCapacitorWithBulk::DeviceClassCapacitorWithBulk ()
{
set_device_combiner (new CapacitorWithBulkDeviceCombiner ());
add_terminal_definition (db::DeviceTerminalDefinition ("W", "Terminal W (well, bulk)"));
// modify SPICE profile with W terminal
db::DeviceClass::SpiceProfile sp = spice_profile ("*");
sp.terminal_order.push_back ("W");
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -561,6 +604,20 @@ DeviceClassInductor::DeviceClassInductor ()
equivalent_terminal_id (terminal_id_A, terminal_id_B);
add_parameter_definition (db::DeviceParameterDefinition ("L", "Inductance (Henry)", 0.0));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "L";
sp.terminal_order = { "A", "B" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "L", "(L||$)/(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -582,6 +639,21 @@ DeviceClassDiode::DeviceClassDiode ()
add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0));
add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "D";
sp.terminal_order = { "A", "C" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "A", "A*(M||1.0)" },
{ "P", "P*(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -614,6 +686,25 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor ()
add_parameter_definition (db::DeviceParameterDefinition ("AD", "Drain area (square micrometer)", 0.0, false, 1e-12, 2.0));
add_parameter_definition (db::DeviceParameterDefinition ("PS", "Source perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
add_parameter_definition (db::DeviceParameterDefinition ("PD", "Drain perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "M";
sp.terminal_order = { "D", "G", "S" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "L", "_" },
{ "W", "W*(M||1.0)" },
{ "AS", "AS*(M||1.0)" },
{ "PS", "PS*(M||1.0)" },
{ "AD", "AD*(M||1.0)" },
{ "PD", "PD*(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
bool
@ -853,6 +944,11 @@ DeviceClassMOS4Transistor::DeviceClassMOS4Transistor ()
{
set_device_combiner (new MOS4DeviceCombiner ());
add_terminal_definition (db::DeviceTerminalDefinition ("B", "Bulk"));
// modify SPICE profile with B terminal
db::DeviceClass::SpiceProfile sp = spice_profile ("*");
sp.terminal_order.push_back ("B");
set_spice_profile (std::string ("*"), sp);
}
bool
@ -893,6 +989,27 @@ DeviceClassBJT3Transistor::DeviceClassBJT3Transistor ()
add_parameter_definition (db::DeviceParameterDefinition ("AC", "Collector area (square micrometer)", 0.0, false, 1e-12, 2.0));
add_parameter_definition (db::DeviceParameterDefinition ("PC", "Collector perimeter (micrometer)", 0.0, false, 1e-6, 1.0));
add_parameter_definition (db::DeviceParameterDefinition ("NE", "Emitter count", 1.0, true));
// declare the default way of persisting in SPICE
db::DeviceClass::SpiceProfile sp;
sp.element = "Q";
sp.terminal_order = { "C", "B", "E" };
sp.incoming_parameters = {
{ "M", "" }, // drop
// apply scaling if available
{ "NE", "_" },
{ "AE", "AE*(M||1.0)" },
{ "PE", "PE*(M||1.0)" },
{ "AB", "AB*(M||1.0)" },
{ "PB", "PB*(M||1.0)" },
{ "AC", "AC*(M||1.0)" },
{ "PC", "PC*(M||1.0)" }
};
set_spice_profile (std::string ("*"), sp);
}
// ------------------------------------------------------------------------------------
@ -904,6 +1021,11 @@ DeviceClassBJT4Transistor::DeviceClassBJT4Transistor ()
{
set_device_combiner (new BJT4DeviceCombiner ());
add_terminal_definition (db::DeviceTerminalDefinition ("S", "Substrate"));
// modify SPICE profile with B terminal
db::DeviceClass::SpiceProfile sp = spice_profile ("*");
sp.terminal_order.push_back ("S");
set_spice_profile (std::string ("*"), sp);
}
}

View File

@ -27,6 +27,8 @@
#include "dbCircuit.h"
#include "dbNetlistDeviceClasses.h"
#include "tlLog.h"
#include "tlExpression.h"
#include "tlClassRegistry.h"
namespace db
{
@ -226,7 +228,7 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin
auto dp = m_spice_profiles.end ();
if (! nn.empty ()) {
m_spice_profiles.find (std::make_pair (element, nn.back ()));
dp = m_spice_profiles.find (std::make_pair (element, nn.back ()));
}
if (dp != m_spice_profiles.end ()) {
@ -246,27 +248,6 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin
element, model, m_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_profile, tos, t->name ()));
}
nn_ordered.push_back (nn[ti]);
}
nn.swap (nn_ordered);
} else if (element == "X") {
// subcircuit call:
@ -403,27 +384,136 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, std::strin
}
}
double NetlistSpiceReaderDelegate::get_multiplier (const std::map<std::string, tl::Variant> &params)
namespace {
class SPICEParameterEval
: public tl::Eval
{
double mult = 1.0;
auto mp = params.find ("M");
if (mp != params.end ()) {
mult = mp->second.to_double ();
public:
SPICEParameterEval (const std::string &name, const std::map<std::string, tl::Variant> &params, const std::map<std::string, const db::DeviceParameterDefinition *> &all_params, double value)
: m_name (name), m_params (params), m_all_params (all_params), m_value (value)
{
// .. nothing yet ..
}
if (mult < 1e-10) {
error (tl::sprintf (tl::to_string (tr ("Invalid multiplier value (M=%.12g) - must not be zero or negative")), mult));
protected:
virtual void resolve_name (const std::string &name, const tl::EvalFunction *& /*function*/, const tl::Variant *&value, tl::Variant *& /*var*/)
{
if (name == "$") {
value = &m_value;
return;
}
auto p = m_params.find (name == "_" ? m_name : name);
if (p != m_params.end ()) {
value = &p->second;
return;
}
auto pp = m_all_params.find (name == "_" ? m_name : name);
if (pp != m_all_params.end ()) {
if (pp->second) {
value = &pp->second->default_value ();
return;
}
}
static tl::Variant nil;
value = &nil;
}
return mult;
private:
const std::string m_name;
const std::map<std::string, tl::Variant> &m_params;
const std::map<std::string, const db::DeviceParameterDefinition *> &m_all_params;
tl::Variant m_value;
};
}
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)
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)
{
std::map<std::string, tl::Variant> params = pv;
std::vector<size_t> terminal_order;
// shortcuts
if (expr.empty ()) {
return tl::Variant ();
} else if (expr == "_") {
auto p = params.find (name);
return p != params.end () ? p->second : tl::Variant ();
} else if (expr == "$") {
return tl::Variant (value);
} else {
// real evaluation
SPICEParameterEval eval (name, params, all_params, value);
return eval.eval (expr);
}
}
static tl::Variant
default_from_value (const tl::Variant &v)
{
if (v.is_long () || v.is_ulong ()) {
return tl::Variant (long (0));
} else if (v.is_double ()) {
return tl::Variant (0.0);
} else if (v.is_a_string ()) {
return tl::Variant ("");
} else {
return tl::Variant ();
}
}
static std::string default_model_name (const std::string &element, size_t nets)
{
for (tl::Registrar<db::DeviceClassTemplateBase>::iterator i = tl::Registrar<db::DeviceClassTemplateBase>::begin (); i != tl::Registrar<db::DeviceClassTemplateBase>::end (); ++i) {
if (i->spice_element () == element && i->spice_num_nets () == nets) {
return i->name ();
}
}
// TODO: raise an error maybe?
return "UNK";
}
static db::DeviceClass *bootstrap_device_class (db::Netlist *netlist, const std::string &name, const std::string &element, size_t nets)
{
for (tl::Registrar<db::DeviceClassTemplateBase>::iterator i = tl::Registrar<db::DeviceClassTemplateBase>::begin (); i != tl::Registrar<db::DeviceClassTemplateBase>::end (); ++i) {
if (i->spice_element () == element && i->spice_num_nets () == nets) {
db::DeviceClass *cls = netlist->device_class_by_name (name);
if (! cls) {
cls = i->create ();
cls->set_name (name);
netlist->add_device_class (cls);
}
return cls;
}
}
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;
// use a default model name if none is given
if (cn.empty ()) {
cn = default_model_name (element, nets.size ());
}
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
if (element.empty ()) {
@ -431,248 +521,122 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
// obtained through SPICE profile.
tl_assert (cls != 0);
} else if (element == "R") {
if (nets.size () == 2) {
if (cls) {
if (! dynamic_cast<db::DeviceClassResistor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a resistor device class as required by 'R' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "RES";
}
cls = make_device_class<db::DeviceClassResistor> (circuit, cn);
}
} else if (nets.size () == 3) {
if (cls) {
if (! dynamic_cast<db::DeviceClassResistorWithBulk *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal resistor device class as required by 'R' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "RES3";
}
cls = make_device_class<db::DeviceClassResistorWithBulk> (circuit, cn);
}
} else {
error (tl::to_string (tr ("A 'R' element requires two or three nets")));
}
// Apply multiplier (divider, according to ngspice manual)
double mult = get_multiplier (params);
value /= mult;
params["R"] = tl::Variant (value);
// Apply multiplier to other parameters
static const char *scale_params[] = { "A", "P", "W" };
for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) {
auto p = params.find (scale_params [i]);
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
}
} else if (element == "L") {
if (nets.size () == 2) {
if (cls) {
if (! dynamic_cast<db::DeviceClassInductor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a inductor device class as required by 'L' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "IND";
}
cls = make_device_class<db::DeviceClassInductor> (circuit, cn);
}
} else {
error (tl::to_string (tr ("A 'L' element requires two nets")));
}
// Apply multiplier (divider, according to ngspice manual)
double mult = get_multiplier (params);
value /= mult;
params["L"] = tl::Variant (value);
} else if (element == "C") {
if (nets.size () == 2) {
if (cls) {
if (! dynamic_cast<db::DeviceClassCapacitor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a capacitor device class as required by 'C' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "CAP";
}
cls = make_device_class<db::DeviceClassCapacitor> (circuit, cn);
}
} else if (nets.size () == 3) {
if (cls) {
if (! dynamic_cast<db::DeviceClassCapacitorWithBulk *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal capacitor device class as required by 'C' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "CAP3";
}
cls = make_device_class<db::DeviceClassCapacitorWithBulk> (circuit, cn);
}
} else {
error (tl::to_string (tr ("A 'C' element requires two or three nets")));
}
// Apply multiplier
double mult = get_multiplier (params);
value *= mult;
params["C"] = tl::Variant (value);
// Apply multiplier to other parameters
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]);
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
}
} else if (element == "D") {
if (cls) {
if (! dynamic_cast<db::DeviceClassDiode *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a diode device class as required by 'D' element")), cn));
}
} else {
if (cn.empty ()) {
cn = "DIODE";
}
cls = make_device_class<db::DeviceClassDiode> (circuit, cn);
}
// 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]);
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
}
} else if (element == "Q") {
if (nets.size () != 3 && nets.size () != 4) {
error (tl::to_string (tr ("'Q' element needs to have 3 or 4 terminals")));
} else if (cls) {
if (nets.size () == 3) {
if (! dynamic_cast<db::DeviceClassBJT3Transistor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 3-terminal BJT device class as required by 'Q' element")), cn));
}
} else {
if (! dynamic_cast<db::DeviceClassBJT4Transistor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal BJT device class as required by 'Q' element")), cn));
}
}
} else {
if (nets.size () == 3) {
if (cn.empty ()) {
cn = "BJT3";
}
cls = make_device_class<db::DeviceClassBJT3Transistor> (circuit, cn);
} else {
if (cn.empty ()) {
cn = "BJT4";
}
cls = make_device_class<db::DeviceClassBJT4Transistor> (circuit, cn);
}
}
// 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]);
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
}
} else if (element == "M") {
if (cls) {
if (! dynamic_cast<db::DeviceClassMOS4Transistor *>(cls)) {
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal MOS device class as required by 'M' element")), cn));
}
} else {
if (nets.size () == 4) {
if (cn.empty ()) {
cn = "MOS4";
}
cls = make_device_class<db::DeviceClassMOS4Transistor> (circuit, cn);
} else {
error (tl::to_string (tr ("'M' element needs to have 4 terminals")));
}
}
// 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]);
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
}
// issue #1304
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_D);
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_G);
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_S);
terminal_order.push_back (DeviceClassMOS4Transistor::terminal_id_B);
} 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));
// create a device class from the device templates
cls = bootstrap_device_class (circuit->netlist (), cn, element, nets.size ());
if (! cls) {
std::vector<std::string> candidates;
for (tl::Registrar<db::DeviceClassTemplateBase>::iterator i = tl::Registrar<db::DeviceClassTemplateBase>::begin (); i != tl::Registrar<db::DeviceClassTemplateBase>::end (); ++i) {
if (i->spice_element () == element) {
candidates.push_back (tl::sprintf (tl::to_string (tr ("%s (%d terminals)")), i->name (), int (nets.size ())));
}
}
if (! candidates.empty ()) {
error (tl::sprintf (tl::to_string (tr ("No matching device found on element '%s' with '%d' terminals. Candidates are: ")), element, int (nets.size ())) + tl::join (candidates, ", "));
} else {
error (tl::sprintf (tl::to_string (tr ("Element '%s' is not understood by SPICE parser")), element, int (nets.size ())));
}
}
}
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 (! 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));
}
terminal_order.push_back (cls->terminal_id_for_name (*to));
}
if (terminal_order.size () != td.size ()) {
error (tl::sprintf (tl::to_string (tr ("Device class '%s' and SPICE profile '%s': inconsistent terminal order - '%d' terminals are defined, '%d' given in terminal order list")),
cls->name (), m_profile, int (td.size ()), int (terminal_order.size ())));
}
// create the device
db::Device *device = new db::Device (cls, name);
circuit->add_device (device);
if (terminal_order.empty ()) {
for (auto t = td.begin (); t != td.end (); ++t) {
device->connect_terminal (t->id (), nets [t - td.begin ()]);
}
} else {
for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) {
device->connect_terminal (*t, nets [t - terminal_order.begin ()]);
// make the device connections
for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) {
device->connect_terminal (*t, nets [t - terminal_order.begin ()]);
}
// transfer parameters into device
const std::map<std::string, std::string> &dict = sp.incoming_parameters;
std::map<std::string, const db::DeviceParameterDefinition *> all_params;
for (auto p = params.begin (); p != params.end (); ++p) {
if (cls->has_parameter_with_name (p->first)) {
all_params.insert (std::make_pair (p->first, cls->parameter_definition (cls->parameter_id_for_name (p->first))));
} else {
all_params.insert (std::make_pair (p->first, (const db::DeviceParameterDefinition *) 0));
}
}
for (auto p = params.begin (); p != params.end (); ++p) {
for (auto p = all_params.begin (); p != all_params.end (); ++p) {
if (cls->has_parameter_with_name (p->first)) {
std::map<std::string, std::string>::const_iterator id;
if ((id = dict.find (p->first)) != dict.end ()) {
device->set_parameter_value (p->first, p->second);
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));
}
} 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));
} 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));
} 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));
} 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));
}
} else if (m_read_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 (""));
auto pp = params.find (p->first);
tl_assert (pp != params.end ());
if (p->second) {
device->set_parameter_value (p->second->id (), pp->second);
} else {
device->set_parameter_value_create (p->first, p->second, false, tl::Variant ());
device->set_parameter_value_create (p->first, pp->second, false, default_from_value (pp->second));
}
}
@ -680,6 +644,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
}
apply_parameter_scaling (device);
return true;
}

View File

@ -219,7 +219,6 @@ private:
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

@ -44,7 +44,7 @@ static const char *s_not_connect_prefix = "nc_";
// --------------------------------------------------------------------------------
NetlistSpiceWriterDelegate::NetlistSpiceWriterDelegate ()
: mp_writer (0), mp_netlist (0), m_write_all_parameters (false)
: mp_writer (0), mp_netlist (0), m_write_all_parameters (true)
{
// .. nothing yet ..
}

View File

@ -418,21 +418,12 @@ 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,
"@brief Sets a flag indicating whether to read all device parameters.\n"
"If this flag is set to true, all parameters of the devices are read in the default implementation. "
"If set to false (the default), only known parameters (i.e. only parameters declared by the device classes in the netlist) are read.\n"
"\n"
"Note, that you can customize the reader's device input also by reimplementing \\parse_element or \\element. This reimplementation may "
"chose to ignore the \\read_all_parameters attribute.\n"
"\n"
"This attribute has been introduced in version 0.31.0."
"@hide\n"
) +
gsi::method ("read_all_parameters=", &NetlistSpiceReaderDelegateImpl::set_read_all_parameters, gsi::arg ("f"),
"@brief Gets a flag indicating whether to read all device parameters.\n"
"See \\read_all_parameters= for a description of this attribute.\n"
"\n"
"This attribute has been introduced in version 0.31.0."
"@hide\n"
) +
gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"),
"@brief This method is called when the reader starts reading a netlist\n"

View File

@ -134,21 +134,12 @@ 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,
"@brief Sets a flag indicating whether to write all device parameters.\n"
"If this flag is set to true, all parameters of the devices are written in the default implementation. "
"If set to false (the default), only primary parameters are written.\n"
"\n"
"Note, that you can customize the writer's device output also by reimplementing \\write_device. This reimplementation may "
"chose to ignore the \\write_all_parameters attribute.\n"
"\n"
"This attribute has been introduced in version 0.31.0."
"@hide"
) +
gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters, gsi::arg ("f"),
"@brief Gets a flag indicating whether to write all device parameters.\n"
"See \\write_all_parameters= for a description of this attribute.\n"
"\n"
"This attribute has been introduced in version 0.31.0."
"@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. "