WIP: Simple SPICE reader.

This commit is contained in:
Matthias Koefferlein 2019-04-01 22:46:33 +02:00
parent 9613ad72c8
commit 89ffd7e3da
15 changed files with 1176 additions and 37 deletions

View File

@ -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") {

View File

@ -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 ..
}

View File

@ -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 <string>
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<db::NetlistReader>
: public tl::type_traits<void>
{
typedef tl::false_tag has_default_constructor;
typedef tl::false_tag has_copy_constructor;
};
}
#endif

View File

@ -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 <sstream>
#include <cctype>
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<std::string> nn;
std::map<std::string, double> 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<std::string>::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<std::string>::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<std::string> nn;
std::map<std::string, double> 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<std::string>::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<std::string>::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<std::string> 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<std::string>::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<std::string> nn;
std::map<std::string, double> 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<std::string>::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<db::DeviceParameterDefinition> &pd = dev_cls->parameter_definitions ();
for (std::vector<db::DeviceParameterDefinition>::const_iterator i = pd.begin (); i != pd.end (); ++i) {
std::map<std::string, double>::const_iterator v = pv.find (i->name ());
if (v != pv.end ()) {
dev->set_parameter_value (i->id (), v->second);
}
}
ex.expect_end ();
}
}

View File

@ -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 <string>
#include <memory>
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<tl::TextInputStream> mp_stream;
std::vector<std::pair<tl::InputStream *, tl::TextInputStream *> > 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<db::NetlistSpiceReader>
: public tl::type_traits<void>
{
typedef tl::false_tag has_default_constructor;
typedef tl::false_tag has_copy_constructor;
};
}
#endif

View File

@ -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

View File

@ -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<db::Netlist> 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<db::Netlist> 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_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne
"This class has been introduced in version 0.26."
);
Class<db::NetlistReader> db_NetlistReader ("db", "NetlistReader",
gsi::Methods (),
"@hide\n"
);
db::NetlistSpiceReader *new_spice_reader ()
{
return new db::NetlistSpiceReader ();
}
Class<db::NetlistSpiceReader> 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."
);
}

View File

@ -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"
);
}

View File

@ -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

View File

@ -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 ()
{

View File

@ -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
*

View File

@ -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)

17
testdata/algo/nreader1.cir vendored Normal file
View File

@ -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

83
testdata/algo/nreader2.cir vendored Normal file
View File

@ -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

27
testdata/algo/nreader3.cir vendored Normal file
View File

@ -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