mirror of https://github.com/KLayout/klayout.git
Merge branch 'more-spice' into issue-1304
This commit is contained in:
commit
ebcf242a9e
|
|
@ -133,6 +133,12 @@ Object::Object (const ant::Object &d)
|
|||
// .. nothing else ..
|
||||
}
|
||||
|
||||
Object::~Object ()
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
|
||||
Object &
|
||||
Object::operator= (const ant::Object &d)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -137,6 +137,11 @@ public:
|
|||
*/
|
||||
Object &operator= (const ant::Object &d);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~Object ();
|
||||
|
||||
/**
|
||||
* @brief Less operator
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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> ¶ms);
|
||||
|
||||
/**
|
||||
* @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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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> ¶ms);
|
||||
|
||||
/**
|
||||
* @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> ¶ms);
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -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> ¶ms, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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> ¶ms, bool *status) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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> ¶meters () const { return m_parameters; }
|
||||
std::map<std::string, double> ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const std::map<std::string, double> ¶meters) { m_parameters = parameters; }
|
||||
const db::NetlistSpiceReader::parameters_type ¶meters () const { return m_parameters; }
|
||||
db::NetlistSpiceReader::parameters_type ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const db::NetlistSpiceReader::parameters_type ¶meters) { 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> ¶meters () const { return m_parameters; }
|
||||
std::map<std::string, double> ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const std::map<std::string, double> ¶meters) { m_parameters = parameters; }
|
||||
const db::NetlistSpiceReader::parameters_type ¶meters () const { return m_parameters; }
|
||||
db::NetlistSpiceReader::parameters_type ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const db::NetlistSpiceReader::parameters_type ¶meters) { 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> ¶ms)
|
||||
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 ¶ms)
|
||||
{
|
||||
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> ¶ms)
|
||||
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 ¶ms)
|
||||
{
|
||||
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. "
|
||||
|
|
|
|||
|
|
@ -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> ¶ms)
|
||||
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> ¶ms)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue