From db8f9d5bcb5418b6843d5494eb61049136e8f8c7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 12 Mar 2023 15:36:50 +0100 Subject: [PATCH] Spice reader enhancements Basic goal is to align ngspice and KLayout Spice format comprehension. ".options scale" was implemented together with a number of other patches. Consistency has been confirmed with respect to these features and formula evaluation. --- src/db/db/dbDeviceClass.h | 55 ++++++--- src/db/db/dbNetlistDeviceClasses.cc | 40 +++---- src/db/db/dbNetlistSpiceReader.cc | 55 +++++++++ src/db/db/dbNetlistSpiceReaderDelegate.cc | 110 +++++++++++++----- src/db/db/dbNetlistSpiceReaderDelegate.h | 28 ++++- .../dbNetlistSpiceReaderExpressionParser.cc | 5 +- .../db/dbNetlistSpiceReaderExpressionParser.h | 3 +- src/db/db/gsiDeclDbNetlist.cc | 28 ++++- src/db/unit_tests/dbNetlistReaderTests.cc | 57 ++++++++- testdata/algo/nreader19.cir | 27 +++++ testdata/ruby/dbNetlist.rb | 25 +++- 11 files changed, 355 insertions(+), 78 deletions(-) create mode 100644 testdata/algo/nreader19.cir diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index 2aa0f239c..697cfd545 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -146,7 +146,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_si_scaling (1.0) + : m_name (), m_description (), m_default_value (0.0), m_id (0), m_is_primary (true), m_si_scaling (1.0), m_geo_scaling (0.0) { // .. nothing yet .. } @@ -154,8 +154,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, 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) + DeviceParameterDefinition (const std::string &name, const std::string &description, double default_value = 0.0, bool is_primary = true, double si_scaling = 1.0, double geo_scaling = 0.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), m_geo_scaling (geo_scaling) { // .. nothing yet .. } @@ -192,18 +192,10 @@ public: m_description = d; } - /** - * @brief Gets the parameter default value - */ - double default_value () const - { - return m_default_value; - } - /** * @brief Gets the SI unit scaling factor * - * Some parameters are given in micrometers for example. This + * Some parameters are given in micrometers - for example W and L of MOS devices. This * scaling factor gives the translation to SI units (1e-6 for micrometers). */ double si_scaling () const @@ -212,7 +204,43 @@ public: } /** - * @brief Sets the parameter description + * @brief Set the SI unit scaling factor + */ + void set_si_scaling (double s) + { + m_si_scaling = s; + } + + /** + * @brief Gets the geometry scaling exponent + * + * The geometry scaling exponent is used for example when applying .option scale + * in Spice reading. It is 0 for "no scaling", 1 for linear scaling and 2 for + * quadratic scaling. + */ + double geo_scaling_exponent () const + { + return m_geo_scaling; + } + + /** + * @brief Sets the geometry scaling exponent + */ + void set_geo_scaling_exponent (double e) + { + m_geo_scaling = e; + } + + /** + * @brief Gets the parameter default value + */ + double default_value () const + { + return m_default_value; + } + + /** + * @brief Sets the parameter default value */ void set_default_value (double d) { @@ -267,6 +295,7 @@ private: size_t m_id; bool m_is_primary; double m_si_scaling; + double m_geo_scaling; void set_id (size_t id) { diff --git a/src/db/db/dbNetlistDeviceClasses.cc b/src/db/db/dbNetlistDeviceClasses.cc index 9fcad4336..de9436339 100644 --- a/src/db/db/dbNetlistDeviceClasses.cc +++ b/src/db/db/dbNetlistDeviceClasses.cc @@ -487,10 +487,10 @@ DeviceClassResistor::DeviceClassResistor () equivalent_terminal_id (terminal_id_A, terminal_id_B); add_parameter_definition (db::DeviceParameterDefinition ("R", "Resistance (Ohm)", 0.0)); - 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)); + add_parameter_definition (db::DeviceParameterDefinition ("L", "Length (micrometer)", 0.0, false, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("W", "Width (micrometer)", 0.0, false, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); } // ------------------------------------------------------------------------------------ @@ -526,8 +526,8 @@ DeviceClassCapacitor::DeviceClassCapacitor () equivalent_terminal_id (terminal_id_A, terminal_id_B); add_parameter_definition (db::DeviceParameterDefinition ("C", "Capacitance (Farad)", 0.0)); - 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)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); } // ------------------------------------------------------------------------------------ @@ -580,8 +580,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, 1e-12)); - add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6)); + add_parameter_definition (db::DeviceParameterDefinition ("A", "Area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("P", "Perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); } // ------------------------------------------------------------------------------------ @@ -608,12 +608,12 @@ DeviceClassMOS3Transistor::DeviceClassMOS3Transistor () add_terminal_definition (db::DeviceTerminalDefinition ("D", "Drain")); equivalent_terminal_id (terminal_id_D, terminal_id_S); - 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)); + add_parameter_definition (db::DeviceParameterDefinition ("L", "Gate length (micrometer)", 0.0, true, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("W", "Gate width (micrometer)", 0.0, true, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("AS", "Source area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("AD", "Drain area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("PS", "Source perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("PD", "Drain perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); } bool @@ -886,12 +886,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, 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 ("AE", "Emitter area (square micrometer)", 0.0, true, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("PE", "Emitter perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("AB", "Base area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("PB", "Base perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); + add_parameter_definition (db::DeviceParameterDefinition ("AC", "Collector area (square micrometer)", 0.0, false, 1e-12, 2.0)); + add_parameter_definition (db::DeviceParameterDefinition ("PC", "Collector perimeter (micrometer)", 0.0, false, 1e-6, 1.0)); 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 978662e3e..2bf40e854 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -388,6 +388,7 @@ private: void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector &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); @@ -614,6 +615,10 @@ SpiceCircuitDict::read_card () 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; @@ -680,6 +685,56 @@ SpiceCircuitDict::read_card () 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 () { diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index 45e45d70d..447f51e5c 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -86,6 +86,18 @@ static std::string unescape_name (const std::string &n) // ------------------------------------------------------------------------------------------------------ +NetlistSpiceReaderOptions::NetlistSpiceReaderOptions () +{ + scale = 1.0; + defad = 0.0; + defas = 0.0; + // ngspice defaults: + defw = 100e-6; + defl = 100e-6; +} + +// ------------------------------------------------------------------------------------------------------ + NetlistSpiceReaderDelegate::NetlistSpiceReaderDelegate () : mp_netlist (0) { @@ -107,7 +119,7 @@ void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/) // .. nothing yet .. } -bool NetlistSpiceReaderDelegate::control_statement(const std::string & /*line*/) +bool NetlistSpiceReaderDelegate::control_statement (const std::string & /*line*/) { return false; } @@ -196,7 +208,8 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, if (ex.try_read_word (n) && ex.test ("=")) { // a parameter - pv [mp_netlist ? mp_netlist->normalize_name (n) : tl::to_upper_case (n)] = read_value (ex, variables); + std::string pn = mp_netlist ? mp_netlist->normalize_name (n) : tl::to_upper_case (n); + pv [pn] = read_value (ex, variables); } else { @@ -231,8 +244,21 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, } } +void NetlistSpiceReaderDelegate::def_values_per_element (const std::string &element, std::map &pv) +{ + if (element == "M") { + + pv.insert (std::make_pair ("W", m_options.defw)); + pv.insert (std::make_pair ("L", m_options.defl)); + pv.insert (std::make_pair ("AD", m_options.defad)); + pv.insert (std::make_pair ("AS", m_options.defas)); + + } +} + void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector &nn, std::map &pv, const std::map &variables) { + def_values_per_element (element, pv); parse_element_components (s, nn, pv, variables); // interpret the parameters according to the code @@ -337,6 +363,8 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin std::map params = pv; std::vector terminal_order; + size_t defp = std::numeric_limits::max (); + double mult = 1.0; auto mp = params.find ("M"); if (mp != params.end ()) { @@ -378,9 +406,20 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin error (tl::to_string (tr ("A 'R' element requires two or three nets"))); } - // Apply multiplier + // Apply multiplier (divider, according to ngspice manual) value /= mult; + defp = db::DeviceClassResistor::param_id_R; + + // Apply multiplier to other parameters + static const char *scale_params[] = { "A", "P", "W" }; + for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { + auto p = params.find (scale_params [i]); + if (p != params.end ()) { + p->second = tl::Variant (p->second.to_double () * mult); + } + } + } else if (element == "L") { if (nets.size () == 2) { @@ -398,9 +437,11 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin error (tl::to_string (tr ("A 'L' element requires two nets"))); } - // Apply multiplier + // Apply multiplier (divider, according to ngspice manual) value /= mult; + defp = db::DeviceClassInductor::param_id_L; + } else if (element == "C") { if (nets.size () == 2) { @@ -432,6 +473,17 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin // Apply multiplier value *= mult; + defp = db::DeviceClassCapacitor::param_id_C; + + // Apply multiplier to other parameters + static const char *scale_params[] = { "A", "P" }; + for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { + auto p = params.find (scale_params [i]); + if (p != params.end ()) { + p->second = tl::Variant (p->second.to_double () * mult); + } + } + } else if (element == "D") { if (cls) { @@ -445,10 +497,13 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin cls = make_device_class (circuit, cn); } - // Apply multiplier to "A" - auto p = params.find ("A"); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); + // Apply multiplier + static const char *scale_params[] = { "A", "P" }; + for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { + auto p = params.find (scale_params [i]); + if (p != params.end ()) { + p->second = tl::Variant (p->second.to_double () * mult); + } } } else if (element == "Q") { @@ -479,10 +534,13 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } } - // Apply multiplier to "AE" - auto p = params.find ("AE"); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); + // Apply multiplier + static const char *scale_params[] = { "AE", "PE", "AB", "PB", "AC", "PC" }; + for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { + auto p = params.find (scale_params [i]); + if (p != params.end ()) { + p->second = tl::Variant (p->second.to_double () * mult); + } } } else if (element == "M") { @@ -502,10 +560,13 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } } - // Apply multiplier to "W" - auto p = params.find ("W"); - if (p != params.end ()) { - p->second = tl::Variant (p->second.to_double () * mult); + // Apply multiplier + static const char *scale_params[] = { "W", "AD", "AS", "PD", "PS" }; + for (size_t i = 0; i < sizeof (scale_params) / sizeof (scale_params[0]); ++i) { + auto p = params.find (scale_params [i]); + if (p != params.end ()) { + p->second = tl::Variant (p->second.to_double () * mult); + } } // issue #1304 @@ -537,23 +598,18 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin } - 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; - } - std::vector &pd = cls->parameter_definitions_non_const (); for (std::vector::iterator i = pd.begin (); i != pd.end (); ++i) { auto v = params.find (i->name ()); + double pv = 0.0; if (v != params.end ()) { - device->set_parameter_value (i->id (), v->second.to_double () / i->si_scaling ()); + pv = v->second.to_double (); } else if (i->id () == defp) { - device->set_parameter_value (i->id (), value / i->si_scaling ()); + pv = value; + } else { + continue; } + device->set_parameter_value (i->id (), pv / i->si_scaling () * pow (m_options.scale, i->geo_scaling_exponent ())); } return true; diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.h b/src/db/db/dbNetlistSpiceReaderDelegate.h index 521773a3b..ce523b538 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.h +++ b/src/db/db/dbNetlistSpiceReaderDelegate.h @@ -40,6 +40,14 @@ class Circuit; class DeviceClass; class Device; +struct DB_PUBLIC NetlistSpiceReaderOptions +{ + NetlistSpiceReaderOptions (); + + double scale; + double defad, defas, defw, defl; +}; + /** * @brief A delegate to handle various forms of devices and translates them * @@ -55,6 +63,22 @@ public: NetlistSpiceReaderDelegate (); virtual ~NetlistSpiceReaderDelegate (); + /** + * @brief Gets the reader options + */ + NetlistSpiceReaderOptions &options () + { + return m_options; + } + + /** + * @brief Gets the reader options + */ + const NetlistSpiceReaderOptions &options () const + { + return m_options; + } + /** * @brief Called when the netlist reading starts */ @@ -122,7 +146,6 @@ public: /** * @brief Reads a set of string components and parameters from the string - * A special key "param:" is recognized for starting a parameter list. */ void parse_element_components (const std::string &s, std::vector &strings, std::map &pv, const std::map &variables); @@ -162,6 +185,9 @@ public: private: db::Netlist *mp_netlist; + NetlistSpiceReaderOptions m_options; + + void def_values_per_element (const std::string &element, std::map &pv); }; } diff --git a/src/db/db/dbNetlistSpiceReaderExpressionParser.cc b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc index 364a11ebe..ef2ad63d4 100644 --- a/src/db/db/dbNetlistSpiceReaderExpressionParser.cc +++ b/src/db/db/dbNetlistSpiceReaderExpressionParser.cc @@ -44,7 +44,8 @@ static bool to_bool (const tl::Variant &v) } // ------------------------------------------------------------------------------------------------------ -NetlistSpiceReaderExpressionParser::NetlistSpiceReaderExpressionParser (const variables_type *vars) +NetlistSpiceReaderExpressionParser::NetlistSpiceReaderExpressionParser (const variables_type *vars, double def_scale) + : m_def_scale (def_scale) { static variables_type empty_variables; mp_variables = vars ? vars : &empty_variables; @@ -202,7 +203,7 @@ NetlistSpiceReaderExpressionParser::read_atomic_value (tl::Extractor &ex, bool * *status = true; } - double f = 1.0; + double f = m_def_scale; if (*ex == 't' || *ex == 'T') { f = 1e12; } else if (*ex == 'g' || *ex == 'G') { diff --git a/src/db/db/dbNetlistSpiceReaderExpressionParser.h b/src/db/db/dbNetlistSpiceReaderExpressionParser.h index 18c1fd8d0..06684e124 100644 --- a/src/db/db/dbNetlistSpiceReaderExpressionParser.h +++ b/src/db/db/dbNetlistSpiceReaderExpressionParser.h @@ -45,7 +45,7 @@ class DB_PUBLIC NetlistSpiceReaderExpressionParser public: typedef std::map variables_type; - NetlistSpiceReaderExpressionParser (const variables_type *vars); + NetlistSpiceReaderExpressionParser (const variables_type *vars, double def_scale = 1.0); tl::Variant read (tl::Extractor &ex) const; tl::Variant read (const std::string &s) const; @@ -54,6 +54,7 @@ public: private: const variables_type *mp_variables; + double m_def_scale; tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const; tl::Variant read_dot_expr (tl::Extractor &ex, bool *status) const; diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 636684007..1dc6c3dee 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -790,19 +790,20 @@ 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, bool is_primary, double si_scaling) +static db::DeviceParameterDefinition *new_parameter_definition (const std::string &name, const std::string &description, double default_value, bool is_primary, double si_scaling, double geo_scaling_exponent) { - return new db::DeviceParameterDefinition (name, description, default_value, is_primary, si_scaling); + return new db::DeviceParameterDefinition (name, description, default_value, is_primary, si_scaling, geo_scaling_exponent); } 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::arg ("is_primary", true), gsi::arg ("si_scaling", 1.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), gsi::arg ("geo_scaling_exponent", 0.0), "@brief Creates a new parameter definition.\n" "@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" + "@param geo_scaling_exponent Indicates how the parameter scales with geometrical scaling (0: no scaling, 1.0: linear, 2.0: quadratic)\n" ) + gsi::method ("name", &db::DeviceParameterDefinition::name, "@brief Gets the name of the parameter." @@ -834,7 +835,26 @@ Class decl_dbDeviceParameterDefinition ("db", "De ) + 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." + "For parameters in micrometers - for example W and L of MOS devices - this factor can be set to 1e-6 to reflect " + "the unit." + ) + + gsi::method ("si_scaling=", &db::DeviceParameterDefinition::set_si_scaling, + "@brief Sets the scaling factor to SI units.\n" + "\n" + "This setter has been added in version 0.28.6." + ) + + gsi::method ("geo_scaling_exponent", &db::DeviceParameterDefinition::geo_scaling_exponent, + "@brief Gets the geometry scaling exponent.\n" + "This value is used when applying '.options scale' in the SPICE reader for example. " + "It is zero for 'no scaling', 1.0 for linear scaling and 2.0 for quadratic scaling.\n" + "\n" + "This attribute has been added in version 0.28.6." + ) + + gsi::method ("geo_scaling_exponent=", &db::DeviceParameterDefinition::set_geo_scaling_exponent, + "@brief Sets the geometry scaling exponent.\n" + "See \\geo_scaling_exponent for details.\n" + "\n" + "This attribute has been added in version 0.28.6." ) + gsi::method ("id", &db::DeviceParameterDefinition::id, "@brief Gets the ID of the parameter.\n" diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index c4934c252..d82d3b02e 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -704,16 +704,50 @@ TEST(18_XSchemOutput) " subcircuit 'PMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' XDUMMY3 (D=VDD,G=VDD,S=VDD,B=VDD);\n" "end;\n" "circuit 'PMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' (D=D,G=G,S=S,B=B);\n" - " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=2.685,PD=1.79);\n" + " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=1.305,AD=0.87,PS=10.74,PD=7.16);\n" "end;\n" "circuit 'NMOS4_STANDARD(L=0.15U,NF=4,W=1.5U)' (D=D,G=G,S=S,B=B);\n" - " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=2.685,PD=1.79);\n" + " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=1.305,AD=0.87,PS=10.74,PD=7.16);\n" "end;\n" "circuit 'NMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' (D=D,G=G,S=S,B=B);\n" - " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=3.58,PD=1.79);\n" + " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.87,AD=0.435,PS=7.16,PD=3.58);\n" "end;\n" "circuit 'PMOS4_STANDARD(L=0.15U,NF=2,W=1.5U)' (D=D,G=G,S=S,B=B);\n" - " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=3.58,PD=1.79);\n" + " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.87,AD=0.435,PS=7.16,PD=3.58);\n" + "end;\n" + ); +} + +TEST(19_ngspice_ref) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader19.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit .TOP ();\n" + " subcircuit 'PMOS4_STANDARD(L=0.15,NF=4,W=1.5)' XPMOS (D=Q,G=I,S=VDD,B=VDD);\n" + " subcircuit 'NMOS4_STANDARD(L=0.15,NF=4,W=1.5)' XNMOS (D=Q,G=I,S=VSS,B=VSS);\n" + " subcircuit 'NMOS4_STANDARD(L=0.15,NF=2,W=1.5)' XDUMMY0 (D=VSS,G=VSS,S=VSS,B=VSS);\n" + " subcircuit 'NMOS4_STANDARD(L=0.15,NF=2,W=1.5)' XDUMMY1 (D=VSS,G=VSS,S=VSS,B=VSS);\n" + " subcircuit 'PMOS4_STANDARD(L=0.15,NF=2,W=1.5)' XDUMMY2 (D=VDD,G=VDD,S=VDD,B=VDD);\n" + " subcircuit 'PMOS4_STANDARD(L=0.15,NF=2,W=1.5)' XDUMMY3 (D=VDD,G=VDD,S=VDD,B=VDD);\n" + "end;\n" + "circuit 'PMOS4_STANDARD(L=0.15,NF=4,W=1.5)' (D=D,G=G,S=S,B=B);\n" + " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=3.99,PD=2.66);\n" + "end;\n" + "circuit 'NMOS4_STANDARD(L=0.15,NF=4,W=1.5)' (D=D,G=G,S=S,B=B);\n" + " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=3.99,PD=2.66);\n" + "end;\n" + "circuit 'NMOS4_STANDARD(L=0.15,NF=2,W=1.5)' (D=D,G=G,S=S,B=B);\n" + " device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=4.16,PD=2.08);\n" + "end;\n" + "circuit 'PMOS4_STANDARD(L=0.15,NF=2,W=1.5)' (D=D,G=G,S=S,B=B);\n" + " device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=4.16,PD=2.08);\n" "end;\n" ); } @@ -828,3 +862,18 @@ TEST(100_ExpressionParser) EXPECT_EQ (parser.try_read ("\"1+2*(2+1)-1\"", v), true); EXPECT_EQ (v.to_string (), "6"); } + +TEST(101_ExpressionParserWithDefScale) +{ + std::map vars; + vars["A"] = 17.5; + + tl::Variant v; + + db::NetlistSpiceReaderExpressionParser parser (&vars, 1e-3); + + EXPECT_EQ (parser.read ("1.75").to_string (), "0.00175"); + EXPECT_EQ (parser.read ("-1.75u").to_string (), "-1.75e-06"); + EXPECT_EQ (parser.read ("1.75k").to_string (), "1750"); + EXPECT_EQ (parser.read ("2*A").to_string (), "0.035"); +} diff --git a/testdata/algo/nreader19.cir b/testdata/algo/nreader19.cir new file mode 100644 index 000000000..38ea5fff3 --- /dev/null +++ b/testdata/algo/nreader19.cir @@ -0,0 +1,27 @@ +* Test + +.options scale=1e-6 + +.model sky130_fd_pr__pfet_01v8 NMOS level=8 version=3.3.0 +.model sky130_fd_pr__nfet_01v8 NMOS level=8 version=3.3.0 + +XXpmos Q I VDD VDD pmos4_standard w=1.5 l=0.15 nf=4 +XXnmos Q I VSS VSS nmos4_standard w=1.5 l=0.15 nf=4 +XXDUMMY0 VSS VSS VSS VSS nmos4_standard w=1.5 l=0.15 nf=2 +XXDUMMY1 VSS VSS VSS VSS nmos4_standard w=1.5 l=0.15 nf=2 +XXDUMMY2 VDD VDD VDD VDD pmos4_standard w=1.5 l=0.15 nf=2 +XXDUMMY3 VDD VDD VDD VDD pmos4_standard w=1.5 l=0.15 nf=2 + +.subckt pmos4_standard D G S B w=0.1 l=0.018 nf=4 +MM1 D G S B sky130_fd_pr__pfet_01v8 L=l W='w * nf ' ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ m=1 +.ends + +.subckt nmos4_standard D G S B w=0.1 l=0.018 nf=4 +MM1 D G S B sky130_fd_pr__nfet_01v8 L=l W='w * nf ' ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ m=1 +.ends + +.end diff --git a/testdata/ruby/dbNetlist.rb b/testdata/ruby/dbNetlist.rb index eed81ce3f..4c5b9fd83 100644 --- a/testdata/ruby/dbNetlist.rb +++ b/testdata/ruby/dbNetlist.rb @@ -625,14 +625,9 @@ class DBNetlist_TestClass < TestBase dc.terminal_definitions.each { |pd| names << pd.name } assert_equal(names, []) - pd = RBA::DeviceParameterDefinition::new("P1", "Parameter 1", 2.0) - assert_equal(pd.default_value, 2.0) + pd = RBA::DeviceParameterDefinition::new("P1", "Parameter 1") pd.default_value = 1.0 - assert_equal(pd.default_value, 1.0) - pd.is_primary = false - assert_equal(pd.is_primary?, false) pd.is_primary = true - assert_equal(pd.is_primary?, true) dc.add_parameter(pd) @@ -1181,6 +1176,24 @@ END end + def test_16_deviceParameterObject + + pd = RBA::DeviceParameterDefinition::new("P1", "Parameter 1", 2.0, false, 17.5, 2.0) + assert_equal(pd.default_value, 2.0) + pd.default_value = 1.0 + assert_equal(pd.default_value, 1.0) + assert_equal(pd.is_primary?, false) + pd.is_primary = true + assert_equal(pd.is_primary?, true) + assert_equal(pd.si_scaling, 17.5) + pd.si_scaling = 1.0 + assert_equal(pd.si_scaling, 1.0) + assert_equal(pd.geo_scaling_exponent, 2.0) + pd.geo_scaling_exponent = 1.0 + assert_equal(pd.geo_scaling_exponent, 1.0) + + end + end load("test_epilogue.rb")