From 3ac1385d87262dfcc1b0be1844d8c816aac14823 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Feb 2023 21:31:34 +0100 Subject: [PATCH] Another testcase for Spice reader with parametric subcircuits, .param statements, bug fixes --- src/db/db/dbNetlistSpiceReader.cc | 150 ++++++++++-------- src/db/db/dbNetlistSpiceReader.h | 12 -- src/db/db/dbNetlistSpiceReaderDelegate.cc | 2 +- .../dbNetlistSpiceReaderExpressionParser.cc | 21 +-- src/db/unit_tests/dbNetlistReaderTests.cc | 22 ++- testdata/algo/nreader17b.cir | 24 +++ 6 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 testdata/algo/nreader17b.cir diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 4b577b2a1..73f7b41e2 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -46,6 +46,31 @@ static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>"; // ------------------------------------------------------------------------------------------------------ +void +read_param_card (tl::Extractor &ex, const db::Netlist *netlist, std::map &variables) +{ + // 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); + + name = netlist->normalize_name (name); + + ex.test ("="); + + tl::Variant value = NetlistSpiceReaderExpressionParser (&variables).read (ex); + variables [name] = value; + + } +} + +// ------------------------------------------------------------------------------------------------------ + class SpiceReaderStream { public: @@ -453,7 +478,7 @@ SpiceCircuitDict::read (tl::InputStream &stream) read_card (); } - } catch (NetlistSpiceReaderError &ex) { + } catch (tl::Exception &ex) { // Translate the exception and add a location error (ex.msg ()); @@ -570,58 +595,43 @@ SpiceCircuitDict::read_card () tl::Extractor ex (l.c_str ()); std::string name; - if (ex.test_without_case (".")) { + if (ex.test_without_case (".model")) { - // control statement - if (ex.test_without_case ("model")) { + // ignore model statements - // ignore model statements + } else if (ex.test_without_case (".global")) { - } else if (ex.test_without_case ("global")) { - - while (! ex.at_end ()) { - std::string n = mp_delegate->translate_net_name (read_name (ex, mp_netlist)); - if (m_global_net_names.find (n) == m_global_net_names.end ()) { - m_global_nets.push_back (n); - m_global_net_names.insert (n); - } + while (! ex.at_end ()) { + std::string n = mp_delegate->translate_net_name (read_name (ex, mp_netlist)); + if (m_global_net_names.find (n) == m_global_net_names.end ()) { + m_global_nets.push_back (n); + m_global_net_names.insert (n); } + } - } else if (ex.test_without_case ("subckt")) { + } else if (ex.test_without_case (".subckt")) { - std::string nc = read_name (ex, mp_netlist); - read_circuit (ex, nc); + std::string nc = read_name (ex, mp_netlist); + read_circuit (ex, nc); - } else if (ex.test_without_case ("ends")) { + } else if (ex.test_without_case (".ends")) { - return true; + return true; - } else if (ex.test_without_case ("end")) { + } else if (ex.test_without_case (".end")) { - // ignore end statements + // ignore end statements - } else if (ex.test_without_case ("param")) { + } else if (ex.test_without_case (".param")) { - // Syntax is: - // .param = [ = ... ] - // taken from: - // https://nmg.gitlab.io/ngspice-manual/circuitdescription/paramparametricnetlists/paramline.html + read_param_card (ex, mp_netlist, m_variables); - while (! ex.at_end ()) { + ensure_circuit (); + mp_circuit->add_card (SpiceCard (m_file_id, m_stream.line_number (), l)); - std::string name; - ex.read_word (name); + } else if (ex.test (".")) { - name = mp_netlist->normalize_name (name); - - ex.test ("="); - - tl::Variant value = NetlistSpiceReaderExpressionParser (&m_variables).read (ex); - m_variables [name] = value; - - } - - } else if (! mp_delegate->control_statement (l)) { + if (! mp_delegate->control_statement (l)) { std::string s; ex.read_word (s); @@ -841,7 +851,7 @@ SpiceNetlistBuilder::build () build_global_nets (); mp_delegate->do_finish (); - } catch (NetlistSpiceReaderError &ex) { + } catch (tl::Exception &ex) { // translate the error and add a source location error (ex.msg ()); @@ -861,30 +871,34 @@ make_circuit_name (const std::string &name, const NetlistSpiceReader::parameters } res += p->first; res += "="; - double v = p->second.to_double (); - double va = fabs (v); - if (va < 1e-15) { - res += tl::sprintf ("%g", v); - } else if (va < 0.1e-12) { - res += tl::sprintf ("%gF", v * 1e15); - } else if (va < 0.1e-9) { - res += tl::sprintf ("%gP", v * 1e12); - } else if (va < 0.1e-6) { - res += tl::sprintf ("%gN", v * 1e9); - } else if (va < 0.1e-3) { - res += tl::sprintf ("%gU", v * 1e6); - } else if (va< 0.1) { - res += tl::sprintf ("%gM", v * 1e3); - } else if (va < 0.1e3) { - res += tl::sprintf ("%g", v); - } else if (va < 0.1e6) { - res += tl::sprintf ("%gK", v * 1e-3); - } else if (va < 0.1e9) { - res += tl::sprintf ("%gMEG", v * 1e-6); - } else if (va < 0.1e12) { - res += tl::sprintf ("%gG", v * 1e-9); + if (p->second.can_convert_to_double()) { + double v = p->second.to_double (); + double va = fabs (v); + if (va < 1e-15) { + res += tl::sprintf ("%g", v); + } else if (va < 0.1e-12) { + res += tl::sprintf ("%gF", v * 1e15); + } else if (va < 0.1e-9) { + res += tl::sprintf ("%gP", v * 1e12); + } else if (va < 0.1e-6) { + res += tl::sprintf ("%gN", v * 1e9); + } else if (va < 0.1e-3) { + res += tl::sprintf ("%gU", v * 1e6); + } else if (va< 0.1) { + res += tl::sprintf ("%gM", v * 1e3); + } else if (va < 0.1e3) { + res += tl::sprintf ("%g", v); + } else if (va < 0.1e6) { + res += tl::sprintf ("%gK", v * 1e-3); + } else if (va < 0.1e9) { + res += tl::sprintf ("%gMEG", v * 1e-6); + } else if (va < 0.1e12) { + res += tl::sprintf ("%gG", v * 1e-9); + } else { + res += tl::sprintf ("%g", v); + } } else { - res += tl::sprintf ("%g", v); + res += p->second.to_string (); } } res += ")"; @@ -900,6 +914,12 @@ SpiceNetlistBuilder::build_circuit (const SpiceCachedCircuit *cc, const paramete return c; } + for (auto p = pv.begin (); p != pv.end (); ++p) { + if (cc->parameters ().find (p->first) == cc->parameters ().end ()) { + warn (tl::sprintf (tl::to_string (tr ("Not a known parameter for circuit '%s': '%s'")), cc->name (), p->first)); + } + } + c = new db::Circuit (); mp_netlist->add_circuit (c); if (pv.empty ()) { @@ -991,7 +1011,11 @@ SpiceNetlistBuilder::process_card (const SpiceCard &card) ex = tl::Extractor (card.text.c_str ()); ex.skip (); - if (isalpha (*ex)) { + if (ex.test_without_case (".param")) { + + read_param_card (ex, mp_netlist, m_variables); + + } else if (isalpha (*ex)) { std::string prefix; prefix.push_back (toupper (*ex)); diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 1f36d2bb0..2d8bce4c3 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -39,18 +39,6 @@ namespace db class Netlist; class NetlistSpiceReaderDelegate; -/** - * @brief A specialized exception class to handle netlist reader delegate errors - */ -class DB_PUBLIC NetlistSpiceReaderError - : public tl::Exception -{ -public: - NetlistSpiceReaderError (const std::string &msg) - : tl::Exception (msg) - { } -}; - /** * @brief A SPICE format reader for netlists */ diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index 9d4c8089a..d62895f18 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -124,7 +124,7 @@ std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &n void NetlistSpiceReaderDelegate::error (const std::string &msg) { - throw NetlistSpiceReaderError (msg); + throw tl::Exception (msg); } template diff --git a/src/db/db/dbNetlistSpiceReaderExpressionParser.cc b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc index 939de34d0..364a11ebe 100644 --- a/src/db/db/dbNetlistSpiceReaderExpressionParser.cc +++ b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc @@ -518,22 +518,15 @@ tl::Variant NetlistSpiceReaderExpressionParser::read (const std::string &s) cons tl::Variant NetlistSpiceReaderExpressionParser::read (tl::Extractor &ex) const { - try { + tl::Variant res; - 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) { - // recast the exception, so we can process it later - throw NetlistSpiceReaderError (error.msg ()); + const char *endquote = start_quote (ex); + res = read_tl_expr (ex, 0); + if (endquote) { + ex.test (endquote); } + + return res; } bool NetlistSpiceReaderExpressionParser::try_read (const std::string &s, tl::Variant &value) const diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index 3fd88ca69..1ff160317 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -636,13 +636,15 @@ TEST(16_issue898) TEST(17_RecursiveExpansion) { - db::Netlist nl; + db::Netlist nl, nl2; - std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader17.cir"); + { + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader17.cir"); - db::NetlistSpiceReader reader; - tl::InputStream is (path); - reader.read (is, nl); + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + } EXPECT_EQ (nl.to_string (), "circuit .TOP ();\n" @@ -670,6 +672,16 @@ TEST(17_RecursiveExpansion) " device NMOS NMOS (S=N1,G=N2,D=N3,B=N1) (L=250000,W=6000000,AS=0,AD=0,PS=0,PD=0);\n" "end;\n" ); + + { + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader17b.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl2); + } + + EXPECT_EQ (nl2.to_string (), nl.to_string ()); } TEST(18_XSchemOutput) diff --git a/testdata/algo/nreader17b.cir b/testdata/algo/nreader17b.cir new file mode 100644 index 000000000..0c2c62722 --- /dev/null +++ b/testdata/algo/nreader17b.cir @@ -0,0 +1,24 @@ + +* recursive expansion of parametrized subcircuits + +.param w1 1.5 w2 {w1*2} +.param l1 0.15 l2 'l1+0.1' + +Xsub1a a b c sub1 w=w1 l=l1 +Xsub1b a b c sub1 w=w2 l=l2 + +.subckt sub1 n1 n2 n3 w l + * declares w and l parameters instead of nodes: + w = 1.0 + l = 1.0 + .param w1 = w + .param l1 = l + Xsub2a n1 n2 n3 sub2 w=w1 l=l1 m=1 + .param w2 "w+0.0" l2 l*(0.5+0.5) + Xsub2b n1 n2 n3 sub2 w=w2 l=l2 m=2 +.ends + +.subckt sub2 n1 n2 n3 w=0.0 l=0.0 m=0 + Mnmos n1 n2 n3 n1 nmos w=w l=l m=m +.ends +