From 89ffd7e3da5bdffc4107f16f84afcb1486fc8bce Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 1 Apr 2019 22:46:33 +0200 Subject: [PATCH] WIP: Simple SPICE reader. --- src/db/db/db.pro | 8 +- src/db/db/dbNetlistReader.cc | 31 ++ src/db/db/dbNetlistReader.h | 68 +++ src/db/db/dbNetlistSpiceReader.cc | 588 ++++++++++++++++++++++ src/db/db/dbNetlistSpiceReader.h | 96 ++++ src/db/db/dbNetlistWriter.h | 22 - src/db/db/gsiDeclDbNetlist.cc | 40 ++ src/db/unit_tests/dbNetlistReaderTests.cc | 125 +++++ src/db/unit_tests/unit_tests.pro | 3 +- src/tl/tl/tlString.cc | 71 ++- src/tl/tl/tlString.h | 7 + src/tl/unit_tests/tlString.cc | 27 + testdata/algo/nreader1.cir | 17 + testdata/algo/nreader2.cir | 83 +++ testdata/algo/nreader3.cir | 27 + 15 files changed, 1176 insertions(+), 37 deletions(-) create mode 100644 src/db/db/dbNetlistReader.cc create mode 100644 src/db/db/dbNetlistReader.h create mode 100644 src/db/db/dbNetlistSpiceReader.cc create mode 100644 src/db/db/dbNetlistSpiceReader.h create mode 100644 src/db/unit_tests/dbNetlistReaderTests.cc create mode 100644 testdata/algo/nreader1.cir create mode 100644 testdata/algo/nreader2.cir create mode 100644 testdata/algo/nreader3.cir diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 1ff2c1882..cd1acda6e 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -173,7 +173,9 @@ SOURCES = \ dbRegionUtils.cc \ dbEdgesUtils.cc \ dbRegionProcessors.cc \ - dbNetlistCompare.cc + dbNetlistCompare.cc \ + dbNetlistReader.cc \ + dbNetlistSpiceReader.cc HEADERS = \ dbArray.h \ @@ -312,7 +314,9 @@ HEADERS = \ dbEdgesUtils.h \ dbRegionProcessors.h \ gsiDeclDbHelpers.h \ - dbNetlistCompare.h + dbNetlistCompare.h \ + dbNetlistReader.h \ + dbNetlistSpiceReader.h !equals(HAVE_QT, "0") { diff --git a/src/db/db/dbNetlistReader.cc b/src/db/db/dbNetlistReader.cc new file mode 100644 index 000000000..ee4525e62 --- /dev/null +++ b/src/db/db/dbNetlistReader.cc @@ -0,0 +1,31 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 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 "dbNetlistReader.h" + +namespace db +{ + + // .. nothing yet .. + +} diff --git a/src/db/db/dbNetlistReader.h b/src/db/db/dbNetlistReader.h new file mode 100644 index 000000000..117793fa5 --- /dev/null +++ b/src/db/db/dbNetlistReader.h @@ -0,0 +1,68 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 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_dbNetlistReader +#define HDR_dbNetlistReader + +#include "dbCommon.h" +#include "tlTypeTraits.h" + +#include + +namespace tl +{ + class InputStream; +} + +namespace db +{ + +class Netlist; + +/** + * @brief A common base class for netlist writers + */ +class DB_PUBLIC NetlistReader +{ +public: + NetlistReader () { } + virtual ~NetlistReader () { } + + virtual void read (tl::InputStream &stream, db::Netlist &netlist) = 0; +}; + +} + +namespace tl +{ + +template <> +struct type_traits + : public tl::type_traits +{ + typedef tl::false_tag has_default_constructor; + typedef tl::false_tag has_copy_constructor; +}; + +} + +#endif diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc new file mode 100644 index 000000000..32e62e9db --- /dev/null +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -0,0 +1,588 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 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 "dbNetlistSpiceReader.h" +#include "dbNetlist.h" +#include "dbNetlistDeviceClasses.h" + +#include "tlStream.h" +#include "tlLog.h" + +#include +#include + +namespace db +{ + +NetlistSpiceReader::NetlistSpiceReader () + : mp_netlist (0), mp_stream (0) +{ + // .. nothing yet .. +} + +NetlistSpiceReader::~NetlistSpiceReader () +{ + // .. nothing yet .. +} + +void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) +{ + mp_stream.reset (new tl::TextInputStream (stream)); + mp_netlist = &netlist; + mp_circuit = 0; + + try { + + while (! at_end ()) { + read_element (); + } + + finish (); + + } catch (tl::Exception &ex) { + + // NOTE: because we do a peek to capture the "+" line continuation character, we're + // one line ahead. + std::string fmt_msg = tl::sprintf ("%s in %s, line %d", ex.msg (), mp_stream->source (), mp_stream->line_number () - 1); + finish (); + throw tl::Exception (fmt_msg); + + } catch (...) { + + finish (); + throw; + + } +} + +void NetlistSpiceReader::finish () +{ + while (! m_streams.empty ()) { + pop_stream (); + } + + mp_stream.reset (0); + mp_netlist = 0; + mp_circuit = 0; +} + +void NetlistSpiceReader::push_stream (const std::string &path) +{ + tl::InputStream *istream = new tl::InputStream (path); + m_streams.push_back (std::make_pair (istream, mp_stream.release ())); + mp_stream.reset (new tl::TextInputStream (*istream)); +} + +void NetlistSpiceReader::pop_stream () +{ + if (! m_streams.empty ()) { + + mp_stream.reset (m_streams.back ().second); + delete m_streams.back ().first; + + m_streams.pop_back (); + + } +} + +bool NetlistSpiceReader::at_end () +{ + return mp_stream->at_end () && m_streams.empty (); +} + +std::string NetlistSpiceReader::get_line () +{ + if (! m_stored_line.empty ()) { + std::string l; + l.swap (m_stored_line); + return l; + } + + std::string l; + + do { + + while (mp_stream->at_end ()) { + if (m_streams.empty ()) { + return std::string (); + } + pop_stream (); + } + + l = mp_stream->get_line (); + while (! mp_stream->at_end () && mp_stream->peek_char () == '+') { + mp_stream->get_char (); + l += mp_stream->get_line (); + } + + tl::Extractor ex (l.c_str ()); + if (ex.test_without_case (".include")) { + + std::string path; + ex.read_word_or_quoted (path, "_.:,!+$/\\"); + + push_stream (path); + + l.clear (); + + } else if (ex.at_end () || ex.test ("*")) { + l.clear (); + } + + } while (l.empty ()); + + return l; +} + +void NetlistSpiceReader::unget_line (const std::string &l) +{ + m_stored_line = l; +} + +bool NetlistSpiceReader::read_element () +{ + std::string l = get_line (); + if (l.empty ()) { + return false; + } + + tl::Extractor ex (l.c_str ()); + + const char *res_device_class_name = "RES"; + const char *cap_device_class_name = "CAP"; + const char *ind_device_class_name = "IND"; + + if (ex.test_without_case (".")) { + + // control statement + if (ex.test_without_case ("model")) { + + // ignore model statements + + } else if (ex.test_without_case ("subckt")) { + + read_circuit (ex); + + } else if (ex.test_without_case ("ends")) { + + return true; + + } else if (ex.test_without_case ("end")) { + + // ignore end statements + + } else { + + std::string s; + ex.read_word (s); + s = tl::to_lower_case (s); + warn (tl::to_string (tr ("Control statement ignored: ")) + s); + + } + + } else if (ex.test_without_case ("r")) { + + db::DeviceClass *dev_cls = mp_netlist->device_class_by_name (res_device_class_name); + if (! dev_cls) { + dev_cls = new db::DeviceClassResistor (); + dev_cls->set_name (res_device_class_name); + mp_netlist->add_device_class (dev_cls); + } + + ensure_circuit (); + read_device (dev_cls, db::DeviceClassResistor::param_id_R, ex); + + } else if (ex.test_without_case ("c")) { + + db::DeviceClass *dev_cls = mp_netlist->device_class_by_name (cap_device_class_name); + if (! dev_cls) { + dev_cls = new db::DeviceClassCapacitor (); + dev_cls->set_name (cap_device_class_name); + mp_netlist->add_device_class (dev_cls); + } + + ensure_circuit (); + read_device (dev_cls, db::DeviceClassCapacitor::param_id_C, ex); + + } else if (ex.test_without_case ("l")) { + + db::DeviceClass *dev_cls = mp_netlist->device_class_by_name (ind_device_class_name); + if (! dev_cls) { + dev_cls = new db::DeviceClassInductor (); + dev_cls->set_name (ind_device_class_name); + mp_netlist->add_device_class (dev_cls); + } + + ensure_circuit (); + read_device (dev_cls, db::DeviceClassInductor::param_id_L, ex); + + } else if (ex.test_without_case ("m")) { + + ensure_circuit (); + read_mos4_device (ex); + + } else if (ex.test_without_case ("x")) { + + ensure_circuit (); + read_subcircuit (ex); + + } else { + + char c = *ex.skip (); + if (c) { + warn (tl::sprintf (tl::to_string (tr ("Element type '%c' ignored")), c)); + } + + } + + return false; +} + +void NetlistSpiceReader::error (const std::string &msg) +{ + throw tl::Exception (msg); +} + +void NetlistSpiceReader::warn (const std::string &msg) +{ + std::string fmt_msg = tl::sprintf ("%s in %s, line %d", msg, mp_stream->source (), mp_stream->line_number ()); + 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) { + + mp_circuit = new db::Circuit (); + // TODO: make top name configurable + mp_circuit->set_name (".TOP"); + mp_netlist->add_circuit (mp_circuit); + + } +} + +db::Net *NetlistSpiceReader::make_net (const std::string &name) +{ + db::Net *net = mp_circuit->net_by_name (name); + if (! net) { + net = new db::Net (); + net->set_name (name); + mp_circuit->add_net (net); + } + return net; +} + +void NetlistSpiceReader::read_subcircuit (tl::Extractor &ex) +{ + std::string sc_name; + ex.read_word_or_quoted (sc_name, "_.:,!+$/\\"); + + std::vector nn; + std::map pv; + + while (! ex.at_end ()) { + + std::string n; + ex.read_word_or_quoted (n, "_.:,!+$/\\"); + + if (ex.test ("=")) { + // a parameter + pv.insert (std::make_pair (tl::to_upper_case (n), read_value (ex))); + } else { + nn.push_back (n); + } + + } + + if (nn.empty ()) { + error (tl::to_string (tr ("No circuit name given for subcircuit call"))); + } + if (! pv.empty ()) { + warn (tl::to_string (tr ("Circuit parameters are not allowed currently"))); + } + + std::string nc = nn.back (); + nn.pop_back (); + + if (nn.empty ()) { + error (tl::to_string (tr ("A circuit call needs at least one net"))); + } + + db::Circuit *cc = mp_netlist->circuit_by_name (nc); + if (! cc) { + cc = new db::Circuit (); + mp_netlist->add_circuit (cc); + cc->set_name (nc); + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + cc->add_pin (std::string ()); + } + } else { + if (cc->pin_count () != nn.size ()) { + error (tl::sprintf (tl::to_string (tr ("Pin count mismatch between circuit definition and circuit call: %d expected, got %d")), int (cc->pin_count ()), int (nn.size ()))); + } + } + + db::SubCircuit *sc = new db::SubCircuit (cc, sc_name); + mp_circuit->add_subcircuit (sc); + + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + db::Net *net = make_net (*i); + sc->connect_pin (i - nn.begin (), net); + } + + ex.expect_end (); +} + +void NetlistSpiceReader::read_circuit (tl::Extractor &ex) +{ + std::string nc; + ex.read_word_or_quoted (nc, "_.:,!+$/\\"); + + std::vector nn; + std::map pv; + + while (! ex.at_end ()) { + + std::string n; + ex.read_word_or_quoted (n, "_.:,!+$/\\"); + + if (ex.test ("=")) { + // a parameter + pv.insert (std::make_pair (tl::to_upper_case (n), read_value (ex))); + } else { + nn.push_back (n); + } + + } + + if (! pv.empty ()) { + warn (tl::to_string (tr ("Circuit parameters are not allowed currently"))); + } + + db::Circuit *cc = mp_netlist->circuit_by_name (nc); + if (! cc) { + cc = new db::Circuit (); + mp_netlist->add_circuit (cc); + cc->set_name (nc); + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + cc->add_pin (std::string ()); + } + } else { + if (cc->pin_count () != nn.size ()) { + error (tl::sprintf (tl::to_string (tr ("Pin count mismatch between implicit (through call) and explicit circuit definition: %d expected, got %d")), int (cc->pin_count ()), int (nn.size ()))); + } + } + + std::swap (cc, mp_circuit); + + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + db::Net *net = make_net (*i); + mp_circuit->connect_pin (i - nn.begin (), net); + } + + while (! at_end ()) { + if (read_element ()) { + break; + } + } + + std::swap (cc, mp_circuit); + + ex.expect_end (); +} + +void NetlistSpiceReader::read_device (db::DeviceClass *dev_cls, size_t param_id, tl::Extractor &ex) +{ + std::string dn; + ex.read_word_or_quoted (dn, "_.:,!+$/\\"); + + std::vector nn; + + while (! ex.at_end () && nn.size () < 2) { + nn.push_back (std::string ()); + ex.read_word_or_quoted (nn.back (), "_.:,!+$/\\"); + } + + if (nn.size () != 2) { + error (tl::to_string (tr ("Two-terminal device needs two nets"))); + } + + double v = read_value (ex); + + db::Device *dev = new db::Device (dev_cls, dn); + mp_circuit->add_device (dev); + + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + db::Net *net = make_net (*i); + dev->connect_terminal (i - nn.begin (), net); + } + + dev->set_parameter_value (param_id, v); + + ex.expect_end (); +} + +void NetlistSpiceReader::read_mos4_device (tl::Extractor &ex) +{ + std::string dn; + ex.read_word_or_quoted (dn, "_.:,!+$/\\"); + + std::vector nn; + std::map pv; + + while (! ex.at_end ()) { + + std::string n; + ex.read_word_or_quoted (n, "_.:,!+$/\\"); + + if (ex.test ("=")) { + // a parameter + pv.insert (std::make_pair (tl::to_upper_case (n), read_value (ex))); + } else { + nn.push_back (n); + } + + } + + if (nn.empty ()) { + error (tl::to_string (tr ("No model name given for MOS transistor element"))); + } + + std::string mn = nn.back (); + nn.pop_back (); + + if (nn.size () != 4) { + error (tl::to_string (tr ("A MOS transistor needs four nets"))); + } + + db::DeviceClass *dev_cls = mp_netlist->device_class_by_name (mn); + if (! dev_cls) { + dev_cls = new db::DeviceClassMOS4Transistor (); + dev_cls->set_name (mn); + mp_netlist->add_device_class (dev_cls); + } + + db::Device *dev = new db::Device (dev_cls, dn); + mp_circuit->add_device (dev); + + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + db::Net *net = make_net (*i); + dev->connect_terminal (i - nn.begin (), net); + } + + const std::vector &pd = dev_cls->parameter_definitions (); + for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { + std::map::const_iterator v = pv.find (i->name ()); + if (v != pv.end ()) { + dev->set_parameter_value (i->id (), v->second); + } + } + + ex.expect_end (); +} + +} diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h new file mode 100644 index 000000000..08d3991a1 --- /dev/null +++ b/src/db/db/dbNetlistSpiceReader.h @@ -0,0 +1,96 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 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_dbNetlistSpiceReader +#define HDR_dbNetlistSpiceReader + +#include "dbCommon.h" +#include "dbNetlistReader.h" +#include "tlStream.h" + +#include +#include + +namespace db +{ + +class Netlist; +class Net; +class Circuit; +class DeviceClass; + +/** + * @brief A SPICE format reader for netlists + */ +class DB_PUBLIC NetlistSpiceReader + : public NetlistReader +{ +public: + NetlistSpiceReader (); + virtual ~NetlistSpiceReader (); + + virtual void read (tl::InputStream &stream, db::Netlist &netlist); + +private: + db::Netlist *mp_netlist; + db::Circuit *mp_circuit; + std::auto_ptr mp_stream; + std::vector > m_streams; + std::string m_stored_line; + + void push_stream (const std::string &path); + void pop_stream (); + bool at_end (); + void read_subcircuit (tl::Extractor &ex); + void read_circuit (tl::Extractor &ex); + void read_device (db::DeviceClass *dev_cls, size_t param_id, tl::Extractor &ex); + void read_mos4_device (tl::Extractor &ex); + bool read_element (); + double read_value (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); + void warn (const std::string &msg); + void finish (); + db::Net *make_net (const std::string &name); + void ensure_circuit (); +}; + +} + +namespace tl +{ + +template <> +struct type_traits + : public tl::type_traits +{ + typedef tl::false_tag has_default_constructor; + typedef tl::false_tag has_copy_constructor; +}; + +} + +#endif diff --git a/src/db/db/dbNetlistWriter.h b/src/db/db/dbNetlistWriter.h index a97f1fd2c..cdbac98cd 100644 --- a/src/db/db/dbNetlistWriter.h +++ b/src/db/db/dbNetlistWriter.h @@ -1,26 +1,4 @@ -/* - - KLayout Layout Viewer - Copyright (C) 2006-2019 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 - -*/ - - /* KLayout Layout Viewer diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 5dc648258..173ea6336 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -24,6 +24,8 @@ #include "dbNetlist.h" #include "dbNetlistWriter.h" #include "dbNetlistSpiceWriter.h" +#include "dbNetlistReader.h" +#include "dbNetlistSpiceReader.h" #include "tlException.h" #include "tlInternational.h" #include "tlStream.h" @@ -943,6 +945,13 @@ static void write_netlist (const db::Netlist *nl, const std::string &file, db::N writer->write (os, *nl, description); } +static void read_netlist (db::Netlist *nl, const std::string &file, db::NetlistReader *reader) +{ + tl_assert (reader != 0); + tl::InputStream os (file); + reader->read (os, *nl); +} + Class decl_dbNetlist ("db", "Netlist", gsi::method_ext ("add", &gsi::add_circuit, gsi::arg ("circuit"), "@brief Adds the circuit to the netlist\n" @@ -1024,6 +1033,10 @@ Class decl_dbNetlist ("db", "Netlist", "Floating nets can be created as effect of reconnections of devices or pins. " "This method will eliminate all nets that make less than two connections." ) + + gsi::method_ext ("read", &read_netlist, gsi::arg ("file"), gsi::arg ("reader"), + "@brief Writes the netlist to the given file using the given reader object to parse the file\n" + "See \\NetlistSpiceReader for an example for a parser. " + ) + gsi::method_ext ("write", &write_netlist, gsi::arg ("file"), gsi::arg ("writer"), gsi::arg ("description", std::string ()), "@brief Writes the netlist to the given file using the given writer object to format the file\n" "See \\NetlistSpiceWriter for an example for a formatter. " @@ -1255,4 +1268,31 @@ Class db_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne "This class has been introduced in version 0.26." ); +Class db_NetlistReader ("db", "NetlistReader", + gsi::Methods (), + "@hide\n" +); + +db::NetlistSpiceReader *new_spice_reader () +{ + return new db::NetlistSpiceReader (); +} + +Class db_NetlistSpiceReader (db_NetlistReader, "db", "NetlistSpiceReader", + gsi::constructor ("new", &new_spice_reader, + "@brief Creates a new reader.\n" + ), + "@brief Implements a netlist Reader for the SPICE format.\n" + "Use the SPICE reader like this:\n" + "\n" + "@code\n" + "writer = RBA::NetlistSpiceReader::new\n" + "netlist = RBA::Netlist::new\n" + "netlist.read(path, reader)\n" + "@endcode\n" + "\n" + "This class has been introduced in version 0.26." +); + + } diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc new file mode 100644 index 000000000..25e50da57 --- /dev/null +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -0,0 +1,125 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 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 "dbNetlistSpiceReader.h" +#include "dbNetlist.h" +#include "dbNetlistDeviceClasses.h" + +#include "tlUnitTest.h" +#include "tlStream.h" +#include "tlFileUtils.h" + +TEST(1_BasicReader) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nreader1.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device RES $1 (A='6',B='1') (R=7650);\n" + " device RES $2 (A='3',B='1') (R=7650);\n" + " device RES $3 (A='3',B='2') (R=2670);\n" + " device MHVPMOS $4 (S='6',G='4',D='7',B='7') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=3.84e-06,PD=3.84e-06);\n" + "end;\n" + ); +} + +TEST(2_ReaderWithSubcircuits) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nreader2.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit RINGO ($1='11',$2='12',$3='13',$4='14',$5='15');\n" + " subcircuit ND2X1 $1 ($1='12',$2='1',$3='15',$4='12',$5='11',$6='14',$7='15');\n" + " subcircuit INVX1 $2 ($1='12',$2='2',$3='15',$4='12',$5='1',$6='15');\n" + " subcircuit INVX1 $3 ($1='12',$2='3',$3='15',$4='12',$5='2',$6='15');\n" + " subcircuit INVX1 $4 ($1='12',$2='4',$3='15',$4='12',$5='3',$6='15');\n" + " subcircuit INVX1 $5 ($1='12',$2='5',$3='15',$4='12',$5='4',$6='15');\n" + " subcircuit INVX1 $6 ($1='12',$2='6',$3='15',$4='12',$5='5',$6='15');\n" + " subcircuit INVX1 $7 ($1='12',$2='7',$3='15',$4='12',$5='6',$6='15');\n" + " subcircuit INVX1 $8 ($1='12',$2='8',$3='15',$4='12',$5='7',$6='15');\n" + " subcircuit INVX1 $9 ($1='12',$2='9',$3='15',$4='12',$5='8',$6='15');\n" + " subcircuit INVX1 $10 ($1='12',$2='10',$3='15',$4='12',$5='9',$6='15');\n" + " subcircuit INVX1 $11 ($1='12',$2='11',$3='15',$4='12',$5='10',$6='15');\n" + " subcircuit INVX1 $12 ($1='12',$2='13',$3='15',$4='12',$5='11',$6='15');\n" + "end;\n" + "circuit ND2X1 ($1='1',$2='2',$3='3',$4='4',$5='5',$6='6',$7='7');\n" + " device MLVPMOS $1 (S='2',G='6',D='1',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=3.85e-06,PD=1.95e-06);\n" + " device MLVPMOS $2 (S='1',G='5',D='2',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=1.95e-06,PD=3.85e-06);\n" + " device MLVNMOS $3 (S='3',G='6',D='8',B='7') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=2.75e-06,PD=1.4e-06);\n" + " device MLVNMOS $4 (S='8',G='5',D='2',B='7') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=1.4e-06,PD=2.75e-06);\n" + "end;\n" + "circuit INVX1 ($1='1',$2='2',$3='3',$4='4',$5='5',$6='6');\n" + " device MLVPMOS $1 (S='1',G='5',D='2',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=3.85e-06,PD=3.85e-06);\n" + " device MLVNMOS $2 (S='3',G='5',D='2',B='6') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=2.75e-06,PD=2.75e-06);\n" + "end;\n" + ); +} + +TEST(3_ReaderWithSubcircuitsAltOrder) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nreader3.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit INVX1 ($1='1',$2='2',$3='3',$4='4',$5='5',$6='6');\n" + " device MLVPMOS $1 (S='1',G='5',D='2',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=0,PD=0);\n" + " device MLVNMOS $2 (S='3',G='5',D='2',B='6') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=0,PD=0);\n" + "end;\n" + "circuit ND2X1 ($1='1',$2='2',$3='3',$4='4',$5='5',$6='6',$7='7');\n" + " device MLVPMOS $1 (S='2',G='6',D='1',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=0,PD=0);\n" + " device MLVPMOS $2 (S='1',G='5',D='2',B='4') (L=2.5e-07,W=1.5e-06,AS=0,AD=0,PS=0,PD=0);\n" + " device MLVNMOS $3 (S='3',G='6',D='8',B='7') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=0,PD=0);\n" + " device MLVNMOS $4 (S='8',G='5',D='2',B='7') (L=2.5e-07,W=9.5e-07,AS=0,AD=0,PS=0,PD=0);\n" + "end;\n" + "circuit RINGO ($1='11',$2='12',$3='13',$4='14',$5='15');\n" + " subcircuit ND2X1 $1 ($1='12',$2='1',$3='15',$4='12',$5='11',$6='14',$7='15');\n" + " subcircuit INVX1 $2 ($1='12',$2='2',$3='15',$4='12',$5='1',$6='15');\n" + " subcircuit INVX1 $3 ($1='12',$2='3',$3='15',$4='12',$5='2',$6='15');\n" + " subcircuit INVX1 $4 ($1='12',$2='4',$3='15',$4='12',$5='3',$6='15');\n" + " subcircuit INVX1 $5 ($1='12',$2='5',$3='15',$4='12',$5='4',$6='15');\n" + " subcircuit INVX1 $6 ($1='12',$2='6',$3='15',$4='12',$5='5',$6='15');\n" + " subcircuit INVX1 $7 ($1='12',$2='7',$3='15',$4='12',$5='6',$6='15');\n" + " subcircuit INVX1 $8 ($1='12',$2='8',$3='15',$4='12',$5='7',$6='15');\n" + " subcircuit INVX1 $9 ($1='12',$2='9',$3='15',$4='12',$5='8',$6='15');\n" + " subcircuit INVX1 $10 ($1='12',$2='10',$3='15',$4='12',$5='9',$6='15');\n" + " subcircuit INVX1 $11 ($1='12',$2='11',$3='15',$4='12',$5='10',$6='15');\n" + " subcircuit INVX1 $12 ($1='12',$2='13',$3='15',$4='12',$5='11',$6='15');\n" + "end;\n" + ); +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index b5eded222..39a1196b1 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -70,7 +70,8 @@ SOURCES = \ dbCellVariantsTests.cc \ dbDeepEdgesTests.cc \ dbDeepEdgePairsTests.cc \ - dbNetlistCompareTests.cc + dbNetlistCompareTests.cc \ + dbNetlistReaderTests.cc INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC diff --git a/src/tl/tl/tlString.cc b/src/tl/tl/tlString.cc index ae5420eed..4c9285b99 100644 --- a/src/tl/tl/tlString.cc +++ b/src/tl/tl/tlString.cc @@ -42,7 +42,7 @@ namespace tl static std::locale c_locale ("C"); // ------------------------------------------------------------------------- -// lower and upper case for wchar_t +// lower and upper case for wchar_t and uint32_t #include "utf_casefolding.h" @@ -66,9 +66,44 @@ wchar_t wupcase (wchar_t c) } } +uint32_t utf32_downcase (uint32_t c32) +{ + if (sizeof (wchar_t) == 2 && c32 >= 0x10000) { + return c32; + } else { + return uint32_t (wdowncase (wchar_t (c32))); + } +} + +uint32_t utf32_upcase (uint32_t c32) +{ + if (sizeof (wchar_t) == 2 && c32 >= 0x10000) { + return c32; + } else { + return uint32_t (wupcase (wchar_t (c32))); + } +} + // ------------------------------------------------------------------------- // Conversion of UTF8 to wchar_t +uint32_t utf32_from_utf8 (const char *&cp, const char *cpe = 0) +{ + uint32_t c32 = (unsigned char) *cp++; + if (c32 >= 0xf0 && ((cpe && cp + 2 < cpe) || (! cpe && cp [0] && cp [1] && cp [2]))) { + c32 = ((c32 & 0x7) << 18) | ((uint32_t (cp [0]) & 0x3f) << 12) | ((uint32_t (cp [1]) & 0x3f) << 6) | (uint32_t (cp [2]) & 0x3f); + cp += 3; + } else if (c32 >= 0xe0 && ((cpe && cp + 1 < cpe) || (! cpe && cp [0] && cp [1]))) { + c32 = ((c32 & 0xf) << 12) | ((uint32_t (cp [0]) & 0x3f) << 6) | (uint32_t (cp [1]) & 0x3f); + cp += 2; + } else if (c32 >= 0xc0 && ((cpe && cp < cpe) || (! cpe && cp [0]))) { + c32 = ((c32 & 0x1f) << 6) | (uint32_t (*cp) & 0x3f); + ++cp; + } + + return c32; +} + std::wstring to_wstring (const std::string &s) { std::wstring ws; @@ -76,17 +111,7 @@ std::wstring to_wstring (const std::string &s) const char *cpe = s.c_str () + s.size (); for (const char *cp = s.c_str (); cp < cpe; ) { - uint32_t c32 = (unsigned char) *cp++; - if (c32 >= 0xf0 && cp + 2 < cpe) { - c32 = ((c32 & 0x7) << 18) | ((uint32_t (cp [0]) & 0x3f) << 12) | ((uint32_t (cp [1]) & 0x3f) << 6) | (uint32_t (cp [2]) & 0x3f); - cp += 3; - } else if (c32 >= 0xe0 && cp + 1 < cpe) { - c32 = ((c32 & 0xf) << 12) | ((uint32_t (cp [0]) & 0x3f) << 6) | (uint32_t (cp [1]) & 0x3f); - cp += 2; - } else if (c32 >= 0xc0 && cp < cpe) { - c32 = ((c32 & 0x1f) << 6) | (uint32_t (*cp) & 0x3f); - ++cp; - } + uint32_t c32 = utf32_from_utf8 (cp, cpe); if (sizeof (wchar_t) == 2 && c32 >= 0x10000) { c32 -= 0x10000; @@ -1324,6 +1349,28 @@ Extractor::test (const char *token) } } +bool +Extractor::test_without_case (const char *token) +{ + skip (); + + const char *cp = m_cp; + while (*cp && *token) { + uint32_t c = utf32_downcase (utf32_from_utf8 (cp)); + uint32_t ct = utf32_downcase (utf32_from_utf8 (token)); + if (c != ct) { + return false; + } + } + + if (! *token) { + m_cp = cp; + return true; + } else { + return false; + } +} + const char * Extractor::skip () { diff --git a/src/tl/tl/tlString.h b/src/tl/tl/tlString.h index 6f69aa1d5..f38ae77de 100644 --- a/src/tl/tl/tlString.h +++ b/src/tl/tl/tlString.h @@ -669,6 +669,13 @@ public: */ bool test (const char *token); + /** + * @brief Test for a token (a certain string) in case-insensitive mode + * + * If the token is not present, return false. + */ + bool test_without_case (const char *token); + /** * @brief Skip blanks * diff --git a/src/tl/unit_tests/tlString.cc b/src/tl/unit_tests/tlString.cc index 846e73956..5e049200a 100644 --- a/src/tl/unit_tests/tlString.cc +++ b/src/tl/unit_tests/tlString.cc @@ -388,6 +388,33 @@ TEST(8) EXPECT_EQ (x.try_read_quoted (s), true); EXPECT_EQ (s, "a_word\'!"); EXPECT_EQ (x.at_end (), true); + + x = Extractor (" foobar"); + EXPECT_EQ (x.test ("foo"), true); + EXPECT_EQ (x.test ("bar"), true); + + x = Extractor (" foo bar"); + EXPECT_EQ (x.test ("foo"), true); + EXPECT_EQ (x.test ("bar"), true); + + x = Extractor (" FOObar"); + EXPECT_EQ (x.test ("foo"), false); + EXPECT_EQ (x.test ("BAR"), false); + + x = Extractor (" FOObar"); + EXPECT_EQ (x.test_without_case ("foo"), true); + EXPECT_EQ (x.test_without_case ("BAR"), true); + + x = Extractor (" µm"); + EXPECT_EQ (x.test ("µm"), true); + + x = Extractor (" µM"); + EXPECT_EQ (x.test ("µm"), false); + EXPECT_EQ (x.test_without_case ("µm"), true); + + x = Extractor (" µm"); + EXPECT_EQ (x.test ("µM"), false); + EXPECT_EQ (x.test_without_case ("µM"), true); } TEST(9) diff --git a/testdata/algo/nreader1.cir b/testdata/algo/nreader1.cir new file mode 100644 index 000000000..8b06b1937 --- /dev/null +++ b/testdata/algo/nreader1.cir @@ -0,0 +1,17 @@ +* VDIV netlist before simplification + +* cell TOP +.SUBCKT TOP +* net 1 OUT +* net 2 GND +* net 4 IN +* net 7 VDD +* device instance $1 1.025,0.335 RES +R$1 6 1 7650 +* device instance $2 2.85,0.335 RES +R$2 3 1 7650 +* device instance $3 4.665,0.335 RES +R$3 3 2 2670 +* device instance $4 1.765,7.485 HVPMOS +M$4 6 4 7 7 MHVPMOS L=0.25U W=1.5U AS=0.63P AD=0.63P PS=3.84U PD=3.84U +.ENDS TOP diff --git a/testdata/algo/nreader2.cir b/testdata/algo/nreader2.cir new file mode 100644 index 000000000..32e7dfc09 --- /dev/null +++ b/testdata/algo/nreader2.cir @@ -0,0 +1,83 @@ +* RINGO netlist after simplification + +* cell RINGO +* pin FB +* pin VDD +* pin OUT +* pin ENABLE +* pin BULK,VSS +.SUBCKT RINGO 11 12 13 14 15 +* net 11 FB +* net 12 VDD +* net 13 OUT +* net 14 ENABLE +* net 15 BULK,VSS +* cell instance $1 r0 *1 1.8,0 +X$1 12 1 15 12 11 14 15 ND2X1 +* cell instance $2 r0 *1 4.2,0 +X$2 12 2 15 12 1 15 INVX1 +* cell instance $3 r0 *1 6,0 +X$3 12 3 15 12 2 15 INVX1 +* cell instance $4 r0 *1 7.8,0 +X$4 12 4 15 12 3 15 INVX1 +* cell instance $5 r0 *1 9.6,0 +X$5 12 5 15 12 4 15 INVX1 +* cell instance $6 r0 *1 11.4,0 +X$6 12 6 15 12 5 15 INVX1 +* cell instance $7 r0 *1 13.2,0 +X$7 12 7 15 12 6 15 INVX1 +* cell instance $8 r0 *1 15,0 +X$8 12 8 15 12 7 15 INVX1 +* cell instance $9 r0 *1 16.8,0 +X$9 12 9 15 12 8 15 INVX1 +* cell instance $10 r0 *1 18.6,0 +X$10 12 10 15 12 9 15 INVX1 +* cell instance $11 r0 *1 20.4,0 +X$11 12 11 15 12 10 15 INVX1 +* cell instance $12 r0 *1 22.2,0 +X$12 12 13 15 12 11 15 INVX1 +.ENDS RINGO + +* cell ND2X1 +* pin VDD +* pin OUT +* pin VSS +* pin +* pin B +* pin A +* pin BULK +.SUBCKT ND2X1 1 2 3 4 5 6 7 +* net 1 VDD +* net 2 OUT +* net 3 VSS +* net 5 B +* net 6 A +* net 7 BULK +* device instance $1 0.85,5.8 LVPMOS +M$1 2 6 1 4 MLVPMOS L=0.25U W=1.5U AS=0.6375P AD=0.3375P PS=3.85U PD=1.95U +* device instance $2 1.55,5.8 LVPMOS +M$2 1 5 2 4 MLVPMOS L=0.25U W=1.5U AS=0.3375P AD=0.6375P PS=1.95U PD=3.85U +* device instance $3 0.85,2.135 LVNMOS +M$3 3 6 8 7 MLVNMOS L=0.25U W=0.95U AS=0.40375P AD=0.21375P PS=2.75U PD=1.4U +* device instance $4 1.55,2.135 LVNMOS +M$4 8 5 2 7 MLVNMOS L=0.25U W=0.95U AS=0.21375P AD=0.40375P PS=1.4U PD=2.75U +.ENDS ND2X1 + +* cell INVX1 +* pin VDD +* pin OUT +* pin VSS +* pin +* pin IN +* pin BULK +.SUBCKT INVX1 1 2 3 4 5 6 +* net 1 VDD +* net 2 OUT +* net 3 VSS +* net 5 IN +* net 6 BULK +* device instance $1 0.85,5.8 LVPMOS +M$1 1 5 2 4 MLVPMOS L=0.25U W=1.5U AS=0.6375P AD=0.6375P PS=3.85U PD=3.85U +* device instance $2 0.85,2.135 LVNMOS +M$2 3 5 2 6 MLVNMOS L=0.25U W=0.95U AS=0.40375P AD=0.40375P PS=2.75U PD=2.75U +.ENDS INVX1 diff --git a/testdata/algo/nreader3.cir b/testdata/algo/nreader3.cir new file mode 100644 index 000000000..f4dfd6cf1 --- /dev/null +++ b/testdata/algo/nreader3.cir @@ -0,0 +1,27 @@ +.subckt INVX1 1 2 3 4 5 6 + m$1 1 5 2 4 MLVPMOS w=1.5um l=0.25um + m$2 3 5 2 6 MLVNMOS w=0.95um l=0.25um +.ends + +.subckt ND2X1 1 2 3 4 5 6 7 + m$1 2 6 1 4 MLVPMOS L=0.25um W=1.5um + m$2 1 5 2 4 MLVPMOS L=0.25um W=1.5um + m$3 3 6 8 7 MLVNMOS L=0.25um W=0.95um + m$4 8 5 2 7 MLVNMOS L=0.25um W=0.95um +.ends ND2X1 + +.subckt RINGO 11 12 13 14 15 + x$1 12 1 15 12 11 14 15 ND2X1 + x$2 12 2 15 12 1 15 INVX1 + x$3 12 3 15 12 2 15 INVX1 + x$4 12 4 15 12 3 15 INVX1 + x$5 12 5 15 12 4 15 INVX1 + x$6 12 6 15 12 5 15 INVX1 + x$7 12 7 15 12 6 15 INVX1 + x$8 12 8 15 12 7 15 INVX1 + x$9 12 9 15 12 8 15 INVX1 + x$10 12 10 15 12 9 15 INVX1 + x$11 12 11 15 12 10 15 INVX1 + x$12 12 13 15 12 11 15 INVX1 +.ends RINGO +