klayout/src/db/db/dbNetlistSpiceReader.cc

1324 lines
32 KiB
C++

/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbNetlistSpiceReader.h"
#include "dbNetlistSpiceReaderExpressionParser.h"
#include "dbNetlistSpiceReaderDelegate.h"
#include "dbNetlist.h"
#include "tlUri.h"
#include "tlFileUtils.h"
#include "tlLog.h"
#include "tlTimer.h"
#include <string>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <cmath>
namespace db
{
// ------------------------------------------------------------------------------------------------------
static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>";
// ------------------------------------------------------------------------------------------------------
void
read_param_card (tl::Extractor &ex, const db::Netlist *netlist, std::map<std::string, tl::Variant> &variables)
{
// Syntax is:
// .param <name> = <value> [ <name> = <value> ... ]
// 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:
SpiceReaderStream ();
SpiceReaderStream (const std::string &lib);
~SpiceReaderStream ();
void set_stream (tl::InputStream &stream);
void set_stream (tl::InputStream *stream);
void close ();
std::pair<std::string, bool> get_line();
int line_number () const;
std::string source () const;
bool at_end () const;
const std::string &lib () const { return m_lib; }
void swap (SpiceReaderStream &other)
{
std::swap (m_lib, other.m_lib);
std::swap (mp_stream, other.mp_stream);
std::swap (m_owns_stream, other.m_owns_stream);
std::swap (mp_text_stream, other.mp_text_stream);
std::swap (m_line_number, other.m_line_number);
std::swap (m_stored_line, other.m_stored_line);
std::swap (m_has_stored_line, other.m_has_stored_line);
}
private:
tl::InputStream *mp_stream;
bool m_owns_stream;
tl::TextInputStream *mp_text_stream;
int m_line_number;
std::string m_stored_line;
bool m_has_stored_line;
std::string m_lib;
};
SpiceReaderStream::SpiceReaderStream ()
: mp_stream (0), m_owns_stream (false), mp_text_stream (0), m_line_number (0), m_stored_line (), m_has_stored_line (false)
{
// .. nothing yet ..
}
SpiceReaderStream::SpiceReaderStream (const std::string &lib)
: mp_stream (0), m_owns_stream (false), mp_text_stream (0), m_line_number (0), m_stored_line (), m_has_stored_line (false), m_lib (lib)
{
// .. nothing yet ..
}
SpiceReaderStream::~SpiceReaderStream ()
{
close ();
}
void
SpiceReaderStream::close ()
{
delete mp_text_stream;
mp_text_stream = 0;
if (m_owns_stream) {
delete mp_stream;
mp_stream = 0;
m_owns_stream = false;
}
}
std::pair<std::string, bool>
SpiceReaderStream::get_line ()
{
if (at_end ()) {
return std::make_pair (std::string (), false);
}
++m_line_number;
std::string l = m_has_stored_line ? m_stored_line : mp_text_stream->get_line ();
m_has_stored_line = false;
m_stored_line.clear ();
while (! mp_text_stream->at_end ()) {
std::string ll = mp_text_stream->get_line ();
tl::Extractor ex (ll.c_str ());
if (! ex.test ("+")) {
m_stored_line = ll;
m_has_stored_line = true;
break;
} else {
++m_line_number;
l += " ";
l += ex.get ();
}
}
return std::make_pair (l, true);
}
int
SpiceReaderStream::line_number () const
{
return m_line_number;
}
std::string
SpiceReaderStream::source () const
{
return mp_stream->source ();
}
bool
SpiceReaderStream::at_end () const
{
return !m_has_stored_line && mp_text_stream->at_end ();
}
void
SpiceReaderStream::set_stream (tl::InputStream &stream)
{
close ();
mp_stream = &stream;
mp_text_stream = new tl::TextInputStream (stream);
m_owns_stream = false;
m_has_stored_line = false;
m_line_number = 0;
}
void
SpiceReaderStream::set_stream (tl::InputStream *stream)
{
close ();
mp_stream = stream;
mp_text_stream = new tl::TextInputStream (*stream);
m_owns_stream = true;
m_has_stored_line = false;
m_line_number = 0;
}
// ------------------------------------------------------------------------------------------------------
struct SpiceCard
{
SpiceCard (int _file_id, int _line, const std::string &_text)
: file_id (_file_id), line (_line), text (_text)
{ }
int file_id;
int line;
std::string text;
};
class SpiceCachedCircuit
{
public:
typedef std::list<SpiceCard> cards_type;
typedef cards_type::const_iterator cards_iterator;
typedef NetlistSpiceReader::parameters_type parameters_type;
typedef std::vector<std::string> pin_list_type;
typedef pin_list_type::const_iterator pin_const_iterator;
SpiceCachedCircuit (const std::string &name)
: m_name (name), m_anonymous (false)
{
// .. nothing yet ..
}
bool is_anonymous () const
{
return m_anonymous;
}
void set_anonymous (bool f)
{
m_anonymous = f;
}
const std::string &name () const
{
return m_name;
}
void set_parameters (const parameters_type &pv)
{
m_parameters = pv;
}
const parameters_type &parameters () const
{
return m_parameters;
}
void make_parameter (const std::string &name, const tl::Variant &value)
{
for (auto p = m_pins.begin (); p != m_pins.end (); ++p) {
if (*p == name) {
// remove pin and make parameter
m_pins.erase (p);
break;
}
}
m_parameters [name] = value;
}
cards_iterator begin_cards () const
{
return m_cards.begin ();
}
cards_iterator end_cards () const
{
return m_cards.end ();
}
void add_card (const SpiceCard &card)
{
m_cards.push_back (card);
}
size_t pin_count () const
{
return m_pins.size ();
}
pin_const_iterator begin_pins () const
{
return m_pins.begin ();
}
pin_const_iterator end_pins () const
{
return m_pins.end ();
}
void set_pins (const pin_list_type &pins)
{
m_pins = pins;
}
void set_pins (pin_list_type &&pins)
{
m_pins = std::move (pins);
}
private:
std::string m_name;
parameters_type m_parameters;
pin_list_type m_pins;
cards_type m_cards;
bool m_anonymous;
};
static std::string
read_name (tl::Extractor &ex, const db::Netlist *netlist)
{
std::string n;
ex.read_word_or_quoted (n, allowed_name_chars);
return netlist->normalize_name (n);
}
class SpiceCircuitDict
{
public:
typedef NetlistSpiceReader::parameters_type parameters_type;
typedef std::map<std::string, SpiceCachedCircuit *> circuits_type;
typedef circuits_type::const_iterator circuits_iterator;
typedef std::vector<std::string> global_nets_type;
typedef global_nets_type::const_iterator global_nets_iterator;
SpiceCircuitDict (NetlistSpiceReader *reader, Netlist *netlist, NetlistSpiceReaderDelegate *delegate);
~SpiceCircuitDict ();
void read (tl::InputStream &stream);
void finish ();
circuits_iterator begin_circuits () const
{
return m_cached_circuits.begin ();
}
circuits_iterator end_circuits () const
{
return m_cached_circuits.end ();
}
bool is_top_circuit (const std::string &name) const
{
return m_called_circuits.find (name) == m_called_circuits.end ();
}
const SpiceCachedCircuit *anonymous_top_level_circuit () const
{
return mp_anonymous_top_level_circuit;
}
global_nets_iterator begin_global_nets () const
{
return m_global_nets.begin ();
}
global_nets_iterator end_global_nets () const
{
return m_global_nets.end ();
}
const std::string &file_path (int file_id) const;
const SpiceCachedCircuit *cached_circuit (const std::string &name) const;
SpiceCachedCircuit *create_cached_circuit (const std::string &name);
private:
NetlistSpiceReader *mp_reader;
Netlist *mp_netlist;
tl::weak_ptr<NetlistSpiceReaderDelegate> mp_delegate;
std::vector<std::string> m_paths;
std::map<std::string, int> m_file_id_per_path;
std::list<SpiceReaderStream> m_streams;
std::list<std::string> m_in_lib;
SpiceReaderStream m_stream;
int m_file_id;
std::map<std::string, SpiceCachedCircuit *> m_cached_circuits;
SpiceCachedCircuit *mp_circuit;
SpiceCachedCircuit *mp_anonymous_top_level_circuit;
std::set<std::string> m_called_circuits;
NetlistSpiceReader::parameters_type m_variables;
std::set<std::string> m_global_net_names;
std::vector<std::string> m_global_nets;
void push_stream (const std::string &path, const std::string &lib = std::string ());
void pop_stream ();
bool at_end ();
void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector<db::Net *> &nets);
void read_circuit (tl::Extractor &ex, const std::string &name);
bool read_card ();
void read_options (tl::Extractor &ex);
void ensure_circuit ();
std::string get_line ();
void error (const std::string &msg);
void warn (const std::string &msg);
int file_id (const std::string &path);
};
SpiceCircuitDict::SpiceCircuitDict (NetlistSpiceReader *reader, Netlist *netlist, NetlistSpiceReaderDelegate *delegate)
: mp_reader (reader), mp_netlist (netlist), mp_delegate (delegate)
{
m_file_id = -1;
mp_circuit = mp_anonymous_top_level_circuit = 0;
}
SpiceCircuitDict::~SpiceCircuitDict ()
{
for (auto c = m_cached_circuits.begin (); c != m_cached_circuits.end (); ++c) {
delete c->second;
}
m_cached_circuits.clear ();
mp_reader = 0;
mp_delegate = 0;
}
const std::string &
SpiceCircuitDict::file_path (int file_id) const
{
if (file_id < 0 || file_id > int (m_paths.size ())) {
static std::string empty;
return empty;
} else {
return m_paths [file_id];
}
}
int
SpiceCircuitDict::file_id (const std::string &path)
{
auto ip = m_file_id_per_path.find (path);
if (ip != m_file_id_per_path.end ()) {
return ip->second;
}
int id = int (m_paths.size ());
m_file_id_per_path.insert (std::make_pair (path, id));
m_paths.push_back (path);
return id;
}
const SpiceCachedCircuit *
SpiceCircuitDict::cached_circuit (const std::string &name) const
{
auto c = m_cached_circuits.find (name);
return c == m_cached_circuits.end () ? 0 : c->second;
}
SpiceCachedCircuit *
SpiceCircuitDict::create_cached_circuit (const std::string &name)
{
auto c = m_cached_circuits.find (name);
if (c != m_cached_circuits.end ()) {
return c->second;
}
SpiceCachedCircuit *cc = new SpiceCachedCircuit (name);
m_cached_circuits.insert (std::make_pair (name, cc));
return cc;
}
void
SpiceCircuitDict::read (tl::InputStream &stream)
{
try {
m_stream.set_stream (stream);
mp_circuit = 0;
mp_anonymous_top_level_circuit = 0;
m_called_circuits.clear ();
m_variables.clear ();
m_global_net_names.clear ();
m_global_nets.clear ();
m_file_id = file_id (stream.source ());
while (! at_end ()) {
read_card ();
}
} catch (tl::Exception &ex) {
// Add a location to the exception
std::string fmt_msg = ex.msg () + tl::sprintf (tl::to_string (tr (" in %s, line %d")), m_stream.source (), m_stream.line_number ());
throw tl::Exception (fmt_msg);
}
}
void
SpiceCircuitDict::push_stream (const std::string &path, const std::string &lib)
{
tl::URI current_uri (m_stream.source ());
tl::URI new_uri (path);
tl::InputStream *istream;
if (current_uri.scheme ().empty () && new_uri.scheme ().empty ()) {
if (tl::is_absolute (path)) {
istream = new tl::InputStream (path);
} else {
istream = new tl::InputStream (tl::combine_path (tl::dirname (m_stream.source ()), path));
}
} else {
istream = new tl::InputStream (current_uri.resolved (new_uri).to_abstract_path ());
}
m_streams.push_back (SpiceReaderStream (lib));
m_streams.back ().swap (m_stream);
m_stream.set_stream (istream);
m_file_id = file_id (m_stream.source ());
}
void
SpiceCircuitDict::pop_stream ()
{
if (! m_streams.empty ()) {
m_stream.swap (m_streams.back ());
m_streams.pop_back ();
m_file_id = file_id (m_stream.source ());
}
}
bool
SpiceCircuitDict::at_end ()
{
return m_stream.at_end () && m_streams.empty ();
}
void
SpiceCircuitDict::error (const std::string &msg)
{
throw tl::Exception (msg);
}
void
SpiceCircuitDict::warn (const std::string &msg)
{
std::string fmt_msg = tl::sprintf ("%s in %s, line %d", msg, m_stream.source (), m_stream.line_number ());
tl::warn << fmt_msg;
}
std::string
SpiceCircuitDict::get_line ()
{
std::pair<std::string, bool> lp;
while (true) {
lp = m_stream.get_line ();
if (! lp.second) {
if (m_streams.empty ()) {
break;
} else {
if (! m_stream.lib ().empty ()) {
m_in_lib.pop_back ();
}
pop_stream ();
}
} else {
bool consider_line = m_in_lib.empty () || (! m_stream.lib ().empty () && m_stream.lib () == m_in_lib.back ());
tl::Extractor ex (lp.first.c_str ());
if (ex.test_without_case (".include") || ex.test_without_case (".inc")) {
std::string path;
ex.read_word_or_quoted (path, allowed_name_chars);
if (consider_line) {
std::string libname = m_stream.lib ();
push_stream (path, libname);
if (! libname.empty ()) {
m_in_lib.push_back (libname);
}
}
ex.expect_end ();
} else if (ex.test_without_case (".lib")) {
std::string path_or_libname;
ex.read_word_or_quoted (path_or_libname, allowed_name_chars);
if (! ex.at_end ()) {
std::string libname;
ex.read_word_or_quoted (libname, allowed_name_chars);
if (consider_line) {
libname = mp_netlist->normalize_name (libname);
push_stream (path_or_libname, libname);
if (! libname.empty ()) {
m_in_lib.push_back (std::string ());
}
}
} else {
std::string libname = mp_netlist->normalize_name (path_or_libname);
m_in_lib.push_back (libname);
ex.expect_end ();
}
} else if (ex.test_without_case (".endl")) {
if (! m_in_lib.empty ()) {
m_in_lib.pop_back ();
} else {
warn (tl::to_string (tr ("Ignoring .endl without .lib")));
}
ex.expect_end ();
} else if (ex.at_end () || ex.test ("*")) {
// skip empty and comment lines
} else if (consider_line) {
break;
}
}
}
return lp.first;
}
bool
SpiceCircuitDict::read_card ()
{
std::string l = get_line ();
if (l.empty ()) {
return false;
}
tl::Extractor ex (l.c_str ());
std::string name;
if (ex.test_without_case (".model")) {
// ignore model statements
} 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);
}
}
} else if (ex.test_without_case (".subckt")) {
std::string nc = read_name (ex, mp_netlist);
read_circuit (ex, nc);
} else if (ex.test_without_case (".options")) {
read_options (ex);
} else if (ex.test_without_case (".ends")) {
return true;
} else if (ex.test_without_case (".end")) {
// ignore end statements
} else if (ex.test_without_case (".param")) {
read_param_card (ex, mp_netlist, m_variables);
ensure_circuit ();
mp_circuit->add_card (SpiceCard (m_file_id, m_stream.line_number (), l));
} else if (ex.test (".")) {
if (! mp_delegate->control_statement (l)) {
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.try_read_word (name)) {
ensure_circuit ();
if (ex.test ("=")) {
name = mp_netlist->normalize_name (name);
tl::Variant value = NetlistSpiceReaderDelegate::read_value (ex, m_variables);
m_variables [name] = value;
mp_circuit->make_parameter (name, value);
} else if (name[0] == 'X') {
// register circuit calls so we can figure out the top level circuits
tl::Extractor ex2 (l.c_str ());
ex2.skip ();
++ex2;
std::vector<std::string> nn;
parameters_type pv;
mp_delegate->parse_element_components (ex2.get (), nn, pv, m_variables);
if (! nn.empty ()) {
m_called_circuits.insert (nn.back ());
}
}
mp_circuit->add_card (SpiceCard (m_file_id, m_stream.line_number (), l));
} else {
warn (tl::to_string (tr ("Line ignored")));
}
return false;
}
void
SpiceCircuitDict::read_options (tl::Extractor &ex)
{
while (! ex.at_end ()) {
std::string n;
ex.read_word_or_quoted (n, allowed_name_chars);
n = tl::to_lower_case (n);
double v = 0.0;
std::string w;
if (ex.test ("=")) {
if (ex.try_read (v)) {
// take value
} else {
// skip until end or next space
ex.skip ();
while (! ex.at_end () && ! isspace (*ex)) {
++ex;
}
}
}
// TODO: further options?
const double min_value = 1e-18;
if (n == "scale") {
if (v > min_value) {
mp_delegate->options ().scale = v;
}
} else if (n == "defad") {
if (v > min_value) {
mp_delegate->options ().defad = v;
}
} else if (n == "defas") {
if (v > min_value) {
mp_delegate->options ().defas = v;
}
} else if (n == "defl") {
if (v > min_value) {
mp_delegate->options ().defl = v;
}
} else if (n == "defw") {
if (v > min_value) {
mp_delegate->options ().defw = v;
}
}
}
}
void
SpiceCircuitDict::ensure_circuit ()
{
if (! mp_circuit) {
// TODO: make top name configurable
mp_circuit = new SpiceCachedCircuit (".TOP");
m_cached_circuits.insert (std::make_pair (mp_circuit->name (), mp_circuit));
mp_anonymous_top_level_circuit = mp_circuit;
}
}
void
SpiceCircuitDict::read_circuit (tl::Extractor &ex, const std::string &nc)
{
std::vector<std::string> nn;
NetlistSpiceReader::parameters_type pv;
mp_delegate->parse_element_components (ex.skip (), nn, pv, m_variables);
if (cached_circuit (nc)) {
error (tl::sprintf (tl::to_string (tr ("Redefinition of circuit %s")), nc));
}
SpiceCachedCircuit *cc = create_cached_circuit (nc);
cc->set_pins (nn);
cc->set_parameters (pv);
std::swap (cc, mp_circuit);
NetlistSpiceReader::parameters_type vars = pv;
m_variables.swap (vars);
while (! at_end ()) {
if (read_card ()) {
break;
}
}
std::swap (cc, mp_circuit);
m_variables.swap (vars);
}
void
SpiceCircuitDict::finish ()
{
m_streams.clear ();
m_stream.close ();
}
// ------------------------------------------------------------------------------------------------------
class SpiceNetlistBuilder
{
public:
typedef NetlistSpiceReader::parameters_type parameters_type;
SpiceNetlistBuilder (SpiceCircuitDict *dict, Netlist *netlist, NetlistSpiceReaderDelegate *delegate);
void set_strict (bool s)
{
m_strict = s;
}
void build ();
private:
SpiceCircuitDict *mp_dict;
tl::weak_ptr<NetlistSpiceReaderDelegate> mp_delegate;
Netlist *mp_netlist;
bool m_strict;
const SpiceCachedCircuit *mp_circuit;
std::map<const SpiceCachedCircuit *, std::map<parameters_type, db::Circuit *> > m_circuits;
db::Circuit *mp_netlist_circuit;
db::Circuit *mp_anonymous_top_level_netlist_circuit;
std::unique_ptr<std::map<std::string, db::Net *> > mp_nets_by_name;
std::map<std::string, bool> m_captured;
NetlistSpiceReader::parameters_type m_variables;
const SpiceCard *mp_current_card;
db::Circuit *circuit_for (const SpiceCachedCircuit *cached_circuit, const parameters_type &pv);
void register_circuit_for (const SpiceCachedCircuit *cc, const parameters_type &pv, db::Circuit *circuit, bool anonymous_top_level);
Circuit *build_circuit (const SpiceCachedCircuit *circuit, const parameters_type &pv, bool anonymous_top_level = false);
std::string get_line ();
void error (const std::string &msg);
void warn (const std::string &msg);
Net *make_net(const std::string &name);
void process_card (const SpiceCard &card);
bool subcircuit_captured (const std::string &nc_name);
bool process_element (tl::Extractor &ex, const std::string &prefix, const std::string &name);
void build_global_nets ();
};
SpiceNetlistBuilder::SpiceNetlistBuilder (SpiceCircuitDict *dict, Netlist *netlist, NetlistSpiceReaderDelegate *delegate)
: mp_dict (dict), mp_delegate (delegate), mp_netlist (netlist), m_strict (true)
{
mp_circuit = 0;
mp_netlist_circuit = 0;
mp_anonymous_top_level_netlist_circuit = 0;
mp_current_card = 0;
}
void
SpiceNetlistBuilder::error (const std::string &msg)
{
throw tl::Exception (msg);
}
void
SpiceNetlistBuilder::warn (const std::string &msg)
{
if (mp_current_card) {
std::string fmt_msg = tl::sprintf ("%s in %s, line %d", msg, mp_dict->file_path (mp_current_card->file_id), mp_current_card->line);
tl::warn << fmt_msg;
} else {
tl::warn << msg;
}
}
db::Circuit *
SpiceNetlistBuilder::circuit_for (const SpiceCachedCircuit *cc, const parameters_type &pv)
{
auto c = m_circuits.find (cc);
if (c == m_circuits.end ()) {
return 0;
}
auto cp = c->second.find (pv);
if (cp == c->second.end ()) {
return 0;
}
return cp->second;
}
void
SpiceNetlistBuilder::register_circuit_for (const SpiceCachedCircuit *cc, const parameters_type &pv, db::Circuit *circuit, bool anonymous_top_level)
{
m_circuits [cc][pv] = circuit;
if (anonymous_top_level) {
mp_anonymous_top_level_netlist_circuit = circuit;
}
}
void
SpiceNetlistBuilder::build ()
{
try {
m_variables.clear ();
mp_netlist_circuit = 0;
mp_anonymous_top_level_netlist_circuit = 0;
mp_circuit = 0;
mp_current_card = 0;
m_captured.clear ();
mp_delegate->do_start ();
for (auto c = mp_dict->begin_circuits (); c != mp_dict->end_circuits (); ++c) {
if (mp_dict->is_top_circuit (c->first) && ! subcircuit_captured (c->first)) {
// we have a top circuit candidate
build_circuit (c->second, c->second->parameters (), c->second == mp_dict->anonymous_top_level_circuit ());
}
}
build_global_nets ();
mp_delegate->do_finish ();
} catch (tl::Exception &ex) {
// add a source location to the exception
if (mp_current_card) {
std::string fmt_msg = ex.msg () + tl::sprintf (tl::to_string (tr (" in %s, line %d")), mp_dict->file_path (mp_current_card->file_id), mp_current_card->line);
throw tl::Exception (fmt_msg);
} else {
throw;
}
}
}
static std::string
make_circuit_name (const std::string &name, const NetlistSpiceReader::parameters_type &pv)
{
std::string res = name;
res += "(";
for (auto p = pv.begin (); p != pv.end (); ++p) {
if (p != pv.begin ()) {
res += ",";
}
res += p->first;
res += "=";
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 += p->second.to_string ();
}
}
res += ")";
return res;
}
db::Circuit *
SpiceNetlistBuilder::build_circuit (const SpiceCachedCircuit *cc, const parameters_type &pv, bool anonymous_top_level)
{
db::Circuit *c = circuit_for (cc, pv);
if (c) {
return c;
}
c = new db::Circuit ();
mp_netlist->add_circuit (c);
if (pv.empty ()) {
c->set_name (cc->name ());
} else {
c->set_name (make_circuit_name (cc->name (), pv));
}
register_circuit_for (cc, pv, c, anonymous_top_level);
std::unique_ptr<std::map<std::string, db::Net *> > n2n (mp_nets_by_name.release ());
mp_nets_by_name.reset (0);
NetlistSpiceReader::parameters_type vars = cc->parameters ();
for (auto p = pv.begin (); p != pv.end (); ++p) {
vars [p->first] = p->second;
}
std::swap (vars, m_variables);
std::swap (c, mp_netlist_circuit);
std::swap (cc, mp_circuit);
// produce the explicit pins
for (auto i = mp_circuit->begin_pins (); i != mp_circuit->end_pins (); ++i) {
std::string net_name = mp_delegate->translate_net_name (mp_netlist->normalize_name (*i));
db::Net *net = make_net (net_name);
// use the net name to name the pin (otherwise SPICE pins are always unnamed)
size_t pin_id = i - mp_circuit->begin_pins ();
if (! i->empty ()) {
mp_netlist_circuit->add_pin (net->name ());
} else {
mp_netlist_circuit->add_pin (std::string ());
}
mp_netlist_circuit->connect_pin (pin_id, net);
}
for (auto card = mp_circuit->begin_cards (); card != mp_circuit->end_cards (); ++card) {
mp_current_card = card.operator-> ();
process_card (*card);
}
mp_current_card = 0;
mp_nets_by_name.reset (n2n.release ());
std::swap (cc, mp_circuit);
std::swap (c, mp_netlist_circuit);
std::swap (vars, m_variables);
return c;
}
db::Net *
SpiceNetlistBuilder::make_net (const std::string &name)
{
if (! mp_nets_by_name.get ()) {
mp_nets_by_name.reset (new std::map<std::string, db::Net *> ());
}
std::map<std::string, db::Net *>::const_iterator n2n = mp_nets_by_name->find (name);
db::Net *net = 0;
if (n2n == mp_nets_by_name->end ()) {
net = new db::Net ();
net->set_name (name);
mp_netlist_circuit->add_net (net);
mp_nets_by_name->insert (std::make_pair (name, net));
} else {
net = n2n->second;
}
return net;
}
void
SpiceNetlistBuilder::process_card (const SpiceCard &card)
{
tl::Extractor ex (card.text.c_str ());
std::string name;
if (ex.try_read_word (name) && ex.test ("=")) {
m_variables.insert (std::make_pair (mp_netlist->normalize_name (name), NetlistSpiceReaderDelegate::read_value (ex, m_variables)));
} else {
ex = tl::Extractor (card.text.c_str ());
ex.skip ();
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));
++ex;
name = read_name (ex, mp_netlist);
if (! process_element (ex, prefix, name)) {
warn (tl::sprintf (tl::to_string (tr ("Element type '%s' ignored")), prefix));
}
} else {
warn (tl::to_string (tr ("Line ignored")));
}
}
}
bool
SpiceNetlistBuilder::subcircuit_captured (const std::string &nc_name)
{
std::map<std::string, bool>::const_iterator c = m_captured.find (nc_name);
if (c != m_captured.end ()) {
return c->second;
} else {
bool cap = mp_delegate->wants_subcircuit (nc_name);
m_captured.insert (std::make_pair (nc_name, cap));
return cap;
}
}
bool
SpiceNetlistBuilder::process_element (tl::Extractor &ex, const std::string &prefix, const std::string &name)
{
// generic parse
std::vector<std::string> nn;
NetlistSpiceReader::parameters_type pv;
std::string model;
double value = 0.0;
mp_delegate->parse_element (ex.skip (), prefix, model, value, nn, pv, m_variables);
model = mp_netlist->normalize_name (model);
std::vector<db::Net *> nets;
for (std::vector<std::string>::const_iterator i = nn.begin (); i != nn.end (); ++i) {
nets.push_back (make_net (mp_delegate->translate_net_name (*i)));
}
if (prefix == "X" && ! subcircuit_captured (model)) {
const db::SpiceCachedCircuit *cc = mp_dict->cached_circuit (model);
if (! cc) {
if (m_strict) {
error (tl::sprintf (tl::to_string (tr ("Subcircuit '%s' not found in netlist")), model));
} else {
db::SpiceCachedCircuit *cc_nc = mp_dict->create_cached_circuit (model);
cc_nc->set_anonymous (true);
cc = cc_nc;
std::vector<std::string> pins;
pins.resize (nn.size ());
cc_nc->set_pins (pins);
}
}
if (! cc->is_anonymous ()) {
// issue warnings on unknown parameters which are skipped otherwise
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));
}
}
}
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 (nets.size ())));
}
db::Circuit *c = build_circuit (cc, pv);
db::SubCircuit *sc = new db::SubCircuit (c, name);
mp_netlist_circuit->add_subcircuit (sc);
for (std::vector<db::Net *>::const_iterator i = nets.begin (); i != nets.end (); ++i) {
sc->connect_pin (i - nets.begin (), *i);
}
return true;
} else {
return mp_delegate->element (mp_netlist_circuit, prefix, name, model, value, nets, pv);
}
}
void
SpiceNetlistBuilder::build_global_nets ()
{
for (auto gn = mp_dict->begin_global_nets (); gn != mp_dict->end_global_nets (); ++gn) {
for (auto c = mp_netlist->begin_bottom_up (); c != mp_netlist->end_bottom_up (); ++c) {
if (c.operator-> () == mp_anonymous_top_level_netlist_circuit) {
// no pins for the anonymous top circuit
continue;
}
db::Net *net = c->net_by_name (*gn);
if (! net || net->pin_count () > 0) {
// only add a pin for a global net if there is a net with this name
// don't add a pin if it already has one
continue;
}
const db::Pin &pin = c->add_pin (*gn);
c->connect_pin (pin.id (), net);
for (db::Circuit::refs_iterator r = c->begin_refs (); r != c->end_refs (); ++r) {
db::SubCircuit &sc = *r;
db::Net *pnet = sc.circuit ()->net_by_name (*gn);
if (! pnet) {
pnet = new db::Net ();
pnet->set_name (*gn);
sc.circuit ()->add_net (pnet);
}
sc.connect_pin (pin.id (), pnet);
}
}
}
}
// ------------------------------------------------------------------------------------------------------
NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate)
: mp_delegate (delegate), m_strict (false)
{
if (! delegate) {
mp_default_delegate.reset (new NetlistSpiceReaderDelegate ());
mp_delegate.reset (mp_default_delegate.get ());
}
}
NetlistSpiceReader::~NetlistSpiceReader ()
{
// .. nothing yet ..
}
void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist)
{
tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading netlist ")) + stream.source ());
try {
mp_delegate->set_netlist (&netlist);
// SPICE netlists are case insensitive
netlist.set_case_sensitive (false);
SpiceCircuitDict dict (this, &netlist, mp_delegate.get ());
try {
dict.read (stream);
dict.finish ();
} catch (...) {
dict.finish ();
throw;
}
SpiceNetlistBuilder builder (&dict, &netlist, mp_delegate.get ());
builder.set_strict (m_strict);
builder.build ();
mp_delegate->set_netlist (0);
} catch (...) {
mp_delegate->set_netlist (0);
throw;
}
}
}