From 51c4b7ed282b2ecb08c558854acbebc9aa0639d6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 22 Feb 2023 09:48:08 +0100 Subject: [PATCH] WIP --- src/db/db/db.pro | 4 + src/db/db/dbNetlistSpiceReader.cc | 1042 +---------------- src/db/db/dbNetlistSpiceReader.h | 138 +-- src/db/db/dbNetlistSpiceReaderDelegate.cc | 561 +++++++++ src/db/db/dbNetlistSpiceReaderDelegate.h | 142 +++ .../dbNetlistSpiceReaderExpressionParser.cc | 534 +++++++++ .../db/dbNetlistSpiceReaderExpressionParser.h | 68 ++ src/db/db/gsiDeclDbNetlist.cc | 1 + src/db/unit_tests/dbNetlistReaderTests.cc | 1 + 9 files changed, 1344 insertions(+), 1147 deletions(-) create mode 100644 src/db/db/dbNetlistSpiceReaderDelegate.cc create mode 100644 src/db/db/dbNetlistSpiceReaderDelegate.h create mode 100644 src/db/db/dbNetlistSpiceReaderExpressionParser.cc create mode 100644 src/db/db/dbNetlistSpiceReaderExpressionParser.h diff --git a/src/db/db/db.pro b/src/db/db/db.pro index a65043b30..066dbb5d2 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -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 \ diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 40be09997..539bc4f0e 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -21,1029 +21,29 @@ */ #include "dbNetlistSpiceReader.h" +#include "dbNetlistSpiceReaderExpressionParser.h" +#include "dbNetlistSpiceReaderDelegate.h" #include "dbNetlist.h" -#include "dbNetlistDeviceClasses.h" -#include "tlStream.h" -#include "tlLog.h" -#include "tlString.h" -#include "tlFileUtils.h" #include "tlUri.h" -#include "tlTimer.h" +#include "tlFileUtils.h" #include "tlLog.h" +#include "tlTimer.h" -#include -#include +#include +#include +#include +#include +#include +#include namespace db { -// ------------------------------------------------------------------------------------------------------ - -SpiceExpressionParser::SpiceExpressionParser (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 -SpiceExpressionParser::eval_func (const std::string &name, const std::vector ¶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 == "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 -SpiceExpressionParser::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 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 SpiceExpressionParser::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 SpiceExpressionParser::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 SpiceExpressionParser::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 SpiceExpressionParser::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 SpiceExpressionParser::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 SpiceExpressionParser::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 SpiceExpressionParser::read_tl_expr (tl::Extractor &ex, bool *status) const -{ - return read_ternary_op (ex, status); -} - -tl::Variant SpiceExpressionParser::read (tl::Extractor &ex) const -{ - try { - return read_tl_expr (ex, 0); - } catch (tl::Exception &error) { - throw NetlistSpiceReaderDelegateError (error.msg ()); - } -} - -bool SpiceExpressionParser::try_read (tl::Extractor &ex, tl::Variant &value) const -{ - tl::Extractor ex_saved = ex; - - bool status = false; - value = read_tl_expr (ex, &status); - if (! status) { - ex = ex_saved; - } - - return status; -} - - // ------------------------------------------------------------------------------------------------------ static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>"; -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 -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 &strings, std::map &pv, const std::map &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 &nn, std::map &pv, const std::map &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::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 &nets, const std::map &pv) -{ - std::map params = pv; - - double mult = 1.0; - std::map::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(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 (circuit, cn); - } - } else if (nets.size () == 3) { - if (cls) { - if (! dynamic_cast(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 (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(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 (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(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 (circuit, cn); - } - } else if (nets.size () == 3) { - if (cls) { - if (! dynamic_cast(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 (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(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 (circuit, cn); - } - - // Apply multiplier to "A" - std::map::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(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(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 (circuit, cn); - } else { - if (cn.empty ()) { - cn = "BJT4"; - } - cls = make_device_class (circuit, cn); - } - } - - // Apply multiplier to "AE" - std::map::iterator p; - p = params.find ("AE"); - if (p != params.end ()) { - p->second *= mult; - } - - } else if (element == "M") { - - if (cls) { - if (! dynamic_cast(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 (circuit, cn); - } else { - error (tl::to_string (tr ("'M' element needs to have 4 terminals"))); - } - } - - // Apply multiplier to "W" - std::map::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 &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::const_iterator t = td.begin (); t != td.end (); ++t) { - device->connect_terminal (t->id (), nets [t - td.begin ()]); - } - - size_t defp = std::numeric_limits::max (); - if (dynamic_cast (cls)) { - defp = db::DeviceClassCapacitor::param_id_C; - } else if (dynamic_cast (cls)) { - defp = db::DeviceClassResistor::param_id_R; - } else if (dynamic_cast (cls)) { - defp = db::DeviceClassInductor::param_id_L; - } - - std::vector &pd = cls->parameter_definitions_non_const (); - for (std::vector::iterator i = pd.begin (); i != pd.end (); ++i) { - std::map::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 &variables) -{ - std::map vvariables; - for (auto i = variables.begin (); i != variables.end (); ++i) { - vvariables.insert (std::make_pair (i->first, tl::Variant (i->second))); - } - - SpiceExpressionParser parser (&vvariables); - return parser.read (ex).to_double (); -} - -bool -NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &v, const std::map &variables) -{ - std::map vvariables; - for (auto i = variables.begin (); i != variables.end (); ++i) { - vvariables.insert (std::make_pair (i->first, tl::Variant (i->second))); - } - - SpiceExpressionParser 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; -} - // ------------------------------------------------------------------------------------------------------ class SpiceReaderStream @@ -1181,6 +181,7 @@ SpiceReaderStream::set_stream (tl::InputStream *stream) // ------------------------------------------------------------------------------------------------------ +// @@@ remove struct ParametersLessFunction { typedef std::map parameters_type; @@ -1198,7 +199,7 @@ struct ParametersLessFunction return ia->first < ib->first; } double avg = 0.5 * fabs (ia->second + ib->second); - if (fabs (ia->second - ib->second) > avg * db::epsilon) { + if (fabs (ia->second - ib->second) > avg * 1e-13) { return ia->second < ib->second; } ++ia, ++ib; @@ -1627,6 +628,25 @@ SpiceCircuitDict::read_card () // ignore end statements + } else if (ex.test_without_case ("param")) { + + // Syntax is: + // .param = [ = ... ] + // taken from: + // https://nmg.gitlab.io/ngspice-manual/circuitdescription/paramparametricnetlists/paramline.html + + while (! ex.at_end ()) { + + std::string name; + ex.read_word (name); + + ex.test ("="); + + tl::Variant value = NetlistSpiceReaderExpressionParser (&m_variables).read (ex); + m_variables [name] = value; + + } + } else if (! mp_delegate->control_statement (l)) { std::string s; diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 3d33728a9..fc32afc30 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -26,50 +26,13 @@ #include "dbCommon.h" #include "dbNetlistReader.h" #include "tlStream.h" - -#include -#include -#include -#include -#include +#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 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 ¶ms, 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 &nets, const std::map ¶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 &nn, std::map &pv, const std::map ¶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. - */ - static void parse_element_components (const std::string &s, std::vector &strings, std::map &pv, const std::map &variables); - - /** - * @brief Reads a value from the extractor (with formula evaluation) - */ - static double read_value (tl::Extractor &ex, const std::map &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 &variables); -}; - /** * @brief A SPICE format reader for netlists */ diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc new file mode 100644 index 000000000..dea8b6306 --- /dev/null +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -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 +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 &strings, std::map &pv, const std::map &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 &nn, std::map &pv, const std::map &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::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 &nets, const std::map &pv) +{ + std::map params = pv; + + double mult = 1.0; + std::map::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(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 (circuit, cn); + } + } else if (nets.size () == 3) { + if (cls) { + if (! dynamic_cast(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 (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(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 (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(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 (circuit, cn); + } + } else if (nets.size () == 3) { + if (cls) { + if (! dynamic_cast(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 (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(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 (circuit, cn); + } + + // Apply multiplier to "A" + std::map::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(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(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 (circuit, cn); + } else { + if (cn.empty ()) { + cn = "BJT4"; + } + cls = make_device_class (circuit, cn); + } + } + + // Apply multiplier to "AE" + std::map::iterator p; + p = params.find ("AE"); + if (p != params.end ()) { + p->second *= mult; + } + + } else if (element == "M") { + + if (cls) { + if (! dynamic_cast(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 (circuit, cn); + } else { + error (tl::to_string (tr ("'M' element needs to have 4 terminals"))); + } + } + + // Apply multiplier to "W" + std::map::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 &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::const_iterator t = td.begin (); t != td.end (); ++t) { + device->connect_terminal (t->id (), nets [t - td.begin ()]); + } + + size_t defp = std::numeric_limits::max (); + if (dynamic_cast (cls)) { + defp = db::DeviceClassCapacitor::param_id_C; + } else if (dynamic_cast (cls)) { + defp = db::DeviceClassResistor::param_id_R; + } else if (dynamic_cast (cls)) { + defp = db::DeviceClassInductor::param_id_L; + } + + std::vector &pd = cls->parameter_definitions_non_const (); + for (std::vector::iterator i = pd.begin (); i != pd.end (); ++i) { + std::map::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 &variables) +{ + std::map 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 &variables) +{ + std::map 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; +} + +} diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.h b/src/db/db/dbNetlistSpiceReaderDelegate.h new file mode 100644 index 000000000..eb014b43b --- /dev/null +++ b/src/db/db/dbNetlistSpiceReaderDelegate.h @@ -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 +#include +#include + +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 &nets, const std::map ¶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 &nn, std::map &pv, const std::map ¶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. + */ + static void parse_element_components (const std::string &s, std::vector &strings, std::map &pv, const std::map &variables); + + /** + * @brief Reads a value from the extractor (with formula evaluation) + */ + static double read_value (tl::Extractor &ex, const std::map &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 &variables); +}; + +} + +#endif diff --git a/src/db/db/dbNetlistSpiceReaderExpressionParser.cc b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc new file mode 100644 index 000000000..788a54475 --- /dev/null +++ b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc @@ -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 + +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 ¶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 == "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 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; +} + +} diff --git a/src/db/db/dbNetlistSpiceReaderExpressionParser.h b/src/db/db/dbNetlistSpiceReaderExpressionParser.h new file mode 100644 index 000000000..a8d28d0c0 --- /dev/null +++ b/src/db/db/dbNetlistSpiceReaderExpressionParser.h @@ -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 +#include +#include + +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 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 ¶ms, bool *status) const; +}; +} + +#endif diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 13a3e3f35..73641470b 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -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" diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index 19197bf4d..0c6fb8968 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -21,6 +21,7 @@ */ #include "dbNetlistSpiceReader.h" +#include "dbNetlistSpiceReaderDelegate.h" #include "dbNetlist.h" #include "dbNetlistDeviceClasses.h"