WIP: SPICE reader delegate, unit tests + debugging

This commit is contained in:
Matthias Koefferlein 2019-06-22 19:44:33 +02:00
parent d174fb73fd
commit 343e340e22
4 changed files with 195 additions and 49 deletions

View File

@ -55,6 +55,11 @@ void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/)
// .. nothing yet ..
}
bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit_name*/)
{
return false;
}
void NetlistSpiceReaderDelegate::error (const std::string &msg)
{
throw tl::Exception (msg);
@ -160,8 +165,8 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
static const char *allowed_name_chars = "_.:,!+$/&\\#[]|";
NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate, const std::string &captured_subcircuits)
: mp_netlist (0), mp_stream (0), mp_delegate (delegate), m_captured (captured_subcircuits)
NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate)
: mp_netlist (0), mp_stream (0), mp_delegate (delegate)
{
static NetlistSpiceReaderDelegate std_delegate;
if (! delegate) {
@ -297,6 +302,18 @@ void NetlistSpiceReader::unget_line (const std::string &l)
m_stored_line = l;
}
bool NetlistSpiceReader::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 NetlistSpiceReader::read_card ()
{
std::string l = get_line ();
@ -318,7 +335,12 @@ bool NetlistSpiceReader::read_card ()
} else if (ex.test_without_case ("subckt")) {
read_circuit (ex);
std::string nc = read_name (ex);
if (subcircuit_captured (nc)) {
skip_circuit (ex);
} else {
read_circuit (ex, nc);
}
} else if (ex.test_without_case ("ends")) {
@ -347,12 +369,12 @@ bool NetlistSpiceReader::read_card ()
std::string es;
es.push_back (next_char);
if (next_char == 'X' && ! m_captured.match (name)) {
read_subcircuit (ex, name);
} else if (! read_element (ex, es, name)) {
if (! read_element (ex, es, name)) {
warn (tl::sprintf (tl::to_string (tr ("Element type '%c' ignored")), next_char));
}
ex.expect_end ();
} else {
warn (tl::to_string (tr ("Line ignored")));
}
@ -675,58 +697,67 @@ bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &ele
nets.push_back (make_net (*i));
}
return mp_delegate->element (mp_circuit, element, name, tl::to_upper_case (model), value, nets, pv);
if (element == "X" && ! subcircuit_captured (model)) {
if (! pv.empty ()) {
warn (tl::to_string (tr ("Circuit parameters are not allowed currently")));
}
read_subcircuit (name, tl::to_upper_case (model), nets);
return true;
} else {
return mp_delegate->element (mp_circuit, element, name, tl::to_upper_case (model), value, nets, pv);
}
}
void NetlistSpiceReader::read_subcircuit (tl::Extractor &ex, const std::string &sc_name)
void NetlistSpiceReader::read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector<db::Net *> &nets)
{
std::vector<std::string> nn;
std::map<std::string, double> pv;
read_pin_and_parameters (ex, nn, pv);
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 ()) {
if (nets.empty ()) {
error (tl::to_string (tr ("A circuit call needs at least one net")));
}
db::Circuit *cc = mp_netlist->circuit_by_name (nc);
db::Circuit *cc = mp_netlist->circuit_by_name (nc_name);
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->set_name (nc_name);
for (std::vector<db::Net *>::const_iterator i = nets.begin (); i != nets.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 ())));
if (cc->pin_count () != nets.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::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);
for (std::vector<db::Net *>::const_iterator i = nets.begin (); i != nets.end (); ++i) {
sc->connect_pin (i - nets.begin (), *i);
}
ex.expect_end ();
}
void NetlistSpiceReader::read_circuit (tl::Extractor &ex)
void NetlistSpiceReader::skip_circuit (tl::Extractor & /*ex*/)
{
std::string nc = read_name (ex);
while (! at_end ()) {
std::string l = get_line ();
tl::Extractor ex (l.c_str ());
if (ex.test_without_case (".")) {
// control statement
if (ex.test_without_case ("subckt")) {
skip_circuit (ex);
} else if (ex.test_without_case ("ends")) {
break;
}
}
}
}
void NetlistSpiceReader::read_circuit (tl::Extractor &ex, const std::string &nc)
{
std::vector<std::string> nn;
std::map<std::string, double> pv;
read_pin_and_parameters (ex, nn, pv);

View File

@ -26,7 +26,6 @@
#include "dbCommon.h"
#include "dbNetlistReader.h"
#include "tlStream.h"
#include "tlGlobPattern.h"
#include <string>
#include <memory>
@ -57,8 +56,8 @@ public:
*
* The reader delegate can be configured to recieve subcircuit elements too.
* In this case, parameters are allowed.
* Such subcircuits must be included in the captured_subcircuits glob
* pattern when configuring the SPICE reader with the delegate.
* For receiving subcircuit elements, the delegate needs to indicate
* this by returning true upon "wants_subcircuit".
*/
class DB_PUBLIC NetlistSpiceReaderDelegate
: public tl::Object
@ -77,16 +76,23 @@ public:
*/
virtual void finish (db::Netlist *netlist);
/**
* @brief Returns true, if the delegate wants subcircuit elements with this name
*
* The name is always upper case.
*/
virtual bool wants_subcircuit (const std::string &circuit_name);
/**
* @brief Makes a device from an element line
*
* @param circuit is the circuit that is currently read.
* @param element is the element code ("M", "R", ...).
* @param name is the element's name.
* @param model is the model name (may be empty).
* @param value is the default value (e.g. registance for resistors) and may be zero.
* @param nets are the nets given in the element line.
* @param parameters are the parameters of the element statement.
* @param circuit The circuit that is currently read.
* @param element The upper-case element code ("M", "R", ...).
* @param name The element's name.
* @param model The upper-case model name (may be empty).
* @param value The default value (e.g. registance for resistors) and may be zero.
* @param nets The nets given in the element line.
* @param parameters The parameters of the element statement (parameter names are upper case).
*
* The default implementation will create corresponding devices for
* some known elements using the Spice writer's parameter conventions.
@ -108,7 +114,7 @@ class DB_PUBLIC NetlistSpiceReader
: public NetlistReader
{
public:
NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate = 0, const std::string &captured_subcircuits = std::string ());
NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate = 0);
virtual ~NetlistSpiceReader ();
virtual void read (tl::InputStream &stream, db::Netlist &netlist);
@ -121,15 +127,16 @@ private:
std::vector<std::pair<tl::InputStream *, tl::TextInputStream *> > m_streams;
std::auto_ptr<std::map<std::string, db::Net *> > mp_nets_by_name;
std::string m_stored_line;
tl::GlobPattern m_captured;
std::map<std::string, bool> m_captured;
void push_stream (const std::string &path);
void pop_stream ();
bool at_end ();
void read_pin_and_parameters (tl::Extractor &ex, std::vector<std::string> &nn, std::map<std::string, double> &pv);
bool read_element (tl::Extractor &ex, const std::string &element, const std::string &name);
void read_subcircuit (tl::Extractor &ex, const std::string &sc_name);
void read_circuit (tl::Extractor &ex);
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);
void skip_circuit (tl::Extractor &ex);
bool read_card ();
double read_value (tl::Extractor &ex);
std::string read_name (tl::Extractor &ex);
@ -143,6 +150,7 @@ private:
void finish ();
db::Net *make_net (const std::string &name);
void ensure_circuit ();
bool subcircuit_captured (const std::string &nc_name);
};
}

View File

@ -177,3 +177,80 @@ TEST(5_CircuitParameters)
"end;\n"
);
}
class MyNetlistReaderDelegate
: public db::NetlistSpiceReaderDelegate
{
public:
MyNetlistReaderDelegate () : db::NetlistSpiceReaderDelegate () { }
bool wants_subcircuit (const std::string &circuit_name)
{
return circuit_name == "HVNMOS" || circuit_name == "HVPMOS";
}
bool element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> &params)
{
if (element == "X") {
if (nets.size () != 4) {
error (tl::sprintf ("Device subcircuit '%s' requires four nets", model));
}
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (model);
if (! cls) {
cls = new db::DeviceClassMOS4Transistor ();
cls->set_name (model);
circuit->netlist ()->add_device_class (cls);
}
db::Device *device = new db::Device (cls);
device->set_name (name);
circuit->add_device (device);
device->connect_terminal (db::DeviceClassMOS4Transistor::terminal_id_S, nets [0]);
device->connect_terminal (db::DeviceClassMOS4Transistor::terminal_id_G, nets [1]);
device->connect_terminal (db::DeviceClassMOS4Transistor::terminal_id_D, nets [2]);
device->connect_terminal (db::DeviceClassMOS4Transistor::terminal_id_B, nets [3]);
const std::vector<db::DeviceParameterDefinition> &td = cls->parameter_definitions ();
for (std::vector<db::DeviceParameterDefinition>::const_iterator i = td.begin (); i != td.end (); ++i) {
std::map<std::string, double>::const_iterator pi = params.find (i->name ());
if (pi != params.end ()) {
device->set_parameter_value (i->id (), pi->second * 1.5);
}
}
return true;
} else {
return db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
}
}
};
TEST(6_ReaderWithDelegate)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::combine_path (tl::testsrc (), "testdata"), "algo"), "nreader6.cir");
MyNetlistReaderDelegate delegate;
db::NetlistSpiceReader reader (&delegate);
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit SUBCKT ($1=$1,$2=A,$3=VDD,$4=Z,$5=gnd,$6=gnd$1);\n"
" device HVPMOS $1 (S=VDD,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);\n"
" device HVPMOS $2 (S=VDD,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);\n"
" device HVNMOS $3 (S=gnd,G=$3,D=gnd,B=gnd$1) (L=1.695,W=3.18,AS=0,AD=0,PS=9,PD=9);\n"
" device HVNMOS $4 (S=gnd,G=$3,D=Z,B=gnd$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=1.74,PD=1.74);\n"
" device HVNMOS $5 (S=gnd,G=A,D=$3,B=gnd$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64);\n"
"end;\n"
"circuit .TOP ();\n"
" subcircuit SUBCKT SUBCKT ($1=(null),$2=(null),$3=(null),$4=(null),$5=VSS,$6=VSS);\n"
"end;\n"
);
}

30
testdata/algo/nreader6.cir vendored Normal file
View File

@ -0,0 +1,30 @@
* Tests the ability to translate subcircuits into plain devices
.SUBCKT HVNMOS S G D B PARAMS: L=0 W=0 AS=0 AD=0 PS=0 PD=0
MINT S G D B MODEL L=L W=W AS=AS AD=AD PS=PS PD=PD
* parasitic stuff
C1 S G 1.5F*L+5.5*L*W
C2 D G 1.2F*L+5.5*L*W
C3 S B 2.1F*AS+0.2*PS
C4 D B 2.1F*AD+0.2*PD
.ENDS HVNMOS
.SUBCKT HVPMOS S G D B PARAMS: L=0 W=0 AS=0 AD=0 PS=0 PD=0
MINT S G D B MODEL L=L W=W AS=AS AD=AD
* parasitic stuff
C1 S G 0.8F*L+4.2*L*W
C2 D G 0.8F*L+4.2*L*W
C3 S B 2.5F*AS+0.3*PS
C4 D B 2.5F*AD+0.3*PD
.ENDS HVPMOS
.SUBCKT SUBCKT \$1 A VDD Z gnd gnd$1
X$1 VDD \$3 Z \$1 HVPMOS PARAMS: L=0.2 W=1 AS=0.18 AD=0.18 PS=2.16 PD=2.16
X$2 VDD A \$3 \$1 HVPMOS PARAMS: L=0.2 W=1 AS=0.18 AD=0.18 PS=2.16 PD=2.16
X$3 gnd \$3 gnd gnd$1 HVNMOS PARAMS: L=1.13 W=2.12 PS=6 PD=6 AS=0 AD=0
X$4 gnd \$3 Z gnd$1 HVNMOS PARAMS: L=0.4 W=0.4 PS=1.16 PD=1.16 AS=0.19 AD=0.19
X$5 gnd A \$3 gnd$1 HVNMOS PARAMS: L=0.4 W=0.4 PS=1.76 PD=1.76 AS=0.19 AD=0.19
.ENDS SUBCKT
XSUBCKT IN OUT VDD Z VSS VSS SUBCKT