From da5680ef24205ffd04dde1910c777091e99f7c20 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Apr 2019 15:19:43 +0200 Subject: [PATCH] Netlist compare: configurable device parameter compare scheme. --- src/db/db/dbDeviceClass.cc | 125 ++++++++++ src/db/db/dbDeviceClass.h | 94 +++++++- src/db/db/dbNetlistCompare.cc | 23 +- src/db/db/gsiDeclDbNetlist.cc | 126 +++++++++- src/db/db/gsiDeclDbNetlistCompare.cc | 57 +++-- src/db/unit_tests/dbNetlistCompareTests.cc | 258 +++++++++++++++++++++ testdata/ruby/dbNetlistCompare.rb | 130 +++++++++++ 7 files changed, 765 insertions(+), 48 deletions(-) diff --git a/src/db/db/dbDeviceClass.cc b/src/db/db/dbDeviceClass.cc index bc0c2864b..6b5b7f1a5 100644 --- a/src/db/db/dbDeviceClass.cc +++ b/src/db/db/dbDeviceClass.cc @@ -21,10 +21,81 @@ */ #include "dbDeviceClass.h" +#include "dbDevice.h" namespace db { +// -------------------------------------------------------------------------------- +// EqualDeviceParameters implementation + +static int compare_parameters (double pa, double pb, double absolute, double relative) +{ + double pa_min = pa - absolute; + double pa_max = pa + absolute; + + double mean = 0.5 * (fabs (pa) + fabs (pb)); + pa_min -= mean * relative; + pa_max += mean * relative; + + // NOTE: parameter values may be small (e.g. pF for caps) -> no epsilon + + if (pa_max < pb) { + return -1; + } else if (pa_min > pb) { + return 1; + } else { + return 0; + } +} + +EqualDeviceParameters::EqualDeviceParameters () +{ + // .. nothing yet .. +} + +EqualDeviceParameters::EqualDeviceParameters (size_t parameter_id) +{ + m_compare_set.push_back (std::make_pair (parameter_id, std::make_pair (0.0, 0.0))); +} + +EqualDeviceParameters::EqualDeviceParameters (size_t parameter_id, double relative, double absolute) +{ + m_compare_set.push_back (std::make_pair (parameter_id, std::make_pair (relative, absolute))); +} + +bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) const +{ + for (std::vector > >::const_iterator c = m_compare_set.begin (); c != m_compare_set.end (); ++c) { + int cmp = compare_parameters (a.parameter_value (c->first), b.parameter_value (c->first), c->second.first, c->second.second); + if (cmp != 0) { + return cmp < 0; + } + } + + return false; +} + +bool EqualDeviceParameters::equal (const db::Device &a, const db::Device &b) const +{ + for (std::vector > >::const_iterator c = m_compare_set.begin (); c != m_compare_set.end (); ++c) { + int cmp = compare_parameters (a.parameter_value (c->first), b.parameter_value (c->first), c->second.first, c->second.second); + if (cmp != 0) { + return false; + } + } + + return true; +} + +EqualDeviceParameters &EqualDeviceParameters::operator+= (const EqualDeviceParameters &other) +{ + for (std::vector > >::const_iterator c = other.m_compare_set.begin (); c != other.m_compare_set.end (); ++c) { + m_compare_set.push_back (*c); + } + return *this; +} + // -------------------------------------------------------------------------------- // DeviceClass class implementation @@ -137,4 +208,58 @@ size_t DeviceClass::terminal_id_for_name (const std::string &name) const throw tl::Exception (tl::to_string (tr ("Invalid terminal name")) + ": '" + name + "'"); } +bool DeviceClass::less (const db::Device &a, const db::Device &b) +{ + tl_assert (a.device_class () != 0); + tl_assert (b.device_class () != 0); + + const db::DeviceParameterCompareDelegate *pcd = a.device_class ()->mp_pc_delegate.get (); + if (! pcd) { + pcd = b.device_class ()->mp_pc_delegate.get (); + } + + if (pcd != 0) { + return pcd->less (a, b); + } else { + + const std::vector &pd = a.device_class ()->parameter_definitions (); + for (std::vector::const_iterator p = pd.begin (); p != pd.end (); ++p) { + int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ()), 0.0, 0.0); + if (cmp != 0) { + return cmp < 0; + } + } + + return false; + + } +} + +bool DeviceClass::equal (const db::Device &a, const db::Device &b) +{ + tl_assert (a.device_class () != 0); + tl_assert (b.device_class () != 0); + + const db::DeviceParameterCompareDelegate *pcd = a.device_class ()->mp_pc_delegate.get (); + if (! pcd) { + pcd = b.device_class ()->mp_pc_delegate.get (); + } + + if (pcd != 0) { + return pcd->equal (a, b); + } else { + + const std::vector &pd = a.device_class ()->parameter_definitions (); + for (std::vector::const_iterator p = pd.begin (); p != pd.end (); ++p) { + int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ()), 0.0, 0.0); + if (cmp != 0) { + return false; + } + } + + return true; + + } +} + } diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index c985ea7a3..bece69f4d 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -207,6 +207,53 @@ private: } }; +/** + * @brief A device parameter compare delegate + * + * Device parameter compare delegates are used to establish + * device equivalence in the context of netlist comparison. + */ +class DB_PUBLIC DeviceParameterCompareDelegate + : public gsi::ObjectBase, public tl::Object +{ +public: + DeviceParameterCompareDelegate () { } + virtual ~DeviceParameterCompareDelegate () { } + + virtual bool less (const db::Device &a, const db::Device &b) const = 0; + virtual bool equal (const db::Device &a, const db::Device &b) const = 0; +}; + +/** + * @brief A parameter compare delegate that compares several parameters either relative or absolute (or both) + * + * The reasoning behind this class is to supply a chainable compare delegate: ab = a + b + * where a and b are compare delegates for two different parameters and ab is the combined compare delegate. + */ +class DB_PUBLIC EqualDeviceParameters + : public DeviceParameterCompareDelegate +{ +public: + EqualDeviceParameters (); + EqualDeviceParameters (size_t parameter_id); + EqualDeviceParameters (size_t parameter_id, double relative, double absolute); + + virtual bool less (const db::Device &a, const db::Device &b) const; + virtual bool equal (const db::Device &a, const db::Device &b) const; + + EqualDeviceParameters &operator+= (const EqualDeviceParameters &other); + + EqualDeviceParameters operator+ (const EqualDeviceParameters &other) const + { + EqualDeviceParameters pc (*this); + pc += other; + return pc; + } + +private: + std::vector > > m_compare_set; +}; + /** * @brief A device class * @@ -228,14 +275,14 @@ public: /** * @brief Copy constructor * NOTE: do not use this copy constructor as the device class - * is intended to subclassing. + * is intended for subclassing. */ DeviceClass (const DeviceClass &other); /** * @brief Assignment * NOTE: do not use this copy constructor as the device class - * is intended to subclassing. + * is intended for subclassing. */ DeviceClass &operator= (const DeviceClass &other); @@ -365,7 +412,7 @@ public: size_t terminal_id_for_name (const std::string &name) const; /** - * @brief Clears the circuit + * @brief Clones the device class */ virtual DeviceClass *clone () const { @@ -414,6 +461,46 @@ public: return tid; } + /** + * @brief Compares the parameters of the devices a and b + * + * a and b are expected to originate from this or an equivalent device class having + * the same parameters. + * This is the "less" operation. If a parameter compare delegate is registered, this + * compare request will be forwarded to the delegate. + * + * If two devices with different device classes are compared and only one of + * the classes features a delegate, the one with the delegate is employed. + */ + static bool less (const db::Device &a, const db::Device &b); + + /** + * @brief Compares the parameters of the devices a and b + * + * a and b are expected to originate from this or an equivalent device class having + * the same parameters. + * This is the "equal" operation. If a parameter compare delegate is registered, this + * compare request will be forwarded to the delegate. + * + * If two devices with different device classes are compared and only one of + * the classes features a delegate, the one with the delegate is employed. + */ + static bool equal (const db::Device &a, const db::Device &b); + + /** + * @brief Registers a compare delegate + * + * The reasoning behind chosing a delegate is that a delegate is efficient + * also in scripts if one of the standard delegates is taken. + * + * The device class takes ownership of the delegate. + */ + virtual void set_parameter_compare_delegate (db::DeviceParameterCompareDelegate *delegate) + { + delegate->keep (); // assume transfer of ownership for scripts + mp_pc_delegate.reset (delegate); + } + private: friend class Netlist; @@ -421,6 +508,7 @@ private: std::vector m_terminal_definitions; std::vector m_parameter_definitions; db::Netlist *mp_netlist; + tl::shared_ptr mp_pc_delegate; void set_netlist (db::Netlist *nl) { diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 3dbd96430..89c21be52 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -40,16 +40,7 @@ struct DeviceCompare if (d1.second != d2.second) { return d1.second < d2.second; } - - const std::vector &dp = d1.first->device_class ()->parameter_definitions (); - for (std::vector::const_iterator i = dp.begin (); i != dp.end (); ++i) { - double v1 = d1.first->parameter_value (i->id ()); - double v2 = d2.first->parameter_value (i->id ()); - if (fabs (v1 - v2) > db::epsilon) { - return v1 < v2; - } - } - return false; + return db::DeviceClass::less (*d1.first, *d2.first); } bool equals (const std::pair &d1, const std::pair &d2) const @@ -57,16 +48,7 @@ struct DeviceCompare if (d1.second != d2.second) { return false; } - - const std::vector &dp = d1.first->device_class ()->parameter_definitions (); - for (std::vector::const_iterator i = dp.begin (); i != dp.end (); ++i) { - double v1 = d1.first->parameter_value (i->id ()); - double v2 = d2.first->parameter_value (i->id ()); - if (fabs (v1 - v2) > db::epsilon) { - return false; - } - } - return true; + return db::DeviceClass::equal (*d1.first, *d2.first); } }; @@ -1419,6 +1401,7 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, good = false; } else { if (mp_logger) { +dc.equals (dm->second, std::make_pair (d.operator-> (), device_cat)); // @@@ mp_logger->match_devices_with_different_parameters (dm->second.first, d.operator-> ()); } good = false; diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index 29f927cf7..40a5968b9 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -473,11 +473,120 @@ Class decl_dbDeviceParameterDefinition ("db", "De "This class has been added in version 0.26." ); +namespace +{ + +/** + * @brief A DeviceParameterCompare implementation that allows reimplementation of the virtual methods + */ +class GenericDeviceParameterCompare + : public db::EqualDeviceParameters +{ +public: + GenericDeviceParameterCompare () + : db::EqualDeviceParameters () + { + // .. nothing yet .. + } + + virtual bool less (const db::Device &a, const db::Device &b) const + { + if (cb_less.can_issue ()) { + return cb_less.issue (&db::EqualDeviceParameters::less, a, b); + } else { + return db::EqualDeviceParameters::less (a, b); + } + } + + virtual bool equal (const db::Device &a, const db::Device &b) const + { + if (cb_equal.can_issue ()) { + return cb_equal.issue (&db::EqualDeviceParameters::equal, a, b); + } else { + return db::EqualDeviceParameters::equal (a, b); + } + } + + gsi::Callback cb_less, cb_equal; +}; + +} + +db::EqualDeviceParameters *make_equal_dp (size_t param_id, double absolute, double relative) +{ + return new db::EqualDeviceParameters (param_id, absolute, relative); +} + +Class decl_dbEqualDeviceParameters ("db", "EqualDeviceParameters", + gsi::constructor ("new", &make_equal_dp, gsi::arg ("param_id"), gsi::arg ("absolute", 0.0), gsi::arg ("relative", 0.0), + "@brief Creates a device parameter comparer for a single parameter.\n" + "'absolute' is the absolute deviation allowed for the parameter values. " + "'relative' is the relative deviation allowed for the parameter values (a value between 0 and 1).\n" + "\n" + "A value of 0 for both absolute and relative deviation means the parameters have to match exactly.\n" + "\n" + "If 'absolute' and 'relative' are both given, their deviations will add to the allowed difference between " + "two parameter values. The relative deviation will be applied to the mean value of both parameter values. " + "For example, when comparing parameter values of 40 and 60, a relative deviation of 0.35 means an absolute " + "deviation of 17.5 (= 0.35 * average of 40 and 60) which does not make both values match." + ) + + gsi::method ("+", &db::EqualDeviceParameters::operator+, gsi::arg ("other"), + "@brief Combines two parameters for comparison.\n" + "The '+' operator will join the parameter comparers and produce one that checks the combined parameters.\n" + ) + + gsi::method ("+=", &db::EqualDeviceParameters::operator+, gsi::arg ("other"), + "@brief Combines two parameters for comparison (in-place).\n" + "The '+=' operator will join the parameter comparers and produce one that checks the combined parameters.\n" + ), + "@brief A device parameter equality comparer.\n" + "Attach this object to a device class with \\DeviceClass#equal_parameters= to make the device " + "class use this comparer:\n" + "\n" + "@code\n" + "# 20nm tolerance for length:\n" + "equal_device_parameters = RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS4Transistor::PARAM_L, 0.02, 0.0)\n" + "# one percent tolerance for width:\n" + "equal_device_parameters += RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS4Transistor::PARAM_W, 0.0, 0.01)\n" + "# applies the compare delegate:\n" + "netlist.device_class_by_name(\"NMOS\").equal_parameters = equal_device_parameters\n" + "@/code\n" + "\n" + "You can use this class to specify fuzzy equality criteria for the comparison of device parameters in " + "netlist verification or to confine the equality of devices to certain parameters only.\n" + "\n" + "This class has been added in version 0.26." +); + +Class decl_GenericDeviceParameterCompare (decl_dbEqualDeviceParameters, "db", "GenericDeviceParameterCompare", + gsi::callback ("equal", &GenericDeviceParameterCompare::equal, &GenericDeviceParameterCompare::cb_equal, gsi::arg ("device_a"), gsi::arg ("device_b"), + "@brief Compares the parameters of two devices for equality. " + "Returns true, if the parameters of device a and b are considered equal." + ) + + gsi::callback ("less", &GenericDeviceParameterCompare::less, &GenericDeviceParameterCompare::cb_less, gsi::arg ("device_a"), gsi::arg ("device_b"), + "@brief Compares the parameters of two devices for a begin less than b. " + "Returns true, if the parameters of device a are considered less than those of device b." + ), + "@brief A class implementing the comparison of device parameters.\n" + "Reimplement this class to provide a custom device parameter compare scheme.\n" + "Attach this object to a device class with \\DeviceClass#equal_parameters= to make the device " + "class use this comparer.\n" + "\n" + "This class is intended for special cases. In most scenarios it is easier to use \\EqualDeviceParameters instead of " + "implementing a custom comparer class.\n" + "\n" + "This class has been added in version 0.26." +); + static tl::id_type id_of_device_class (const db::DeviceClass *cls) { return tl::id_of (cls); } +static void equal_parameters (db::DeviceClass *cls, db::EqualDeviceParameters *comparer) +{ + cls->set_parameter_compare_delegate (comparer); +} + Class decl_dbDeviceClass ("db", "DeviceClass", gsi::method ("name", &db::DeviceClass::name, "@brief Gets the name of the device class." @@ -533,6 +642,15 @@ Class decl_dbDeviceClass ("db", "DeviceClass", "@brief Returns the terminal ID of the terminal with the given name.\n" "An exception is thrown if there is no terminal with the given name. Use \\has_terminal to check " "whether the name is a valid terminal name." + ) + + gsi::method_ext ("equal_parameters=", &equal_parameters, gsi::arg ("comparer"), + "@brief Specifies a device parameter comparer for netlist verification.\n" + "By default, all devices are compared with all parameters. If you want to select only certain parameters " + "for comparison or use a fuzzy compare criterion, use an \\EqualDeviceParameters object and assign it " + "to the device class of one netlist. You can also chain multiple \\EqualDeviceParameters objects with the '+' operator " + "for specifying multiple parameters in the equality check.\n" + "\n" + "In special cases, you can even implement a custom compare scheme by deriving your own comparer from the \\GenericDeviceParameterCompare class." ), "@brief A class describing a specific type of device.\n" "Device class objects live in the context of a \\Netlist object. After a " @@ -1231,14 +1349,14 @@ Class db_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne "@code\n" "writer = RBA::NetlistSpiceWriter::new\n" "netlist.write(path, writer)\n" - "@endcode\n" + "@/code\n" "\n" "You can give a custom description for the headline:\n" "\n" "@code\n" "writer = RBA::NetlistSpiceWriter::new\n" "netlist.write(path, writer, \"A custom description\")\n" - "@endcode\n" + "@/code\n" "\n" "To customize the output, you can use a device writer delegate.\n" "The delegate is an object of a class derived from \\NetlistSpiceWriterDelegate which " @@ -1279,7 +1397,7 @@ Class db_NetlistSpiceWriter (db_NetlistWriter, "db", "Ne "# write the netlist with delegate:\n" "writer = RBA::NetlistSpiceWriter::new(MyDelegate::new)\n" "netlist.write(path, writer)\n" - "@endcode\n" + "@/code\n" "\n" "This class has been introduced in version 0.26." ); @@ -1305,7 +1423,7 @@ Class db_NetlistSpiceReader (db_NetlistReader, "db", "Ne "writer = RBA::NetlistSpiceReader::new\n" "netlist = RBA::Netlist::new\n" "netlist.read(path, reader)\n" - "@endcode\n" + "@/code\n" "\n" "This class has been introduced in version 0.26." ); diff --git a/src/db/db/gsiDeclDbNetlistCompare.cc b/src/db/db/gsiDeclDbNetlistCompare.cc index 50b91ae69..2e7b6485e 100644 --- a/src/db/db/gsiDeclDbNetlistCompare.cc +++ b/src/db/db/gsiDeclDbNetlistCompare.cc @@ -29,7 +29,7 @@ namespace { * @brief A NetlistDeviceExtractor implementation that allows reimplementation of the virtual methods */ class GenericNetlistCompareLogger - : public db::NetlistCompareLogger + : public gsi::ObjectBase, public db::NetlistCompareLogger { public: GenericNetlistCompareLogger () @@ -296,6 +296,7 @@ public: }; } + namespace gsi { @@ -340,11 +341,13 @@ Class decl_GenericNetlistCompareLogger ("db", "Gene gsi::callback ("match_nets", &GenericNetlistCompareLogger::match_nets, &GenericNetlistCompareLogger::cb_match_nets, gsi::arg ("a"), gsi::arg ("b"), "@brief This function is called when two nets are identified.\n" "If two nets are identified as a corresponding pair, this method will be called with both nets.\n" - "If the nets can be paired, but this match is ambiguous, \\match_ambiguous_nets_fb will be called instead.\n" + "If the nets can be paired, but this match is ambiguous, \\match_ambiguous_nets will be called instead.\n" "If nets can't be matched to a partner, \\net_mismatch will be called.\n" ) + gsi::callback ("match_ambiguous_nets", &GenericNetlistCompareLogger::match_ambiguous_nets, &GenericNetlistCompareLogger::cb_match_ambiguous_nets, gsi::arg ("a"), gsi::arg ("b"), "@brief This function is called when two nets are identified, but this choice is ambiguous.\n" + "This choice is a last-resort fallback to allow continuation of the compare procedure. It is likely that this " + "compare will fail later. Looking for ambiguous nets allows deduction of the origin of this faulty decision. " "See \\match_nets for more details." ) + gsi::callback ("net_mismatch", &GenericNetlistCompareLogger::net_mismatch, &GenericNetlistCompareLogger::cb_net_mismatch, gsi::arg ("a"), gsi::arg ("b"), @@ -369,7 +372,7 @@ Class decl_GenericNetlistCompareLogger ("db", "Gene ) + gsi::callback ("device_mismatch", &GenericNetlistCompareLogger::device_mismatch, &GenericNetlistCompareLogger::cb_device_mismatch, gsi::arg ("a"), gsi::arg ("b"), "@brief This function is called when two devices can't be paired.\n" - "This will report the device considered in a or b. The other argument is nil." + "This will report the device considered in a or b. The other argument is nil. " "See \\match_devices for details.\n" ) + gsi::callback ("match_pins", &GenericNetlistCompareLogger::match_pins, &GenericNetlistCompareLogger::cb_match_pins, gsi::arg ("a"), gsi::arg ("b"), @@ -379,7 +382,7 @@ Class decl_GenericNetlistCompareLogger ("db", "Gene ) + gsi::callback ("pin_mismatch", &GenericNetlistCompareLogger::pin_mismatch, &GenericNetlistCompareLogger::cb_pin_mismatch, gsi::arg ("a"), gsi::arg ("b"), "@brief This function is called when two pins can't be paired.\n" - "This will report the pin considered in a or b. The other argument is nil." + "This will report the pin considered in a or b. The other argument is nil. " "See \\match_pins for details.\n" ) + gsi::callback ("match_subcircuits", &GenericNetlistCompareLogger::match_subcircuits, &GenericNetlistCompareLogger::cb_match_subcircuits, gsi::arg ("a"), gsi::arg ("b"), @@ -389,7 +392,7 @@ Class decl_GenericNetlistCompareLogger ("db", "Gene ) + gsi::callback ("subcircuit_mismatch", &GenericNetlistCompareLogger::subcircuit_mismatch, &GenericNetlistCompareLogger::cb_subcircuit_mismatch, gsi::arg ("a"), gsi::arg ("b"), "@brief This function is called when two subcircuits can't be paired.\n" - "This will report the subcircuit considered in a or b. The other argument is nil." + "This will report the subcircuit considered in a or b. The other argument is nil. " "See \\match_subcircuits for details.\n" ), "@brief An event receiver for the netlist compare feature.\n" @@ -400,19 +403,28 @@ Class decl_GenericNetlistCompareLogger ("db", "Gene "This class has been introduced in version 0.26.\n" ); -static db::NetlistComparer *make_comparer (GenericNetlistCompareLogger *logger) +static db::NetlistComparer *make_comparer0 () +{ + return new db::NetlistComparer (0); +} + +static db::NetlistComparer *make_comparer1 (GenericNetlistCompareLogger *logger) { return new db::NetlistComparer (logger); } Class decl_dbNetlistComparer ("db", "NetlistComparer", - gsi::constructor ("new", &make_comparer, gsi::arg ("logger", (GenericNetlistCompareLogger *) 0), - "@brief Creates a new comparer object." + gsi::constructor ("new", &make_comparer0, + "@brief Creates a new comparer object.\n" + "See the class description for more details." + ) + + gsi::constructor ("new", &make_comparer1, gsi::arg ("logger"), + "@brief Creates a new comparer object.\n" "The logger is a delegate or event receiver which the comparer will send compare events to. " "See the class description for more details." ) + gsi::method ("same_nets", &db::NetlistComparer::same_nets, gsi::arg ("net_a"), gsi::arg ("net_b"), - "@brief Marks two nets as identical\n" + "@brief Marks two nets as identical.\n" "This makes a net net_a in netlist a identical to the corresponding\n" "net net_b in netlist b (see \\compare).\n" "Otherwise, the algorithm will try to identify nets according to their topology. " @@ -420,43 +432,46 @@ Class decl_dbNetlistComparer ("db", "NetlistComparer", "these hints to derive further identities." ) + gsi::method ("equivalent_pins", (void (db::NetlistComparer::*) (const db::Circuit *, size_t, size_t)) &db::NetlistComparer::equivalent_pins, gsi::arg ("circuit_b"), gsi::arg ("pin_id1"), gsi::arg ("pin_id2"), - "@brief Marks two pins of the given circuit as equivalent (i.e. they can be swapped)\n" + "@brief Marks two pins of the given circuit as equivalent (i.e. they can be swapped).\n" "Only circuits from the second input can be given swappable pins. " "This will imply the same swappable pins on the equivalent circuit of the first input. " "To mark multiple pins as swappable, use the version that takes a list of pins." ) + gsi::method ("equivalent_pins", (void (db::NetlistComparer::*) (const db::Circuit *, const std::vector &)) &db::NetlistComparer::equivalent_pins, gsi::arg ("circuit_b"), gsi::arg ("pin_ids"), - "@brief Marks several pins of the given circuit as equivalent (i.e. they can be swapped)\n" + "@brief Marks several pins of the given circuit as equivalent (i.e. they can be swapped).\n" "Only circuits from the second input can be given swappable pins. " "This will imply the same swappable pins on the equivalent circuit of the first input. " "This version is a generic variant of the two-pin version of this method." ) + gsi::method ("same_device_classes", &db::NetlistComparer::same_device_classes, gsi::arg ("dev_cls_a"), gsi::arg ("dev_cls_b"), - "@brief Marks two device classes as identical\n" + "@brief Marks two device classes as identical.\n" "This makes a device class dev_cls_a in netlist a identical to the corresponding\n" "device class dev_cls_b in netlist b (see \\compare).\n" "By default device classes with the same name are identical.\n" ) + gsi::method ("same_circuits", &db::NetlistComparer::same_circuits, gsi::arg ("circuit_a"), gsi::arg ("circuit_b"), - "@brief Marks two circuits as identical\n" - "This method makes a circuit circuit_a in netlist a identical to the corresponding\n" - "circuit circuit_b in netlist b (see \\compare). By default circuits with the same name are identical.\n" + "@brief Marks two circuits as identical.\n" + "This method makes a circuit circuit_a in netlist a identical to the corresponding\n" + "circuit circuit_b in netlist b (see \\compare). By default circuits with the same name are identical.\n" ) + gsi::method ("compare", &db::NetlistComparer::compare, gsi::arg ("netlist_a"), gsi::arg ("netlist_b"), - "@brief Compares two netlists\n" + "@brief Compares two netlists.\n" "This method will perform the actual netlist compare. It will return true if both netlists are identical. " "If the comparer has been configured with \\same_nets or similar methods, the objects given there must " "be located inside 'circuit_a' and 'circuit_b' respectively." ), "@brief Compares two netlists\n" - "This class performs the actual comparison of two netlists.\n" - "It can be used with an event receiver to log the errors and net mismatches. " + "This class performs a comparison of two netlists.\n" + "It can be used with an event receiver (logger) to track the errors and net mismatches. " "Event receivers are derived from class \\GenericNetlistCompareLogger." "\n" - "The netlist comparer can be configured in different ways. Specifically hints can be given for nets, device classes or circuits. " - "Equivalence hints can be given with \\same_nets, \\same_circuits etc.\n" + "The netlist comparer can be configured in different ways. Specific hints can be given for nets, device classes or circuits " + "to improve efficiency and reliability of the graph equivalence deduction algorithm. " + "For example, objects can be marked as equivalent using \\same_nets, \\same_circuits etc. " + "The compare algorithm will then use these hints to derive further equivalences. This way, " + "ambiguities can be resolved.\n" "\n" - "Another configuration relates to swappable pins of subcircuits. If pins marked this way, the compare algorithm may swap them to " + "Another configuration option relates to swappable pins of subcircuits. If pins are marked this way, the compare algorithm may swap them to " "achieve net matching. Swappable pins belong to an 'equivalence group' and can be defined with \\equivalent_pins.\n" "\n" "This class has been introduced in version 0.26." diff --git a/src/db/unit_tests/dbNetlistCompareTests.cc b/src/db/unit_tests/dbNetlistCompareTests.cc index 43f6b9eba..f873f5bcf 100644 --- a/src/db/unit_tests/dbNetlistCompareTests.cc +++ b/src/db/unit_tests/dbNetlistCompareTests.cc @@ -199,6 +199,106 @@ static void prep_nl (db::Netlist &nl, const char *str) nl.from_string (str); } +TEST(0_EqualDeviceParameters) +{ + db::DeviceClassMOS3Transistor dc; + + db::EqualDeviceParameters *eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.0, 0.0); + dc.set_parameter_compare_delegate (eqp); + + db::Device d1 (&dc); + db::Device d2 (&dc); + + d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 41.0); + + EXPECT_EQ (dc.equal (d1, d2), false); + EXPECT_EQ (dc.equal (d2, d1), false); + EXPECT_EQ (dc.less (d1, d2), true); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.9, 0.0); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), false); + EXPECT_EQ (dc.equal (d2, d1), false); + EXPECT_EQ (dc.less (d1, d2), true); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 1.0, 0.0); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 1.1, 0.0); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.5, 0.01); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), false); + EXPECT_EQ (dc.equal (d2, d1), false); + EXPECT_EQ (dc.less (d1, d2), true); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.5, 0.013); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); + + d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.5); + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.2); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.5, 0.013); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), false); + EXPECT_EQ (dc.equal (d2, d1), false); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), true); + + eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.5, 0.013); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W, 0.3, 1e-6); + dc.set_parameter_compare_delegate (eqp); + + EXPECT_EQ (dc.equal (d1, d2), true); + EXPECT_EQ (dc.equal (d2, d1), true); + EXPECT_EQ (dc.less (d1, d2), false); + EXPECT_EQ (dc.less (d2, d1), false); +} + TEST(1_SimpleInverter) { const char *nls1 = @@ -496,6 +596,164 @@ TEST(5_BufferTwoPathsDifferentParameters) "end_circuit BUF BUF NOMATCH" ); EXPECT_EQ (good, false); + + logger.clear (); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 1.5, 0.0)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_ambiguous_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF MATCH" + ); + EXPECT_EQ (good, true); + + logger.clear (); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.0, 0.0)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices_with_different_parameters $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF NOMATCH" + ); + EXPECT_EQ (good, false); + + logger.clear (); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.0, 0.2)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices_with_different_parameters $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF NOMATCH" + ); + EXPECT_EQ (good, false); + + logger.clear (); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.0, 0.4)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_ambiguous_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF MATCH" + ); + EXPECT_EQ (good, true); + + logger.clear (); + db::EqualDeviceParameters eq_dp = db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W) + db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.2, 0.0); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (eq_dp)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_ambiguous_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF MATCH" + ); + EXPECT_EQ (good, true); + + logger.clear (); + eq_dp = db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W) + db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L); + nl2.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (eq_dp)); + good = comp.compare (&nl1, &nl2); + + EXPECT_EQ (logger.text (), + "begin_circuit BUF BUF\n" + "match_nets OUT OUT\n" + "match_nets IN IN\n" + "match_ambiguous_nets INT $10\n" + "match_nets INT2 $11\n" + "match_pins $0 $1\n" + "match_pins $1 $3\n" + "match_pins $2 $0\n" + "match_pins $3 $2\n" + "match_devices $1 $1\n" + "match_devices $3 $2\n" + "match_devices $5 $3\n" + "match_devices $7 $4\n" + "match_devices $2 $5\n" + "match_devices $4 $6\n" + "match_devices_with_different_parameters $6 $7\n" + "match_devices $8 $8\n" + "end_circuit BUF BUF NOMATCH" + ); + EXPECT_EQ (good, false); } TEST(5_BufferTwoPathsDifferentDeviceClasses) diff --git a/testdata/ruby/dbNetlistCompare.rb b/testdata/ruby/dbNetlistCompare.rb index 69d9f44ce..b9d772e3c 100644 --- a/testdata/ruby/dbNetlistCompare.rb +++ b/testdata/ruby/dbNetlistCompare.rb @@ -448,6 +448,92 @@ END assert_equal(good, false) + logger.clear + eqp = RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS3Transistor::PARAM_L, 0.2, 0.0) + nl2.device_class_by_name("NMOS").equal_parameters = eqp + good = comp.compare(nl1, nl2) + + assert_equal(logger.text, <<"END") +begin_circuit BUF BUF +match_nets OUT OUT +match_nets IN IN +match_ambiguous_nets INT $10 +match_ambiguous_nets INT2 $11 +match_pins $0 $1 +match_pins $1 $3 +match_pins $2 $0 +match_pins $3 $2 +match_devices $1 $1 +match_devices $3 $2 +match_devices $5 $3 +match_devices $7 $4 +match_devices $2 $5 +match_devices $4 $6 +match_devices $6 $7 +match_devices $8 $8 +end_circuit BUF BUF MATCH +END + + assert_equal(good, true) + + logger.clear + eqp = RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS3Transistor::PARAM_W, 0.01, 0.0) + eqp = eqp + RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS3Transistor::PARAM_L, 0.2, 0.0) + nl2.device_class_by_name("NMOS").equal_parameters = eqp + good = comp.compare(nl1, nl2) + + assert_equal(logger.text, <<"END") +begin_circuit BUF BUF +match_nets OUT OUT +match_nets IN IN +match_ambiguous_nets INT $10 +match_ambiguous_nets INT2 $11 +match_pins $0 $1 +match_pins $1 $3 +match_pins $2 $0 +match_pins $3 $2 +match_devices $1 $1 +match_devices $3 $2 +match_devices $5 $3 +match_devices $7 $4 +match_devices $2 $5 +match_devices $4 $6 +match_devices $6 $7 +match_devices $8 $8 +end_circuit BUF BUF MATCH +END + + assert_equal(good, true) + + logger.clear + eqp = RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS3Transistor::PARAM_W, 0.01, 0.0) + eqp += RBA::EqualDeviceParameters::new(RBA::DeviceClassMOS3Transistor::PARAM_L, 0.2, 0.0) + nl2.device_class_by_name("NMOS").equal_parameters = eqp + good = comp.compare(nl1, nl2) + + assert_equal(logger.text, <<"END") +begin_circuit BUF BUF +match_nets OUT OUT +match_nets IN IN +match_ambiguous_nets INT $10 +match_ambiguous_nets INT2 $11 +match_pins $0 $1 +match_pins $1 $3 +match_pins $2 $0 +match_pins $3 $2 +match_devices $1 $1 +match_devices $3 $2 +match_devices $5 $3 +match_devices $7 $4 +match_devices $2 $5 +match_devices $4 $6 +match_devices $6 $7 +match_devices $8 $8 +end_circuit BUF BUF MATCH +END + + assert_equal(good, true) + end def test_6 @@ -735,6 +821,50 @@ match_pins $4 $4 match_subcircuits $2 $1 match_subcircuits $1 $2 end_circuit TOP TOP MATCH +END + + assert_equal(good, true) + + logger.clear + comp = RBA::NetlistComparer::new(logger) + + comp.equivalent_pins(nl2.circuit_by_name("NAND"), [ 1, 0 ]) + + good = comp.compare(nl1, nl2) + + assert_equal(logger.text, <<"END") +begin_circuit NAND NAND +match_nets VSS VSS +match_nets VDD VDD +match_nets B B +match_nets OUT OUT +match_nets A A +match_nets INT INT +match_pins $0 $0 +match_pins $1 $1 +match_pins $2 $2 +match_pins $3 $3 +match_pins $4 $4 +match_devices $1 $1 +match_devices $2 $2 +match_devices $3 $3 +match_devices $4 $4 +end_circuit NAND NAND MATCH +begin_circuit TOP TOP +match_nets OUT OUT +match_nets IN2 IN2 +match_nets VSS VSS +match_nets VDD VDD +match_nets IN1 IN1 +match_nets INT INT +match_pins $0 $0 +match_pins $1 $1 +match_pins $2 $2 +match_pins $3 $3 +match_pins $4 $4 +match_subcircuits $2 $1 +match_subcircuits $1 $2 +end_circuit TOP TOP MATCH END assert_equal(good, true)