Merge branch 'more-spice' into issue-1304

This commit is contained in:
Matthias Koefferlein 2023-02-27 00:21:20 +01:00
commit ebcf242a9e
31 changed files with 2824 additions and 819 deletions

View File

@ -133,6 +133,12 @@ Object::Object (const ant::Object &d)
// .. nothing else ..
}
Object::~Object ()
{
// .. nothing yet ..
}
Object &
Object::operator= (const ant::Object &d)
{

View File

@ -137,6 +137,11 @@ public:
*/
Object &operator= (const ant::Object &d);
/**
* @brief Destructor
*/
~Object ();
/**
* @brief Less operator
*/

View File

@ -1009,6 +1009,7 @@ View::ruler (const ant::Object *r)
void
View::render (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas)
{
// .. nothing yet ..
if (! mp_ruler) {
return;
}
@ -1700,6 +1701,7 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type)
void
Service::selection_to_view ()
{
clear_transient_selection ();
annotation_selection_changed_event ();
// the selection objects need to be recreated since we destroyed the old rulers

View File

@ -60,6 +60,8 @@ SOURCES = \
dbNetlistCompareCore.cc \
dbNetlistCompareGraph.cc \
dbNetlistCompareUtils.cc \
dbNetlistSpiceReaderDelegate.cc \
dbNetlistSpiceReaderExpressionParser.cc \
dbObject.cc \
dbPath.cc \
dbPCellDeclaration.cc \
@ -279,6 +281,8 @@ HEADERS = \
dbNetlistCompareCore.h \
dbNetlistCompareGraph.h \
dbNetlistCompareUtils.h \
dbNetlistSpiceReaderDelegate.h \
dbNetlistSpiceReaderExpressionParser.h \
dbObject.h \
dbObjectTag.h \
dbObjectWithProperties.h \

File diff suppressed because it is too large Load Diff

View File

@ -25,136 +25,19 @@
#include "dbCommon.h"
#include "dbNetlistReader.h"
#include "tlStream.h"
#include <string>
#include <set>
#include "tlStream.h"
#include "tlObject.h"
#include "tlVariant.h"
#include <map>
#include <memory>
#include <list>
#include <string>
namespace db
{
class Netlist;
class Net;
class Circuit;
class DeviceClass;
class Device;
/**
* @brief A specialized exception class to handle netlist reader delegate errors
*/
class DB_PUBLIC NetlistSpiceReaderDelegateError
: public tl::Exception
{
public:
NetlistSpiceReaderDelegateError (const std::string &msg)
: tl::Exception (msg)
{ }
};
/**
* @brief A delegate to handle various forms of devices and translates them
*
* The reader delegate can be configured to receive subcircuit elements too.
* In this case, parameters are allowed.
* For receiving subcircuit elements, the delegate needs to indicate
* this by returning true upon "wants_subcircuit".
*/
class DB_PUBLIC NetlistSpiceReaderDelegate
: public tl::Object
{
public:
NetlistSpiceReaderDelegate ();
virtual ~NetlistSpiceReaderDelegate ();
/**
* @brief Called when the netlist reading starts
*/
virtual void start (db::Netlist *netlist);
/**
* @brief Called when the netlist reading ends
*/
virtual void finish (db::Netlist *netlist);
/**
* @brief Called when an unknown control statement is encountered
*
* Returns true if the statement is understood.
*/
virtual bool control_statement (const std::string &line);
/**
* @brief Returns true, if the delegate wants subcircuit elements with this name
*
* The name is always upper case.
*/
virtual bool wants_subcircuit (const std::string &circuit_name);
/**
* @brief This method translates a raw net name to a valid net name
*
* The default implementation will unescape backslash sequences into plain characters.
*/
virtual std::string translate_net_name (const std::string &nn);
/**
* @brief Makes a device from an element line
*
* @param circuit The circuit that is currently read.
* @param element The upper-case element code ("M", "R", ...).
* @param name The element's name.
* @param model The upper-case model name (may be empty).
* @param value The default value (e.g. resistance for resistors) and may be zero.
* @param nets The nets given in the element line.
* @param parameters The parameters of the element statement (parameter names are upper case).
*
* The default implementation will create corresponding devices for
* some known elements using the Spice writer's parameter conventions.
*
* This method returns true, if the element was read.
*/
virtual bool 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, double> &params);
/**
* @brief Parses an element from a line
*
* @param s The line to parse (the part after the element and name)
* @param model Out parameter: the model name if given
* @param value Out parameter: the value if given (for R, L, C)
* @param nn Out parameter: the net names
* @param pv Out parameter: the parameter values (key/value pairs)
*/
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv);
/**
* @brief Produces an error with the given message
*/
virtual void error (const std::string &msg);
/**
* @brief Reads a set of string components and parameters from the string
* A special key "param:" is recognized for starting a parameter list.
*/
void parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, double> &pv);
/**
* @brief Reads a value from the extractor (with formula evaluation)
*/
double read_value (tl::Extractor &ex);
/**
* @brief Tries to read a value from the extractor (with formula evaluation)
*/
bool try_read_value (const std::string &s, double &v);
private:
double read_atomic_value (tl::Extractor &ex);
double read_dot_expr (tl::Extractor &ex);
double read_bar_expr (tl::Extractor &ex);
};
class NetlistSpiceReaderDelegate;
/**
* @brief A SPICE format reader for netlists
@ -163,76 +46,25 @@ class DB_PUBLIC NetlistSpiceReader
: public NetlistReader
{
public:
typedef std::map<std::string, tl::Variant> parameters_type;
NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate = 0);
virtual ~NetlistSpiceReader ();
virtual void read (tl::InputStream &stream, db::Netlist &netlist);
private:
class SpiceReaderStream
/**
* @brief Sets or resets strict mode
* In strict mode, all subcircuits need to be present in the net list for example.
*/
void set_strict (bool s)
{
public:
SpiceReaderStream ();
~SpiceReaderStream ();
m_strict = s;
}
void set_stream (tl::InputStream &stream);
void set_stream (tl::InputStream *stream);
void close ();
std::pair<std::string, bool> get_line();
int line_number () const;
std::string source () const;
bool at_end () const;
void swap (SpiceReaderStream &other)
{
std::swap (mp_stream, other.mp_stream);
std::swap (m_owns_stream, other.m_owns_stream);
std::swap (mp_text_stream, other.mp_text_stream);
std::swap (m_line_number, other.m_line_number);
std::swap (m_stored_line, other.m_stored_line);
std::swap (m_has_stored_line, other.m_has_stored_line);
}
private:
tl::InputStream *mp_stream;
bool m_owns_stream;
tl::TextInputStream *mp_text_stream;
int m_line_number;
std::string m_stored_line;
bool m_has_stored_line;
};
db::Netlist *mp_netlist;
db::Circuit *mp_circuit;
db::Circuit *mp_anonymous_top_circuit;
private:
tl::weak_ptr<NetlistSpiceReaderDelegate> mp_delegate;
std::list<SpiceReaderStream> m_streams;
SpiceReaderStream m_stream;
std::unique_ptr<std::map<std::string, db::Net *> > mp_nets_by_name;
std::map<std::string, bool> m_captured;
std::vector<std::string> m_global_nets;
std::set<std::string> m_global_net_names;
std::set<const db::Circuit *> m_circuits_read;
void push_stream (const std::string &path);
void pop_stream ();
bool at_end ();
bool read_element (tl::Extractor &ex, const std::string &element, const std::string &name);
void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector<db::Net *> &nets);
void read_circuit (tl::Extractor &ex, const std::string &name);
void skip_circuit (tl::Extractor &ex);
bool read_card ();
std::string read_name (tl::Extractor &ex);
std::string get_line ();
void error (const std::string &msg);
void warn (const std::string &msg);
void finish ();
db::Net *make_net (const std::string &name);
void ensure_circuit ();
bool subcircuit_captured (const std::string &nc_name);
void build_global_nets ();
bool m_strict;
};
}

View File

@ -0,0 +1,574 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbNetlistSpiceReaderDelegate.h"
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderExpressionParser.h"
#include "dbNetlist.h"
#include "dbCircuit.h"
#include "dbNetlistDeviceClasses.h"
namespace db
{
// ------------------------------------------------------------------------------------------------------
inline static int hex_num (char c)
{
if (c >= '0' && c <= '9') {
return (int (c - '0'));
} else if (c >= 'a' && c <= 'f') {
return (int (c - 'f') + 10);
} else {
return -1;
}
}
static std::string unescape_name (const std::string &n)
{
std::string nn;
nn.reserve (n.size ());
const char *cp = n.c_str ();
while (*cp) {
if (*cp == '\\' && cp[1]) {
if (tolower (cp[1]) == 'x') {
cp += 2;
char c = 0;
for (int i = 0; i < 2 && *cp; ++i) {
int n = hex_num (*cp);
if (n >= 0) {
++cp;
c = c * 16 + char (n);
} else {
break;
}
}
nn += c;
} else {
++cp;
nn += *cp++;
}
} else {
nn += *cp++;
}
}
return nn;
}
// ------------------------------------------------------------------------------------------------------
NetlistSpiceReaderDelegate::NetlistSpiceReaderDelegate ()
: mp_netlist (0)
{
// .. nothing yet ..
}
NetlistSpiceReaderDelegate::~NetlistSpiceReaderDelegate ()
{
// .. nothing yet ..
}
void NetlistSpiceReaderDelegate::start (db::Netlist * /*netlist*/)
{
// .. nothing yet ..
}
void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/)
{
// .. nothing yet ..
}
bool NetlistSpiceReaderDelegate::control_statement(const std::string & /*line*/)
{
return false;
}
bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit_name*/)
{
return false;
}
std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &nn)
{
return unescape_name (nn);
}
void NetlistSpiceReaderDelegate::error (const std::string &msg)
{
throw tl::Exception (msg);
}
template <class Cls>
static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::string &name)
{
if (! circuit || ! circuit->netlist ()) {
return 0;
}
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (name);
if (! cls) {
cls = new Cls ();
cls->set_name (name);
circuit->netlist ()->add_device_class (cls);
}
return cls;
}
static std::string parse_component (tl::Extractor &ex)
{
const char *cp = ex.skip ();
const char *cp0 = cp;
char quote = 0;
unsigned int brackets = 0;
while (*cp) {
if (quote) {
if (*cp == quote) {
quote = 0;
} else if (*cp == '\\' && cp[1]) {
++cp;
}
} else if ((isspace (*cp) || *cp == '=') && ! brackets) {
break;
} else if (*cp == '"' || *cp == '\'') {
quote = *cp;
} else if (*cp == '(') {
++brackets;
} else if (*cp == ')') {
if (brackets > 0) {
--brackets;
}
}
++cp;
}
ex = tl::Extractor (cp);
return std::string (cp0, cp - cp0);
}
void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &variables)
{
tl::Extractor ex (s.c_str ());
bool in_params = false;
while (! ex.at_end ()) {
if (ex.test_without_case ("params:")) {
in_params = true;
} else {
tl::Extractor ex0 = ex;
std::string n;
if (ex.try_read_word (n) && ex.test ("=")) {
// a parameter
pv [mp_netlist ? mp_netlist->normalize_name (n) : tl::to_upper_case (n)] = read_value (ex, variables);
} else {
// a net/model component
ex = ex0;
if (in_params) {
ex.error (tl::to_string (tr ("Invalid syntax for parameter assignment - needs keyword followed by '='")));
}
std::string comp_name = parse_component (ex);
comp_name = mp_netlist ? mp_netlist->normalize_name (comp_name) : tl::to_upper_case (comp_name);
// resolve variables if string type
auto v = variables.find (comp_name);
if (v != variables.end ()) {
if (v->second.is_a_string ()) {
strings.push_back (v->second.to_string ());
} else if (v->second.can_convert_to_double ()) {
// NOTE: this allows using a variable name "x" instead of "x=x"
pv [comp_name] = v->second;
} else {
strings.push_back (comp_name);
}
} else {
strings.push_back (comp_name);
}
}
}
}
}
void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &variables)
{
parse_element_components (s, nn, pv, variables);
// interpret the parameters according to the code
if (element == "X") {
// subcircuit call:
// Xname n1 n2 ... nn circuit [params]
if (nn.empty ()) {
error (tl::to_string (tr ("No circuit name given for subcircuit call")));
}
model = nn.back ();
nn.pop_back ();
} else if (element == "R" || element == "C" || element == "L") {
// resistor, cap, inductor: two-terminal devices with a value
// Rname n1 n2 value
// Rname n1 n2 n3 value
// Rname n1 n2 value model [params]
// Rname n1 n2 n3 value model [params]
// Rname n1 n2 [params]
// Rname n1 n2 model [params]
// Rname n1 n2 n3 model [params]
// NOTE: there is no "Rname n1 n2 n3 [params]"!
// (same for C, L instead of R)
if (nn.size () < 2) {
error (tl::to_string (tr ("Not enough specs for a R, C or L device")));
}
auto rv = pv.find (element);
if (rv != pv.end ()) {
// value given by parameter
value = rv->second.to_double ();
if (nn.size () >= 3) {
// Rname n1 n2 model [params]
// Rname n1 n2 n3 model [params]
model = nn.back ();
nn.pop_back ();
}
} else if (nn.size () >= 3) {
if (try_read_value (nn.back (), value, variables)) {
// Rname n1 n2 value
// Rname n1 n2 n3 value
nn.pop_back ();
} else {
// Rname n1 n2 value model [params]
// Rname n1 n2 n3 value model [params]
model = nn.back ();
nn.pop_back ();
if (! try_read_value (nn.back (), value, variables)) {
error (tl::to_string (tr ("Can't find a value for a R, C or L device")));
} else {
nn.pop_back ();
}
}
}
} else {
// others: n-terminal devices with a model (last node)
if (nn.empty ()) {
error (tl::sprintf (tl::to_string (tr ("No model name given for element '%s'")), element));
}
model = nn.back ();
nn.pop_back ();
if (element == "M") {
if (nn.size () != 4) {
error (tl::to_string (tr ("'M' element must have four nodes")));
}
} else if (element == "Q") {
if (nn.size () != 3 && nn.size () != 4) {
error (tl::to_string (tr ("'Q' element must have three or four nodes")));
}
} else if (element == "D") {
if (nn.size () != 2) {
error (tl::to_string (tr ("'D' element must have two nodes")));
}
}
// TODO: other devices?
}
}
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, tl::Variant> &pv)
{
std::map<std::string, tl::Variant> params = pv;
double mult = 1.0;
auto mp = params.find ("M");
if (mp != params.end ()) {
mult = mp->second.to_double ();
}
if (mult < 1e-10) {
error (tl::sprintf (tl::to_string (tr ("Invalid multiplier value (M=%.12g) - must not be zero or negative")), mult));
}
std::string cn = model;
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
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
value /= 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
value /= mult;
} 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
value *= 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 to "A"
auto p = params.find ("A");
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 to "AE"
auto p = params.find ("AE");
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 to "W"
auto p = params.find ("W");
if (p != params.end ()) {
p->second = tl::Variant (p->second.to_double () * mult);
}
} else {
error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element));
}
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 ())));
}
db::Device *device = new db::Device (cls, name);
circuit->add_device (device);
for (std::vector<db::DeviceTerminalDefinition>::const_iterator t = td.begin (); t != td.end (); ++t) {
device->connect_terminal (t->id (), nets [t - td.begin ()]);
}
size_t defp = std::numeric_limits<size_t>::max ();
if (dynamic_cast<db::DeviceClassCapacitor *> (cls)) {
defp = db::DeviceClassCapacitor::param_id_C;
} else if (dynamic_cast<db::DeviceClassResistor *> (cls)) {
defp = db::DeviceClassResistor::param_id_R;
} else if (dynamic_cast<db::DeviceClassInductor *> (cls)) {
defp = db::DeviceClassInductor::param_id_L;
}
std::vector<db::DeviceParameterDefinition> &pd = cls->parameter_definitions_non_const ();
for (std::vector<db::DeviceParameterDefinition>::iterator i = pd.begin (); i != pd.end (); ++i) {
auto v = params.find (i->name ());
if (v != params.end ()) {
device->set_parameter_value (i->id (), v->second.to_double () / i->si_scaling ());
} else if (i->id () == defp) {
device->set_parameter_value (i->id (), value / i->si_scaling ());
}
}
return true;
}
tl::Variant
NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map<std::string, tl::Variant> &variables)
{
NetlistSpiceReaderExpressionParser parser (&variables);
return parser.read (ex);
}
bool
NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &v, const std::map<std::string, tl::Variant> &variables)
{
NetlistSpiceReaderExpressionParser parser (&variables);
tl::Variant vv;
tl::Extractor ex (s.c_str ());
bool res = parser.try_read (ex, vv);
if (res && ! vv.can_convert_to_double ()) {
res = false;
}
if (res) {
v = vv.to_double ();
}
return res;
}
}

View File

@ -0,0 +1,169 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbNetlistSpiceReaderDelegate
#define HDR_dbNetlistSpiceReaderDelegate
#include "dbCommon.h"
#include "tlStream.h"
#include "tlException.h"
#include <string>
#include <vector>
#include <map>
namespace db
{
class Netlist;
class Net;
class Circuit;
class DeviceClass;
class Device;
/**
* @brief A delegate to handle various forms of devices and translates them
*
* The reader delegate can be configured to receive subcircuit elements too.
* In this case, parameters are allowed.
* For receiving subcircuit elements, the delegate needs to indicate
* this by returning true upon "wants_subcircuit".
*/
class DB_PUBLIC NetlistSpiceReaderDelegate
: public tl::Object
{
public:
NetlistSpiceReaderDelegate ();
virtual ~NetlistSpiceReaderDelegate ();
/**
* @brief Called when the netlist reading starts
*/
virtual void start (db::Netlist *netlist);
/**
* @brief Called when the netlist reading ends
*/
virtual void finish (db::Netlist *netlist);
/**
* @brief Called when an unknown control statement is encountered
*
* Returns true if the statement is understood.
*/
virtual bool control_statement (const std::string &line);
/**
* @brief Returns true, if the delegate wants subcircuit elements with this name
*
* The name is always upper case.
*/
virtual bool wants_subcircuit (const std::string &circuit_name);
/**
* @brief This method translates a raw net name to a valid net name
*
* The default implementation will unescape backslash sequences into plain characters.
*/
virtual std::string translate_net_name (const std::string &nn);
/**
* @brief Makes a device from an element line
*
* @param circuit The circuit that is currently read.
* @param element The upper-case element code ("M", "R", ...).
* @param name The element's name.
* @param model The upper-case model name (may be empty).
* @param value The default value (e.g. resistance for resistors) and may be zero.
* @param nets The nets given in the element line.
* @param parameters The parameters of the element statement (parameter names are upper case).
*
* The default implementation will create corresponding devices for
* some known elements using the Spice writer's parameter conventions.
*
* This method returns true, if the element was read.
*/
virtual bool 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);
/**
* @brief Parses an element from a line
*
* @param s The line to parse (the part after the element and name)
* @param model Out parameter: the model name if given
* @param value Out parameter: the value if given (for R, L, C)
* @param nn Out parameter: the net names
* @param pv Out parameter: the parameter values (key/value pairs)
*/
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &params);
/**
* @brief Produces an error with the given message
*/
virtual void error (const std::string &msg);
/**
* @brief Reads a set of string components and parameters from the string
* A special key "param:" is recognized for starting a parameter list.
*/
void parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &variables);
/**
* @brief Reads a value from the extractor (with formula evaluation)
*/
static tl::Variant read_value(tl::Extractor &ex, const std::map<std::string, tl::Variant> &variables);
/**
* @brief Tries to read a value from the extractor (with formula evaluation)
*/
static bool try_read_value (const std::string &s, double &v, const std::map<std::string, tl::Variant> &variables);
/**
* @brief External interface for start
*/
void do_start ()
{
start (mp_netlist);
}
/**
* @brief External interface for finish
*/
void do_finish ()
{
finish (mp_netlist);
}
/**
* @brief Sets the netlist
*/
void set_netlist (db::Netlist *netlist)
{
mp_netlist = netlist;
}
private:
db::Netlist *mp_netlist;
};
}
#endif

View File

@ -0,0 +1,556 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbNetlistSpiceReaderExpressionParser.h"
#include "dbNetlistSpiceReader.h"
#include <cmath>
namespace db
{
// ------------------------------------------------------------------------------------------------------
static bool to_bool (const tl::Variant &v)
{
if (v.is_bool ()) {
return v.to_bool ();
} else if (v.is_nil ()) {
return false;
} else if (v.can_convert_to_double ()) {
return v.to_double () != 0.0;
} else {
return true;
}
}
// ------------------------------------------------------------------------------------------------------
NetlistSpiceReaderExpressionParser::NetlistSpiceReaderExpressionParser (const variables_type *vars)
{
static variables_type empty_variables;
mp_variables = vars ? vars : &empty_variables;
}
// expression syntax taken from ngspice:
// https://nmg.gitlab.io/ngspice-manual/circuitdescription/paramparametricnetlists/syntaxofexpressions.html
static double sqrt_f (double v) { return sqrt (v); }
static double sin_f (double v) { return sin (v); }
static double cos_f (double v) { return cos (v); }
static double tan_f (double v) { return tan (v); }
static double sinh_f (double v) { return sinh (v); }
static double cosh_f (double v) { return cosh (v); }
static double tanh_f (double v) { return tanh (v); }
static double asin_f (double v) { return asin (v); }
static double acos_f (double v) { return acos (v); }
static double atan_f (double v) { return atan (v); }
static double asinh_f (double v) { return asinh (v); }
static double acosh_f (double v) { return acosh (v); }
static double atanh_f (double v) { return atanh (v); }
static double exp_f (double v) { return exp (v); }
static double ln_f (double v) { return log (v); }
static double log_f (double v) { return log10 (v); }
static double abs_f (double v) { return abs (v); }
static double nint_f (double v) { return nearbyint (v); } // we basically should we the rounding mode before ...
static double floor_f (double v) { return floor (v); }
static double ceil_f (double v) { return ceil (v); }
static double sgn_f (double v) { return v == 0.0 ? 0.0 : (v < 0.0 ? -1.0 : 1.0); }
static double int_f (double v) { return sgn_f (v) * floor (sgn_f (v) * v); }
tl::Variant
NetlistSpiceReaderExpressionParser::eval_func (const std::string &name, const std::vector<tl::Variant> &params, bool * /*status*/) const
{
double (*f) (double) = 0;
if (name == "SQRT") { f = sqrt_f; } else
if (name == "SIN") { f = sin_f; } else
if (name == "COS") { f = cos_f; } else
if (name == "TAN") { f = tan_f; } else
if (name == "SINH") { f = sinh_f; } else
if (name == "COSH") { f = cosh_f; } else
if (name == "TANH") { f = tanh_f; } else
if (name == "ASIN") { f = asin_f; } else
if (name == "ACOS") { f = acos_f; } else
if (name == "ATAN" || name == "arctan") { f = atan_f; } else
if (name == "ASINH") { f = asinh_f; } else
if (name == "ACOSH") { f = acosh_f; } else
if (name == "ATANH") { f = atanh_f; } else
if (name == "EXP") { f = exp_f; } else
if (name == "LN") { f = ln_f; } else
if (name == "LOG") { f = log_f; } else
if (name == "ABS") { f = abs_f; } else
if (name == "NINT") { f = nint_f; } else
if (name == "FLOOR") { f = floor_f; } else
if (name == "CEIL") { f = ceil_f; } else
if (name == "SGN") { f = sgn_f; } else
if (name == "INT") { f = int_f; }
if (f != 0) {
if (params.size () < 1 || ! params.front ().can_convert_to_double ()) {
return tl::Variant ();
} else {
return tl::Variant ((*f) (params.front ().to_double ()));
}
} else if (name == "PWR" || name == "POW") {
if (params.size () < 2 || ! params [0].can_convert_to_double () || ! params [1].can_convert_to_double ()) {
return tl::Variant ();
} else {
return tl::Variant (pow (params [0].to_double (), params [1].to_double ()));
}
} else if (name == "TERNERY_FCN") {
if (params.size () < 3) {
return tl::Variant ();
} else {
return to_bool (params [0]) ? params [1] : params [2];
}
} else if (name == "MIN") {
if (params.size () < 1) {
return tl::Variant ();
}
tl::Variant v = params [0];
for (size_t i = 1; i < params.size (); ++i) {
if (params [i] < v) {
v = params [i];
}
}
return v;
} else if (name == "MAX") {
if (params.size () < 1) {
return tl::Variant ();
}
tl::Variant v = params [0];
for (size_t i = 1; i < params.size (); ++i) {
if (v < params [i]) {
v = params [i];
}
}
return v;
} else {
return tl::Variant ();
}
}
tl::Variant
NetlistSpiceReaderExpressionParser::read_atomic_value (tl::Extractor &ex, bool *status) const
{
double vd = 0.0;
std::string var;
if (ex.test ("-")) {
tl::Variant v = read_atomic_value (ex, status);
if (v.can_convert_to_double ()) {
return tl::Variant (-v.to_double ());
} else {
return tl::Variant ();
}
} else if (ex.test ("!")) {
tl::Variant v = read_atomic_value (ex, status);
return tl::Variant (! to_bool (v));
} else if (ex.test ("(")) {
tl::Variant v = read_tl_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (status) {
*status = ex.test (")");
} else {
ex.expect (")");
}
return v;
} else if (ex.try_read (vd)) {
if (status) {
*status = true;
}
double f = 1.0;
if (*ex == 't' || *ex == 'T') {
f = 1e12;
} else if (*ex == 'g' || *ex == 'G') {
f = 1e9;
} else if (*ex == 'k' || *ex == 'K') {
f = 1e3;
} else if (*ex == 'm' || *ex == 'M') {
f = 1e-3;
if (ex.test_without_case ("meg")) {
f = 1e6;
}
} else if (*ex == 'u' || *ex == 'U') {
f = 1e-6;
} else if (*ex == 'n' || *ex == 'N') {
f = 1e-9;
} else if (*ex == 'p' || *ex == 'P') {
f = 1e-12;
} else if (*ex == 'f' || *ex == 'F') {
f = 1e-15;
} else if (*ex == 'a' || *ex == 'A') {
f = 1e-18;
}
while (*ex && isalpha (*ex)) {
++ex;
}
vd *= f;
return tl::Variant (vd);
} else if (ex.try_read_word (var)) {
var = tl::to_upper_case (var);
if (ex.test ("(")) {
// a function
std::vector<tl::Variant> params;
if (! ex.test (")")) {
while (! ex.at_end ()) {
params.push_back (read_tl_expr (ex, status));
if (status && !*status) {
return tl::Variant ();
}
if (! ex.test (",")) {
break;
}
}
if (status && ! ex.test (")")) {
*status = false;
return tl::Variant ();
} else {
ex.expect (")");
}
}
return eval_func (var, params, status);
} else {
auto vi = mp_variables->find (var);
if (vi != mp_variables->end ()) {
return vi->second;
} else {
// keep word as string value
return tl::Variant (var);
}
}
} else {
if (status) {
*status = false;
} else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Expected number of variable name here: '...%s'")), ex.get ()));
}
return tl::Variant ();
}
}
tl::Variant NetlistSpiceReaderExpressionParser::read_pwr_expr (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_atomic_value (ex, status);
if (status && !*status) {
return tl::Variant ();
}
while (true) {
if (ex.test ("**") || ex.test ("^")) {
tl::Variant vv = read_atomic_value (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = tl::Variant (pow (v.to_double (), vv.to_double ()));
}
} else {
break;
}
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_dot_expr (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_pwr_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
while (true) {
if (ex.test ("*")) {
tl::Variant vv = read_pwr_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = v.to_double () * vv.to_double ();
}
} else if (ex.test ("/")) {
tl::Variant vv = read_pwr_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = v.to_double () / vv.to_double ();
}
} else if (ex.test ("%")) {
tl::Variant vv = read_pwr_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = tl::Variant ((long int) v.to_double () % (long int) vv.to_double ());
}
} else {
break;
}
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_bar_expr (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_dot_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
while (true) {
if (ex.test ("+")) {
tl::Variant vv = read_dot_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = v.to_double () + vv.to_double ();
}
} else if (ex.test ("-")) {
tl::Variant vv = read_dot_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) {
v = tl::Variant ();
} else {
v = v.to_double () - vv.to_double ();
}
} else {
break;
}
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_compare_expr (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
while (true) {
if (ex.test ("==")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (v == vv);
} else if (ex.test ("!=")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (!(v == vv));
} else if (ex.test ("<=")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (v < vv || v == vv);
} else if (ex.test ("<")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (v < vv);
} else if (ex.test (">=")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (vv < v || v == vv);
} else if (ex.test (">")) {
tl::Variant vv = read_bar_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (vv < v);
} else {
break;
}
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_logical_op (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_compare_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
while (true) {
if (ex.test ("&&")) {
tl::Variant vv = read_compare_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (to_bool (v) && to_bool (vv));
} else if (ex.test ("||")) {
tl::Variant vv = read_compare_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (to_bool (v) || to_bool (vv));
} else {
break;
}
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_ternary_op (tl::Extractor &ex, bool *status) const
{
tl::Variant v = read_logical_op (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (ex.test ("?")) {
tl::Variant vv1 = read_logical_op (ex, status);
if (status && !*status) {
return tl::Variant ();
}
if (! ex.test (":")) {
if (status) {
*status = false;
} else {
ex.expect (":");
}
}
tl::Variant vv2 = read_logical_op (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = to_bool (v) ? vv1 : vv2;
}
return v;
}
tl::Variant NetlistSpiceReaderExpressionParser::read_tl_expr (tl::Extractor &ex, bool *status) const
{
return read_ternary_op (ex, status);
}
static const char *start_quote (tl::Extractor &ex)
{
if (ex.test ("'")) {
return "'";
} else if (ex.test ("\"")) {
return "\"";
} else if (ex.test ("{")) {
return "}";
} else {
return 0;
}
}
tl::Variant NetlistSpiceReaderExpressionParser::read (const std::string &s) const
{
tl::Extractor ex (s.c_str ());
return read (ex);
}
tl::Variant NetlistSpiceReaderExpressionParser::read (tl::Extractor &ex) const
{
tl::Variant res;
const char *endquote = start_quote (ex);
res = read_tl_expr (ex, 0);
if (endquote) {
ex.test (endquote);
}
return res;
}
bool NetlistSpiceReaderExpressionParser::try_read (const std::string &s, tl::Variant &value) const
{
tl::Extractor ex (s.c_str ());
return try_read (ex, value);
}
bool NetlistSpiceReaderExpressionParser::try_read (tl::Extractor &ex, tl::Variant &value) const
{
tl::Extractor ex_saved = ex;
bool status = false;
const char *endquote = start_quote (ex);
value = read_tl_expr (ex, &status);
if (endquote && ! ex.test (endquote)) {
status = false;
}
if (! status) {
value = tl::Variant ();
ex = ex_saved;
}
return status;
}
}

View File

@ -0,0 +1,70 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbNetlistSpiceReaderExpressionParser
#define HDR_dbNetlistSpiceReaderExpressionParser
#include "dbCommon.h"
#include "tlStream.h"
#include "tlVariant.h"
#include "tlString.h"
#include <string>
#include <map>
#include <vector>
namespace db
{
/**
* @brief A class implementing the expression parser
*
* This class is exposed mainly for testing purposes.
*/
class DB_PUBLIC NetlistSpiceReaderExpressionParser
{
public:
typedef std::map<std::string, tl::Variant> variables_type;
NetlistSpiceReaderExpressionParser (const variables_type *vars);
tl::Variant read (tl::Extractor &ex) const;
tl::Variant read (const std::string &s) const;
bool try_read (tl::Extractor &ex, tl::Variant &v) const;
bool try_read (const std::string &s, tl::Variant &v) const;
private:
const variables_type *mp_variables;
tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const;
tl::Variant read_dot_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_bar_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_pwr_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_compare_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_logical_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_ternary_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_tl_expr (tl::Extractor &ex, bool *status) const;
tl::Variant eval_func (const std::string &name, const std::vector<tl::Variant> &params, bool *status) const;
};
}
#endif

View File

@ -26,6 +26,7 @@
#include "dbNetlistSpiceWriter.h"
#include "dbNetlistReader.h"
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderDelegate.h"
#include "tlException.h"
#include "tlInternational.h"
#include "tlStream.h"
@ -2417,15 +2418,15 @@ public:
const std::vector<std::string> &net_names () const { return m_net_names; }
std::vector<std::string> &net_names_nc () { return m_net_names; }
void set_net_names (const std::vector<std::string> &nn) { m_net_names = nn; }
const std::map<std::string, double> &parameters () const { return m_parameters; }
std::map<std::string, double> &parameters_nc () { return m_parameters; }
void set_parameters (const std::map<std::string, double> &parameters) { m_parameters = parameters; }
const db::NetlistSpiceReader::parameters_type &parameters () const { return m_parameters; }
db::NetlistSpiceReader::parameters_type &parameters_nc () { return m_parameters; }
void set_parameters (const db::NetlistSpiceReader::parameters_type &parameters) { m_parameters = parameters; }
private:
std::string m_model;
double m_value;
std::vector<std::string> m_net_names;
std::map<std::string, double> m_parameters;
db::NetlistSpiceReader::parameters_type m_parameters;
};
/**
@ -2439,13 +2440,13 @@ public:
const std::vector<std::string> &strings () const { return m_strings; }
std::vector<std::string> &strings_nc () { return m_strings; }
void set_strings (const std::vector<std::string> &nn) { m_strings = nn; }
const std::map<std::string, double> &parameters () const { return m_parameters; }
std::map<std::string, double> &parameters_nc () { return m_parameters; }
void set_parameters (const std::map<std::string, double> &parameters) { m_parameters = parameters; }
const db::NetlistSpiceReader::parameters_type &parameters () const { return m_parameters; }
db::NetlistSpiceReader::parameters_type &parameters_nc () { return m_parameters; }
void set_parameters (const db::NetlistSpiceReader::parameters_type &parameters) { m_parameters = parameters; }
private:
std::vector<std::string> m_strings;
std::map<std::string, double> m_parameters;
db::NetlistSpiceReader::parameters_type m_parameters;
};
/**
@ -2456,7 +2457,7 @@ class NetlistSpiceReaderDelegateImpl
{
public:
NetlistSpiceReaderDelegateImpl ()
: db::NetlistSpiceReaderDelegate ()
: db::NetlistSpiceReaderDelegate (), mp_variables (0)
{
// .. nothing yet ..
}
@ -2566,15 +2567,16 @@ public:
ParseElementData parse_element_helper (const std::string &s, const std::string &element)
{
ParseElementData data;
db::NetlistSpiceReaderDelegate::parse_element (s, element, data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc ());
db::NetlistSpiceReaderDelegate::parse_element (s, element, data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc (), variables ());
return data;
}
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv)
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, db::NetlistSpiceReader::parameters_type &pv, const db::NetlistSpiceReader::parameters_type &variables)
{
try {
m_error.clear ();
mp_variables = &variables;
ParseElementData data;
if (cb_parse_element.can_issue ()) {
@ -2588,21 +2590,27 @@ public:
nn = data.net_names ();
pv = data.parameters ();
mp_variables = 0;
} catch (tl::Exception &) {
mp_variables = 0;
if (! m_error.empty ()) {
db::NetlistSpiceReaderDelegate::error (m_error);
} else {
throw;
}
} catch (...) {
mp_variables = 0;
throw;
}
}
virtual bool 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, double> &params)
virtual bool 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 db::NetlistSpiceReader::parameters_type &params)
{
try {
m_error.clear ();
if (cb_element.can_issue ()) {
return cb_element.issue<db::NetlistSpiceReaderDelegate, bool, db::Circuit *, const std::string &, const std::string &, const std::string &, double, const std::vector<db::Net *> &, const std::map<std::string, double> &> (&db::NetlistSpiceReaderDelegate::element, circuit, element, name, model, value, nets, params);
return cb_element.issue<db::NetlistSpiceReaderDelegate, bool, db::Circuit *, const std::string &, const std::string &, const std::string &, double, const std::vector<db::Net *> &, const db::NetlistSpiceReader::parameters_type &> (&db::NetlistSpiceReaderDelegate::element, circuit, element, name, model, value, nets, params);
} else {
return db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
}
@ -2616,6 +2624,12 @@ public:
}
}
const db::NetlistSpiceReader::parameters_type &variables () const
{
static db::NetlistSpiceReader::parameters_type empty;
return mp_variables ? *mp_variables : empty;
}
gsi::Callback cb_start;
gsi::Callback cb_finish;
gsi::Callback cb_control_statement;
@ -2626,6 +2640,7 @@ public:
private:
std::string m_error;
const db::NetlistSpiceReader::parameters_type *mp_variables;
};
static void start_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Netlist *netlist)
@ -2653,7 +2668,7 @@ static std::string translate_net_name_fb (NetlistSpiceReaderDelegateImpl *delega
return delegate->db::NetlistSpiceReaderDelegate::translate_net_name (name);
}
static bool element_fb (NetlistSpiceReaderDelegateImpl *delegate, 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, double> &params)
static bool element_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const db::NetlistSpiceReader::parameters_type &params)
{
return delegate->db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
}
@ -2663,41 +2678,43 @@ static ParseElementData parse_element_fb (NetlistSpiceReaderDelegateImpl *delega
return delegate->parse_element_helper (s, element);
}
static tl::Variant value_from_string (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s)
static tl::Variant value_from_string (NetlistSpiceReaderDelegateImpl * /*delegate*/, const std::string &s, const db::NetlistSpiceReader::parameters_type &variables)
{
tl::Variant res;
double v = 0.0;
if (delegate->try_read_value (s, v)) {
if (db::NetlistSpiceReaderDelegate::try_read_value (s, v, variables)) {
res = v;
}
return res;
}
static ParseElementComponentsData parse_element_components (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s)
static ParseElementComponentsData parse_element_components (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s, const db::NetlistSpiceReader::parameters_type &variables)
{
ParseElementComponentsData data;
delegate->parse_element_components (s, data.strings_nc (), data.parameters_nc ());
delegate->parse_element_components (s, data.strings_nc (), data.parameters_nc (), variables);
return data;
}
Class<ParseElementComponentsData> db_ParseElementComponentsData ("db", "ParseElementComponentsData",
gsi::method ("strings", &ParseElementComponentsData::strings,
"@brief Gets the string parameters\n"
"@brief Gets the (unnamed) string parameters\n"
"These parameters are typically net names or model name."
) +
gsi::method ("strings=", &ParseElementComponentsData::set_strings, gsi::arg ("list"),
"@brief Sets the string parameters\n"
"@brief Sets the (unnamed) string parameters\n"
) +
gsi::method ("parameters", &ParseElementComponentsData::parameters,
"@brief Gets the (named) numerical parameters\n"
"@brief Gets the (named) parameters\n"
"Named parameters are typically (but not neccessarily) numerical, like 'w=0.15u'."
) +
gsi::method ("parameters=", &ParseElementComponentsData::set_parameters, gsi::arg ("dict"),
"@brief Sets the (named) numerical parameters\n"
"@brief Sets the (named) parameters\n"
),
"@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element_components.\n"
"This is a structure with two members: 'strings' for the string arguments and 'parameters' for the "
"named numerical arguments.\n"
"named arguments.\n"
"\n"
"This helper class has been introduced in version 0.27.1.\n"
"This helper class has been introduced in version 0.27.1. Starting with version 0.28.6, named parameters can be string types too.\n"
);
Class<ParseElementData> db_ParseElementData ("db", "ParseElementData",
@ -2720,16 +2737,16 @@ Class<ParseElementData> db_ParseElementData ("db", "ParseElementData",
"@brief Sets the net names\n"
) +
gsi::method ("parameters", &ParseElementData::parameters,
"@brief Gets the (named) numerical parameters\n"
"@brief Gets the (named) parameters\n"
) +
gsi::method ("parameters=", &ParseElementData::set_parameters, gsi::arg ("dict"),
"@brief Sets the (named) numerical parameters\n"
"@brief Sets the (named) parameters\n"
),
"@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element.\n"
"This is a structure with four members: 'model_name' for the model name, 'value' for the default numerical value, 'net_names' for the net names and 'parameters' for the "
"named numerical parameters.\n"
"named parameters.\n"
"\n"
"This helper class has been introduced in version 0.27.1.\n"
"This helper class has been introduced in version 0.27.1. Starting with version 0.28.6, named parameters can be string types too.\n"
);
Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "NetlistSpiceReaderDelegate",
@ -2765,6 +2782,13 @@ Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "Netl
"\n"
"This method has been introduced in version 0.27.1\n"
) +
gsi::method ("variables", &NetlistSpiceReaderDelegateImpl::variables,
"@brief Gets the variables defined inside the SPICE file during execution of 'parse_element'\n"
"In order to evaluate formulas, this method allows accessing the variables that are "
"present during the execution of the SPICE reader.\n"
"\n"
"This method has been introduced in version 0.28.6."
) +
gsi::callback ("parse_element", &NetlistSpiceReaderDelegateImpl::parse_element_helper, &NetlistSpiceReaderDelegateImpl::cb_parse_element,
gsi::arg ("s"), gsi::arg ("element"),
"@brief Parses an element card\n"
@ -2795,27 +2819,33 @@ Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "Netl
"some known elements using the Spice writer's parameter conventions.\n"
"\n"
"The method must return true, if the element was was understood and false otherwise.\n"
"\n"
"Starting with version 0.28.6, the parameter values can be strings too."
) +
gsi::method ("error", &NetlistSpiceReaderDelegateImpl::error, gsi::arg ("msg"),
"@brief Issues an error with the given message.\n"
"Use this method to generate an error."
) +
gsi::method_ext ("value_from_string", &value_from_string, gsi::arg ("s"),
gsi::method_ext ("value_from_string", &value_from_string, gsi::arg ("s"), gsi::arg ("variables", db::NetlistSpiceReader::parameters_type (), "{}"),
"@brief Translates a string into a value\n"
"This function simplifies the implementation of SPICE readers by providing a translation of a unit-annotated string "
"into double values. For example, '1k' is translated to 1000.0. In addition, simple formula evaluation is supported, e.g "
"'(1+3)*2' is translated into 8.0.\n"
"\n"
"This method has been introduced in version 0.27.1\n"
"The variables dictionary defines named variables with the given values.\n"
"\n"
"This method has been introduced in version 0.27.1. The variables argument has been added in version 0.28.6.\n"
) +
gsi::method_ext ("parse_element_components", &parse_element_components, gsi::arg ("s"),
gsi::method_ext ("parse_element_components", &parse_element_components, gsi::arg ("s"), gsi::arg ("variables", db::NetlistSpiceReader::parameters_type (), "{}"),
"@brief Parses a string into string and parameter components.\n"
"This method is provided to simplify the implementation of 'parse_element'. It takes a string and splits it into "
"string arguments and parameter values. For example, 'a b c=6' renders two string arguments in 'nn' and one parameter ('C'->6.0). "
"It returns data \\ParseElementComponentsData object with the strings and parameters.\n"
"The parameter names are already translated to upper case.\n"
"\n"
"This method has been introduced in version 0.27.1\n"
"The variables dictionary defines named variables with the given values.\n"
"\n"
"This method has been introduced in version 0.27.1. The variables argument has been added in version 0.28.6.\n"
),
"@brief Provides a delegate for the SPICE reader for translating device statements\n"
"Supply a customized class to provide a specialized reading scheme for devices. "

View File

@ -21,6 +21,8 @@
*/
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderDelegate.h"
#include "dbNetlistSpiceReaderExpressionParser.h"
#include "dbNetlist.h"
#include "dbNetlistDeviceClasses.h"
@ -175,25 +177,41 @@ TEST(4_ReaderWithUnconnectedPins)
TEST(5_CircuitParameters)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader5.cir");
db::NetlistSpiceReader reader;
reader.set_strict (true);
try {
db::Netlist nl;
tl::InputStream is (path);
reader.read (is, nl);
// strict mode makes this sample fail
EXPECT_EQ (true, false);
} catch (...) {
// ..
}
db::Netlist nl;
reader.set_strict (false);
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit SUBCKT ($1=$1,'A[5]<1>'='A[5]<1>','V42(%)'='V42(%)',Z=Z,GND=GND,GND$1=GND$1);\n"
" subcircuit HVPMOS D_$1 ($1='V42(%)',$2=$3,$3=Z,$4=$1);\n"
" subcircuit HVPMOS D_$2 ($1='V42(%)',$2='A[5]<1>',$3=$3,$4=$1);\n"
" subcircuit HVNMOS D_$3 ($1=GND,$2=$3,$3=GND,$4=GND$1);\n"
" subcircuit HVNMOS D_$4 ($1=GND,$2=$3,$3=Z,$4=GND$1);\n"
" subcircuit HVNMOS D_$5 ($1=GND,$2='A[5]<1>',$3=$3,$4=GND$1);\n"
" subcircuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' D_$1 ($1='V42(%)',$2=$3,$3=Z,$4=$1);\n"
" subcircuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' D_$2 ($1='V42(%)',$2='A[5]<1>',$3=$3,$4=$1);\n"
" subcircuit 'HVNMOS(AD=0,AS=0,L=1.13,PD=6,PS=6,W=2.12)' D_$3 ($1=GND,$2=$3,$3=GND,$4=GND$1);\n"
" subcircuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.16,PS=1.16,W=0.4)' D_$4 ($1=GND,$2=$3,$3=Z,$4=GND$1);\n"
" subcircuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.76,PS=1.76,W=0.4)' D_$5 ($1=GND,$2='A[5]<1>',$3=$3,$4=GND$1);\n"
"end;\n"
"circuit HVPMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n"
"circuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit HVNMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n"
"circuit 'HVNMOS(AD=0,AS=0,L=1.13,PD=6,PS=6,W=2.12)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.16,PS=1.16,W=0.4)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.76,PS=1.76,W=0.4)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
);
}
@ -209,7 +227,7 @@ public:
return circuit_name == "HVNMOS" || circuit_name == "HVPMOS";
}
bool 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, double> &params)
bool 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)
{
if (element == "X") {
@ -235,9 +253,9 @@ public:
const std::vector<db::DeviceParameterDefinition> &td = cls->parameter_definitions ();
for (std::vector<db::DeviceParameterDefinition>::const_iterator i = td.begin (); i != td.end (); ++i) {
std::map<std::string, double>::const_iterator pi = params.find (i->name ());
auto pi = params.find (i->name ());
if (pi != params.end ()) {
device->set_parameter_value (i->id (), pi->second * 1.5);
device->set_parameter_value (i->id (), pi->second.to_double () * 1.5);
}
}
@ -262,6 +280,9 @@ TEST(6_ReaderWithDelegate)
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit .TOP ();\n"
" subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VDD=VDD,Z=Z,GND=VSS,GND$1=VSS);\n"
"end;\n"
"circuit SUBCKT ($1=$1,A=A,VDD=VDD,Z=Z,GND=GND,GND$1=GND$1);\n"
" device HVPMOS $1 (S=VDD,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);\n"
" device HVPMOS $2 (S=VDD,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);\n"
@ -270,9 +291,6 @@ TEST(6_ReaderWithDelegate)
" device HVNMOS $5 (S=GND,G=A,D=$3,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64);\n"
" device RES $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0);\n"
"end;\n"
"circuit .TOP ();\n"
" subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VDD=VDD,Z=Z,GND=VSS,GND$1=VSS);\n"
"end;\n"
);
}
@ -527,16 +545,16 @@ TEST(13_NoGlobalNetsIfNotUsed)
" subcircuit C3 '3' (VDD=VDD,GND=GND);\n"
" subcircuit C4 '4' (VDD=VDD,GND=GND);\n"
"end;\n"
"circuit C1 (VDD=VDD,GND=GND);\n"
" subcircuit FILLER_CAP '1' (VDD=VDD,GND=GND);\n"
" subcircuit DUMMY '2' ();\n"
"end;\n"
"circuit FILLER_CAP (VDD=VDD,GND=GND);\n"
" device NMOS '1' (S=GND,G=VDD,D=GND,B=GND) (L=10,W=10,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
"circuit DUMMY ();\n"
" device NMOS '1' (S=A,G=A,D=A,B=B) (L=1,W=1,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
"circuit C1 (VDD=VDD,GND=GND);\n"
" subcircuit FILLER_CAP '1' (VDD=VDD,GND=GND);\n"
" subcircuit DUMMY '2' ();\n"
"end;\n"
"circuit C2 ();\n"
" subcircuit DUMMY '1' ();\n"
"end;\n"
@ -578,15 +596,19 @@ TEST(15_ContinuationWithBlanks)
EXPECT_EQ (nl.to_string (),
"circuit SUBCKT ($1=$1,'A[5]<1>'='A[5]<1>','V42(%)'='V42(%)',Z=Z,GND=GND,GND$1=GND$1);\n"
" subcircuit HVPMOS D_$1 ($1='V42(%)',$2=$3,$3=Z,$4=$1);\n"
" subcircuit HVPMOS D_$2 ($1='V42(%)',$2='A[5]<1>',$3=$3,$4=$1);\n"
" subcircuit HVNMOS D_$3 ($1=GND,$2=$3,$3=GND,$4=GND$1);\n"
" subcircuit HVNMOS D_$4 ($1=GND,$2=$3,$3=Z,$4=GND$1);\n"
" subcircuit HVNMOS D_$5 ($1=GND,$2='A[5]<1>',$3=$3,$4=GND$1);\n"
" subcircuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' D_$1 ($1='V42(%)',$2=$3,$3=Z,$4=$1);\n"
" subcircuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' D_$2 ($1='V42(%)',$2='A[5]<1>',$3=$3,$4=$1);\n"
" subcircuit 'HVNMOS(AD=0,AS=0,L=1.13,PD=6,PS=6,W=2.12)' D_$3 ($1=GND,$2=$3,$3=GND,$4=GND$1);\n"
" subcircuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.16,PS=1.16,W=0.4)' D_$4 ($1=GND,$2=$3,$3=Z,$4=GND$1);\n"
" subcircuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.76,PS=1.76,W=0.4)' D_$5 ($1=GND,$2='A[5]<1>',$3=$3,$4=GND$1);\n"
"end;\n"
"circuit HVPMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n"
"circuit 'HVPMOS(AD=0.18,AS=0.18,L=0.2,PD=2.16,PS=2.16,W=1)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit HVNMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n"
"circuit 'HVNMOS(AD=0,AS=0,L=1.13,PD=6,PS=6,W=2.12)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.16,PS=1.16,W=0.4)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
"circuit 'HVNMOS(AD=0.19,AS=0.19,L=0.4,PD=1.76,PS=1.76,W=0.4)' ($1=$0,$2=$0,$3=$0,$4=$0);\n"
"end;\n"
);
}
@ -611,3 +633,198 @@ TEST(16_issue898)
"end;\n"
);
}
TEST(17_RecursiveExpansion)
{
db::Netlist nl, nl2;
{
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader17.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl);
}
EXPECT_EQ (nl.to_string (),
"circuit .TOP ();\n"
" subcircuit 'SUB1(L=0.15,W=1.5)' SUB1A (N1=A,N2=B,N3=C);\n"
" subcircuit 'SUB1(L=0.25,W=3)' SUB1B (N1=A,N2=B,N3=C);\n"
"end;\n"
"circuit 'SUB1(L=0.15,W=1.5)' (N1=N1,N2=N2,N3=N3);\n"
" subcircuit 'SUB2(L=0.15,M=1,W=1.5)' SUB2A (N1=N1,N2=N2,N3=N3);\n"
" subcircuit 'SUB2(L=0.15,M=2,W=1.5)' SUB2B (N1=N1,N2=N2,N3=N3);\n"
"end;\n"
"circuit 'SUB2(L=0.15,M=1,W=1.5)' (N1=N1,N2=N2,N3=N3);\n"
" device NMOS NMOS (S=N1,G=N2,D=N3,B=N1) (L=150000,W=1500000,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
"circuit 'SUB2(L=0.15,M=2,W=1.5)' (N1=N1,N2=N2,N3=N3);\n"
" device NMOS NMOS (S=N1,G=N2,D=N3,B=N1) (L=150000,W=3000000,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
"circuit 'SUB1(L=0.25,W=3)' (N1=N1,N2=N2,N3=N3);\n"
" subcircuit 'SUB2(L=0.25,M=1,W=3)' SUB2A (N1=N1,N2=N2,N3=N3);\n"
" subcircuit 'SUB2(L=0.25,M=2,W=3)' SUB2B (N1=N1,N2=N2,N3=N3);\n"
"end;\n"
"circuit 'SUB2(L=0.25,M=1,W=3)' (N1=N1,N2=N2,N3=N3);\n"
" device NMOS NMOS (S=N1,G=N2,D=N3,B=N1) (L=250000,W=3000000,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
"circuit 'SUB2(L=0.25,M=2,W=3)' (N1=N1,N2=N2,N3=N3);\n"
" device NMOS NMOS (S=N1,G=N2,D=N3,B=N1) (L=250000,W=6000000,AS=0,AD=0,PS=0,PD=0);\n"
"end;\n"
);
{
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader17b.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl2);
}
EXPECT_EQ (nl2.to_string (), nl.to_string ());
}
TEST(18_XSchemOutput)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader18.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit .TOP ();\n"
" subcircuit 'PMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' XPMOS (D=Q,G=I,S=VDD,B=VDD);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' XNMOS (D=Q,G=I,S=VSS,B=VSS);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' XDUMMY0 (D=VSS,G=VSS,S=VSS,B=VSS);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' XDUMMY1 (D=VSS,G=VSS,S=VSS,B=VSS);\n"
" subcircuit 'PMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' XDUMMY2 (D=VDD,G=VDD,S=VDD,B=VDD);\n"
" subcircuit 'PMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' XDUMMY3 (D=VDD,G=VDD,S=VDD,B=VDD);\n"
"end;\n"
"circuit 'PMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__PFET_01V8 M1 (S=D,G=G,D=S,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=2.685,PD=1.79);\n"
"end;\n"
"circuit 'NMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__NFET_01V8 M1 (S=D,G=G,D=S,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=2.685,PD=1.79);\n"
"end;\n"
"circuit 'NMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__NFET_01V8 M1 (S=D,G=G,D=S,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=3.58,PD=1.79);\n"
"end;\n"
"circuit 'PMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__PFET_01V8 M1 (S=D,G=G,D=S,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=3.58,PD=1.79);\n"
"end;\n"
);
}
TEST(100_ExpressionParser)
{
std::map<std::string, tl::Variant> vars;
vars["A"] = 17.5;
vars["B"] = 42;
vars["S"] = "string";
tl::Variant v;
db::NetlistSpiceReaderExpressionParser parser (&vars);
EXPECT_EQ (parser.read ("1.75").to_string (), "1.75");
EXPECT_EQ (parser.read ("-1.75").to_string (), "-1.75");
EXPECT_EQ (parser.read ("-a*0.1").to_string (), "-1.75");
EXPECT_EQ (parser.read ("-A*0.1").to_string (), "-1.75");
EXPECT_EQ (parser.read ("b/6").to_string (), "7");
EXPECT_EQ (parser.read ("B/6").to_string (), "7");
EXPECT_EQ (parser.read ("s").to_string (), "string");
EXPECT_EQ (parser.read ("S").to_string (), "string");
EXPECT_EQ (parser.read ("!0").to_string (), "true");
EXPECT_EQ (parser.read ("!1").to_string (), "false");
EXPECT_EQ (parser.read ("4*2+1").to_string (), "9");
EXPECT_EQ (parser.read ("4*2-1").to_string (), "7");
EXPECT_EQ (parser.read ("4/2-1").to_string (), "1");
EXPECT_EQ (parser.read ("4%2-1").to_string (), "-1");
EXPECT_EQ (parser.read ("5%2-1").to_string (), "0");
EXPECT_EQ (parser.read ("2**2*2+1").to_string (), "9");
EXPECT_EQ (parser.read ("2**2*(2+1)").to_string (), "12");
EXPECT_EQ (parser.read ("pow(2,2)*(2+1)").to_string (), "12");
EXPECT_EQ (parser.read ("POW(2,2)*(2+1)").to_string (), "12");
EXPECT_EQ (parser.read ("pwr(2,2)*(2+1)").to_string (), "12");
EXPECT_EQ (parser.read ("PWR(2,2)*(2+1)").to_string (), "12");
EXPECT_EQ (parser.read ("3==2+1").to_string (), "true");
EXPECT_EQ (parser.read ("4==2+1").to_string (), "false");
EXPECT_EQ (parser.read ("3!=2+1").to_string (), "false");
EXPECT_EQ (parser.read ("4!=2+1").to_string (), "true");
EXPECT_EQ (parser.read ("2<2+1").to_string (), "true");
EXPECT_EQ (parser.read ("3<2+1").to_string (), "false");
EXPECT_EQ (parser.read ("4<2+1").to_string (), "false");
EXPECT_EQ (parser.read ("2<=2+1").to_string (), "true");
EXPECT_EQ (parser.read ("3<=2+1").to_string (), "true");
EXPECT_EQ (parser.read ("4<=2+1").to_string (), "false");
EXPECT_EQ (parser.read ("2>2+1").to_string (), "false");
EXPECT_EQ (parser.read ("3>2+1").to_string (), "false");
EXPECT_EQ (parser.read ("4>2+1").to_string (), "true");
EXPECT_EQ (parser.read ("2>=2+1").to_string (), "false");
EXPECT_EQ (parser.read ("3>=2+1").to_string (), "true");
EXPECT_EQ (parser.read ("4>=2+1").to_string (), "true");
EXPECT_EQ (parser.read ("1==2||2==2").to_string (), "true");
EXPECT_EQ (parser.read ("1==2||3==2").to_string (), "false");
EXPECT_EQ (parser.read ("1==2&&2==2").to_string (), "false");
EXPECT_EQ (parser.read ("1==1&&2==2").to_string (), "true");
EXPECT_EQ (parser.read ("1==2?2:3").to_string (), "3");
EXPECT_EQ (parser.read ("ternery_fcn(1==2,2,3)").to_string (), "3");
EXPECT_EQ (parser.read ("1==1?2:3").to_string (), "2");
EXPECT_EQ (parser.read ("ternery_fcn(1==1,2,3)").to_string (), "2");
EXPECT_EQ (parser.read ("sin(0)").to_string (), "0");
EXPECT_EQ (parser.read ("sin(atan(1.0)*2)").to_string (), "1");
EXPECT_EQ (parser.read ("cos(0)").to_string (), "1");
EXPECT_EQ (parser.read ("cos(atan(1.0)*2)").to_string (), "0");
EXPECT_EQ (parser.read ("tan(0)").to_string (), "0");
EXPECT_EQ (parser.read ("tan(atan(1.0))").to_string (), "1");
EXPECT_EQ (parser.read ("sin(asin(0.5))").to_string (), "0.5");
EXPECT_EQ (parser.read ("cos(acos(0.5))").to_string (), "0.5");
EXPECT_EQ (parser.read ("ln(exp(0.5))").to_string (), "0.5");
EXPECT_EQ (parser.read ("exp(0.0)").to_string (), "1");
EXPECT_EQ (parser.read ("log(10**0.5)").to_string (), "0.5");
EXPECT_EQ (parser.read ("int(-0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("int(-1.5)").to_string (), "-1");
EXPECT_EQ (parser.read ("int(0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("int(1.5)").to_string (), "1");
EXPECT_EQ (parser.read ("floor(-0.5)").to_string (), "-1");
EXPECT_EQ (parser.read ("floor(-1.5)").to_string (), "-2");
EXPECT_EQ (parser.read ("floor(0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("floor(1.5)").to_string (), "1");
EXPECT_EQ (parser.read ("ceil(-0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("ceil(-1.5)").to_string (), "-1");
EXPECT_EQ (parser.read ("ceil(0.5)").to_string (), "1");
EXPECT_EQ (parser.read ("ceil(1.5)").to_string (), "2");
EXPECT_EQ (parser.read ("nint(-0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("nint(-1.5)").to_string (), "-2");
EXPECT_EQ (parser.read ("nint(0.5)").to_string (), "0");
EXPECT_EQ (parser.read ("nint(1.5)").to_string (), "2");
EXPECT_EQ (parser.read ("min(4,1,3)").to_string (), "1");
EXPECT_EQ (parser.read ("min(4,3)").to_string (), "3");
EXPECT_EQ (parser.read ("min(4)").to_string (), "4");
EXPECT_EQ (parser.read ("max(1,4,3)").to_string (), "4");
EXPECT_EQ (parser.read ("max(4,3)").to_string (), "4");
EXPECT_EQ (parser.read ("max(4)").to_string (), "4");
EXPECT_EQ (parser.read ("max(a,b)").to_string (), "42");
EXPECT_EQ (parser.try_read ("a syntax error", v), false);
v = tl::Variant ();
EXPECT_EQ (parser.try_read ("1+2*(2+1)-1", v), true);
EXPECT_EQ (v.to_string (), "6");
EXPECT_EQ (parser.try_read ("{1+2*(2+1)-1)", v), false);
EXPECT_EQ (parser.try_read ("'1+2*(2+1)-1)", v), false);
EXPECT_EQ (parser.try_read ("\"1+2*(2+1)-1)", v), false);
EXPECT_EQ (parser.try_read ("\"1+2*(2+1)-1'", v), false);
v = tl::Variant ();
EXPECT_EQ (parser.try_read ("{1+2*(2+1)-1}", v), true);
EXPECT_EQ (v.to_string (), "6");
v = tl::Variant ();
EXPECT_EQ (parser.try_read ("'1+2*(2+1)-1'", v), true);
EXPECT_EQ (v.to_string (), "6");
v = tl::Variant ();
EXPECT_EQ (parser.try_read ("\"1+2*(2+1)-1\"", v), true);
EXPECT_EQ (v.to_string (), "6");
}

View File

@ -62,7 +62,7 @@ static tl::PixelBuffer read_pixel_buffer (const std::string &file)
#if defined(HAVE_PNG)
tl::InputStream stream (file);
return tl::PixelBuffer::read_png (stream);
#elif defined(HAVE_QT) && defined(HAVE_QTBINDINGS)
#elif defined(HAVE_QT)
// QImage is fallback
QImage img;
img.load (tl::to_qstring (file), "PNG");
@ -80,7 +80,7 @@ static tl::PixelBuffer pixel_buffer_from_png (const std::vector<char> &data)
tl::InputMemoryStream data_stream (data.begin ().operator-> (), data.size ());
tl::InputStream stream (data_stream);
return tl::PixelBuffer::read_png (stream);
#elif defined(HAVE_QT) && defined(HAVE_QTBINDINGS)
#elif defined(HAVE_QT)
// QImage is fallback
tl_assert (data.size () < std::numeric_limits<int>::max ());
QImage img = QImage::fromData ((const uchar *) data.begin ().operator-> (), int (data.size ()));
@ -96,7 +96,7 @@ static void write_pixel_buffer (const tl::PixelBuffer *pb, const std::string &fi
#if defined(HAVE_PNG)
tl::OutputStream stream (file);
pb->write_png (stream);
#elif defined(HAVE_QT) && defined(HAVE_QTBINDINGS)
#elif defined(HAVE_QT)
// QImage is fallback
QImage img = pb->to_image ();
img.save (tl::to_qstring (file), "PNG");
@ -115,7 +115,7 @@ static std::vector<char> pixel_buffer_to_png (const tl::PixelBuffer *pb)
pb->write_png (stream);
}
return std::vector<char> (data_stream.data (), data_stream.data () + data_stream.size ());
#elif defined(HAVE_QT) && defined(HAVE_QTBINDINGS)
#elif defined(HAVE_QT)
// QImage is fallback
QImage img = pb->to_image ();
QBuffer data;

View File

@ -418,7 +418,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view)
mp_transient_view (0),
m_move_mode (Service::move_none),
m_moved_landmark (0),
m_keep_selection_for_landmark (false),
m_keep_selection_for_move (false),
m_images_visible (true)
{
// place images behind the grid
@ -623,7 +623,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang
m_move_mode = mm;
m_moved_landmark = ml;
m_keep_selection_for_landmark = true;
m_keep_selection_for_move = true;
// found a handle of one of the selected object: make the moved image the selection
clear_selection ();
@ -660,7 +660,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang
m_move_mode = mm;
m_moved_landmark = ml;
m_keep_selection_for_landmark = false;
m_keep_selection_for_move = false;
// found anything: make the moved image the selection
clear_selection ();
@ -893,7 +893,7 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type)
image_changed_event (id);
// clear the selection (that was artificially created before)
if (! m_keep_selection_for_landmark) {
if (! m_keep_selection_for_move) {
clear_selection ();
} else {
selection_to_view ();
@ -907,7 +907,11 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type)
image_changed_event (id);
// clear the selection (that was artificially created before)
clear_selection ();
if (! m_keep_selection_for_move) {
clear_selection ();
} else {
selection_to_view ();
}
}
@ -956,6 +960,7 @@ Service::find_image (const db::DPoint &p, const db::DBox &search_box, double l,
void
Service::selection_to_view (img::View::Mode mode)
{
clear_transient_selection ();
image_selection_changed_event ();
// the selection objects need to be recreated since we destroyed the old images
@ -1006,8 +1011,10 @@ Service::transform (const db::DCplxTrans &trans)
void
Service::edit_cancel ()
{
m_move_mode = move_none;
selection_to_view ();
if (m_move_mode != move_none) {
m_move_mode = move_none;
selection_to_view ();
}
}
void

View File

@ -506,7 +506,7 @@ private:
// The index of the landmark being moved
size_t m_moved_landmark;
// Flag indicating that we want to keep the selection after the landmark was moved
bool m_keep_selection_for_landmark;
bool m_keep_selection_for_move;
// Flag indicating whether images are visible
bool m_images_visible;

View File

@ -195,7 +195,7 @@ AnnotationShapes::replace (iterator pos, const shape_type &&sh)
manager ()->queue (this, new AnnotationLayerOp (true /*insert*/, sh));
}
invalidate_state (); // HINT: must come before the change is done!
m_layer.replace (pos, sh);
m_layer.replace (pos, std::move (sh));
}
return *pos;
}

View File

@ -318,7 +318,6 @@ Editables::transient_to_selection ()
{
bool had_transient_selection = false;
bool had_selection = false;
cancel_edits ();
for (iterator e = begin (); e != end (); ++e) {
if (e->has_selection ()) {
had_selection = true;

View File

@ -5305,6 +5305,7 @@ LayoutViewBase::paste_interactive ()
void
LayoutViewBase::copy ()
{
cancel_edits ();
if (! lay::Editables::has_selection ()) {
// try to use the transient selection for the real one
lay::Editables::transient_to_selection ();
@ -5316,6 +5317,7 @@ LayoutViewBase::copy ()
void
LayoutViewBase::cut ()
{
cancel_edits ();
if (! lay::Editables::has_selection ()) {
// try to use the transient selection for the real one
lay::Editables::transient_to_selection ();

View File

@ -590,6 +590,7 @@ LayoutView::show_properties ()
return;
}
cancel_edits ();
if (! has_selection ()) {
// try to use the transient selection for the real one
transient_to_selection ();

View File

@ -142,7 +142,7 @@ TEST(16_private)
TEST(17_private)
{
test_is_long_runner ();
run_test (_this, "test_17.lylvs", "test_17b.cir.gz", "test_17.gds.gz", true, "test_17b_3.lvsdb");
run_test (_this, "test_17.lylvs", "test_17b.cir.gz", "test_17.gds.gz", true, "test_17b_4.lvsdb");
}
TEST(18_private)
@ -165,7 +165,7 @@ TEST(20_private)
TEST(21_private)
{
run_test (_this, "test_21.lylvs", "test_21.cir.gz", "test_21.gds.gz", true, "test_21.lvsdb");
run_test (_this, "test_21.lylvs", "test_21.cir.gz", "test_21.gds.gz", true, "test_21_2.lvsdb");
}
// issue #1021

View File

@ -27,6 +27,7 @@
#include <string.h>
#include <limits>
#include <cmath>
#if defined(HAVE_QT)
@ -1054,6 +1055,18 @@ normalized_type (Variant::type type1, Variant::type type2)
}
}
static const double epsilon = 1e-13;
static inline bool fequal (double a, double b)
{
double avg = 0.5 * (fabs (a) + fabs (b));
return fabs (a - b) <= epsilon * avg;
}
static inline bool fless (double a, double b)
{
return fequal (a, b) ? false : a < b;
}
bool
Variant::operator== (const tl::Variant &d) const
@ -1083,7 +1096,7 @@ Variant::operator== (const tl::Variant &d) const
} else if (t == t_id) {
return m_var.m_id == d.m_var.m_id;
} else if (t == t_double) {
return to_double () == d.to_double ();
return fequal (to_double (), d.to_double ());
} else if (t == t_string) {
return strcmp (to_string (), d.to_string ()) == 0;
} else if (t == t_bytearray) {
@ -1139,7 +1152,7 @@ Variant::operator< (const tl::Variant &d) const
} else if (t == t_id) {
return m_var.m_id < d.m_var.m_id;
} else if (t == t_double) {
return to_double () < d.to_double ();
return fless (to_double (), d.to_double ());
} else if (t == t_string) {
return strcmp (to_string (), d.to_string ()) < 0;
} else if (t == t_bytearray) {

View File

@ -1059,7 +1059,35 @@ TEST(5)
EXPECT_EQ (m [" 3"], 0);
}
// fuzzy compare of doubles
TEST(6)
{
volatile double a = 10.0;
EXPECT_EQ (tl::Variant (0.0) == tl::Variant (0.0), true);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (1.0 / a), true);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (0.1 * (1.0 + 1e-14)), true);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (0.1 * (1.0 + 0.9e-13)), true);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (0.1 * (1.0 + 1.1e-13)), false);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (0.1 * (1.0 + 1e-12)), false);
EXPECT_EQ (tl::Variant (-0.1) == tl::Variant (-0.1 * (1.0 + 0.9e-13)), true);
EXPECT_EQ (tl::Variant (-0.1) == tl::Variant (-0.1 * (1.0 + 1.1e-13)), false);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (-0.1 * (1.0 + 0.9e-13)), false);
EXPECT_EQ (tl::Variant (0.1) == tl::Variant (-0.1 * (1.0 + 1.1e-13)), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (1.0 / a), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (0.1 * (1.0 + 1e-14)), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (0.1 * (1.0 + 0.9e-13)), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (0.1 * (1.0 + 1.1e-13)), true);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (0.1 * (1.0 + 1e-12)), true);
EXPECT_EQ (tl::Variant (-0.1) < tl::Variant (-0.1 * (1.0 + 0.9e-13)), false);
EXPECT_EQ (tl::Variant (-0.1) < tl::Variant (-0.1 * (1.0 + 1.1e-13)), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (-0.1 * (1.0 + 0.9e-13)), false);
EXPECT_EQ (tl::Variant (0.1) < tl::Variant (-0.1 * (1.0 + 1.1e-13)), false);
EXPECT_EQ (tl::Variant (-0.1 * (1.0 + 0.9e-13)) < tl::Variant (-0.1), false);
EXPECT_EQ (tl::Variant (-0.1 * (1.0 + 1.1e-13)) < tl::Variant (-0.1), true);
EXPECT_EQ (tl::Variant (-0.1 * (1.0 + 0.9e-13)) < tl::Variant (0.1), true);
EXPECT_EQ (tl::Variant (-0.1 * (1.0 + 1.1e-13)) < tl::Variant (0.1), true);
}
}

View File

@ -497,6 +497,37 @@ reference(
pin(5 1)
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
circuit(RINGO
@ -567,37 +598,6 @@ reference(
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
)
# Cross reference

View File

@ -497,6 +497,37 @@ reference(
pin(5 1)
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
circuit(RINGO
@ -567,37 +598,6 @@ reference(
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
)
# Cross reference

View File

@ -497,6 +497,37 @@ reference(
pin(5 1)
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
circuit(RINGO
@ -567,37 +598,6 @@ reference(
)
)
circuit(INV2PAIRX
# Nets
net(1 name('1'))
net(2 name('2'))
net(3 name('3'))
net(4 name('4'))
net(5 name('5'))
net(6 name('6'))
net(7 name('7'))
# Outgoing pins and their connections to nets
pin(1 name('1'))
pin(2 name('2'))
pin(3 name('3'))
pin(4 name('4'))
pin(5 name('5'))
pin(6 name('6'))
pin(7 name('7'))
# Subcircuits and their connections
circuit(1 INV2 name($2)
pin(0 7)
pin(1 4)
pin(2 6)
pin(3 3)
pin(4 2)
pin(5 1)
)
)
)
# Cross reference

15
testdata/algo/nreader17.cir vendored Normal file
View File

@ -0,0 +1,15 @@
* recursive expansion of parametrized subcircuits
Xsub1a a b c sub1 w=1.5 l=0.15
Xsub1b a b c sub1 w=3.0 l=0.25
.subckt sub1 n1 n2 n3 w=1.0 l=0.5
Xsub2a n1 n2 n3 sub2 w l m=1
Xsub2b n1 n2 n3 sub2 w l m=2
.ends
.subckt sub2 n1 n2 n3 w=0.0 l=0.0 m=0
Mnmos n1 n2 n3 n1 nmos w=w l=l m=m
.ends

24
testdata/algo/nreader17b.cir vendored Normal file
View File

@ -0,0 +1,24 @@
* recursive expansion of parametrized subcircuits
.param w1 1.5 w2 {w1*2}
.param l1 0.15 l2 'l1+0.1'
Xsub1a a b c sub1 w=w1 l=l1
Xsub1b a b c sub1 w=w2 l=l2
.subckt sub1 n1 n2 n3 w l
* declares w and l parameters instead of nodes:
w = 1.0
l = 1.0
.param w1 = w
.param l1 = l
Xsub2a n1 n2 n3 sub2 w=w1 l=l1 m=1
.param w2 "w+0.0" l2 l*(0.5+0.5)
Xsub2b n1 n2 n3 sub2 w=w2 l=l2 m=2
.ends
.subckt sub2 n1 n2 n3 w=0.0 l=0.0 m=0
Mnmos n1 n2 n3 n1 nmos w=w l=l m=m
.ends

59
testdata/algo/nreader18.cir vendored Normal file
View File

@ -0,0 +1,59 @@
** sch_path:
*+ /home/matthias/dev/bag/mosaic_bag/opensource_db_template/inverter1_generated_sky130_fd_pr/inverter1/inverter1.sch
**.subckt inverter1 I Q VDD VSS
*.ipin I
*.opin Q
*.iopin VDD
*.iopin VSS
XXpmos Q I VDD VDD pmos4_standard w=1.5u l=150n nf=4
XXnmos Q I VSS VSS nmos4_standard w=1.5u l=150n nf=4
XXDUMMY0 VSS VSS VSS VSS nmos4_standard w=1.5u l=150n nf=2
XXDUMMY1 VSS VSS VSS VSS nmos4_standard w=1.5u l=150n nf=2
XXDUMMY2 VDD VDD VDD VDD pmos4_standard w=1.5u l=150n nf=2
XXDUMMY3 VDD VDD VDD VDD pmos4_standard w=1.5u l=150n nf=2
**.ends
* expanding symbol: BAG_prim/pmos4_standard/pmos4_standard.sym # of pins=4
** sym_path:
*+ /home/matthias/dev/bag/mosaic_bag/opensource_db_template/BAG2_technology_definition/BAG_prim/pmos4_standard/pmos4_standard.sym
** sch_path:
*+ /home/matthias/dev/bag/mosaic_bag/opensource_db_template/BAG2_technology_definition/BAG_prim/pmos4_standard/pmos4_standard.sch
.subckt pmos4_standard D G S B model w l nf
w=100n
l=18n
nf=4
model=pmos4_standard
spiceprefix=X
*.iopin D
*.iopin G
*.iopin S
*.iopin B
MM1 D G S B sky130_fd_pr__pfet_01v8 L=l W=w ad='int((nf+1)/2) * W/nf * 0.29u' as='int((nf+2)/2) * W/nf * 0.29u'
+ pd='2*int((nf+1)/2) * (W + 0.29u)/nf' ps='2*int((nf+2)/2) * (W + 0.29u)/nf' nrd='0.29u / W' nrs='0.29u / W'
+ sa=0 sb=0 sd=0 mult=1 m=nf
.ends
* expanding symbol: BAG_prim/nmos4_standard/nmos4_standard.sym # of pins=4
** sym_path:
*+ /home/matthias/dev/bag/mosaic_bag/opensource_db_template/BAG2_technology_definition/BAG_prim/nmos4_standard/nmos4_standard.sym
** sch_path:
*+ /home/matthias/dev/bag/mosaic_bag/opensource_db_template/BAG2_technology_definition/BAG_prim/nmos4_standard/nmos4_standard.sch
.subckt nmos4_standard D G S B model w l nf
w=100n
l=18n
nf=4
model=nmos4_standard
spiceprefix=X
*.iopin D
*.iopin G
*.iopin S
*.iopin B
MM1 D G S B sky130_fd_pr__nfet_01v8 L=l W=w ad='int((nf+1)/2) * W/nf * 0.29u' as='int((nf+2)/2) * W/nf * 0.29u'
+ pd='2*int((nf+1)/2) * (W + 0.29u)/nf' ps='2*int((nf+2)/2) * (W + 0.29u)/nf' nrd='0.29u / W' nrs='0.29u / W'
+ sa=0 sb=0 sd=0 mult=1 m=nf
.ends
.end

View File

@ -609,8 +609,8 @@ layout(
reference(
# Device class section
class(NMOS MOS4)
class(PMOS MOS4)
class(NMOS MOS4)
# Circuit section
# Circuits are the hierarchical building blocks of the netlist.

View File

@ -116,6 +116,9 @@ class DBNetlistReaderTests_TestClass < TestBase
assert_equal(nl.description, "Read by MyDelegate (sucessfully)")
assert_equal(nl.to_s, <<"END")
circuit .TOP ();
subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VDD=VDD,Z=Z,GND=VSS,GND$1=VSS);
end;
circuit SUBCKT ($1=$1,A=A,VDD=VDD,Z=Z,GND=GND,GND$1=GND$1);
device HVPMOS $1 (S=VDD,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
device HVPMOS $2 (S=VDD,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
@ -124,9 +127,6 @@ circuit SUBCKT ($1=$1,A=A,VDD=VDD,Z=Z,GND=GND,GND$1=GND$1);
device HVNMOS $5 (S=GND,G=A,D=$3,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64);
device RES $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0);
end;
circuit .TOP ();
subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VDD=VDD,Z=Z,GND=VSS,GND$1=VSS);
end;
END
end
@ -147,6 +147,9 @@ END
assert_equal(nl.description, "Read by MyDelegate2 (sucessfully)")
assert_equal(nl.to_s, <<"END")
circuit .TOP ();
subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VXX=VXX,Z=Z,GND=VSS,GND$1=VSS);
end;
circuit SUBCKT ($1=$1,A=A,VXX=VXX,Z=Z,GND=GND,GND$1=GND$1);
device HVPMOS $1 (S=VXX,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
device HVPMOS $2 (S=VXX,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
@ -155,9 +158,6 @@ circuit SUBCKT ($1=$1,A=A,VXX=VXX,Z=Z,GND=GND,GND$1=GND$1);
device HVNMOS $5 (S=GND,G=A,D=$3,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64);
device WIDERSTAND $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0);
end;
circuit .TOP ();
subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VXX=VXX,Z=Z,GND=VSS,GND$1=VSS);
end;
END
end
@ -212,7 +212,7 @@ END
pd.net_names = [ "x", "y", "z" ]
assert_equal(pd.net_names.join(","), "x,y,z")
pd.parameters = { "A" => 17.5, "B" => 1 }
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}")
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1}")
end
@ -222,7 +222,7 @@ END
pd.strings = [ "x", "y", "z" ]
assert_equal(pd.strings.join(","), "x,y,z")
pd.parameters = { "A" => 17.5, "B" => 1 }
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}")
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1}")
end
@ -230,7 +230,7 @@ END
dg = RBA::NetlistSpiceReaderDelegate::new
pd = dg.parse_element_components("17 5 1e-9 a=17 b=1k")
assert_equal(pd.strings.join(","), "17,5,1e-9")
assert_equal(pd.strings.join(","), "17,5,1E-9")
assert_equal(pd.parameters.inspect, "{\"A\"=>17.0, \"B\"=>1000.0}")
end

View File

@ -139,25 +139,16 @@ class LAYPixelBuffer_TestClass < TestBase
assert_equal(compare(pb, pb_copy), true)
end
png = nil
begin
png = pb.to_png_data
rescue => ex
# No PNG support
end
png = pb.to_png_data
if png
assert_equal(png.size > 20 && png.size < 200, true) # some range because implementations may differ
pb_copy = RBA::PixelBuffer.from_png_data(png)
assert_equal(compare(pb, pb_copy), true)
assert_equal(png.size > 20 && png.size < 200, true) # some range because implementations may differ
pb_copy = RBA::PixelBuffer.from_png_data(png)
assert_equal(compare(pb, pb_copy), true)
tmp = File::join($ut_testtmp, "tmp.png")
pb.write_png(tmp)
pb_copy = RBA::PixelBuffer.read_png(tmp)
assert_equal(compare(pb, pb_copy), true)
end
tmp = File::join($ut_testtmp, "tmp.png")
pb.write_png(tmp)
pb_copy = RBA::PixelBuffer.read_png(tmp)
assert_equal(compare(pb, pb_copy), true)
end