diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 7de401f75..6f400cb6e 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -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 &nets, const std::map ¶ms); /** * @brief Produces an error with the given message */ - void error (const std::string &msg); + virtual void error (const std::string &msg); }; /** diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 3736e52d7..1799448a1 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -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 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", "@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::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::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::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 &nets, const std::map ¶ms) + { + try { + m_error.clear (); + if (cb_element.can_issue ()) { + return cb_element.issue &, const std::map &> (&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 &nets, const std::map ¶ms) +{ + return delegate->db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params); +} + +Class 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 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_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_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." ); - } diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index c662f3fed..bc7c1f262 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -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" diff --git a/src/rba/unit_tests/rba.cc b/src/rba/unit_tests/rba.cc index e1ea746c4..2e96d4e2b 100644 --- a/src/rba/unit_tests/rba.cc +++ b/src/rba/unit_tests/rba.cc @@ -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") diff --git a/testdata/algo/nreader6.cir b/testdata/algo/nreader6.cir index 76531a328..b1809189a 100644 --- a/testdata/algo/nreader6.cir +++ b/testdata/algo/nreader6.cir @@ -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 diff --git a/testdata/ruby/dbNetlistWriterTests.rb b/testdata/ruby/dbNetlistWriterTests.rb index b935bbec5..172e24680 100644 --- a/testdata/ruby/dbNetlistWriterTests.rb +++ b/testdata/ruby/dbNetlistWriterTests.rb @@ -43,7 +43,7 @@ class MyDelegate < RBA::NetlistSpiceWriterDelegate end -class DBLayoutToNetlist_TestClass < TestBase +class DBNetlistWriterTests_TestClass < TestBase def test_1_Basic