From 4eb8f69a22cea3f95027dbca3ed015253b3fbdbc Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 13 May 2021 20:40:28 +0200 Subject: [PATCH] Spice reader: Support for resistance, capacitance and inductance values within parameters, basic support for 3-terminal resistors, more flexibility in SpiceReaderDelegate. --- src/db/db/dbNetlistSpiceReader.cc | 692 +++++++++++++++----------- src/db/db/dbNetlistSpiceReader.h | 52 +- src/db/db/gsiDeclDbNetlist.cc | 281 ++++++++++- testdata/ruby/dbNetlistReaderTests.rb | 103 ++++ 4 files changed, 828 insertions(+), 300 deletions(-) diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 8431400ba..bfc644f03 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -40,6 +40,62 @@ namespace db // ------------------------------------------------------------------------------------------------------ +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 .. @@ -60,11 +116,21 @@ void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/) // .. nothing yet .. } +bool NetlistSpiceReaderDelegate::control_statement(const std::string & /*line*/) +{ + return false; +} + bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit_name*/) { return false; } +std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &nn) +{ + return unescape_name (nn); +} + void NetlistSpiceReaderDelegate::error (const std::string &msg) { throw tl::Exception (msg); @@ -87,6 +153,270 @@ static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::stri 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) +{ + 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))); + } 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)); + } + + } + + } +} + +double NetlistSpiceReaderDelegate::read_atomic_value (tl::Extractor &ex) +{ + if (ex.test ("(")) { + + double v = read_dot_expr (ex); + ex.expect (")"); + return v; + + } else { + + double v = 0.0; + ex.read (v); + + 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; + } + + v *= f; + return v; + + } +} + +double NetlistSpiceReaderDelegate::read_bar_expr (tl::Extractor &ex) +{ + double v = read_atomic_value (ex); + while (true) { + if (ex.test ("+")) { + double vv = read_atomic_value (ex); + v += vv; + } else if (ex.test ("+")) { + double vv = read_atomic_value (ex); + v -= vv; + } else { + break; + } + } + return v; +} + +double NetlistSpiceReaderDelegate::read_dot_expr (tl::Extractor &ex) +{ + double v = read_bar_expr (ex); + while (true) { + if (ex.test ("*")) { + double vv = read_bar_expr (ex); + v *= vv; + } else if (ex.test ("/")) { + double vv = read_bar_expr (ex); + v /= vv; + } else { + break; + } + } + return v; +} + +double NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex) +{ + return read_dot_expr (ex); +} + +bool NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &value) +{ + tl::Extractor ve (s.c_str ()); + double vv = 0; + if (ve.try_read (vv) || ve.test ("(")) { + ve = tl::Extractor (s.c_str ()); + value = read_value (ve); + return true; + } else { + return false; + } +} + +void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector &nn, std::map &pv) +{ + parse_element_components (s, nn, pv); + + // 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)) { + + // 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)) { + 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; @@ -106,15 +436,30 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin if (element == "R") { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a resistor device class as required by 'R' element")), cn)); + 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 = "RES"; + } + cls = make_device_class (circuit, cn); } } else { - if (cn.empty ()) { - cn = "RES"; - } - cls = make_device_class (circuit, cn); + error (tl::to_string (tr ("A 'R' element requires two or three nets"))); } // Apply multiplier @@ -122,15 +467,19 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } else if (element == "L") { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a inductor device class as required by 'L' element")), cn)); + 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 { - if (cn.empty ()) { - cn = "IND"; - } - cls = make_device_class (circuit, cn); + error (tl::to_string (tr ("A 'L' element requires two nets"))); } // Apply multiplier @@ -138,15 +487,30 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } else if (element == "C") { - if (cls) { - if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a capacitor device class as required by 'C' element")), cn)); + 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 = "CAP"; + } + cls = make_device_class (circuit, cn); } } else { - if (cn.empty ()) { - cn = "CAP"; - } - cls = make_device_class (circuit, cn); + error (tl::to_string (tr ("A 'C' element requires two or three nets"))); } // Apply multiplier @@ -156,7 +520,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin if (cls) { if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a diode device class as required by 'D' element")), cn)); + 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 ()) { @@ -179,11 +543,11 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } else if (cls) { if (nets.size () == 3) { if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a 3-terminal BJT device class as required by 'Q' element")), cn)); + 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 not a 4-terminal BJT device class as required by 'Q' element")), cn)); + error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal BJT device class as required by 'Q' element")), cn)); } } } else { @@ -211,7 +575,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin if (cls) { if (! dynamic_cast(cls)) { - error (tl::sprintf (tl::to_string (tr ("Class %s not a 4-terminal MOS device class as required by 'M' element")), cn)); + 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) { @@ -271,8 +635,6 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin // ------------------------------------------------------------------------------------------------------ -static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>"; - NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate) : mp_netlist (0), mp_stream (), mp_delegate (delegate) { @@ -450,7 +812,8 @@ std::string NetlistSpiceReader::get_line () tl::Extractor ex (l.c_str ()); if (ex.test_without_case (".include") || ex.test_without_case (".inc")) { - std::string path = read_name_with_case (ex); + std::string path; + ex.read_word_or_quoted (path, allowed_name_chars); push_stream (path); @@ -504,7 +867,7 @@ bool NetlistSpiceReader::read_card () } else if (ex.test_without_case ("global")) { while (! ex.at_end ()) { - std::string n = read_name (ex); + std::string n = mp_delegate->translate_net_name (read_name (ex)); if (m_global_net_names.find (n) == m_global_net_names.end ()) { m_global_nets.push_back (n); m_global_net_names.insert (n); @@ -528,7 +891,7 @@ bool NetlistSpiceReader::read_card () // ignore end statements - } else { + } else if (! mp_delegate->control_statement (l)) { std::string s; ex.read_word (s); @@ -551,8 +914,6 @@ bool NetlistSpiceReader::read_card () warn (tl::sprintf (tl::to_string (tr ("Element type '%c' ignored")), next_char)); } - ex.expect_end (); - } else { warn (tl::to_string (tr ("Line ignored"))); } @@ -571,91 +932,6 @@ void NetlistSpiceReader::warn (const std::string &msg) tl::warn << fmt_msg; } -double NetlistSpiceReader::read_atomic_value (tl::Extractor &ex) -{ - if (ex.test ("(")) { - - double v = read_dot_expr (ex); - ex.expect (")"); - return v; - - } else { - - double v = 0.0; - ex.read (v); - - 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; - } - - v *= f; - return v; - - } -} - -double NetlistSpiceReader::read_bar_expr (tl::Extractor &ex) -{ - double v = read_atomic_value (ex); - while (true) { - if (ex.test ("+")) { - double vv = read_atomic_value (ex); - v += vv; - } else if (ex.test ("+")) { - double vv = read_atomic_value (ex); - v -= vv; - } else { - break; - } - } - return v; -} - -double NetlistSpiceReader::read_dot_expr (tl::Extractor &ex) -{ - double v = read_atomic_value (ex); - while (true) { - if (ex.test ("*")) { - double vv = read_atomic_value (ex); - v *= vv; - } else if (ex.test ("/")) { - double vv = read_atomic_value (ex); - v /= vv; - } else { - break; - } - } - return v; -} - -double NetlistSpiceReader::read_value (tl::Extractor &ex) -{ - return read_dot_expr (ex); -} - void NetlistSpiceReader::ensure_circuit () { if (! mp_circuit) { @@ -693,92 +969,11 @@ db::Net *NetlistSpiceReader::make_net (const std::string &name) return net; } -void NetlistSpiceReader::read_pin_and_parameters (tl::Extractor &ex, std::vector &nn, std::map &pv) -{ - bool in_params = false; - - while (! ex.at_end ()) { - - if (ex.test_without_case ("params:")) { - - in_params = true; - - } else { - - std::string n = read_name (ex); - - if (ex.test ("=")) { - // a parameter - pv.insert (std::make_pair (n, read_value (ex))); - } else { - if (in_params) { - error (tl::to_string (tr ("Missing '=' in parameter assignment"))); - } - nn.push_back (n); - } - - } - - } -} - -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; - } -} - -std::string NetlistSpiceReader::read_name_with_case (tl::Extractor &ex) +std::string NetlistSpiceReader::read_name (tl::Extractor &ex) { std::string n; ex.read_word_or_quoted (n, allowed_name_chars); - - 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; -} - -std::string NetlistSpiceReader::read_name (tl::Extractor &ex) -{ - return mp_netlist->normalize_name (read_name_with_case (ex)); + return mp_netlist->normalize_name (n); } bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &element, const std::string &name) @@ -786,99 +981,16 @@ bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &ele // generic parse std::vector nn; std::map pv; - std::string model; double value = 0.0; - // interpret the parameters according to the code - if (element == "X") { + mp_delegate->parse_element (ex.skip (), element, model, value, nn, pv); - // subcircuit call: - // Xname n1 n2 ... nn circuit [params] - - read_pin_and_parameters (ex, nn, pv); - - 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 value model [params] - // Rname n1 n2 model [params] - // (same for C, L instead of R) - - while (! ex.at_end () && nn.size () < 2) { - nn.push_back (read_name (ex)); - } - - if (nn.size () != 2) { - error (tl::to_string (tr ("Two-terminal device needs two nets"))); - } - - tl::Extractor ve (ex); - double vv = 0.0; - if (ve.try_read (vv) || ve.test ("(")) { - value = read_value (ex); - } - - while (! ex.at_end ()) { - std::string n = read_name (ex); - if (ex.test ("=")) { - pv [n] = read_value (ex); - } else if (! model.empty ()) { - error (tl::sprintf (tl::to_string (tr ("Too many arguments for two-terminal device (additional argumen is '%s')")), n)); - } else { - model = n; - } - } - - } else { - - // others: n-terminal devices with a model (last node) - - while (! ex.at_end ()) { - std::string n = read_name (ex); - if (ex.test ("=")) { - pv [n] = read_value (ex); - } else { - nn.push_back (n); - } - } - - 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? - - } + model = mp_netlist->normalize_name (model); std::vector nets; for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { - nets.push_back (make_net (*i)); + nets.push_back (make_net (mp_delegate->translate_net_name (mp_netlist->normalize_name (*i)))); } if (element == "X" && ! subcircuit_captured (model)) { @@ -946,7 +1058,11 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex, const std::string &nc) { std::vector nn; std::map pv; - read_pin_and_parameters (ex, nn, pv); + mp_delegate->parse_element_components (ex.skip (), nn, pv); + + for (std::vector::iterator i = nn.begin (); i != nn.end (); ++i) { + *i = mp_delegate->translate_net_name (mp_netlist->normalize_name (*i)); + } if (! pv.empty ()) { warn (tl::to_string (tr ("Circuit parameters are not allowed currently"))); @@ -999,8 +1115,6 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex, const std::string &nc) mp_nets_by_name.reset (n2n.release ()); std::swap (cc, mp_circuit); - - ex.expect_end (); } } diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 5af74b90e..034b47f44 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -78,6 +78,13 @@ public: */ 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 * @@ -85,6 +92,13 @@ public: */ 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 * @@ -103,10 +117,42 @@ public: */ 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); + /** * @brief Produces an error with the given message */ virtual void error (const std::string &msg); + + /** + * @brief Reads a set of string components and parameters from the string + * A special key "param:" is recognized for starting a parameter list. + */ + void parse_element_components (const std::string &s, std::vector &strings, std::map &pv); + + /** + * @brief Reads a value from the extractor (with formula evaluation) + */ + double read_value (tl::Extractor &ex); + + /** + * @brief Tries to read a value from the extractor (with formula evaluation) + */ + bool try_read_value (const std::string &s, double &v); + +private: + double read_atomic_value (tl::Extractor &ex); + double read_dot_expr (tl::Extractor &ex); + double read_bar_expr (tl::Extractor &ex); }; /** @@ -138,18 +184,12 @@ private: void push_stream (const std::string &path); void pop_stream (); bool at_end (); - void read_pin_and_parameters (tl::Extractor &ex, std::vector &nn, std::map &pv); bool read_element (tl::Extractor &ex, const std::string &element, const std::string &name); void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector &nets); void read_circuit (tl::Extractor &ex, const std::string &name); void skip_circuit (tl::Extractor &ex); bool read_card (); - double read_value (tl::Extractor &ex); - std::string read_name_with_case (tl::Extractor &ex); std::string read_name (tl::Extractor &ex); - double read_atomic_value (tl::Extractor &ex); - double read_dot_expr (tl::Extractor &ex); - double read_bar_expr (tl::Extractor &ex); std::string get_line (); void unget_line (const std::string &l); void error (const std::string &msg); diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index fa4e05969..fba0d99f0 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -2122,6 +2122,54 @@ Class db_NetlistReader ("db", "NetlistReader", "@hide\n" ); +/** + * @brief A helper class wrapping the return values for NetlistSpiceReaderDelegateImpl::parse_element + */ +class ParseElementData +{ +public: + ParseElementData () : m_value (0.0) { } + + const std::string &model_name () const { return m_model; } + std::string &model_name_nc () { return m_model; } + void set_model_name (const std::string &model) { m_model = model; } + double value () const { return m_value; } + double &value_nc () { return m_value; } + void set_value (double value) { m_value = value; } + const std::vector &net_names () const { return m_net_names; } + std::vector &net_names_nc () { return m_net_names; } + void set_net_names (const std::vector &nn) { m_net_names = nn; } + const std::map ¶meters () const { return m_parameters; } + std::map ¶meters_nc () { return m_parameters; } + void set_parameters (const std::map ¶meters) { m_parameters = parameters; } + +private: + std::string m_model; + double m_value; + std::vector m_net_names; + std::map m_parameters; +}; + +/** + * @brief A helper class for the return values of NetlistSpiceReaderDelegateImpl::parse_element_components + */ +class ParseElementComponentsData +{ +public: + ParseElementComponentsData () { } + + const std::vector &strings () const { return m_strings; } + std::vector &strings_nc () { return m_strings; } + void set_strings (const std::vector &nn) { m_strings = nn; } + const std::map ¶meters () const { return m_parameters; } + std::map ¶meters_nc () { return m_parameters; } + void set_parameters (const std::map ¶meters) { m_parameters = parameters; } + +private: + std::vector m_strings; + std::map m_parameters; +}; + /** * @brief A SPICE reader delegate base class for reimplementation */ @@ -2180,6 +2228,25 @@ public: } } + virtual bool control_statement (const std::string &line) + { + try { + m_error.clear (); + if (cb_control_statement.can_issue ()) { + return cb_control_statement.issue (&db::NetlistSpiceReaderDelegate::control_statement, line); + } else { + return db::NetlistSpiceReaderDelegate::control_statement (line); + } + } catch (tl::Exception &) { + if (! m_error.empty ()) { + db::NetlistSpiceReaderDelegate::error (m_error); + } else { + throw; + } + return false; + } + } + virtual bool wants_subcircuit (const std::string &circuit_name) { try { @@ -2199,6 +2266,59 @@ public: } } + virtual std::string translate_net_name (const std::string &nn) + { + try { + m_error.clear (); + if (cb_translate_net_name.can_issue ()) { + return cb_translate_net_name.issue (&db::NetlistSpiceReaderDelegate::translate_net_name, nn); + } else { + return db::NetlistSpiceReaderDelegate::translate_net_name (nn); + } + } catch (tl::Exception &) { + if (! m_error.empty ()) { + db::NetlistSpiceReaderDelegate::error (m_error); + } else { + throw; + } + return std::string (); + } + } + + ParseElementData parse_element_helper (const std::string &s, const std::string &element) + { + ParseElementData data; + db::NetlistSpiceReaderDelegate::parse_element (s, element, data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc ()); + return data; + } + + virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector &nn, std::map &pv) + { + try { + + m_error.clear (); + + ParseElementData data; + if (cb_parse_element.can_issue ()) { + data = cb_parse_element.issue (&NetlistSpiceReaderDelegateImpl::parse_element_helper, s, element); + } else { + data = parse_element_helper (s, element); + } + + model = data.model_name (); + value = data.value (); + nn = data.net_names (); + pv = data.parameters (); + + } catch (tl::Exception &) { + if (! m_error.empty ()) { + db::NetlistSpiceReaderDelegate::error (m_error); + } else { + throw; + } + } + } + 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) { try { @@ -2220,38 +2340,128 @@ public: gsi::Callback cb_start; gsi::Callback cb_finish; + gsi::Callback cb_control_statement; gsi::Callback cb_wants_subcircuit; + gsi::Callback cb_translate_net_name; gsi::Callback cb_element; + gsi::Callback cb_parse_element; private: std::string m_error; }; -static void start_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist) +static void start_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Netlist *netlist) { delegate->db::NetlistSpiceReaderDelegate::start (netlist); } -static void finish_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist) +static void finish_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Netlist *netlist) { delegate->db::NetlistSpiceReaderDelegate::finish (netlist); } -static bool wants_subcircuit_fb (db::NetlistSpiceReaderDelegate *delegate, const std::string &model) +static bool wants_subcircuit_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &model) { return delegate->db::NetlistSpiceReaderDelegate::wants_subcircuit (model); } -static bool element_fb (db::NetlistSpiceReaderDelegate *delegate, 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) +static bool control_statement_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &line) +{ + return delegate->db::NetlistSpiceReaderDelegate::control_statement (line); +} + +static std::string translate_net_name_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &name) +{ + return delegate->db::NetlistSpiceReaderDelegate::translate_net_name (name); +} + +static bool element_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector &nets, const std::map ¶ms) { return delegate->db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params); } +static ParseElementData parse_element_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s, const std::string &element) +{ + return delegate->parse_element_helper (s, element); +} + +static tl::Variant value_from_string (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s) +{ + tl::Variant res; + double v = 0.0; + if (delegate->try_read_value (s, v)) { + res = v; + } + return res; +} + +static ParseElementComponentsData parse_element_components (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s) +{ + ParseElementComponentsData data; + delegate->parse_element_components (s, data.strings_nc (), data.parameters_nc ()); + return data; +} + +Class db_ParseElementComponentsData ("db", "ParseElementComponentsData", + gsi::method ("strings", &ParseElementComponentsData::strings, + "@brief Gets the string parameters\n" + ) + + gsi::method ("strings=", &ParseElementComponentsData::set_strings, gsi::arg ("list"), + "@brief Sets the string parameters\n" + ) + + gsi::method ("parameters", &ParseElementComponentsData::parameters, + "@brief Gets the (named) numerical parameters\n" + ) + + gsi::method ("parameters=", &ParseElementComponentsData::set_parameters, gsi::arg ("dict"), + "@brief Sets the (named) numerical parameters\n" + ), + "@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element_components.\n" + "This is a structure with two members: 'strings' for the string arguments and 'parameters' for the " + "named numerical arguments.\n" + "\n" + "This helper class has been introduced in version 0.27.1.\n" +); + +Class db_ParseElementData ("db", "ParseElementData", + gsi::method ("value", &ParseElementData::value, + "@brief Gets the value\n" + ) + + gsi::method ("value=", &ParseElementData::set_value, gsi::arg ("v"), + "@brief Sets the value\n" + ) + + gsi::method ("model_name", &ParseElementData::model_name, + "@brief Gets the model name\n" + ) + + gsi::method ("model_name=", &ParseElementData::set_model_name, gsi::arg ("m"), + "@brief Sets the model name\n" + ) + + gsi::method ("net_names", &ParseElementData::net_names, + "@brief Gets the net names\n" + ) + + gsi::method ("net_names=", &ParseElementData::set_net_names, gsi::arg ("list"), + "@brief Sets the net names\n" + ) + + gsi::method ("parameters", &ParseElementData::parameters, + "@brief Gets the (named) numerical parameters\n" + ) + + gsi::method ("parameters=", &ParseElementData::set_parameters, gsi::arg ("dict"), + "@brief Sets the (named) numerical parameters\n" + ), + "@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element.\n" + "This is a structure with four members: 'model_name' for the model name, 'value' for the default numerical value, 'net_names' for the net names and 'parameters' for the " + "named numerical parameters.\n" + "\n" + "This helper class has been introduced in version 0.27.1.\n" +); + Class db_NetlistSpiceReaderDelegate ("db", "NetlistSpiceReaderDelegate", gsi::method_ext ("start", &start_fb, "@hide") + gsi::method_ext ("finish", &finish_fb, "@hide") + gsi::method_ext ("wants_subcircuit", &wants_subcircuit_fb, "@hide") + gsi::method_ext ("element", &element_fb, "@hide") + + gsi::method_ext ("parse_element", &parse_element_fb, "@hide") + + gsi::method_ext ("control_statement", &control_statement_fb, "@hide") + + gsi::method_ext ("translate_net_name", &translate_net_name_fb, "@hide") + gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"), "@brief This method is called when the reader starts reading a netlist\n" ) + @@ -2262,6 +2472,36 @@ Class db_NetlistSpiceReaderDelegate ("db", "Netl "@brief Returns true, if the delegate wants subcircuit elements with this name\n" "The name is always upper case.\n" ) + + gsi::callback ("control_statement", &NetlistSpiceReaderDelegateImpl::control_statement, &NetlistSpiceReaderDelegateImpl::cb_control_statement, gsi::arg ("line"), + "@brief Receives control statements not understood by the standard reader\n" + "When the reader encounters a control statement not understood by the parser, it will pass the line to the delegate using this method.\n" + "The delegate can decide if it wants to read this statement. It should return true in this case.\n" + "\n" + "This method has been introduced in version 0.27.1\n" + ) + + gsi::callback ("translate_net_name", &NetlistSpiceReaderDelegateImpl::translate_net_name, &NetlistSpiceReaderDelegateImpl::cb_translate_net_name, gsi::arg ("net_name"), + "@brief Translates a net name from the raw net name to the true net name\n" + "The default implementation will replace backslash sequences by the corresponding character.\n" + "'translate_net_name' is called before a net name is turned into a net object.\n" + "The method can be reimplemented to supply a different translation scheme for net names. For example, to translate special characters.\n" + "\n" + "This method has been introduced in version 0.27.1\n" + ) + + gsi::callback ("parse_element", &NetlistSpiceReaderDelegateImpl::parse_element_helper, &NetlistSpiceReaderDelegateImpl::cb_parse_element, + gsi::arg ("s"), gsi::arg ("element"), + "@brief Parses an element card\n" + "@param s The specification part of the element line (the part after element code and name).\n" + "@param element The upper-case element code (\"M\", \"R\", ...).\n" + "@return A \\ParseElementData object with the parts of the element.\n" + "\n" + "This method receives a string with the element specification and the element code. It is supposed to " + "parse the element line and return a model name, a value, a list of net names and a parameter value dictionary.\n" + "\n" + "'parse_element' is called one every element card. The results of this call go into the \\element method " + "to actually create the device. This method can be reimplemented to support other flavors of SPICE.\n" + "\n" + "This method has been introduced in version 0.27.1\n" + ) + gsi::callback ("element", &NetlistSpiceReaderDelegateImpl::element, &NetlistSpiceReaderDelegateImpl::cb_element, gsi::arg ("circuit"), gsi::arg ("element"), gsi::arg ("name"), gsi::arg ("model"), gsi::arg ("value"), gsi::arg ("nets"), gsi::arg ("parameters"), "@brief Makes a device from an element line\n" @@ -2281,6 +2521,23 @@ Class db_NetlistSpiceReaderDelegate ("db", "Netl gsi::method ("error", &NetlistSpiceReaderDelegateImpl::error, gsi::arg ("msg"), "@brief Issues an error with the given message.\n" "Use this method to generate an error." + ) + + gsi::method_ext ("value_from_string", &value_from_string, gsi::arg ("s"), + "@brief Translates a string into a value\n" + "This function simplifies the implementation of SPICE readers by providing a translation of a unit-annotated string " + "into double values. For example, '1k' is translated to 1000.0. In addition, simple formula evaluation is supported, e.g " + "'(1+3)*2' is translated into 8.0.\n" + "\n" + "This method has been introduced in version 0.27.1\n" + ) + + gsi::method_ext ("parse_element_components", &parse_element_components, gsi::arg ("s"), + "@brief Parses a string into string and parameter components.\n" + "This method is provided for simplifying the implementation of 'parse_element'. It takes a string and splits it into " + "string arguments and parameter values. For example, 'a b c=6' renders two string arguments in 'nn' and one parameter in pv ('C'->6.0). " + "It returns data \\ParseElementComponentsData object with the strings and parameters.\n" + "The parameter names are already translated to upper case.\n" + "\n" + "This method has been introduced in version 0.27.1\n" ), "@brief Provides a delegate for the SPICE reader for translating device statements\n" "Supply a customized class to provide a specialized reading scheme for devices. " @@ -2396,7 +2653,21 @@ Class db_NetlistSpiceReader (db_NetlistReader, "db", "Ne "nl.read(input_file, reader)\n" "@/code\n" "\n" - "This class has been introduced in version 0.26." + "A somewhat contrived example for using the delegate to translate net names is this:\n" + "\n" + "@code\n" + "class MyDelegate < RBA::NetlistSpiceReaderDelegate\n" + "\n" + " # translates 'VDD' to 'VXX' and leave all other net names as is:\n" + " alias translate_net_name_org translate_net_name\n" + " def translate_net_name(n)\n" + " return n == \"VDD\" ? \"VXX\" : translate_net_name_org(n)}\n" + " end\n" + "\n" + "end\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.26. It has been extended in version 0.27.1." ); } diff --git a/testdata/ruby/dbNetlistReaderTests.rb b/testdata/ruby/dbNetlistReaderTests.rb index 463764f0d..d6d2c7b63 100644 --- a/testdata/ruby/dbNetlistReaderTests.rb +++ b/testdata/ruby/dbNetlistReaderTests.rb @@ -74,6 +74,34 @@ class MyNetlistSpiceReaderDelegate < RBA::NetlistSpiceReaderDelegate end +class MyNetlistSpiceReaderDelegate2 < MyNetlistSpiceReaderDelegate + + def start(netlist) + netlist.description = "Read by MyDelegate2" + end + + def finish(netlist) + netlist.description = "Read by MyDelegate2 (sucessfully)" + end + + alias translate_net_name_org translate_net_name + + def translate_net_name(n) + return n == "VDD" ? "VXX" : translate_net_name_org(n) + end + + alias parse_element_org parse_element + + def parse_element(s, element) + data = parse_element_org(s, element) + if element == "R" + data.model_name = "WIDERSTAND" + end + data + end + +end + class DBNetlistReaderTests_TestClass < TestBase def test_1_Basic @@ -107,6 +135,37 @@ END end + def test_1b_Basic + + nl = RBA::Netlist::new + + input = File.join($ut_testsrc, "testdata", "algo", "nreader6.cir") + + mydelegate = MyNetlistSpiceReaderDelegate2::new + reader = RBA::NetlistSpiceReader::new(mydelegate) + # the delegate is kept by the SPICE writer .. + mydelegate = nil + GC.start + nl.read(input, reader) + + assert_equal(nl.description, "Read by MyDelegate2 (sucessfully)") + + assert_equal(nl.to_s, <<"END") +circuit SUBCKT ($1=$1,A=A,VXX=VXX,Z=Z,GND=GND,GND$1=GND$1); + device HVPMOS $1 (S=VXX,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24); + device HVPMOS $2 (S=VXX,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24); + device HVNMOS $3 (S=GND,G=$3,D=GND,B=GND$1) (L=1.695,W=3.18,AS=0,AD=0,PS=9,PD=9); + device HVNMOS $4 (S=GND,G=$3,D=Z,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=1.74,PD=1.74); + device HVNMOS $5 (S=GND,G=A,D=$3,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64); + device WIDERSTAND $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0); +end; +circuit .TOP (); + subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VXX=VXX,Z=Z,GND=VSS,GND$1=VSS); +end; +END + + end + def test_2_WithError nl = RBA::Netlist::new @@ -136,6 +195,50 @@ END end + def test_3_delegateHelpers + + dg = RBA::NetlistSpiceReaderDelegate::new + assert_equal(dg.value_from_string("xy").inspect, "nil") + assert_equal(dg.value_from_string("17.5").inspect, "17.5") + assert_equal(dg.value_from_string("1k").inspect, "1000.0") + assert_equal(dg.value_from_string("1pF*2.5").inspect, "2.5e-12") + assert_equal(dg.value_from_string("(1+3)*2").inspect, "8.0") + + end + + def test_4_ParseElementData + + pd = RBA::ParseElementData::new + pd.model_name = "a" + assert_equal(pd.model_name, "a") + pd.value = 42 + assert_equal(pd.value, 42) + pd.net_names = [ "x", "y", "z" ] + assert_equal(pd.net_names.join(","), "x,y,z") + pd.parameters = { "A" => 17.5, "B" => 1 } + assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}") + + end + + def test_5_ParseElementComponentsData + + pd = RBA::ParseElementComponentsData::new + pd.strings = [ "x", "y", "z" ] + assert_equal(pd.strings.join(","), "x,y,z") + pd.parameters = { "A" => 17.5, "B" => 1 } + assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}") + + end + + def test_6_delegateHelpers2 + + dg = RBA::NetlistSpiceReaderDelegate::new + pd = dg.parse_element_components("17 5 1e-9 a=17 b=1k") + assert_equal(pd.strings.join(","), "17,5,1e-9") + assert_equal(pd.parameters.inspect, "{\"A\"=>17.0, \"B\"=>1000.0}") + + end + end load("test_epilogue.rb")