WIP: reader delegate - GSI binding, tests.

This commit is contained in:
Matthias Koefferlein 2019-06-22 22:03:32 +02:00
parent 343e340e22
commit 621c3f74ed
6 changed files with 280 additions and 4 deletions

View File

@ -97,14 +97,14 @@ public:
* The default implementation will create corresponding devices for
* some known elements using the Spice writer's parameter conventions.
*
* This return returns true, if the element was read.
* This method returns true, if the element was read.
*/
virtual 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);
/**
* @brief Produces an error with the given message
*/
void error (const std::string &msg);
virtual void error (const std::string &msg);
};
/**

View File

@ -1487,7 +1487,25 @@ public:
gsi::Callback cb_write_device;
};
static void write_header_fb (const db::NetlistSpiceWriterDelegate *delegate)
{
delegate->write_header ();
}
static void write_device_intro_fb (const db::NetlistSpiceWriterDelegate *delegate, const db::DeviceClass &ccls)
{
delegate->write_device_intro (ccls);
}
static void write_device_fb (const db::NetlistSpiceWriterDelegate *delegate, const db::Device &cdev)
{
delegate->write_device (cdev);
}
Class<NetlistSpiceWriterDelegateImpl> db_NetlistSpiceWriterDelegate ("db", "NetlistSpiceWriterDelegate",
gsi::method_ext ("write_header", &write_header_fb, "@hide") +
gsi::method_ext ("write_device_intro", &write_device_intro_fb, "@hide") +
gsi::method_ext ("write_device", &write_device_fb, "@hide") +
gsi::callback ("write_header", &NetlistSpiceWriterDelegateImpl::write_header, &NetlistSpiceWriterDelegateImpl::cb_write_header,
"@brief Writes the text at the beginning of the SPICE netlist\n"
"Reimplement this method to insert your own text at the beginning of the file"
@ -1651,14 +1669,212 @@ Class<db::NetlistReader> db_NetlistReader ("db", "NetlistReader",
"@hide\n"
);
/**
* @brief A SPICE reader delegate base class for reimplementation
*/
class NetlistSpiceReaderDelegateImpl
: public db::NetlistSpiceReaderDelegate, public gsi::ObjectBase
{
public:
NetlistSpiceReaderDelegateImpl ()
: db::NetlistSpiceReaderDelegate ()
{
// .. nothing yet ..
}
virtual void error (const std::string &msg)
{
// doing this avoids passing exceptions through script code which spoils the message
// (the exception will be decorated with a stack trace). TODO: a better solution was
// to define a specific exception type for "raw exception".
m_error = msg;
db::NetlistSpiceReaderDelegate::error (msg);
}
virtual void start (db::Netlist *netlist)
{
try {
m_error.clear ();
if (cb_start.can_issue ()) {
cb_start.issue<db::NetlistSpiceReaderDelegate, db::Netlist *> (&db::NetlistSpiceReaderDelegate::start, netlist);
} else {
db::NetlistSpiceReaderDelegate::start (netlist);
}
} catch (tl::Exception &) {
if (! m_error.empty ()) {
db::NetlistSpiceReaderDelegate::error (m_error);
} else {
throw;
}
}
}
virtual void finish (db::Netlist *netlist)
{
try {
m_error.clear ();
if (cb_finish.can_issue ()) {
cb_finish.issue<db::NetlistSpiceReaderDelegate, db::Netlist *> (&db::NetlistSpiceReaderDelegate::finish, netlist);
} else {
db::NetlistSpiceReaderDelegate::finish (netlist);
}
} catch (tl::Exception &) {
if (! m_error.empty ()) {
db::NetlistSpiceReaderDelegate::error (m_error);
} else {
throw;
}
}
}
virtual bool wants_subcircuit (const std::string &circuit_name)
{
try {
m_error.clear ();
if (cb_wants_subcircuit.can_issue ()) {
return cb_wants_subcircuit.issue<db::NetlistSpiceReaderDelegate, bool, const std::string &> (&db::NetlistSpiceReaderDelegate::wants_subcircuit, circuit_name);
} else {
return db::NetlistSpiceReaderDelegate::wants_subcircuit (circuit_name);
}
} catch (tl::Exception &) {
if (! m_error.empty ()) {
db::NetlistSpiceReaderDelegate::error (m_error);
} else {
throw;
}
return false;
}
}
virtual 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)
{
try {
m_error.clear ();
if (cb_element.can_issue ()) {
return cb_element.issue<db::NetlistSpiceReaderDelegate, bool, db::Circuit *, const std::string &, const std::string &, const std::string &, double, const std::vector<db::Net *> &, const std::map<std::string, double> &> (&db::NetlistSpiceReaderDelegate::element, circuit, element, name, model, value, nets, params);
} else {
return db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
}
} catch (tl::Exception &) {
if (! m_error.empty ()) {
db::NetlistSpiceReaderDelegate::error (m_error);
} else {
throw;
}
return false;
}
}
gsi::Callback cb_start;
gsi::Callback cb_finish;
gsi::Callback cb_wants_subcircuit;
gsi::Callback cb_element;
private:
std::string m_error;
};
static void start_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist)
{
delegate->db::NetlistSpiceReaderDelegate::start (netlist);
}
static void finish_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist)
{
delegate->db::NetlistSpiceReaderDelegate::finish (netlist);
}
static bool wants_subcircuit_fb (db::NetlistSpiceReaderDelegate *delegate, const std::string &model)
{
return delegate->db::NetlistSpiceReaderDelegate::wants_subcircuit (model);
}
static bool element_fb (db::NetlistSpiceReaderDelegate *delegate, 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)
{
return delegate->db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
}
Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "NetlistSpiceReaderDelegate",
gsi::method_ext ("start", &start_fb, "@hide") +
gsi::method_ext ("finish", &finish_fb, "@hide") +
gsi::method_ext ("wants_subcircuit", &wants_subcircuit_fb, "@hide") +
gsi::method_ext ("element", &element_fb, "@hide") +
gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"),
"@brief This method is called when the reader starts reading a netlist\n"
) +
gsi::callback ("finish", &NetlistSpiceReaderDelegateImpl::finish, &NetlistSpiceReaderDelegateImpl::cb_finish, gsi::arg ("netlist"),
"@brief This method is called when the reader is done reading a netlist successfully\n"
) +
gsi::callback ("wants_subcircuit", &NetlistSpiceReaderDelegateImpl::wants_subcircuit, &NetlistSpiceReaderDelegateImpl::cb_wants_subcircuit, gsi::arg ("circuit_name"),
"@brief Returns true, if the delegate wants subcircuit elements with this name\n"
"The name is always upper case.\n"
) +
gsi::callback ("element", &NetlistSpiceReaderDelegateImpl::element, &NetlistSpiceReaderDelegateImpl::cb_element,
gsi::arg ("circuit"), gsi::arg ("element"), gsi::arg ("name"), gsi::arg ("model"), gsi::arg ("value"), gsi::arg ("nets"), gsi::arg ("parameters"),
"@brief Makes a device from an element line\n"
"@param circuit The circuit that is currently read.\n"
"@param element The upper-case element code (\"M\", \"R\", ...).\n"
"@param name The element's name.\n"
"@param model The upper-case model name (may be empty).\n"
"@param value The default value (e.g. registance for resistors) and may be zero.\n"
"@param nets The nets given in the element line.\n"
"@param parameters The parameters of the element statement (parameter names are upper case).\n"
"\n"
"The default implementation will create corresponding devices for\n"
"some known elements using the Spice writer's parameter conventions.\n"
"\n"
"The method must return true, if the element was was understood and false otherwise.\n"
) +
gsi::method ("error", &NetlistSpiceReaderDelegateImpl::error, gsi::arg ("msg"),
"@brief Issues an error with the given message.\n"
"Use this method to generate an error."
),
"@brief Provides a delegate for the SPICE reader for translating device statements\n"
"Supply a customized class to provide a specialized reading scheme for devices. "
"You need a customized class if you want to implement device reading from model subcircuits or to "
"translate device parameters.\n"
"\n"
"See \\NetlistSpiceReader for more details.\n"
"\n"
"This class has been introduced in version 0.26."
);
namespace {
class NetlistSpiceReaderWithOwnership
: public db::NetlistSpiceReader
{
public:
NetlistSpiceReaderWithOwnership (NetlistSpiceReaderDelegateImpl *delegate)
: db::NetlistSpiceReader (delegate), m_ownership (delegate)
{
if (delegate) {
delegate->keep ();
}
}
private:
tl::shared_ptr<NetlistSpiceReaderDelegateImpl> m_ownership;
};
}
db::NetlistSpiceReader *new_spice_reader ()
{
return new db::NetlistSpiceReader ();
}
db::NetlistSpiceReader *new_spice_reader2 (NetlistSpiceReaderDelegateImpl *delegate)
{
return new NetlistSpiceReaderWithOwnership (delegate);
}
Class<db::NetlistSpiceReader> db_NetlistSpiceReader (db_NetlistReader, "db", "NetlistSpiceReader",
gsi::constructor ("new", &new_spice_reader,
"@brief Creates a new reader.\n"
) +
gsi::constructor ("new", &new_spice_reader2,
"@brief Creates a new reader with a delegate.\n"
),
"@brief Implements a netlist Reader for the SPICE format.\n"
"Use the SPICE reader like this:\n"
@ -1669,8 +1885,65 @@ Class<db::NetlistSpiceReader> db_NetlistSpiceReader (db_NetlistReader, "db", "Ne
"netlist.read(path, reader)\n"
"@/code\n"
"\n"
"The translation of SPICE elements can be tailored by providing a \\NetlistSpiceReaderDelegate class. "
"This allows translating of device parameters and mapping of some subcircuits to devices.\n"
"\n"
"The following example is a delegate that turns subcircuits called HVNMOS and HVPMOS into "
"MOS4 devices with the parameters scaled by 1.5:\n"
"\n"
"@code\n"
"class MyDelegate < RBA::NetlistSpiceReaderDelegate\n"
"\n"
" # says we want to catch these subcircuits as devices\n"
" def wants_subcircuit(name)\n"
" name == \"HVNMOS\" || name == \"HVPMOS\"\n"
" end\n"
"\n"
" # translate the element\n"
" def element(circuit, el, name, model, value, nets, params)\n"
"\n"
" if el != \"X\"\n"
" # all other elements are left to the standard implementation\n"
" return super\n"
" end\n"
"\n"
" if nets.size != 4\n"
" error(\"Subcircuit #{model} needs four nodes\")\n"
" end\n"
"\n"
" # provide a device class\n"
" cls = circuit.netlist.device_class_by_name(model)\n"
" if ! cls\n"
" cls = RBA::DeviceClassMOS4Transistor::new\n"
" cls.name = model\n"
" circuit.netlist.add(cls)\n"
" end\n"
"\n"
" # create a device\n"
" device = circuit.create_device(cls, name)\n"
"\n"
" # and configure the device\n"
" [ \"S\", \"G\", \"D\", \"B\" ].each_with_index do |t,index|\n"
" device.connect_terminal(t, nets[index])\n"
" end\n"
" params.each do |p,value|\n"
" device.set_parameter(p, value * 1.5)\n"
" end\n"
"\n"
" end\n"
"\n"
"end\n"
"\n"
"# usage:\n"
"\n"
"mydelegate = MyDelegate::new\n"
"reader = RBA::NetlistSpiceReader::new(mydelegate)\n"
"\n"
"nl = RBA::Netlist::new\n"
"nl.read(input_file, reader)\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.26."
);
}

View File

@ -248,6 +248,7 @@ TEST(6_ReaderWithDelegate)
" 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"
" device RES $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0);\n"
"end;\n"
"circuit .TOP ();\n"
" subcircuit SUBCKT SUBCKT ($1=(null),$2=(null),$3=(null),$4=(null),$5=VSS,$6=VSS);\n"

View File

@ -116,6 +116,7 @@ RUBYTEST (dbNetlist, "dbNetlist.rb")
RUBYTEST (dbNetlistDeviceClasses, "dbNetlistDeviceClasses.rb")
RUBYTEST (dbNetlistDeviceExtractors, "dbNetlistDeviceExtractors.rb")
RUBYTEST (dbNetlistWriterTests, "dbNetlistWriterTests.rb")
RUBYTEST (dbNetlistReaderTests, "dbNetlistReaderTests.rb")
RUBYTEST (dbNetlistCompare, "dbNetlistCompare.rb")
RUBYTEST (dbPathTest, "dbPathTest.rb")
RUBYTEST (dbPCells, "dbPCells.rb")

View File

@ -24,6 +24,7 @@ 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
R$1 A Z 100k
.ENDS SUBCKT
XSUBCKT IN OUT VDD Z VSS VSS SUBCKT

View File

@ -43,7 +43,7 @@ class MyDelegate < RBA::NetlistSpiceWriterDelegate
end
class DBLayoutToNetlist_TestClass < TestBase
class DBNetlistWriterTests_TestClass < TestBase
def test_1_Basic