mirror of https://github.com/KLayout/klayout.git
WIP
This commit is contained in:
parent
8b6d6cef52
commit
be79d3fbcd
|
|
@ -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 &);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 ()) {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public:
|
|||
~TokenizedOutput ();
|
||||
|
||||
TokenizedOutput &operator<< (const std::string &s);
|
||||
TokenizedOutput &operator<< (double d);
|
||||
|
||||
tl::OutputStream &stream () { return *mp_stream; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> ¶ms)
|
||||
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> ¶ms, 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> ¶ms, 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> ¶ms)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ..
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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. "
|
||||
|
|
|
|||
Loading…
Reference in New Issue