From d174fb73fd68372e10bdac7502dc77e6bf9d6f12 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Jun 2019 18:37:32 +0200 Subject: [PATCH] WIP: preparations for SPICE reader delegate. --- src/db/db/dbDeviceClass.h | 18 +- src/db/db/dbNetlistDeviceClasses.cc | 40 +-- src/db/db/dbNetlistSpiceReader.cc | 410 ++++++++++++++-------- src/db/db/dbNetlistSpiceReader.h | 74 +++- src/db/db/dbNetlistSpiceWriter.cc | 36 +- src/db/db/dbNetlistSpiceWriter.h | 3 +- src/db/db/gsiDeclDbNetlist.cc | 15 +- src/db/unit_tests/dbNetlistWriterTests.cc | 4 +- testdata/algo/nreader3.cir | 4 +- testdata/algo/nwriter13_au.txt | 4 +- testdata/algo/nwriter14_au.txt | 4 +- testdata/algo/nwriter4_au.txt | 4 +- 12 files changed, 400 insertions(+), 216 deletions(-) diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index 9e5706e65..bb433ea92 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -124,7 +124,7 @@ public: * @brief Creates an empty device parameter definition */ DeviceParameterDefinition () - : m_name (), m_description (), m_default_value (0.0), m_id (0), m_is_primary (true) + : m_name (), m_description (), m_default_value (0.0), m_id (0), m_is_primary (true), m_si_scaling (1.0) { // .. nothing yet .. } @@ -132,8 +132,8 @@ public: /** * @brief Creates a device parameter definition with the given name and description */ - DeviceParameterDefinition (const std::string &name, const std::string &description, double default_value = 0.0, bool is_primary = true) - : m_name (name), m_description (description), m_default_value (default_value), m_id (0), m_is_primary (is_primary) + DeviceParameterDefinition (const std::string &name, const std::string &description, double default_value = 0.0, bool is_primary = true, double si_scaling = 1.0) + : m_name (name), m_description (description), m_default_value (default_value), m_id (0), m_is_primary (is_primary), m_si_scaling (si_scaling) { // .. nothing yet .. } @@ -178,6 +178,17 @@ public: return m_default_value; } + /** + * @brief Gets the SI unit scaling factor + * + * Some parameters are given in micrometers for example. This + * scaling factor gives the translation to SI units (1e-6 for micrometers). + */ + double si_scaling () const + { + return m_si_scaling; + } + /** * @brief Sets the parameter description */ @@ -220,6 +231,7 @@ private: double m_default_value; size_t m_id; bool m_is_primary; + double m_si_scaling; void set_id (size_t id) { diff --git a/src/db/db/dbNetlistDeviceClasses.cc b/src/db/db/dbNetlistDeviceClasses.cc index 29acef640..360eae9b0 100644 --- a/src/db/db/dbNetlistDeviceClasses.cc +++ b/src/db/db/dbNetlistDeviceClasses.cc @@ -110,10 +110,10 @@ DeviceClassResistor::DeviceClassResistor () add_terminal_definition (db::DeviceTerminalDefinition ("B", "Terminal B")); add_parameter_definition (db::DeviceParameterDefinition ("R", "Resistance (Ohm)", 0.0)); - add_parameter_definition (db::DeviceParameterDefinition ("L", "Length (micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("W", "Width (micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false)); + add_parameter_definition (db::DeviceParameterDefinition ("L", "Length (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("W", "Width (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6)); } void DeviceClassResistor::parallel (Device *a, Device *b) const @@ -221,8 +221,8 @@ DeviceClassCapacitor::DeviceClassCapacitor () add_terminal_definition (db::DeviceTerminalDefinition ("B", "Terminal B")); add_parameter_definition (db::DeviceParameterDefinition ("C", "Capacitance (Farad)", 0.0)); - add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6)); } void DeviceClassCapacitor::serial (Device *a, Device *b) const @@ -325,8 +325,8 @@ DeviceClassDiode::DeviceClassDiode () add_terminal_definition (db::DeviceTerminalDefinition ("A", "Anode")); add_terminal_definition (db::DeviceTerminalDefinition ("C", "Cathode")); - add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6)); } bool DeviceClassDiode::combine_devices (Device *a, Device *b) const @@ -372,12 +372,12 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor () add_terminal_definition (db::DeviceTerminalDefinition ("G", "Gate")); add_terminal_definition (db::DeviceTerminalDefinition ("D", "Drain")); - add_parameter_definition (db::DeviceParameterDefinition ("L", "Gate length (micrometer)", 0.0)); - add_parameter_definition (db::DeviceParameterDefinition ("W", "Gate width (micrometer)", 0.0)); - add_parameter_definition (db::DeviceParameterDefinition ("AS", "Source area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("AD", "Drain area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("PS", "Source perimeter (micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("PD", "Drain perimeter (micrometer)", 0.0, false)); + add_parameter_definition (db::DeviceParameterDefinition ("L", "Gate length (micrometer)", 0.0, true, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("W", "Gate width (micrometer)", 0.0, true, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("AS", "Source area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("AD", "Drain area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("PS", "Source perimeter (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("PD", "Drain perimeter (micrometer)", 0.0, false, 1e-6)); } bool DeviceClassMOS3Transistor::combine_devices (Device *a, Device *b) const @@ -496,12 +496,12 @@ DeviceClassBJT3Transistor::DeviceClassBJT3Transistor () add_terminal_definition (db::DeviceTerminalDefinition ("E", "Emitter")); // NOTE: the emitter area and the emitter count are the primary parameters - add_parameter_definition (db::DeviceParameterDefinition ("AE", "Emitter area (square micrometer)", 0.0, true)); - add_parameter_definition (db::DeviceParameterDefinition ("PE", "Emitter perimeter (micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("AB", "Base area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("PB", "Base perimeter (micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("AC", "Collector area (square micrometer)", 0.0, false)); - add_parameter_definition (db::DeviceParameterDefinition ("PC", "Collector perimeter (micrometer)", 0.0, false)); + add_parameter_definition (db::DeviceParameterDefinition ("AE", "Emitter area (square micrometer)", 0.0, true, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("PE", "Emitter perimeter (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("AB", "Base area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("PB", "Base perimeter (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("AC", "Collector area (square micrometer)", 0.0, false, 1e-12)); + add_parameter_definition (db::DeviceParameterDefinition ("PC", "Collector perimeter (micrometer)", 0.0, false, 1e-6)); add_parameter_definition (db::DeviceParameterDefinition ("NE", "Emitter count", 1.0, true)); } diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 28128ab63..2f37fc479 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -33,14 +33,142 @@ namespace db { -static const char *allowed_name_chars = "_.:,!+$/&\\#[]|"; +// ------------------------------------------------------------------------------------------------------ -NetlistSpiceReader::NetlistSpiceReader () - : mp_netlist (0), mp_stream (0) +NetlistSpiceReaderDelegate::NetlistSpiceReaderDelegate () { // .. nothing yet .. } +NetlistSpiceReaderDelegate::~NetlistSpiceReaderDelegate () +{ + // .. nothing yet .. +} + +void NetlistSpiceReaderDelegate::start (db::Netlist * /*netlist*/) +{ + // .. nothing yet .. +} + +void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/) +{ + // .. nothing yet .. +} + +void NetlistSpiceReaderDelegate::error (const std::string &msg) +{ + throw tl::Exception (msg); +} + +bool NetlistSpiceReaderDelegate::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) +{ + std::string cn = model; + db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn); + db::DeviceClass *new_cls = 0; + + if (cls) { + // use given class + } else if (element == "R") { + if (cn.empty ()) { + cn = "RES"; + } + new_cls = new db::DeviceClassResistor (); + } else if (element == "L") { + if (cn.empty ()) { + cn = "IND"; + } + new_cls = new db::DeviceClassInductor (); + } else if (element == "C") { + if (cn.empty ()) { + cn = "CAP"; + } + new_cls = new db::DeviceClassCapacitor (); + } else if (element == "D") { + if (cn.empty ()) { + cn = "DIODE"; + } + new_cls = new db::DeviceClassDiode (); + } else if (element == "Q") { + if (nets.size () == 3) { + if (cn.empty ()) { + cn = "BJT3"; + } + new_cls = new db::DeviceClassBJT3Transistor (); + } else if (nets.size () == 4) { + if (cn.empty ()) { + cn = "BJT4"; + } + new_cls = new db::DeviceClassBJT4Transistor (); + } + } else if (element == "M") { + if (nets.size () == 3) { + if (cn.empty ()) { + cn = "MOS3"; + } + new_cls = new db::DeviceClassMOS3Transistor (); + } else if (nets.size () == 4) { + if (cn.empty ()) { + cn = "MOS4"; + } + new_cls = new db::DeviceClassMOS4Transistor (); + } + } + + if (new_cls) { + cls = new_cls; + cls->set_name (cn); + circuit->netlist ()->add_device_class (cls); + } else if (! cls) { + error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element)); + } + + const std::vector &td = cls->terminal_definitions (); + if (td.size () != nets.size ()) { + error (tl::sprintf (tl::to_string (tr ("Wrong number of terminals: class '%s' expects %d, but %d are given")), cn, int (td.size ()), int (nets.size ()))); + } + + db::Device *device = new db::Device (cls, name); + circuit->add_device (device); + + for (std::vector::const_iterator t = td.begin (); t != td.end (); ++t) { + device->connect_terminal (t->id (), nets [t - td.begin ()]); + } + + size_t defp = std::numeric_limits::max (); + if (dynamic_cast (cls)) { + defp = db::DeviceClassCapacitor::param_id_C; + } else if (dynamic_cast (cls)) { + defp = db::DeviceClassResistor::param_id_R; + } else if (dynamic_cast (cls)) { + defp = db::DeviceClassInductor::param_id_L; + } + + const std::vector &pd = cls->parameter_definitions (); + for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { + std::map::const_iterator v = params.find (i->name ()); + if (v != params.end ()) { + device->set_parameter_value (i->id (), v->second / i->si_scaling ()); + } else if (i->id () == defp) { + device->set_parameter_value (i->id (), value / i->si_scaling ()); + } + } + + return true; +} + +// ------------------------------------------------------------------------------------------------------ + +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) +{ + static NetlistSpiceReaderDelegate std_delegate; + if (! delegate) { + mp_delegate.reset (&std_delegate); + } +} + NetlistSpiceReader::~NetlistSpiceReader () { // .. nothing yet .. @@ -55,10 +183,13 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) try { + mp_delegate->start (&netlist); + while (! at_end ()) { - read_element (); + read_card (); } + mp_delegate->finish (&netlist); finish (); } catch (tl::Exception &ex) { @@ -166,7 +297,7 @@ void NetlistSpiceReader::unget_line (const std::string &l) m_stored_line = l; } -bool NetlistSpiceReader::read_element () +bool NetlistSpiceReader::read_card () { std::string l = get_line (); if (l.empty ()) { @@ -175,9 +306,8 @@ bool NetlistSpiceReader::read_element () 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"; + ex.skip (); + char next_char = toupper (*ex); if (ex.test_without_case (".")) { @@ -207,59 +337,24 @@ bool NetlistSpiceReader::read_element () } - } else if (ex.test_without_case ("r")) { + } else if (isalpha (next_char)) { - 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); + ++ex; + + std::string name = read_name (ex); + ensure_circuit (); + + 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)) { + warn (tl::sprintf (tl::to_string (tr ("Element type '%c' ignored")), next_char)); } - 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)); - } - + warn (tl::to_string (tr ("Line ignored"))); } return false; @@ -480,10 +575,111 @@ std::string NetlistSpiceReader::read_name (tl::Extractor &ex) return nn; } -void NetlistSpiceReader::read_subcircuit (tl::Extractor &ex) +bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &element, const std::string &name) { - std::string sc_name = read_name (ex); + // generic parse + std::vector nn; + std::map pv; + std::string model; + double value = 0.0; + + // interpret the parameters according to the code + if (element == "X") { + + // subcircuit call: + // Xname n1 n2 ... nn circuit [params] + + read_pin_and_parameters (ex, nn, pv); + + if (nn.empty ()) { + error (tl::to_string (tr ("No circuit name given for subcircuit call"))); + } + + model = nn.back (); + nn.pop_back (); + + } else if (element == "R" || element == "C" || element == "L") { + + // resistor, cap, inductor: two-terminal devices with a value + // Rname n1 n2 value + // Rname n1 n2 value model [params] + // Rname n1 n2 model [params] + // (same for C, L instead of R) + + while (! ex.at_end () && nn.size () < 2) { + nn.push_back (read_name (ex)); + } + + if (nn.size () != 2) { + error (tl::to_string (tr ("Two-terminal device needs two nets"))); + } + + tl::Extractor ve (ex); + double vv = 0.0; + if (ve.try_read (vv) || ve.test ("(")) { + value = read_value (ex); + } + + while (! ex.at_end ()) { + std::string n = read_name (ex); + if (ex.test ("=")) { + pv [tl::to_upper_case (n)] = read_value (ex); + } else if (! model.empty ()) { + error (tl::sprintf (tl::to_string (tr ("Too many arguments for two-terminal device (additional argumen is '%s')")), n)); + } else { + model = n; + } + } + + } else { + + // others: n-terminal devices with a model (last node) + + while (! ex.at_end ()) { + std::string n = read_name (ex); + if (ex.test ("=")) { + pv [tl::to_upper_case (n)] = read_value (ex); + } else { + nn.push_back (n); + } + } + + if (nn.empty ()) { + error (tl::sprintf (tl::to_string (tr ("No model name given for element '%s'")), element)); + } + + model = nn.back (); + nn.pop_back (); + + if (element == "M") { + if (nn.size () != 4) { + error (tl::to_string (tr ("'M' element must have four nodes"))); + } + } else if (element == "Q") { + if (nn.size () != 3 && nn.size () != 4) { + error (tl::to_string (tr ("'Q' element must have three or four nodes"))); + } + } else if (element == "D") { + if (nn.size () != 2) { + error (tl::to_string (tr ("'D' element must have two nodes"))); + } + } + + // TODO: other devices? + + } + + std::vector nets; + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + nets.push_back (make_net (*i)); + } + + 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) +{ std::vector nn; std::map pv; read_pin_and_parameters (ex, nn, pv); @@ -564,7 +760,7 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex) } while (! at_end ()) { - if (read_element ()) { + if (read_card ()) { break; } } @@ -575,98 +771,4 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex) ex.expect_end (); } -void NetlistSpiceReader::read_device (db::DeviceClass *dev_cls, size_t param_id, tl::Extractor &ex) -{ - std::string dn = read_name (ex); - - std::vector nn; - - while (! ex.at_end () && nn.size () < 2) { - nn.push_back (read_name (ex)); - } - - 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::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 = read_name (ex); - - std::vector nn; - std::map pv; - - while (! ex.at_end ()) { - - std::string n = read_name (ex); - - 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::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 &pd = dev_cls->parameter_definitions (); - for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { - std::map::const_iterator v = pv.find (i->name ()); - if (v != pv.end ()) { - // by conventions, dimensions are in micrometer - if (i->id () == db::DeviceClassMOS4Transistor::param_id_AD || i->id () == db::DeviceClassMOS4Transistor::param_id_AS) { - dev->set_parameter_value (i->id (), v->second * 1e12); - } else if (i->id () == db::DeviceClassMOS4Transistor::param_id_W - || i->id () == db::DeviceClassMOS4Transistor::param_id_L - || i->id () == db::DeviceClassMOS4Transistor::param_id_PD - || i->id () == db::DeviceClassMOS4Transistor::param_id_PS) { - dev->set_parameter_value (i->id (), v->second * 1e6); - } - } - } - - ex.expect_end (); -} - } diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index cd0ce274e..7664b45f6 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -26,6 +26,7 @@ #include "dbCommon.h" #include "dbNetlistReader.h" #include "tlStream.h" +#include "tlGlobPattern.h" #include #include @@ -37,6 +38,68 @@ class Netlist; class Net; class Circuit; class DeviceClass; +class Device; + +/** + * @brief A specialized exception class to handle netlist reader delegate errors + */ +class DB_PUBLIC NetlistSpiceReaderDelegateError + : public tl::Exception +{ +public: + NetlistSpiceReaderDelegateError (const std::string &msg) + : tl::Exception (msg) + { } +}; + +/** + * @brief A delegate to handle various forms of devices and translates them + * + * 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. + */ +class DB_PUBLIC NetlistSpiceReaderDelegate + : public tl::Object +{ +public: + NetlistSpiceReaderDelegate (); + virtual ~NetlistSpiceReaderDelegate (); + + /** + * @brief Called when the netlist reading starts + */ + virtual void start (db::Netlist *netlist); + + /** + * @brief Called when the netlist reading ends + */ + virtual void finish (db::Netlist *netlist); + + /** + * @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. + * + * 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. + */ + 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); +}; /** * @brief A SPICE format reader for netlists @@ -45,7 +108,7 @@ class DB_PUBLIC NetlistSpiceReader : public NetlistReader { public: - NetlistSpiceReader (); + NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate = 0, const std::string &captured_subcircuits = std::string ()); virtual ~NetlistSpiceReader (); virtual void read (tl::InputStream &stream, db::Netlist &netlist); @@ -54,19 +117,20 @@ private: db::Netlist *mp_netlist; db::Circuit *mp_circuit; std::auto_ptr mp_stream; + tl::weak_ptr mp_delegate; std::vector > m_streams; std::auto_ptr > mp_nets_by_name; std::string m_stored_line; + tl::GlobPattern 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 &nn, std::map &pv); - void read_subcircuit (tl::Extractor &ex); + 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_device (db::DeviceClass *dev_cls, size_t param_id, tl::Extractor &ex); - void read_mos4_device (tl::Extractor &ex); - bool read_element (); + bool read_card (); double read_value (tl::Extractor &ex); std::string read_name (tl::Extractor &ex); double read_atomic_value (tl::Extractor &ex); diff --git a/src/db/db/dbNetlistSpiceWriter.cc b/src/db/db/dbNetlistSpiceWriter.cc index e42a90593..6f5d18471 100644 --- a/src/db/db/dbNetlistSpiceWriter.cc +++ b/src/db/db/dbNetlistSpiceWriter.cc @@ -135,9 +135,7 @@ void NetlistSpiceWriterDelegate::write_device (const db::Device &dev) const // Use device class name for the model os << " "; os << format_name (dev.device_class ()->name ()); - - os << " A=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassDiode::param_id_A)); - os << " P=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassDiode::param_id_P)); + os << format_params (dev); } else if (mos3 || mos4) { @@ -154,13 +152,7 @@ void NetlistSpiceWriterDelegate::write_device (const db::Device &dev) const // Use device class name for the model os << " "; os << format_name (dev.device_class ()->name ()); - - os << " L=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_L)); - os << " W=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_W)); - os << " AS=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_AS)); - os << " AD=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_AD)); - os << " PS=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_PS)); - os << " PD=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassMOS3Transistor::param_id_PD)); + os << format_params (dev); } else if (bjt3 || bjt4) { @@ -171,14 +163,7 @@ void NetlistSpiceWriterDelegate::write_device (const db::Device &dev) const // Use device class name for the model os << " "; os << format_name (dev.device_class ()->name ()); - - os << " AE=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_AE)); - os << " AB=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_AB)); - os << " AC=" << tl::sprintf ("%.12gP", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_AC)); - os << " PE=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_PE)); - os << " PB=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_PB)); - os << " PC=" << tl::sprintf ("%.12gU", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_PC)); - os << " NE=" << tl::sprintf ("%.0f", dev.parameter_value (db::DeviceClassBJT3Transistor::param_id_NE)); + os << format_params (dev); } else { @@ -207,13 +192,24 @@ std::string NetlistSpiceWriterDelegate::format_terminals (const db::Device &dev) return os.str (); } -std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev) const +std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, size_t without_id) const { std::ostringstream os; const std::vector &pd = dev.device_class ()->parameter_definitions (); for (std::vector::const_iterator i = pd.begin (); i != pd.end (); ++i) { - os << " " << i->name () << "=" << tl::to_string (dev.parameter_value (i->id ())); + if (i->id () != without_id) { + double sis = i->si_scaling (); + os << " " << i->name () << "="; + // for compatibility + if (fabs (sis * 1e6 - 1.0) < 1e-10) { + os << tl::to_string (dev.parameter_value (i->id ())) << "U"; + } else if (fabs (sis * 1e12 - 1.0) < 1e-10) { + os << tl::to_string (dev.parameter_value (i->id ())) << "P"; + } else { + os << tl::to_string (dev.parameter_value (i->id ()) * sis); + } + } } return os.str (); diff --git a/src/db/db/dbNetlistSpiceWriter.h b/src/db/db/dbNetlistSpiceWriter.h index 206c09a70..62d2417c4 100644 --- a/src/db/db/dbNetlistSpiceWriter.h +++ b/src/db/db/dbNetlistSpiceWriter.h @@ -29,6 +29,7 @@ #include #include +#include namespace db { @@ -62,7 +63,7 @@ public: void emit_comment (const std::string &comment) const; std::string format_name (const std::string &s) const; std::string format_terminals (const db::Device &dev) const; - std::string format_params (const db::Device &dev) const; + std::string format_params (const db::Device &dev, size_t without_id = std::numeric_limits::max ()) const; private: friend class NetlistSpiceWriter; diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index fb8574355..3736e52d7 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -622,14 +622,19 @@ Class decl_dbDeviceTerminalDefinition ("db", "Devi "This class has been added in version 0.26." ); -static db::DeviceParameterDefinition *new_parameter_definition (const std::string &name, const std::string &description, double default_value) +static db::DeviceParameterDefinition *new_parameter_definition (const std::string &name, const std::string &description, double default_value, bool is_primary, double si_scaling) { - return new db::DeviceParameterDefinition (name, description, default_value); + return new db::DeviceParameterDefinition (name, description, default_value, is_primary, si_scaling); } Class decl_dbDeviceParameterDefinition ("db", "DeviceParameterDefinition", - gsi::constructor ("new", &gsi::new_parameter_definition, gsi::arg ("name"), gsi::arg ("description", std::string ()), gsi::arg ("default_value", 0.0), + gsi::constructor ("new", &gsi::new_parameter_definition, gsi::arg ("name"), gsi::arg ("description", std::string ()), gsi::arg ("default_value", 0.0), gsi::arg ("is_primary", true), gsi::arg ("si_scaling", 1.0), "@brief Creates a new parameter definition." + "@param name The name of the parameter\n" + "@param description The human-readable description\n" + "@param default_value The initial value\n" + "@param is_primary True, if the parameter is a primary parameter (see \\is_primary=)\n" + "@param si_scaling The scaling factor to SI units\n" ) + gsi::method ("name", &db::DeviceParameterDefinition::name, "@brief Gets the name of the parameter." @@ -659,6 +664,10 @@ Class decl_dbDeviceParameterDefinition ("db", "De "If this flag is set to true (the default), the parameter is considered a primary parameter.\n" "Only primary parameters are compared by default.\n" ) + + gsi::method ("si_scaling", &db::DeviceParameterDefinition::si_scaling, + "@brief Gets the scaling factor to SI units.\n" + "For parameters in micrometers for example, this factor will be 1e-6." + ) + gsi::method ("id", &db::DeviceParameterDefinition::id, "@brief Gets the ID of the parameter.\n" "The ID of the parameter is used in some places to refer to a specific parameter (e.g. in " diff --git a/src/db/unit_tests/dbNetlistWriterTests.cc b/src/db/unit_tests/dbNetlistWriterTests.cc index 4420117aa..b6e1b5182 100644 --- a/src/db/unit_tests/dbNetlistWriterTests.cc +++ b/src/db/unit_tests/dbNetlistWriterTests.cc @@ -308,9 +308,9 @@ TEST(4_WriterDiodeDevices) circuit1->add_net (n3); db::Device *ddev1 = new db::Device (dcls); - ddev1->set_parameter_value (db::DeviceClassDiode::param_id_A, 1.7e-10); + ddev1->set_parameter_value (db::DeviceClassDiode::param_id_A, 1.7); db::Device *ddev2 = new db::Device (dcls); - ddev2->set_parameter_value (db::DeviceClassDiode::param_id_A, 42e-9); + ddev2->set_parameter_value (db::DeviceClassDiode::param_id_A, 0.42); circuit1->add_device (ddev1); circuit1->add_device (ddev2); diff --git a/testdata/algo/nreader3.cir b/testdata/algo/nreader3.cir index f4dfd6cf1..b9aaf131a 100644 --- a/testdata/algo/nreader3.cir +++ b/testdata/algo/nreader3.cir @@ -1,6 +1,6 @@ .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 + 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 diff --git a/testdata/algo/nwriter13_au.txt b/testdata/algo/nwriter13_au.txt index 4588e3f15..75526276c 100644 --- a/testdata/algo/nwriter13_au.txt +++ b/testdata/algo/nwriter13_au.txt @@ -10,7 +10,7 @@ * net 3 n3 * net 4 n4 * device instance $1 r0 *1 0,0 B3CLS -Q$1 3 4 1 B3CLS AE=0.25P AB=1.2P AC=1P PE=0.18U PB=0.75U PC=0.6U NE=1 +Q$1 3 4 1 B3CLS AE=0.25P PE=0.18U AB=1.2P PB=0.75U AC=1P PC=0.6U NE=1 * device instance $2 r0 *1 0,0 B3CLS -Q$2 2 4 3 B3CLS AE=1.2P AB=1.4P AC=1.5P PE=2.5U PB=2.8U PC=3U NE=1 +Q$2 2 4 3 B3CLS AE=1.2P PE=2.5U AB=1.4P PB=2.8U AC=1.5P PC=3U NE=1 .ENDS C1 diff --git a/testdata/algo/nwriter14_au.txt b/testdata/algo/nwriter14_au.txt index cfae3364a..5a9b92861 100644 --- a/testdata/algo/nwriter14_au.txt +++ b/testdata/algo/nwriter14_au.txt @@ -12,7 +12,7 @@ * net 4 n4 * net 5 n5 * device instance $1 r0 *1 0,0 B4CLS -Q$1 3 4 1 5 B4CLS AE=0.25P AB=1.2P AC=1P PE=0.18U PB=0.75U PC=0.6U NE=1 +Q$1 3 4 1 5 B4CLS AE=0.25P PE=0.18U AB=1.2P PB=0.75U AC=1P PC=0.6U NE=1 * device instance $2 r0 *1 0,0 B4CLS -Q$2 2 4 3 5 B4CLS AE=1.2P AB=1.4P AC=1.5P PE=2.5U PB=2.8U PC=3U NE=1 +Q$2 2 4 3 5 B4CLS AE=1.2P PE=2.5U AB=1.4P PB=2.8U AC=1.5P PC=3U NE=1 .ENDS C1 diff --git a/testdata/algo/nwriter4_au.txt b/testdata/algo/nwriter4_au.txt index 181fefac7..f93844b69 100644 --- a/testdata/algo/nwriter4_au.txt +++ b/testdata/algo/nwriter4_au.txt @@ -8,7 +8,7 @@ * net 2 n2 * net 3 n3 * device instance $1 r0 *1 0,0 DCLS -D$1 1 3 DCLS A=1.7e-10P P=0P +D$1 1 3 DCLS A=1.7P P=0U * device instance $2 r0 *1 0,0 DCLS -D$2 3 2 DCLS A=4.2e-08P P=0P +D$2 3 2 DCLS A=0.42P P=0U .ENDS C1