This commit is contained in:
Matthias Koefferlein 2023-02-22 09:48:08 +01:00
parent 715c7ed282
commit 51c4b7ed28
9 changed files with 1344 additions and 1147 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -26,50 +26,13 @@
#include "dbCommon.h"
#include "dbNetlistReader.h"
#include "tlStream.h"
#include <string>
#include <set>
#include <map>
#include <memory>
#include <list>
#include "tlObject.h"
namespace db
{
class Netlist;
class Net;
class Circuit;
class DeviceClass;
class Device;
/**
* @brief A class implementing the expression parser
*
* This class is exposed mainly for testing purposes.
*/
class DB_PUBLIC SpiceExpressionParser
{
public:
typedef std::map<std::string, tl::Variant> variables_type;
SpiceExpressionParser (const variables_type *vars);
tl::Variant read (tl::Extractor &ex) const;
bool try_read (tl::Extractor &ex, tl::Variant &v) const;
private:
const variables_type *mp_variables;
tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const;
tl::Variant read_dot_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_bar_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_pwr_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_compare_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_logical_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_ternary_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_tl_expr (tl::Extractor &ex, bool *status) const;
tl::Variant eval_func (const std::string &name, const std::vector<tl::Variant> &params, bool *status) const;
};
class NetlistSpiceReaderDelegate;
/**
* @brief A specialized exception class to handle netlist reader delegate errors
@ -83,103 +46,6 @@ public:
{ }
};
/**
* @brief A delegate to handle various forms of devices and translates them
*
* The reader delegate can be configured to receive subcircuit elements too.
* In this case, parameters are allowed.
* For receiving subcircuit elements, the delegate needs to indicate
* this by returning true upon "wants_subcircuit".
*/
class DB_PUBLIC NetlistSpiceReaderDelegate
: public tl::Object
{
public:
NetlistSpiceReaderDelegate ();
virtual ~NetlistSpiceReaderDelegate ();
/**
* @brief Called when the netlist reading starts
*/
virtual void start (db::Netlist *netlist);
/**
* @brief Called when the netlist reading ends
*/
virtual void finish (db::Netlist *netlist);
/**
* @brief Called when an unknown control statement is encountered
*
* Returns true if the statement is understood.
*/
virtual bool control_statement (const std::string &line);
/**
* @brief Returns true, if the delegate wants subcircuit elements with this name
*
* The name is always upper case.
*/
virtual bool wants_subcircuit (const std::string &circuit_name);
/**
* @brief This method translates a raw net name to a valid net name
*
* The default implementation will unescape backslash sequences into plain characters.
*/
virtual std::string translate_net_name (const std::string &nn);
/**
* @brief Makes a device from an element line
*
* @param circuit The circuit that is currently read.
* @param element The upper-case element code ("M", "R", ...).
* @param name The element's name.
* @param model The upper-case model name (may be empty).
* @param value The default value (e.g. resistance for resistors) and may be zero.
* @param nets The nets given in the element line.
* @param parameters The parameters of the element statement (parameter names are upper case).
*
* The default implementation will create corresponding devices for
* some known elements using the Spice writer's parameter conventions.
*
* This method returns true, if the element was read.
*/
virtual bool element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> &params);
/**
* @brief Parses an element from a line
*
* @param s The line to parse (the part after the element and name)
* @param model Out parameter: the model name if given
* @param value Out parameter: the value if given (for R, L, C)
* @param nn Out parameter: the net names
* @param pv Out parameter: the parameter values (key/value pairs)
*/
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv, const std::map<std::string, double> &params);
/**
* @brief Produces an error with the given message
*/
virtual void error (const std::string &msg);
/**
* @brief Reads a set of string components and parameters from the string
* A special key "param:" is recognized for starting a parameter list.
*/
static void parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, double> &pv, const std::map<std::string, double> &variables);
/**
* @brief Reads a value from the extractor (with formula evaluation)
*/
static double read_value (tl::Extractor &ex, const std::map<std::string, double> &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, double> &variables);
};
/**
* @brief A SPICE format reader for netlists
*/

View File

@ -0,0 +1,561 @@
/*
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 ()
{
// .. 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 NetlistSpiceReaderDelegateError (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, double> &pv, const std::map<std::string, double> &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. Note that parameter names are always made upper case.
pv.insert (std::make_pair (tl::to_upper_case (n), read_value (ex, variables)));
} else {
ex = ex0;
if (in_params) {
ex.error (tl::to_string (tr ("Invalid syntax for parameter assignment - needs keyword followed by '='")));
}
strings.push_back (parse_component (ex));
}
}
}
}
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, double> &pv, const std::map<std::string, double> &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")));
}
std::map<std::string, double>::const_iterator rv = pv.find (element);
if (rv != pv.end ()) {
// value given by parameter
value = rv->second;
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, double> &pv)
{
std::map<std::string, double> params = pv;
double mult = 1.0;
std::map<std::string, double>::const_iterator mp = params.find ("M");
if (mp != params.end ()) {
mult = mp->second;
}
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"
std::map<std::string, double>::iterator p;
p = params.find ("A");
if (p != params.end ()) {
p->second *= 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"
std::map<std::string, double>::iterator p;
p = params.find ("AE");
if (p != params.end ()) {
p->second *= 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"
std::map<std::string, double>::iterator p;
p = params.find ("W");
if (p != params.end ()) {
p->second *= 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) {
std::map<std::string, double>::const_iterator v = params.find (i->name ());
if (v != params.end ()) {
device->set_parameter_value (i->id (), v->second / i->si_scaling ());
} else if (i->id () == defp) {
device->set_parameter_value (i->id (), value / i->si_scaling ());
}
}
return true;
}
double
NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map<std::string, double> &variables)
{
std::map<std::string, tl::Variant> vvariables;
for (auto i = variables.begin (); i != variables.end (); ++i) {
vvariables.insert (std::make_pair (i->first, tl::Variant (i->second)));
}
NetlistSpiceReaderExpressionParser parser (&vvariables);
return parser.read (ex).to_double ();
}
bool
NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &v, const std::map<std::string, double> &variables)
{
std::map<std::string, tl::Variant> vvariables;
for (auto i = variables.begin (); i != variables.end (); ++i) {
vvariables.insert (std::make_pair (i->first, tl::Variant (i->second)));
}
NetlistSpiceReaderExpressionParser parser (&vvariables);
tl::Variant vv;
tl::Extractor ex (s.c_str ());
bool res = parser.try_read (ex, vv);
if (res) {
v = vv.to_double ();
}
return res;
}
}

View File

@ -0,0 +1,142 @@
/*
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, double> &params);
/**
* @brief Parses an element from a line
*
* @param s The line to parse (the part after the element and name)
* @param model Out parameter: the model name if given
* @param value Out parameter: the value if given (for R, L, C)
* @param nn Out parameter: the net names
* @param pv Out parameter: the parameter values (key/value pairs)
*/
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv, const std::map<std::string, double> &params);
/**
* @brief Produces an error with the given message
*/
virtual void error (const std::string &msg);
/**
* @brief Reads a set of string components and parameters from the string
* A special key "param:" is recognized for starting a parameter list.
*/
static void parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, double> &pv, const std::map<std::string, double> &variables);
/**
* @brief Reads a value from the extractor (with formula evaluation)
*/
static double read_value (tl::Extractor &ex, const std::map<std::string, double> &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, double> &variables);
};
}
#endif

View File

@ -0,0 +1,534 @@
/*
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
{
// ------------------------------------------------------------------------------------------------------
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 round (v); }
static double floor_f (double v) { return floor (v); }
static double ceil_f (double v) { return ceil (v); }
static double sgn_f (double v) { return v == 0.0 ? 0.0 : (v < 0.0 ? -1.0 : 1.0); }
static double int_f (double v) { return sgn_f (v) * floor (sgn_f (v) * v); }
tl::Variant
NetlistSpiceReaderExpressionParser::eval_func (const std::string &name, const std::vector<tl::Variant> &params, bool * /*status*/) const
{
double (*f) (double) = 0;
if (name == "sqrt") { f = sqrt_f; } else
if (name == "sin") { f = sin_f; } else
if (name == "cos") { f = cos_f; } else
if (name == "tan") { f = tan_f; } else
if (name == "sinh") { f = sinh_f; } else
if (name == "cosh") { f = cosh_f; } else
if (name == "tanh") { f = tanh_f; } else
if (name == "asin") { f = asin_f; } else
if (name == "acos") { f = acos_f; } else
if (name == "atan" || name == "arctan") { f = atan_f; } else
if (name == "asinh") { f = asinh_f; } else
if (name == "acosh") { f = acosh_f; } else
if (name == "atanh") { f = atanh_f; } else
if (name == "exp") { f = exp_f; } else
if (name == "ln") { f = ln_f; } else
if (name == "log") { f = log_f; } else
if (name == "abs") { f = abs_f; } else
if (name == "nint") { f = nint_f; } else
if (name == "floor") { f = floor_f; } else
if (name == "ceil") { f = ceil_f; } else
if (name == "sgn") { f = sgn_f; } else
if (name == "int") { f = int_f; }
if (f != 0) {
if (params.size () < 1 || ! params.front ().can_convert_to_double ()) {
return tl::Variant ();
} else {
return tl::Variant ((*f) (params.front ().to_double ()));
}
} else if (name == "pwr" || name == "pow") {
if (params.size () < 2 || ! params [0].can_convert_to_double () || ! params [1].can_convert_to_double ()) {
return tl::Variant ();
} else {
return tl::Variant (pow (params [0].to_double (), params [1].to_double ()));
}
} else if (name == "ternary_fcn") {
if (params.size () < 3) {
return tl::Variant ();
} else {
return params [0].to_bool () ? 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 (! v.to_bool ());
} 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)) {
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 (tl::to_upper_case (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 (v.to_bool () && vv.to_bool ());
} else if (ex.test ("||")) {
tl::Variant vv = read_compare_expr (ex, status);
if (status && !*status) {
return tl::Variant ();
}
v = tl::Variant (v.to_bool () && vv.to_bool ());
} 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 = v.to_bool () ? 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 (tl::Extractor &ex) const
{
try {
tl::Variant res;
const char *endquote = start_quote (ex);
res = read_tl_expr (ex, 0);
if (endquote) {
ex.test (endquote);
}
return res;
} catch (tl::Exception &error) {
throw NetlistSpiceReaderDelegateError (error.msg ());
}
}
bool NetlistSpiceReaderExpressionParser::try_read (tl::Extractor &ex, tl::Variant &value) const
{
tl::Extractor ex_saved = ex;
bool status = false;
const char *endquote = start_quote (ex);
value = read_tl_expr (ex, &status);
if (endquote && ! ex.test (endquote)) {
status = false;
}
if (! status) {
value = tl::Variant ();
ex = ex_saved;
}
return status;
}
}

View File

@ -0,0 +1,68 @@
/*
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;
bool try_read (tl::Extractor &ex, tl::Variant &v) const;
private:
const variables_type *mp_variables;
tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const;
tl::Variant read_dot_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_bar_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_pwr_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_compare_expr (tl::Extractor &ex, bool *status) const;
tl::Variant read_logical_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_ternary_op (tl::Extractor &ex, bool *status) const;
tl::Variant read_tl_expr (tl::Extractor &ex, bool *status) const;
tl::Variant eval_func (const std::string &name, const std::vector<tl::Variant> &params, bool *status) const;
};
}
#endif

View File

@ -26,6 +26,7 @@
#include "dbNetlistSpiceWriter.h"
#include "dbNetlistReader.h"
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderDelegate.h"
#include "tlException.h"
#include "tlInternational.h"
#include "tlStream.h"

View File

@ -21,6 +21,7 @@
*/
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderDelegate.h"
#include "dbNetlist.h"
#include "dbNetlistDeviceClasses.h"