mirror of https://github.com/KLayout/klayout.git
SPICE profiles: outgoing parameters
This commit is contained in:
parent
d09eb21096
commit
bc27086abb
|
|
@ -692,7 +692,9 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor ()
|
|||
|
||||
db::DeviceClass::SpiceProfile sp;
|
||||
sp.element = "M";
|
||||
sp.terminal_order = { "D", "G", "S" };
|
||||
// NOTE: we add another dummy terminal which is skipped on reader and
|
||||
// duplicates the S terminal in the writer.
|
||||
sp.terminal_order = { "D", "G", "S", "?S" };
|
||||
|
||||
sp.incoming_parameters = {
|
||||
{ "M", "" }, // drop
|
||||
|
|
@ -705,6 +707,8 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor ()
|
|||
{ "PD", "PD*M" }
|
||||
};
|
||||
|
||||
sp.outgoing_parameters = { { "**", "_" }};
|
||||
|
||||
set_spice_profile (std::string ("*"), sp);
|
||||
}
|
||||
|
||||
|
|
@ -948,7 +952,7 @@ DeviceClassMOS4Transistor::DeviceClassMOS4Transistor ()
|
|||
|
||||
// modify SPICE profile with B terminal
|
||||
db::DeviceClass::SpiceProfile sp = spice_profile ("*");
|
||||
sp.terminal_order.push_back ("B");
|
||||
sp.terminal_order = { "D", "G", "S", "B" };
|
||||
set_spice_profile (std::string ("*"), sp);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1189,6 +1189,17 @@ SpiceNetlistBuilder::subcircuit_captured (const std::string &nc_name)
|
|||
}
|
||||
}
|
||||
|
||||
static size_t min_terminals (const std::vector<std::string> &to)
|
||||
{
|
||||
for (size_t i = 0; i < to.size (); ++i) {
|
||||
if (to[i].front () == '?') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return to.size ();
|
||||
}
|
||||
|
||||
bool
|
||||
SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &prefix, const std::string &name)
|
||||
{
|
||||
|
|
@ -1216,9 +1227,12 @@ SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &pref
|
|||
|
||||
// check the number of terminals
|
||||
const std::vector<std::string> &to = dc->spice_profile (mp_delegate->profile ()).terminal_order;
|
||||
if (nn.size () != to.size ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals, but got %d")),
|
||||
if (nn.size () > to.size ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals max, but got %d")),
|
||||
element, model, mp_delegate->profile (), int (to.size ()), int (nn.size ())));
|
||||
} else if (nn.size () < min_terminals (to)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Element '%s' bound to model '%s' in SPICE profile '%s' requires %d terminals min, but got %d")),
|
||||
element, model, mp_delegate->profile (), int (min_terminals (to)), int (nn.size ())));
|
||||
}
|
||||
|
||||
// indicates that we must not use the element name further
|
||||
|
|
|
|||
|
|
@ -547,16 +547,15 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
const db::DeviceClass::SpiceProfile &sp = cls->spice_profile (m_profile);
|
||||
const std::vector<db::DeviceTerminalDefinition> &td = cls->terminal_definitions ();
|
||||
|
||||
if (td.size () != nets.size ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d, but %d are given")), cn, int (td.size ()), int (nets.size ())));
|
||||
}
|
||||
|
||||
// derive the terminal order from the SPICE profile
|
||||
|
||||
std::vector<size_t> terminal_order;
|
||||
terminal_order.reserve (sp.terminal_order.size ());
|
||||
|
||||
for (auto to = sp.terminal_order.begin (); to != sp.terminal_order.end (); ++to) {
|
||||
if (to->front () == '?') {
|
||||
break;
|
||||
}
|
||||
if (! cls->has_terminal_with_name (*to)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Device class '%s' and SPICE profile '%s': inconsistent terminal order - '%s' is not a valid terminal name")),
|
||||
cls->name (), m_profile, *to));
|
||||
|
|
@ -569,6 +568,14 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
cls->name (), m_profile, int (td.size ()), int (terminal_order.size ())));
|
||||
}
|
||||
|
||||
// check number of nets available
|
||||
|
||||
if (nets.size () > sp.terminal_order.size ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d max, but %d are given")), cn, int (sp.terminal_order.size ()), int (nets.size ())));
|
||||
} else if (nets.size () < terminal_order.size ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d min, but %d are given")), cn, int (terminal_order.size ()), int (nets.size ())));
|
||||
}
|
||||
|
||||
// create the device
|
||||
|
||||
db::Device *device = new db::Device (cls, name);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "tlUniqueName.h"
|
||||
#include "tlTimer.h"
|
||||
#include "tlLog.h"
|
||||
#include "tlExpression.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
|
@ -106,18 +107,160 @@ void NetlistSpiceWriterDelegate::write_device (const db::Device &dev) const
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
write_parameter_value (std::ostringstream &os, const tl::Variant &v, double sis)
|
||||
{
|
||||
if (v.is_double ()) {
|
||||
|
||||
// for compatibility
|
||||
if (fabs (sis * 1e6 - 1.0) < db::epsilon) {
|
||||
os << tl::to_string (v.to_double ()) << "U";
|
||||
} else if (fabs (sis * 1e12 - 1.0) < db::epsilon) {
|
||||
os << tl::to_string (v.to_double ()) << "P";
|
||||
} else {
|
||||
os << tl::to_string (v.to_double () * sis);
|
||||
}
|
||||
|
||||
} else if (v.is_long () || v.is_ulong ()) {
|
||||
os << v.to_string ();
|
||||
} else {
|
||||
os << tl::to_word_or_quoted_string (v.to_string ());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class SPICEParameterEval
|
||||
: public tl::Eval
|
||||
{
|
||||
public:
|
||||
SPICEParameterEval (const std::string &name, const db::Device &dev)
|
||||
: m_name (name), mp_dev (&dev)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void resolve_name (const std::string &name, const tl::EvalFunction *& /*function*/, const tl::Variant *&value, tl::Variant *& /*var*/)
|
||||
{
|
||||
std::string n = (name == "_" ? m_name : name);
|
||||
if (mp_dev->device_class ()->has_parameter_with_name (n)) {
|
||||
value = &mp_dev->parameter_value (mp_dev->device_class ()->parameter_id_for_name (n));
|
||||
return;
|
||||
}
|
||||
|
||||
static tl::Variant nil;
|
||||
value = &nil;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_name;
|
||||
const db::Device *mp_dev;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static tl::Variant
|
||||
eval_parameter_expression (const std::string &name, const std::string &expr, const db::Device &dev)
|
||||
{
|
||||
// shortcuts
|
||||
if (expr.empty ()) {
|
||||
|
||||
return tl::Variant ();
|
||||
|
||||
} else if (expr == "_") {
|
||||
|
||||
if (dev.device_class ()->has_parameter_with_name (name)) {
|
||||
auto id = dev.device_class ()->parameter_id_for_name (name);
|
||||
return dev.parameter_value (id);
|
||||
} else {
|
||||
return tl::Variant ();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
SPICEParameterEval eval (name, dev);
|
||||
return eval.eval (expr);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void NetlistSpiceWriterDelegate::write_device_profile (const db::Device &dev, const db::DeviceClass::SpiceProfile &profile) const
|
||||
{
|
||||
std::ostringstream os;
|
||||
|
||||
os << profile.element;
|
||||
os << format_name (dev.expanded_name ());
|
||||
|
||||
os << format_terminals_with_order (dev, profile.terminal_order);
|
||||
if (! dev.device_class ()->name ().empty ()) {
|
||||
os << " ";
|
||||
os << format_name (dev.device_class ()->name ());
|
||||
|
||||
// transfer parameters into device
|
||||
|
||||
const std::map<std::string, std::string> &dict = profile.outgoing_parameters;
|
||||
|
||||
std::vector<std::pair<std::string, const db::DeviceParameterDefinition *> > all_params;
|
||||
std::vector<std::pair<std::string, std::pair<tl::Variant, const db::DeviceParameterDefinition *> > > param_values;
|
||||
|
||||
const db::DeviceClass *cls = dev.device_class ();
|
||||
tl_assert (cls != 0);
|
||||
|
||||
const auto &pdef = cls->parameter_definitions ();
|
||||
for (auto p = pdef.begin (); p != pdef.end (); ++p) {
|
||||
all_params.push_back (std::make_pair (p->name (), p.operator-> ()));
|
||||
}
|
||||
|
||||
for (auto d = dict.begin (); d != dict.end (); ++d) {
|
||||
if (! d->first.empty () && d->first.front () != '*' && ! cls->has_parameter_with_name (d->first)) {
|
||||
all_params.push_back (std::make_pair (d->first, (const db::DeviceParameterDefinition *) 0));
|
||||
}
|
||||
}
|
||||
|
||||
tl::Variant direct_value;
|
||||
|
||||
for (auto p = all_params.begin (); p != all_params.end (); ++p) {
|
||||
|
||||
auto id = dict.end ();
|
||||
|
||||
if ((id = dict.find (p->first)) != dict.end () ||
|
||||
(p->second && p->second->is_primary () && (id = dict.find ("*!")) != dict.end ()) ||
|
||||
(p->second && ! p->second->is_primary () && (id = dict.find ("*?")) != dict.end ()) ||
|
||||
(p->second && (id = dict.find ("**")) != dict.end ()) ||
|
||||
((id = dict.find ("*")) != dict.end ()) ||
|
||||
m_write_all_parameters) {
|
||||
|
||||
tl::Variant pv;
|
||||
if (id != dict.end ()) {
|
||||
pv = eval_parameter_expression (p->first, id->second, dev);
|
||||
} else if (p->second) {
|
||||
pv = dev.parameter_value (p->second->id ());
|
||||
}
|
||||
|
||||
if (! pv.is_nil ()) {
|
||||
if (p->first == "$") {
|
||||
direct_value = pv;
|
||||
} else {
|
||||
param_values.push_back (std::make_pair (p->first, std::make_pair (pv, p->second)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (! direct_value.is_nil ()) {
|
||||
write_parameter_value (os, direct_value, 1.0);
|
||||
}
|
||||
|
||||
if (! cls->name ().empty ()) {
|
||||
os << " ";
|
||||
os << format_name (cls->name ());
|
||||
}
|
||||
|
||||
for (auto p = param_values.begin (); p != param_values.end (); ++p) {
|
||||
os << " " << p->first << "=";
|
||||
write_parameter_value (os, p->second.first, p->second.second ? p->second.second->si_scaling () : 1.0);
|
||||
}
|
||||
os << format_params (dev);
|
||||
|
||||
emit_line (os.str ());
|
||||
}
|
||||
|
|
@ -125,6 +268,7 @@ void NetlistSpiceWriterDelegate::write_device_profile (const db::Device &dev, co
|
|||
void NetlistSpiceWriterDelegate::write_device_default (const db::Device &dev) const
|
||||
{
|
||||
const db::DeviceClass *dc = dev.device_class ();
|
||||
tl_assert (dc != 0);
|
||||
|
||||
const db::DeviceClassCapacitor *cap = dynamic_cast<const db::DeviceClassCapacitor *> (dc);
|
||||
const db::DeviceClassCapacitor *cap3 = dynamic_cast<const db::DeviceClassCapacitorWithBulk *> (dc);
|
||||
|
|
@ -260,10 +404,14 @@ std::string NetlistSpiceWriterDelegate::format_terminals_with_order (const db::D
|
|||
std::ostringstream os;
|
||||
|
||||
for (auto t = terminal_order.begin (); t != terminal_order.end (); ++t) {
|
||||
if (! dev.device_class ()->has_terminal_with_name (*t)) {
|
||||
std::string tn = *t;
|
||||
if (tn.front () == '?') {
|
||||
tn.erase (0, 1);
|
||||
}
|
||||
if (! dev.device_class ()->has_terminal_with_name (tn)) {
|
||||
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid terminal name '%s' for device of class '%s' in SPICE profile '%s")), *t, dev.device_class ()->name (), mp_writer->profile ()));
|
||||
}
|
||||
os << " " << net_to_string (dev.net_for_terminal (dev.device_class ()->terminal_id_for_name (*t)));
|
||||
os << " " << net_to_string (dev.net_for_terminal (dev.device_class ()->terminal_id_for_name (tn)));
|
||||
}
|
||||
|
||||
return os.str ();
|
||||
|
|
@ -283,25 +431,7 @@ std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, si
|
|||
if (i->id () != without_id && (! only_primary || i->is_primary ())) {
|
||||
|
||||
os << " " << i->name () << "=";
|
||||
|
||||
const tl::Variant &v = dev.parameter_value (i->id ());
|
||||
if (v.is_double ()) {
|
||||
|
||||
double sis = i->si_scaling ();
|
||||
// for compatibility
|
||||
if (fabs (sis * 1e6 - 1.0) < db::epsilon) {
|
||||
os << tl::to_string (v.to_double ()) << "U";
|
||||
} else if (fabs (sis * 1e12 - 1.0) < db::epsilon) {
|
||||
os << tl::to_string (v.to_double ()) << "P";
|
||||
} else {
|
||||
os << tl::to_string (v.to_double () * sis);
|
||||
}
|
||||
|
||||
} else if (v.is_long () || v.is_ulong ()) {
|
||||
os << v.to_string ();
|
||||
} else {
|
||||
os << tl::to_word_or_quoted_string (v.to_string ());
|
||||
}
|
||||
write_parameter_value (os, dev.parameter_value (i->id ()), i->si_scaling ());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,14 @@ public:
|
|||
return m_with_comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the delegate
|
||||
*/
|
||||
NetlistSpiceWriterDelegate *delegate ()
|
||||
{
|
||||
return mp_delegate.get ();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class NetlistSpiceWriterDelegate;
|
||||
|
||||
|
|
|
|||
|
|
@ -133,13 +133,6 @@ Class<NetlistSpiceWriterDelegateImpl> db_NetlistSpiceWriterDelegate ("db", "Netl
|
|||
) +
|
||||
gsi::method ("format_name", &NetlistSpiceWriterDelegateImpl::format_name, gsi::arg ("name"),
|
||||
"@brief Formats the given name in a SPICE-compatible way"
|
||||
) +
|
||||
// provided for test purposes
|
||||
gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters,
|
||||
"@hide"
|
||||
) +
|
||||
gsi::method ("write_all_parameters=", &NetlistSpiceWriterDelegateImpl::set_write_all_parameters, gsi::arg ("f"),
|
||||
"@hide"
|
||||
),
|
||||
"@brief Provides a delegate for the SPICE writer for doing special formatting for devices\n"
|
||||
"Supply a customized class to provide a specialized writing scheme for devices. "
|
||||
|
|
@ -193,6 +186,19 @@ Class<db::NetlistWriter> db_NetlistWriter ("db", "NetlistWriter",
|
|||
"This class has been introduced in version 0.26."
|
||||
);
|
||||
|
||||
|
||||
void set_write_all_parameters (db::NetlistSpiceWriter *writer, bool f)
|
||||
{
|
||||
if (writer->delegate ()) {
|
||||
writer->delegate ()->set_write_all_parameters (f);
|
||||
}
|
||||
}
|
||||
|
||||
bool write_all_parameters (db::NetlistSpiceWriter *writer)
|
||||
{
|
||||
return writer->delegate () ? writer->delegate ()->write_all_parameters () : false;
|
||||
}
|
||||
|
||||
Class<db::NetlistSpiceWriter> db_NetlistSpiceWriter (db_NetlistWriter, "db", "NetlistSpiceWriter",
|
||||
gsi::constructor ("new", &new_spice_writer, gsi::arg ("profile", std::string ()),
|
||||
"@brief Creates a new writer without delegate.\n"
|
||||
|
|
@ -208,6 +214,21 @@ Class<db::NetlistSpiceWriter> db_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne
|
|||
"\n"
|
||||
"The profile argument has been added in version 0.31.0."
|
||||
) +
|
||||
gsi::method_ext ("write_all_parameters=", &set_write_all_parameters,
|
||||
"@brief Sets a flag indicating whether to write all parameters\n"
|
||||
"\n"
|
||||
"With this flag all device parameters are written to the SPICE file, even ones which are normally not. "
|
||||
"Note that a SPICE writer delegate or a SPICE profile may override the default behavior.\n"
|
||||
"\n"
|
||||
"This attribute has been introduced in version 0.31.0."
|
||||
) +
|
||||
gsi::method_ext ("write_all_parameters=", &set_write_all_parameters,
|
||||
"@brief Gets a flag indicating whether to write all parameters\n"
|
||||
"\n"
|
||||
"See \\write_all_parameters= for details about this attribute.\n"
|
||||
"\n"
|
||||
"This attribute has been introduced in version 0.31.0."
|
||||
) +
|
||||
gsi::method ("not_connect_prefix", &db::NetlistSpiceWriter::not_connect_prefix,
|
||||
"@brief Gets the prefix used for terminals or pins which are not connected.\n"
|
||||
"See \\not_connect_prefix= for details.\n"
|
||||
|
|
|
|||
Loading…
Reference in New Issue