From 3777d311af6ac45d6386b064f0720241a6c44e00 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Mar 2021 00:07:16 +0100 Subject: [PATCH 01/16] typos fixed, doc update --- src/db/db/dbNetlistCompare.cc | 2 +- src/lvs/lvs/built-in-macros/_lvs_netter.rb | 35 ++++++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 99ad08829..501343089 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -3008,7 +3008,7 @@ NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const } - // device whether to use a device category in strict mode + // decide whether to use a device category in strict mode device_categorizer.clear_strict_device_categories (); diff --git a/src/lvs/lvs/built-in-macros/_lvs_netter.rb b/src/lvs/lvs/built-in-macros/_lvs_netter.rb index 2f4219f69..42cc069d1 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_netter.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_netter.rb @@ -297,8 +297,11 @@ module LVS # Use this method to provide hints for the comparer in cases which are difficult to # resolve otherwise. # - # Before this method can be used, a schematic netlist needs to be loaded with - # \schematic. + # circuit_a and net_a are for the layout netlist, circuit_b and net_b for the schematic netlist. + # Note that SPICE netlists are normalized to @b upper case @/b. So enter + # upper case circuit or net names for SPICE schematic netlists. + # + # Use this method andwhere in the script before the \compare call. def same_nets(*args) @@ -373,12 +376,15 @@ module LVS # This method will force an equivalence between the two circuits. # By default, circuits are identified by name. If names are different, this # method allows establishing an explicit correspondence. + # + # circuit_a is for the layout netlist, circuit_b for the schematic netlist. + # Note that SPICE netlists are normalized to @b upper case @/b. So enter + # upper case circuit names for SPICE schematic netlists. # # One of the circuits may be nil. In this case, the corresponding # other circuit is mapped to "nothing", i.e. ignored. # - # Before this method can be used, a schematic netlist needs to be loaded with - # \schematic. + # Use this method andwhere in the script before the \compare call. def same_circuits(a, b) @@ -413,6 +419,10 @@ module LVS # Before this method can be used, a schematic netlist needs to be loaded with # \schematic. # + # class_a is for the layout netlist, class_b for the schematic netlist. + # Note that SPICE netlists are normalized to @b upper case @/b. So enter + # upper case device names for SPICE schematic netlists. + # # One of the device classes may be "nil". In this case, the corresponding # other device class is mapped to "nothing", i.e. ignored. # @@ -429,7 +439,11 @@ module LVS # # Once a device class is mentioned with "same_device_classes", matching by # name is disabled for this class. So after using 'same_device_classes("A", "B")' - # "A" is no longer equivalent to "A" on the other side. + # "A" is no longer equivalent to "A" on the other side. If you want "A" to + # stay equivalent to "A" too, you need to use 'same_device_classes("A", "A")' + # in addition. + # + # Use this method andwhere in the script before the \compare call. def same_device_classes(a, b) @@ -468,13 +482,16 @@ module LVS # @/code # # The circuit argument is either a circuit name (a string) or a Circuit object - # from the schematic netlist. + # from the schematic netlist. + # + # Note that SPICE netlists are normalized to @b upper case @/b. So enter + # upper case circuit names for SPICE schematic netlists. # # The pin arguments are zero-based pin numbers, where 0 is the first number, 1 the second etc. - # If the netlist provides named pins, names can be used instead of numbers. + # If the netlist provides named pins, names can be used instead of numbers. Again, use upper + # case pin names for SPICE netlists. # - # Before this method can be used, a schematic netlist needs to be loaded with - # \schematic. + # Use this method andwhere in the script before the \compare call. def equivalent_pins(circuit, *pins) From c48be51cb6afe0ba54d88a5f98204a0b42de14c7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Mar 2021 22:11:15 +0100 Subject: [PATCH 02/16] Made SPICE netlist elements case insensitive in LVS scripts --- src/db/db/dbCircuit.cc | 19 +++++- src/db/db/dbCircuit.h | 15 +--- src/db/db/dbNet.cc | 10 +++ src/db/db/dbNet.h | 13 ++++ src/db/db/dbNetlist.cc | 39 ++++++++++- src/db/db/dbNetlist.h | 40 +++++++++-- src/db/db/dbNetlistCompare.cc | 68 +++++++++++-------- src/db/db/dbNetlistCompare.h | 1 + src/db/db/dbNetlistSpiceReader.cc | 11 ++- src/lvs/lvs/built-in-macros/_lvs_netter.rb | 12 ++-- src/lvs/unit_tests/lvsSimpleTests.cc | 11 ++- testdata/lvs/ringo_device_subcircuits.lvs | 2 +- ...ngo_simple_net_and_circuit_equivalence.lvs | 6 +- testdata/lvs/ringo_simple_pin_swapping.lvs | 2 +- 14 files changed, 178 insertions(+), 71 deletions(-) diff --git a/src/db/db/dbCircuit.cc b/src/db/db/dbCircuit.cc index 49b6a1842..9fef782d0 100644 --- a/src/db/db/dbCircuit.cc +++ b/src/db/db/dbCircuit.cc @@ -186,8 +186,10 @@ void Circuit::rename_pin (size_t id, const std::string &name) const Pin *Circuit::pin_by_name (const std::string &name) const { + std::string nn = mp_netlist ? mp_netlist->normalize_name (name) : name; + for (Circuit::const_pin_iterator p = begin_pins (); p != end_pins (); ++p) { - if (p->name () == name) { + if (p->name () == nn) { return p.operator-> (); } } @@ -331,6 +333,11 @@ void Circuit::remove_pin (size_t id) } } +Net *Circuit::net_by_name (const std::string &name) +{ + return m_net_by_name.object_by (mp_netlist ? mp_netlist->normalize_name (name) : name); +} + void Circuit::add_net (Net *net) { if (! net) { @@ -423,6 +430,11 @@ void Circuit::remove_device (Device *device) m_devices.erase (device); } +Device *Circuit::device_by_name (const std::string &name) +{ + return m_device_by_name.object_by (mp_netlist ? mp_netlist->normalize_name (name) : name); +} + void Circuit::add_subcircuit (SubCircuit *subcircuit) { if (! subcircuit) { @@ -456,6 +468,11 @@ void Circuit::remove_subcircuit (SubCircuit *subcircuit) m_subcircuits.erase (subcircuit); } +SubCircuit *Circuit::subcircuit_by_name (const std::string &name) +{ + return m_subcircuit_by_name.object_by (mp_netlist ? mp_netlist->normalize_name (name) : name); +} + void Circuit::register_ref (SubCircuit *r) { m_refs.push_back (r); diff --git a/src/db/db/dbCircuit.h b/src/db/db/dbCircuit.h index e4e459b80..a33be121f 100644 --- a/src/db/db/dbCircuit.h +++ b/src/db/db/dbCircuit.h @@ -496,10 +496,7 @@ public: * * If the name is not valid, null is returned. */ - Net *net_by_name (const std::string &name) - { - return m_net_by_name.object_by (name); - } + Net *net_by_name (const std::string &name); /** * @brief Adds a device to this circuit @@ -556,10 +553,7 @@ public: * * If the name is not valid, null is returned. */ - Device *device_by_name (const std::string &name) - { - return m_device_by_name.object_by (name); - } + Device *device_by_name (const std::string &name); /** * @brief Begin iterator for the devices of the circuit (non-const version) @@ -648,10 +642,7 @@ public: * * If the name is not valid, null is returned. */ - SubCircuit *subcircuit_by_name (const std::string &name) - { - return m_subcircuit_by_name.object_by (name); - } + SubCircuit *subcircuit_by_name (const std::string &name); /** * @brief Begin iterator for the subcircuits of the circuit (non-const version) diff --git a/src/db/db/dbNet.cc b/src/db/db/dbNet.cc index 1f0488e60..f3f74f914 100644 --- a/src/db/db/dbNet.cc +++ b/src/db/db/dbNet.cc @@ -204,6 +204,16 @@ Net::~Net () clear (); } +Netlist *Net::netlist () +{ + return mp_circuit ? mp_circuit->netlist () : 0; +} + +const Netlist *Net::netlist () const +{ + return mp_circuit ? mp_circuit->netlist () : 0; +} + void Net::clear () { m_name.clear (); diff --git a/src/db/db/dbNet.h b/src/db/db/dbNet.h index fdd060928..66cf97d36 100644 --- a/src/db/db/dbNet.h +++ b/src/db/db/dbNet.h @@ -41,6 +41,7 @@ class Circuit; class DeviceTerminalDefinition; class DeviceClass; class Pin; +class Netlist; /** * @brief A reference to a terminal of a device @@ -418,6 +419,18 @@ public: return mp_circuit; } + /** + * @brief Gets the netlist the net lives in + * This pointer is 0 if the net is not part of a netlist. + */ + Netlist *netlist (); + + /** + * @brief Gets the netlist the net lives in (const version) + * This pointer is 0 if the net is not part of a netlist. + */ + const Netlist *netlist () const; + /** * @brief Clears the circuit */ diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index 5989b952d..c369b4c47 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -31,7 +31,7 @@ namespace db // Netlist class implementation Netlist::Netlist (NetlistManipulationCallbacks *callbacks) - : mp_callbacks (callbacks), + : m_case_sensitive (true), mp_callbacks (callbacks), m_valid_topology (false), m_lock_count (0), m_circuit_by_name (this, &Netlist::begin_circuits, &Netlist::end_circuits), m_circuit_by_cell_index (this, &Netlist::begin_circuits, &Netlist::end_circuits), @@ -44,7 +44,8 @@ Netlist::Netlist (NetlistManipulationCallbacks *callbacks) } Netlist::Netlist (const Netlist &other) - : gsi::ObjectBase (other), tl::Object (other), m_valid_topology (false), m_lock_count (0), + : gsi::ObjectBase (other), tl::Object (other), m_case_sensitive (true), + m_valid_topology (false), m_lock_count (0), m_circuit_by_name (this, &Netlist::begin_circuits, &Netlist::end_circuits), m_circuit_by_cell_index (this, &Netlist::begin_circuits, &Netlist::end_circuits), m_device_abstract_by_name (this, &Netlist::begin_device_abstracts, &Netlist::end_device_abstracts), @@ -69,6 +70,8 @@ Netlist &Netlist::operator= (const Netlist &other) clear (); + set_case_sensitive (other.is_case_sensitive ()); + std::map dct; for (const_device_class_iterator dc = other.begin_device_classes (); dc != other.end_device_classes (); ++dc) { DeviceClass *dc_new = dc->clone (); @@ -100,6 +103,34 @@ Netlist &Netlist::operator= (const Netlist &other) return *this; } +void Netlist::set_case_sensitive (bool f) +{ + m_case_sensitive = f; +} + +int Netlist::name_compare (bool case_sensitive, const std::string &n1, const std::string &n2) +{ + // TODO: unicode support? + if (case_sensitive) { + return strcmp (n1.c_str (), n2.c_str ()); + } else { +#if defined(_WIN32) + return _stricmp (n1.c_str (), n2.c_str ()); +#else + return strcasecmp (n1.c_str (), n2.c_str ()); +#endif + } +} + +std::string Netlist::normalize_name (bool case_sensitive, const std::string &n) +{ + if (case_sensitive) { + return n; + } else { + return tl::to_upper_case (n); + } +} + void Netlist::circuits_changed () { m_circuit_by_cell_index.invalidate (); @@ -472,8 +503,10 @@ void Netlist::flatten () DeviceClass *Netlist::device_class_by_name (const std::string &name) { + std::string nn = m_case_sensitive ? name : normalize_name (name); + for (device_class_iterator d = begin_device_classes (); d != end_device_classes (); ++d) { - if (d->name () == name) { + if (d->name () == nn) { return d.operator-> (); } } diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 79ff6bb2e..bfac2d402 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -102,6 +102,19 @@ public: */ void clear (); + /** + * @brief Returns a value indicating whether the netlist names are case sensitive + */ + bool is_case_sensitive () const + { + return m_case_sensitive; + } + + /** + * @brief Sets a value indicating whether the netlist names are case sensitive + */ + void set_case_sensitive (bool f); + /** * @brief Returns a parsable string representation of the netlist * @@ -225,7 +238,7 @@ public: */ Circuit *circuit_by_name (const std::string &name) { - return m_circuit_by_name.object_by (name); + return m_circuit_by_name.object_by (normalize_name (name)); } /** @@ -235,7 +248,7 @@ public: */ const Circuit *circuit_by_name (const std::string &name) const { - return m_circuit_by_name.object_by (name); + return m_circuit_by_name.object_by (normalize_name (name)); } /** @@ -429,7 +442,7 @@ public: */ DeviceAbstract *device_abstract_by_name (const std::string &name) { - return m_device_abstract_by_name.object_by (name); + return m_device_abstract_by_name.object_by (normalize_name (name)); } /** @@ -439,7 +452,7 @@ public: */ const DeviceAbstract *device_abstract_by_name (const std::string &name) const { - return m_device_abstract_by_name.object_by (name); + return m_device_abstract_by_name.object_by (normalize_name (name)); } /** @@ -502,10 +515,29 @@ public: */ void combine_devices (); + /** + * @brief Compares two names with the given case sensitivity + */ + static int name_compare (bool case_sensitive, const std::string &n1, const std::string &n2); + + /** + * @brief Normalizes a name with the given case sensitivity + */ + static std::string normalize_name (bool case_sensitive, const std::string &n); + + /** + * @brief Normalizes a name with the given case sensitivity of the netlist + */ + std::string normalize_name (const std::string &n) const + { + return normalize_name (is_case_sensitive (), n); + } + private: friend class Circuit; friend class DeviceAbstract; + bool m_case_sensitive; tl::weak_ptr mp_callbacks; circuit_list m_circuits; device_class_list m_device_classes; diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 501343089..0bd23ec43 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -48,15 +48,12 @@ struct GlobalCompareOptions debug_netcompare = tl::app_flag ("netlist-compare-debug-netcompare"); // $KLAYOUT_NETLIST_COMPARE_DEBUG_NETGRAPH debug_netgraph = tl::app_flag ("netlist-compare-debug-netgraph"); - // $KLAYOUT_NETLIST_COMPARE_CASE_SENSITIVE - compare_case_sensitive = tl::app_flag ("netlist-compare-case-sensitive"); m_is_initialized = true; } } bool debug_netcompare; bool debug_netgraph; - bool compare_case_sensitive; private: bool m_is_initialized; @@ -90,29 +87,19 @@ namespace db // -------------------------------------------------------------------------------------------------------------------- // A generic string compare -static int name_compare (const std::string &n1, const std::string &n2) +bool combined_case_sensitive (const db::Netlist *a, const db::Netlist *b) { - // TODO: unicode support? - if (options ()->compare_case_sensitive) { - return strcmp (n1.c_str (), n2.c_str ()); - } else { -#if defined(_WIN32) - return _stricmp (n1.c_str (), n2.c_str ()); -#else - return strcasecmp (n1.c_str (), n2.c_str ()); -#endif - } + bool csa = a ? a->is_case_sensitive () : true; + bool csb = b ? b->is_case_sensitive () : true; + return csa && csb; } -static inline std::string normalize_name (const std::string &n) +int name_compare (const db::Net *a, const db::Net *b) { - if (options ()->compare_case_sensitive) { - return n; - } else { - return tl::to_upper_case (n); - } + return db::Netlist::name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), a->name (), b->name ()); } + // -------------------------------------------------------------------------------------------------------------------- // DeviceCompare definition and implementation @@ -404,11 +391,16 @@ class generic_categorizer { public: generic_categorizer (bool with_name = true) - : m_next_cat (0), m_with_name (with_name) + : m_next_cat (0), m_with_name (with_name), m_case_sensitive (true) { // .. nothing yet .. } + void set_case_sensitive (bool f) + { + m_case_sensitive = f; + } + void same (const Obj *ca, const Obj *cb) { if (! ca && ! cb) { @@ -471,7 +463,7 @@ public: if (m_with_name) { - std::string cls_name = normalize_name (cls->name ()); + std::string cls_name = db::Netlist::normalize_name (m_case_sensitive, cls->name ()); std::map::const_iterator c = m_cat_by_name.find (cls_name); if (c != m_cat_by_name.end ()) { @@ -498,6 +490,7 @@ public: std::map m_cat_by_name; size_t m_next_cat; bool m_with_name; + bool m_case_sensitive; }; // -------------------------------------------------------------------------------------------------------------------- @@ -559,6 +552,11 @@ public: return m_strict_device_categories.find (cat) != m_strict_device_categories.end (); } + void set_case_sensitive (bool f) + { + generic_categorizer::set_case_sensitive (f); + } + private: std::set m_strict_device_categories; }; @@ -606,6 +604,11 @@ public: { return generic_categorizer::cat_for (cr); } + + void set_case_sensitive (bool f) + { + generic_categorizer::set_case_sensitive (f); + } }; // -------------------------------------------------------------------------------------------------------------------- @@ -1461,7 +1464,7 @@ NetGraphNode::net_less (const db::Net *a, const db::Net *b) const std::string &pna = a->begin_pins ()->pin ()->name (); const std::string &pnb = b->begin_pins ()->pin ()->name (); if (! pna.empty () && ! pnb.empty ()) { - return name_compare (pna, pnb) < 0; + return db::Netlist::name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), pna, pnb) < 0; } } return false; @@ -1484,7 +1487,7 @@ NetGraphNode::edge_equal (const db::Net *a, const db::Net *b) const std::string &pna = a->begin_pins ()->pin ()->name (); const std::string &pnb = b->begin_pins ()->pin ()->name (); if (! pna.empty () && ! pnb.empty ()) { - return name_compare (pna, pnb) == 0; + return db::Netlist::name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), pna, pnb) == 0; } } return true; @@ -2256,7 +2259,7 @@ namespace { bool operator() (const std::pair &a, const std::pairb) const { tl_assert (a.first->net () && b.first->net ()); - return name_compare (a.first->net ()->name (), b.first->net ()->name ()) < 0; + return name_compare (a.first->net (), b.first->net ()) < 0; } }; @@ -2317,7 +2320,7 @@ static bool net_names_are_different (const db::Net *a, const db::Net *b) if (! a || ! b || a->name ().empty () || b->name ().empty ()) { return false; } else { - return name_compare (a->name (), b->name ()) != 0; + return name_compare (a, b) != 0; } } @@ -2326,7 +2329,7 @@ static bool net_names_are_equal (const db::Net *a, const db::Net *b) if (! a || ! b || a->name ().empty () || b->name ().empty ()) { return false; } else { - return name_compare (a->name (), b->name ()) == 0; + return name_compare (a, b) == 0; } } @@ -2815,6 +2818,7 @@ NetlistComparer::NetlistComparer (NetlistCompareLogger *logger) m_depth_first = true; m_dont_consider_net_names = false; + m_case_sensitive = false; } NetlistComparer::~NetlistComparer () @@ -2887,6 +2891,7 @@ NetlistComparer::unmatched_circuits (db::Netlist *a, db::Netlist *b, std::vector { // we need to create a copy because this method is supposed to be const. db::CircuitCategorizer circuit_categorizer = *mp_circuit_categorizer; + circuit_categorizer.set_case_sensitive (m_case_sensitive); std::map, std::vector > > cat2circuits; @@ -2928,11 +2933,16 @@ NetlistComparer::unmatched_circuits (db::Netlist *a, db::Netlist *b, std::vector bool NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const { + m_case_sensitive = combined_case_sensitive (a, b); + // we need to create a copy because this method is supposed to be const. db::CircuitCategorizer circuit_categorizer = *mp_circuit_categorizer; db::DeviceCategorizer device_categorizer = *mp_device_categorizer; db::CircuitPinMapper circuit_pin_mapper = *mp_circuit_pin_mapper; + circuit_categorizer.set_case_sensitive (m_case_sensitive); + device_categorizer.set_case_sensitive (m_case_sensitive); + bool good = true; std::map, std::vector > > cat2circuits; @@ -3745,14 +3755,14 @@ NetlistComparer::do_pin_assignment (const db::Circuit *c1, const db::NetGraph &g for (db::Circuit::const_pin_iterator p = c2->begin_pins (); p != c2->end_pins (); ++p) { const db::Net *net = c2->net_for_pin (p->id ()); if (!net && !p->name ().empty ()) { - abstract_pins_by_name.insert (std::make_pair (normalize_name (p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.second = p.operator-> (); + abstract_pins_by_name.insert (std::make_pair (db::Netlist::normalize_name (m_case_sensitive, p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.second = p.operator-> (); } } for (db::Circuit::const_pin_iterator p = c1->begin_pins (); p != c1->end_pins (); ++p) { const db::Net *net = c1->net_for_pin (p->id ()); if (!net && !p->name ().empty ()) { - abstract_pins_by_name.insert (std::make_pair (normalize_name (p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.first = p.operator-> (); + abstract_pins_by_name.insert (std::make_pair (db::Netlist::normalize_name (m_case_sensitive, p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.first = p.operator-> (); } } diff --git a/src/db/db/dbNetlistCompare.h b/src/db/db/dbNetlistCompare.h index 3876cc31b..e437c8987 100644 --- a/src/db/db/dbNetlistCompare.h +++ b/src/db/db/dbNetlistCompare.h @@ -364,6 +364,7 @@ protected: size_t m_max_depth; bool m_depth_first; bool m_dont_consider_net_names; + mutable bool m_case_sensitive; }; } diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index e5f1da83b..8431400ba 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -299,6 +299,9 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) m_global_nets.clear (); m_circuits_read.clear (); + // SPICE netlists are case insensitive + netlist.set_case_sensitive (false); + try { mp_delegate->start (&netlist); @@ -775,13 +778,7 @@ std::string NetlistSpiceReader::read_name_with_case (tl::Extractor &ex) std::string NetlistSpiceReader::read_name (tl::Extractor &ex) { - // TODO: allow configuring Spice reader as case sensitive? - // this is easy to do: just avoid to_upper here: -#if 1 - return tl::to_upper_case (read_name_with_case (ex)); -#else - return read_name_with_case (ex); -#endif + return mp_netlist->normalize_name (read_name_with_case (ex)); } bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &element, const std::string &name) diff --git a/src/lvs/lvs/built-in-macros/_lvs_netter.rb b/src/lvs/lvs/built-in-macros/_lvs_netter.rb index 42cc069d1..f80bc7940 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_netter.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_netter.rb @@ -298,8 +298,7 @@ module LVS # resolve otherwise. # # circuit_a and net_a are for the layout netlist, circuit_b and net_b for the schematic netlist. - # Note that SPICE netlists are normalized to @b upper case @/b. So enter - # upper case circuit or net names for SPICE schematic netlists. + # Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. # # Use this method andwhere in the script before the \compare call. @@ -378,8 +377,7 @@ module LVS # method allows establishing an explicit correspondence. # # circuit_a is for the layout netlist, circuit_b for the schematic netlist. - # Note that SPICE netlists are normalized to @b upper case @/b. So enter - # upper case circuit names for SPICE schematic netlists. + # Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. # # One of the circuits may be nil. In this case, the corresponding # other circuit is mapped to "nothing", i.e. ignored. @@ -420,8 +418,7 @@ module LVS # \schematic. # # class_a is for the layout netlist, class_b for the schematic netlist. - # Note that SPICE netlists are normalized to @b upper case @/b. So enter - # upper case device names for SPICE schematic netlists. + # Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. # # One of the device classes may be "nil". In this case, the corresponding # other device class is mapped to "nothing", i.e. ignored. @@ -484,8 +481,7 @@ module LVS # The circuit argument is either a circuit name (a string) or a Circuit object # from the schematic netlist. # - # Note that SPICE netlists are normalized to @b upper case @/b. So enter - # upper case circuit names for SPICE schematic netlists. + # Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. # # The pin arguments are zero-based pin numbers, where 0 is the first number, 1 the second etc. # If the netlist provides named pins, names can be used instead of numbers. Again, use upper diff --git a/src/lvs/unit_tests/lvsSimpleTests.cc b/src/lvs/unit_tests/lvsSimpleTests.cc index f9e07d3a2..66cdcb4a7 100644 --- a/src/lvs/unit_tests/lvsSimpleTests.cc +++ b/src/lvs/unit_tests/lvsSimpleTests.cc @@ -28,7 +28,7 @@ #include "lymMacro.h" #include "tlFileUtils.h" -void run_test (tl::TestBase *_this, const std::string &suffix, const std::string &layout, bool with_l2n = false, const std::string &top = std::string ()) +void run_test (tl::TestBase *_this, const std::string &suffix, const std::string &layout, bool with_l2n = false, const std::string &top = std::string (), bool change_case = false) { std::string rs = tl::testsrc (); rs += "/testdata/lvs/" + suffix + ".lvs"; @@ -58,7 +58,8 @@ void run_test (tl::TestBase *_this, const std::string &suffix, const std::string "$lvs_test_target_cir = '%s'\n" "$lvs_test_target_l2n = '%s'\n" "$lvs_test_top = '%s'\n" - , src, output_lvsdb, output_cir, output_l2n, top) + "$change_case = %s\n" + , src, output_lvsdb, output_cir, output_l2n, top, change_case ? "true" : "false") ); config.set_interpreter (lym::Macro::Ruby); EXPECT_EQ (config.run (), 0); @@ -108,11 +109,15 @@ TEST(5_simple_same_device_classes) TEST(6_simple_pin_swapping) { run_test (_this, "ringo_simple_pin_swapping", "ringo.gds"); + // change case + run_test (_this, "ringo_simple_pin_swapping", "ringo.gds", false, std::string (), true); } TEST(7_net_and_circuit_equivalence) { run_test (_this, "ringo_simple_net_and_circuit_equivalence", "ringo_renamed.gds"); + // change case + run_test (_this, "ringo_simple_net_and_circuit_equivalence", "ringo_renamed.gds", false, std::string (), true); } TEST(8_simplification) @@ -143,6 +148,8 @@ TEST(12_simple_dmos) TEST(13_simple_ringo_device_subcircuits) { run_test (_this, "ringo_device_subcircuits", "ringo.gds"); + // change case + run_test (_this, "ringo_device_subcircuits", "ringo.gds", false, std::string (), true); } TEST(14_simple_ringo_mixed_hierarchy) diff --git a/testdata/lvs/ringo_device_subcircuits.lvs b/testdata/lvs/ringo_device_subcircuits.lvs index 83b63cec8..f12f62d8d 100644 --- a/testdata/lvs/ringo_device_subcircuits.lvs +++ b/testdata/lvs/ringo_device_subcircuits.lvs @@ -116,7 +116,7 @@ connect_global(bulk, "SUBSTRATE") connect_global(ptie, "SUBSTRATE") # Test same_device_classes -same_device_classes("PMOS", "XPMOS") +same_device_classes("PMOS", $change_case ? "xpMos" : "XPMOS") # Compare section diff --git a/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs b/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs index 7ff601882..02724522a 100644 --- a/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs +++ b/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs @@ -8,7 +8,7 @@ target_netlist($lvs_test_target_cir, write_spice, "Extracted by KLayout") schematic("ringo.cir") # preempt configuration (see below) -same_nets("top", "ENABLE", "RINGO", "ENABLE") +same_nets("top", "ENABLE", $change_case ? "Ringo" : "RINGO", $change_case ? "enable" : "ENABLE") deep @@ -71,8 +71,8 @@ connect_global(ptie, "SUBSTRATE") # Compare section -same_circuits("top", "RINGO") -same_circuits("INV", "INVX1") +same_circuits("top", $change_case ? "ringo" : "RINGO") +same_circuits("INV", $change_case ? "invX1" : "INVX1") same_circuits("DOESNOTEXIST", "DOESNOTEXIST2") same_nets("DOESNOTEXIST", "ENABLE", "DOESNOTEXIST2", "ENABLE") diff --git a/testdata/lvs/ringo_simple_pin_swapping.lvs b/testdata/lvs/ringo_simple_pin_swapping.lvs index d54d3f1fa..166fa320f 100644 --- a/testdata/lvs/ringo_simple_pin_swapping.lvs +++ b/testdata/lvs/ringo_simple_pin_swapping.lvs @@ -8,7 +8,7 @@ target_netlist($lvs_test_target_cir, write_spice, "Extracted by KLayout") schematic("ringo_pin_swapping.cir") # preempt configuration -equivalent_pins("ND2X1", 4, 5) +equivalent_pins($change_case ? "nd2X1" : "ND2X1", 4, 5) deep From 42520856630d53f71c2b7e3eb66905ec9fd468c7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 25 Mar 2021 01:56:23 +0100 Subject: [PATCH 03/16] Fixed an internal error in netlist compare Problem was that during ambiguity resolution a choice may be invalidated by further choices. This is taken care by locking nets against re-mapping. --- src/db/db/dbNetlistCompare.cc | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 0bd23ec43..752bed8cf 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -2357,7 +2357,7 @@ NetGraph::derive_node_identities_from_ambiguity_group (const NodeRange &nr, Devi { // marks the nodes from the ambiguity group as unknown so we don't revisit them (causing deep recursion) - TentativeNodeMapping tn2unknown; + TentativeNodeMapping tn_temp; // collect and mark the ambiguity combinations to consider std::vector >::const_iterator> iters1, iters2; @@ -2366,7 +2366,7 @@ NetGraph::derive_node_identities_from_ambiguity_group (const NodeRange &nr, Devi if (! i1->first->has_any_other ()) { iters1.push_back (i1); size_t ni = node_index_for_net (i1->first->net ()); - TentativeNodeMapping::map_to_unknown (&tn2unknown, this, ni); + TentativeNodeMapping::map_to_unknown (&tn_temp, this, ni); } } @@ -2374,7 +2374,7 @@ NetGraph::derive_node_identities_from_ambiguity_group (const NodeRange &nr, Devi if (! i2->first->has_any_other ()) { iters2.push_back (i2); size_t other_ni = data->other->node_index_for_net (i2->first->net ()); - TentativeNodeMapping::map_to_unknown (&tn2unknown, data->other, other_ni); + TentativeNodeMapping::map_to_unknown (&tn_temp, data->other, other_ni); } } @@ -2463,7 +2463,22 @@ NetGraph::derive_node_identities_from_ambiguity_group (const NodeRange &nr, Devi } if (to_remove != iters2.end ()) { + + // Add the new pair to the temporary mapping (even in tentative mode) + // Reasoning: doing the mapping may render other nets incompatible, so to ensure "edges_are_compatible" works properly we + // need to lock the current pairs resources such as devices by listing them in the mapping. This is doing by "derive_*_equivalence" inside + // TentativeNodeMapping::map_pair + + std::vector >::const_iterator i2 = *to_remove; + + size_t ni = node_index_for_net (i1->first->net ()); + size_t other_ni = data->other->node_index_for_net (i2->first->net ()); + + TentativeNodeMapping::map_pair (&tn_temp, this, ni, data->other, other_ni, dm, dm_other, *data->device_equivalence, scm, scm_other, *data->subcircuit_equivalence, depth); + + // now we can get rid of the node and reduce the "other" list of ambiguous nodes iters2.erase (to_remove); + } if (! any && tentative) { From 8d5ef02c3d9871799bd3001a33c182678f381fee Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 25 Mar 2021 23:50:37 +0100 Subject: [PATCH 04/16] Using unlimited complexity and depth for LVS compare --- src/db/db/dbNetlistCompare.cc | 8 ++++---- src/db/db/gsiDeclDbNetlistCompare.cc | 6 ++++++ src/lvs/lvs/built-in-macros/_lvs_netter.rb | 12 ++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 752bed8cf..49fef7934 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -2649,7 +2649,7 @@ NetGraph::derive_node_identities_from_node_set (std::vector data->max_depth) { + if (data->max_depth != std::numeric_limits::max() && depth > data->max_depth) { if (options ()->debug_netcompare) { tl::info << indent_s << "max. depth exhausted (" << depth + 1 << ">" << data->max_depth << ")"; } @@ -2783,7 +2783,7 @@ NetGraph::derive_node_identities_from_node_set (std::vectornum * n_branch > data->max_n_branch) { + } else if (data->max_n_branch != std::numeric_limits::max () && double (nr->num) * double (n_branch) > double (data->max_n_branch)) { if (options ()->debug_netcompare) { tl::info << indent_s << "max. complexity exhausted (" << nr->num << "*" << n_branch << ">" << data->max_n_branch << ") - mismatch."; @@ -2828,8 +2828,8 @@ NetlistComparer::NetlistComparer (NetlistCompareLogger *logger) m_cap_threshold = -1.0; // not set m_res_threshold = -1.0; // not set - m_max_depth = 50; - m_max_n_branch = 500; + m_max_depth = std::numeric_limits::max (); + m_max_n_branch = std::numeric_limits::max (); m_depth_first = true; m_dont_consider_net_names = false; diff --git a/src/db/db/gsiDeclDbNetlistCompare.cc b/src/db/db/gsiDeclDbNetlistCompare.cc index ee10d9365..3b886efcf 100644 --- a/src/db/db/gsiDeclDbNetlistCompare.cc +++ b/src/db/db/gsiDeclDbNetlistCompare.cc @@ -519,6 +519,9 @@ Class decl_dbNetlistComparer ("db", "NetlistComparer", "@brief Sets the maximum seach depth\n" "This value limits the search depth of the backtracking algorithm to the\n" "given number of jumps.\n" + "\n" + "By default, from version 0.27 on the depth is unlimited and can be reduced in cases where runtimes need to be limited at the cost " + "less elaborate matching evaluation.\n" ) + gsi::method ("max_depth", &db::NetlistComparer::max_depth, "@brief Gets the maximum seach depth\n" @@ -531,6 +534,9 @@ Class decl_dbNetlistComparer ("db", "NetlistComparer", "net matches. Backtracking will stop when the maximum number of options\n" "has been exceeded.\n" "\n" + "By default, from version 0.27 on the complexity is unlimited and can be reduced in cases where runtimes need to be limited at the cost " + "less elaborate matching evaluation.\n" + "\n" "As the computational complexity is the square of the branch count,\n" "this value should be adjusted carefully.\n" ) + diff --git a/src/lvs/lvs/built-in-macros/_lvs_netter.rb b/src/lvs/lvs/built-in-macros/_lvs_netter.rb index f80bc7940..ca20ae82f 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_netter.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_netter.rb @@ -616,6 +616,12 @@ module LVS # pursues this "deduction path" in greater depth while with # smaller values, the algorithm prefers picking nets in a random fashion # as the seeds for this deduction path. The default value is 8. + # + # By default, the depth is unlimited, but it may + # be reduced in order to limit the compare runtimes at the cost + # of a less elaborate compare attempt. The preferred solution + # however is to use labels for net name hints which also reduces + # the branch complexity. def max_depth(value) v = value.to_i @@ -638,6 +644,12 @@ module LVS # can be increased at the expense of potentially larger runtimes. # The runtime penality is roughly proportional to the branch # complexity. + # + # By default, the branch complexity is unlimited, but it may + # be reduced in order to limit the compare runtimes at the cost + # of a less elaborate compare attempt. The preferred solution + # however is to use labels for net name hints which also reduces + # the depth. def max_branch_complexity(value) v = value.to_i From cb2d76def6d237517eeceb9590339f44a577558f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Mar 2021 00:02:01 +0100 Subject: [PATCH 05/16] New test case for LVS --- src/lvs/unit_tests/lvsTests.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lvs/unit_tests/lvsTests.cc b/src/lvs/unit_tests/lvsTests.cc index b4aa56f4e..aa147204c 100644 --- a/src/lvs/unit_tests/lvsTests.cc +++ b/src/lvs/unit_tests/lvsTests.cc @@ -154,3 +154,10 @@ TEST(17_private) test_is_long_runner (); run_test (_this, "test_17.lylvs", "test_17.cir.gz", "test_17.gds.gz", true, "test_17.lvsdb"); } + +TEST(18_private) +{ + // test_is_long_runner (); + run_test (_this, "test_18.lvs", "test_18.cir.gz", "test_18.gds.gz", true); +} + From a0cab9832f8e2127b855798ad8b82c135698e514 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Mar 2021 00:40:18 +0100 Subject: [PATCH 06/16] Fixed probe feature of LVS dialog for flat extraction. --- src/db/db/dbDeepShapeStore.cc | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/db/db/dbDeepShapeStore.cc b/src/db/db/dbDeepShapeStore.cc index 32b7988a6..b793391c6 100644 --- a/src/db/db/dbDeepShapeStore.cc +++ b/src/db/db/dbDeepShapeStore.cc @@ -399,6 +399,18 @@ DeepShapeStoreState::max_vertex_count () const static size_t s_instance_count = 0; +static unsigned int init_layer (db::Layout &layout, const db::RecursiveShapeIterator &si) +{ + unsigned int layer_index = layout.insert_layer (); + + if (si.layout () && si.layer () < si.layout ()->layers ()) { + // try to preserve the layer properties + layout.set_properties (layer_index, si.layout ()->get_properties (si.layer ())); + } + + return layer_index; +} + DeepShapeStore::DeepShapeStore () { ++s_instance_count; @@ -433,7 +445,9 @@ DeepLayer DeepShapeStore::create_from_flat (const db::Region ®ion, bool for_n require_singular (); - unsigned int layer = layout ().insert_layer (); + unsigned int layer = init_layer (layout (), region.iter ()); + + // attempt to initialize the layer properties if (max_area_ratio == 0.0) { max_area_ratio = m_state.max_area_ratio (); @@ -480,7 +494,7 @@ DeepLayer DeepShapeStore::create_from_flat (const db::Edges &edges, const db::IC require_singular (); - unsigned int layer = layout ().insert_layer (); + unsigned int layer = init_layer (layout (), edges.iter ()); db::Shapes *shapes = &initial_cell ().shapes (layer); db::Box world = db::Box::world (); @@ -510,7 +524,7 @@ DeepLayer DeepShapeStore::create_from_flat (const db::Texts &texts, const db::IC require_singular (); - unsigned int layer = layout ().insert_layer (); + unsigned int layer = init_layer (layout (), texts.iter ()); db::Shapes *shapes = &initial_cell ().shapes (layer); db::Box world = db::Box::world (); @@ -780,18 +794,6 @@ void DeepShapeStore::make_layout (unsigned int layout_index, const db::Recursive m_layout_map[std::make_pair (si, trans)] = layout_index; } -static unsigned int init_layer (db::Layout &layout, const db::RecursiveShapeIterator &si) -{ - unsigned int layer_index = layout.insert_layer (); - - if (si.layout () && si.layer () < si.layout ()->layers ()) { - // try to preserve the layer properties - layout.set_properties (layer_index, si.layout ()->get_properties (si.layer ())); - } - - return layer_index; -} - DeepLayer DeepShapeStore::create_polygon_layer (const db::RecursiveShapeIterator &si, double max_area_ratio, size_t max_vertex_count, const db::ICplxTrans &trans) { if (max_area_ratio == 0.0) { From 3ce13f676922cb70369fe960ac732133519b60db Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Mar 2021 17:46:10 +0100 Subject: [PATCH 07/16] Attempt to get a better picture of mismatching device pins at the presence of device pin swapping --- .../laybasic/layNetlistBrowserModel.cc | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/laybasic/laybasic/layNetlistBrowserModel.cc b/src/laybasic/laybasic/layNetlistBrowserModel.cc index 697bb1c5e..72568aa67 100644 --- a/src/laybasic/laybasic/layNetlistBrowserModel.cc +++ b/src/laybasic/laybasic/layNetlistBrowserModel.cc @@ -530,10 +530,26 @@ static std::pairnormalize_terminal_id (td1->id ()); const db::Net *n2 = model->second_net_for (devices.first->net_for_terminal (terminal_id)); + // this scheme makes a guess what terminal to use when a pair of terminals does not connect + // to matching nets + std::set n2_excluded; + if (! n2) { + for (size_t i = 0; i < device_classes.first->terminal_definitions ().size (); ++i) { + const db::DeviceTerminalDefinition &td = device_classes.first->terminal_definitions () [i]; + if (device_classes.first->normalize_terminal_id (td.id ()) == td1_id_norm) { + const db::Net *n2x = model->second_net_for (devices.first->net_for_terminal (td.id ())); + if (n2x) { + n2_excluded.insert (n2x); + } + } + } + } + for (size_t i = 0; i < device_classes.second->terminal_definitions ().size (); ++i) { const db::DeviceTerminalDefinition &td = device_classes.second->terminal_definitions () [i]; if (device_classes.second->normalize_terminal_id (td.id ()) == td1_id_norm) { - if (devices.second->net_for_terminal (i) == n2) { + const db::Net *n2_actual = devices.second->net_for_terminal (i); + if ((! n2 || n2_actual == n2) && n2_excluded.find (n2_actual) == n2_excluded.end ()) { second_terminal_id = i; break; } From e0eac2147b6fa8a5aab002012c5c2977d277bcd4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Mar 2021 00:44:48 +0100 Subject: [PATCH 08/16] Refining the display enhancements for terminals in netlist browser. --- .../laybasic/layNetlistBrowserModel.cc | 141 ++++++++++-------- 1 file changed, 81 insertions(+), 60 deletions(-) diff --git a/src/laybasic/laybasic/layNetlistBrowserModel.cc b/src/laybasic/laybasic/layNetlistBrowserModel.cc index 72568aa67..b160ac73a 100644 --- a/src/laybasic/laybasic/layNetlistBrowserModel.cc +++ b/src/laybasic/laybasic/layNetlistBrowserModel.cc @@ -513,56 +513,90 @@ static std::pairterminal_def () : 0, termrefs.second ? termrefs.second->terminal_def () : 0); } -static std::pair terminal_defs_from_device_classes (IndexedNetlistModel *model, const std::pair &device_classes, const std::pair &devices, size_t terminal_id) +static std::vector > terminal_defs_from_device_classes (IndexedNetlistModel *model, const std::pair &device_classes, const std::pair &devices) { - const db::DeviceTerminalDefinition *td1 = 0, *td2 = 0; - if (device_classes.first && device_classes.first->terminal_definitions ().size () > terminal_id) { - td1 = &device_classes.first->terminal_definitions () [terminal_id]; + std::vector > result; + + std::map >, std::vector > > > nets; + + size_t n1 = 0; + if (device_classes.first) { + n1 = device_classes.first->terminal_definitions ().size (); } + size_t n2 = 0; if (device_classes.second) { + n2 = device_classes.second->terminal_definitions ().size (); + } - size_t second_terminal_id = terminal_id; + for (size_t i = 0; i < n1 || i < n2; ++i) { - // Because of terminal swapping we need to look up the second terminal by looking for equivalent terminals which carry the corresponding net - if (td1 && devices.first && devices.second) { + if (i < n2) { + const db::DeviceTerminalDefinition &td = device_classes.second->terminal_definitions () [i]; + size_t id = td.id (); + size_t id_norm = device_classes.second->normalize_terminal_id (id); + nets [id_norm].second.push_back (std::make_pair (&td, devices.second->net_for_terminal (id))); + } - size_t td1_id_norm = device_classes.first->normalize_terminal_id (td1->id ()); - const db::Net *n2 = model->second_net_for (devices.first->net_for_terminal (terminal_id)); + if (i < n1) { + const db::DeviceTerminalDefinition &td = device_classes.first->terminal_definitions () [i]; + size_t id = td.id (); + size_t id_norm = device_classes.first->normalize_terminal_id (id); + nets [id_norm].first.push_back (std::make_pair (&td, devices.first->net_for_terminal (id))); + } - // this scheme makes a guess what terminal to use when a pair of terminals does not connect - // to matching nets - std::set n2_excluded; - if (! n2) { - for (size_t i = 0; i < device_classes.first->terminal_definitions ().size (); ++i) { - const db::DeviceTerminalDefinition &td = device_classes.first->terminal_definitions () [i]; - if (device_classes.first->normalize_terminal_id (td.id ()) == td1_id_norm) { - const db::Net *n2x = model->second_net_for (devices.first->net_for_terminal (td.id ())); - if (n2x) { - n2_excluded.insert (n2x); - } - } - } + } + + for (std::map >, std::vector > > >::iterator n = nets.begin (); n != nets.end (); ++n) { + + std::vector > &nn1 = n->second.first; + std::vector > &nn2 = n->second.second; + + if (nn2.empty ()) { + + for (std::vector >::const_iterator i = nn1.begin (); i != nn1.end (); ++i) { + result.push_back (std::make_pair (i->first, (const db::DeviceTerminalDefinition *) 0)); } - for (size_t i = 0; i < device_classes.second->terminal_definitions ().size (); ++i) { - const db::DeviceTerminalDefinition &td = device_classes.second->terminal_definitions () [i]; - if (device_classes.second->normalize_terminal_id (td.id ()) == td1_id_norm) { - const db::Net *n2_actual = devices.second->net_for_terminal (i); - if ((! n2 || n2_actual == n2) && n2_excluded.find (n2_actual) == n2_excluded.end ()) { - second_terminal_id = i; - break; + } else if (nn1.empty ()) { + + for (std::vector >::const_iterator j = nn2.begin (); j != nn2.end (); ++j) { + result.push_back (std::make_pair ((const db::DeviceTerminalDefinition *) 0, j->first)); + } + + } else { + + std::vector >::iterator w = nn1.begin (); + for (std::vector >::const_iterator i = nn1.begin (); i != nn1.end (); ++i) { + + bool found = false; + + for (std::vector >::iterator j = nn2.begin (); j != nn2.end () && ! found; ++j) { + const db::Net *n2 = model->second_net_for (i->second); + if (n2 == j->second) { + result.push_back (std::make_pair (i->first, j->first)); + nn2.erase (j); + found = true; } } + + if (! found) { + *w++ = *i; + } + + } + + nn1.erase (w, nn1.end ()); + + for (size_t i = 0; i < nn1.size () && i < nn2.size (); ++i) { + result.push_back (std::make_pair (nn1 [i].first, nn2 [i].first)); } } - td2 = &device_classes.second->terminal_definitions () [second_terminal_id]; - } - return std::make_pair (td1, td2); + return result; } static @@ -601,24 +635,6 @@ static std::string combine_search_strings (const std::string &s1, const std::str } } -static size_t rows_for (const db::Device *device) -{ - if (! device || ! device->device_class ()) { - return 0; - } else { - return device->device_class ()->terminal_definitions ().size (); - } -} - -static size_t rows_for (const db::NetTerminalRef *ref) -{ - if (! ref || ! ref->device_class ()) { - return 0; - } else { - return ref->device_class ()->terminal_definitions ().size (); - } -} - static QIcon icon_for_net () { static QIcon icon; @@ -1981,12 +1997,14 @@ CircuitNetDeviceTerminalItemData::do_ensure_children (NetlistBrowserModel *model return; } - size_t n = std::max (rows_for (m_tp.first), rows_for (m_tp.second)); - for (size_t i = 0; i < n; ++i) { - std::pair device_classes = device_classes_from_devices (dp ()); - std::pair termdefs = terminal_defs_from_device_classes (model->indexer (), device_classes, dp (), i); - IndexedNetlistModel::net_pair nets = nets_from_device_terminals (dp (), termdefs); - push_back (new CircuitNetDeviceTerminalOthersItemData (this, nets, termdefs)); + std::pair devices = dp (); + std::pair device_classes = device_classes_from_devices (devices); + + std::vector > tp = terminal_defs_from_device_classes (model->indexer (), device_classes, devices); + + for (std::vector >::const_iterator i = tp.begin (); i != tp.end (); ++i) { + IndexedNetlistModel::net_pair nets = nets_from_device_terminals (dp (), *i); + push_back (new CircuitNetDeviceTerminalOthersItemData (this, nets, *i)); } } @@ -2336,10 +2354,13 @@ CircuitDeviceItemData::CircuitDeviceItemData (NetlistModelItemData *parent, cons void CircuitDeviceItemData::do_ensure_children (NetlistBrowserModel *model) { - size_t n = std::max (rows_for (dp ().first), rows_for (dp ().second)); - for (size_t i = 0; i < n; ++i) { - std::pair tp = terminal_defs_from_device_classes (model->indexer (), device_classes_from_devices (dp ()), dp (), i); - push_back (new CircuitDeviceTerminalItemData (this, tp)); + std::pair devices = dp (); + std::pair device_classes = device_classes_from_devices (devices); + + std::vector > tp = terminal_defs_from_device_classes (model->indexer (), device_classes, devices); + + for (std::vector >::const_iterator i = tp.begin (); i != tp.end (); ++i) { + push_back (new CircuitDeviceTerminalItemData (this, *i)); } } From 9f295523e40ecabd07dfedac0cefd7ade7634b7a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Mar 2021 21:56:53 +0100 Subject: [PATCH 09/16] explicit net joining - preparations --- src/db/db/dbLayoutToNetlist.cc | 86 ++++- src/db/db/dbLayoutToNetlist.h | 110 ++++++- src/db/db/dbNetlistExtractor.cc | 105 ++++++- src/db/db/dbNetlistExtractor.h | 28 +- src/db/db/gsiDeclDbLayoutToNetlist.cc | 165 ++++++++-- src/db/unit_tests/dbLayoutToNetlistTests.cc | 251 ++++++++++++++- src/db/unit_tests/dbNetlistExtractorTests.cc | 297 +++++++++++++++++- .../lay_plugin/layNetTracerDialog.cc | 7 +- src/tl/tl/tlGlobPattern.h | 24 ++ .../algo/device_extract_au14_circuits.gds | Bin 0 -> 48570 bytes .../algo/device_extract_au1_join_nets.gds | Bin 0 -> 10712 bytes testdata/algo/device_extract_l14.gds | Bin 0 -> 3158 bytes testdata/algo/device_extract_l1_join_nets.gds | Bin 0 -> 3404 bytes testdata/ruby/dbLayoutToNetlist.rb | 40 +++ 14 files changed, 1030 insertions(+), 83 deletions(-) create mode 100644 testdata/algo/device_extract_au14_circuits.gds create mode 100644 testdata/algo/device_extract_au1_join_nets.gds create mode 100644 testdata/algo/device_extract_l14.gds create mode 100644 testdata/algo/device_extract_l1_join_nets.gds diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index 56eb28675..4f3331992 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -40,7 +40,7 @@ namespace db // the iterator provides the hierarchical selection (enabling/disabling cells etc.) LayoutToNetlist::LayoutToNetlist (const db::RecursiveShapeIterator &iter) - : m_iter (iter), m_layout_index (0), m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0) + : m_iter (iter), m_layout_index (0), m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0), m_include_floating_subcircuits (false) { // check the iterator if (iter.has_complex_region () || iter.region () != db::Box::world ()) { @@ -60,7 +60,7 @@ LayoutToNetlist::LayoutToNetlist (const db::RecursiveShapeIterator &iter) } LayoutToNetlist::LayoutToNetlist (db::DeepShapeStore *dss, unsigned int layout_index) - : mp_dss (dss), m_layout_index (layout_index), m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0) + : mp_dss (dss), m_layout_index (layout_index), m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0), m_include_floating_subcircuits (false) { if (dss->is_valid_layout_index (m_layout_index)) { m_iter = db::RecursiveShapeIterator (dss->layout (m_layout_index), dss->initial_cell (m_layout_index), std::set ()); @@ -68,7 +68,7 @@ LayoutToNetlist::LayoutToNetlist (db::DeepShapeStore *dss, unsigned int layout_i } LayoutToNetlist::LayoutToNetlist (const std::string &topcell_name, double dbu) - : m_iter (), m_netlist_extracted (false), m_is_flat (true), m_device_scaling (1.0) + : m_iter (), m_netlist_extracted (false), m_is_flat (true), m_device_scaling (1.0), m_include_floating_subcircuits (false) { mp_internal_dss.reset (new db::DeepShapeStore (topcell_name, dbu)); mp_dss.reset (mp_internal_dss.get ()); @@ -79,7 +79,7 @@ LayoutToNetlist::LayoutToNetlist (const std::string &topcell_name, double dbu) LayoutToNetlist::LayoutToNetlist () : m_iter (), mp_internal_dss (new db::DeepShapeStore ()), mp_dss (mp_internal_dss.get ()), m_layout_index (0), - m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0) + m_netlist_extracted (false), m_is_flat (false), m_device_scaling (1.0), m_include_floating_subcircuits (false) { init (); } @@ -310,37 +310,91 @@ size_t LayoutToNetlist::global_net_id (const std::string &name) return m_conn.global_net_id (name); } -void LayoutToNetlist::extract_netlist (const std::string &joined_net_names, bool include_floating_subcircuits) +void LayoutToNetlist::set_include_floating_subcircuits (bool f) { - extract_netlist (joined_net_names, std::map (), include_floating_subcircuits); + m_include_floating_subcircuits = f; } -void LayoutToNetlist::extract_netlist (const std::string &joined_net_names, const std::map &joined_net_names_per_cell, bool include_floating_subcircuits) +void LayoutToNetlist::clear_join_net_names () +{ + m_joined_net_names.clear (); + m_joined_net_names_per_cell.clear (); +} + +void LayoutToNetlist::join_net_names (const tl::GlobPattern &gp) +{ + m_joined_net_names.push_back (gp); +} + +void LayoutToNetlist::join_net_names (const tl::GlobPattern &cell, const tl::GlobPattern &gp) +{ + m_joined_net_names_per_cell.push_back (std::make_pair (cell, gp)); +} + +void LayoutToNetlist::clear_join_nets () +{ + m_joined_nets.clear (); + m_joined_nets_per_cell.clear (); +} + +void LayoutToNetlist::join_nets (const std::set &jn) +{ + m_joined_nets.push_back (jn); +} + +void LayoutToNetlist::join_nets (const tl::GlobPattern &cell, const std::set &gp) +{ + m_joined_nets_per_cell.push_back (std::make_pair (cell, gp)); +} + +void LayoutToNetlist::extract_netlist () { if (m_netlist_extracted) { throw tl::Exception (tl::to_string (tr ("The netlist has already been extracted"))); } ensure_netlist (); + const db::Layout &layout = dss ().layout (m_layout_index); + db::NetlistExtractor netex; - netex.set_joined_net_names (joined_net_names); + netex.set_joined_net_names (m_joined_net_names); - const db::Layout &layout = dss ().layout (m_layout_index); - for (std::map::const_iterator j = joined_net_names_per_cell.begin (); j != joined_net_names_per_cell.end (); ++j) { - tl::GlobPattern pat (j->first); - if (pat.is_const ()) { - netex.set_joined_net_names (j->first, j->second); + std::map > jp_per_cell; + for (std::list >::const_iterator j = m_joined_net_names_per_cell.begin (); j != m_joined_net_names_per_cell.end (); ++j) { + if (j->first.is_const ()) { + jp_per_cell [j->first.pattern ()].push_back (j->second); } else { for (db::Layout::const_iterator c = layout.begin (); c != layout.end (); ++c) { - if (pat.match (layout.cell_name (c->cell_index ()))) { - netex.set_joined_net_names (layout.cell_name (c->cell_index ()), j->second); + if (j->first.match (layout.cell_name (c->cell_index ()))) { + jp_per_cell [layout.cell_name (c->cell_index ())].push_back (j->second); } } } } + for (std::map >::const_iterator i = jp_per_cell.begin (); i != jp_per_cell.end (); ++i) { + netex.set_joined_net_names (i->first, i->second); + } - netex.set_include_floating_subcircuits (include_floating_subcircuits); + netex.set_joined_nets (m_joined_nets); + + std::map > > jn_per_cell; + for (std::list > >::const_iterator j = m_joined_nets_per_cell.begin (); j != m_joined_nets_per_cell.end (); ++j) { + if (j->first.is_const ()) { + jn_per_cell [j->first.pattern ()].push_back (j->second); + } else { + for (db::Layout::const_iterator c = layout.begin (); c != layout.end (); ++c) { + if (j->first.match (layout.cell_name (c->cell_index ()))) { + jn_per_cell [layout.cell_name (c->cell_index ())].push_back (j->second); + } + } + } + } + for (std::map > >::const_iterator i = jn_per_cell.begin (); i != jn_per_cell.end (); ++i) { + netex.set_joined_nets (i->first, i->second); + } + + netex.set_include_floating_subcircuits (m_include_floating_subcircuits); netex.extract_nets (dss (), m_layout_index, m_conn, *mp_netlist, m_net_clusters); m_netlist_extracted = true; diff --git a/src/db/db/dbLayoutToNetlist.h b/src/db/db/dbLayoutToNetlist.h index 7faf276ec..a1bf8716f 100644 --- a/src/db/db/dbLayoutToNetlist.h +++ b/src/db/db/dbLayoutToNetlist.h @@ -27,6 +27,7 @@ #include "dbCellMapping.h" #include "dbNetlistExtractor.h" #include "dbNetlistDeviceExtractor.h" +#include "tlGlobPattern.h" namespace db { @@ -423,20 +424,106 @@ public: size_t global_net_id (const std::string &name); /** - * @brief Runs the netlist extraction - * See the class description for more details. + * @brief Sets a flag indicating whether to include floating subcircuits */ - void extract_netlist (const std::string &joined_net_names = std::string (), bool include_floating_subcircuits = false); + void set_include_floating_subcircuits (bool f); + + /** + * @brief Sets a flag indicating whether to include floating subcircuits + */ + bool include_floating_subcircuits () const + { + return m_include_floating_subcircuits; + } + + /** + * @brief Clears the "join net names" settings + */ + void clear_join_net_names (); + + /** + * @brief Joins net names matching the given expression + * + * Using this function will *add* one more rule. To clear all registered rules, use "clear_join_net_names". + * These pattern will only act on top level cells. + */ + void join_net_names (const tl::GlobPattern &gp); + + /** + * @brief Joins net names matching the given expression + * + * Using this function will *add* one more rule specific to cells matching the first glob pattern. To clear all registered rules, use "clear_join_net_names". + * Pattern registered with this function will act on the given cells, regardless of whether it's top level or not. + */ + void join_net_names (const tl::GlobPattern &cell, const tl::GlobPattern &gp); + + /** + * @brief Gets the joined net names for top level + * + * This method is mainly provided to test purposes. + */ + const std::list &joined_net_names () const + { + return m_joined_net_names; + } + + /** + * @brief Gets the joined net names per cell + * + * This method is mainly provided to test purposes. + */ + const std::list > &joined_net_names_per_cell () const + { + return m_joined_net_names_per_cell; + } + + /** + * @brief Clears the "join nets" settings + */ + void clear_join_nets (); + + /** + * @brief Joins the given nets for the top level cell + * + * This method will make an explicit connection between the nets given in the name set. + * This applies implicit joining of different nets with the same label (intra-net joining) + * and of nets with different names (inter-net joining). Intra-net joining is implied always. + */ + void join_nets (const std::set &jn); + + /** + * @brief Joins the given nets for cells matching the given pattern + * + * Using this function will *add* one more rule specific to cells matching the first glob pattern. To clear all registered rules, use "clear_join_nets". + * Pattern registered with this function will act on the given cells, regardless of whether it's top level or not. + */ + void join_nets (const tl::GlobPattern &cell, const std::set &gp); + + /** + * @brief Gets the joined nets for top level + * + * This method is mainly provided to test purposes. + */ + const std::list > &joined_nets () const + { + return m_joined_nets; + } + + /** + * @brief Gets the joined nets per cell + * + * This method is mainly provided to test purposes. + */ + const std::list > > &joined_nets_per_cell () const + { + return m_joined_nets_per_cell; + } /** * @brief Runs the netlist extraction - * In addition to the previous version, this extraction method allows specification of a per-cell list of - * joined (labelled) net names. - * The key of the "joined_net_names_per_cell" is a cell name or a glob expression for cells. On all matching cells, - * the value is applied as a label selector for labels that are joined together. The "joined_net_names" expressions - * is only applied to the top cell. + * See the class description for more details. */ - void extract_netlist (const std::string &joined_net_names, const std::map &joined_net_names_per_cell, bool include_floating_subcircuits = false); + void extract_netlist (); /** * @brief Marks the netlist as extracted @@ -830,6 +917,11 @@ private: double m_device_scaling; db::DeepLayer m_dummy_layer; std::string m_generator; + bool m_include_floating_subcircuits; + std::list m_joined_net_names; + std::list > m_joined_net_names_per_cell; + std::list > m_joined_nets; + std::list > > m_joined_nets_per_cell; struct CellReuseTableKey { diff --git a/src/db/db/dbNetlistExtractor.cc b/src/db/db/dbNetlistExtractor.cc index 9277124b9..8ebeaa720 100644 --- a/src/db/db/dbNetlistExtractor.cc +++ b/src/db/db/dbNetlistExtractor.cc @@ -35,33 +35,44 @@ NetlistExtractor::NetlistExtractor () // .. nothing yet .. } -void NetlistExtractor::set_joined_net_names (const std::string &jnn) +void NetlistExtractor::set_joined_net_names (const std::list &jnn) { m_joined_net_names = jnn; } -void NetlistExtractor::set_joined_net_names (const std::string &cellname, const std::string &jnn) +void NetlistExtractor::set_joined_net_names (const std::string &cellname, const std::list &jnn) { m_joined_net_names_per_cell.push_back (std::make_pair (cellname, jnn)); } +void NetlistExtractor::set_joined_nets (const std::list > &jnn) +{ + m_joined_nets = jnn; +} + +void NetlistExtractor::set_joined_nets (const std::string &cell_name, const std::list > &jnn) +{ + m_joined_nets_per_cell.push_back (std::make_pair (cell_name, jnn)); +} + void NetlistExtractor::set_include_floating_subcircuits (bool f) { m_include_floating_subcircuits = f; } static void -build_net_name_equivalence (const db::Layout *layout, const db::Connectivity &conn, db::property_names_id_type net_name_id, const std::string &joined_net_names, tl::equivalence_clusters &eq) +build_net_name_equivalence (const db::Layout *layout, const db::Connectivity &conn, db::property_names_id_type net_name_id, const std::list &jn_pattern, tl::equivalence_clusters &eq) { std::map > prop_by_name; - tl::GlobPattern jn_pattern (joined_net_names); for (db::PropertiesRepository::iterator i = layout->properties_repository ().begin (); i != layout->properties_repository ().end (); ++i) { for (db::PropertiesRepository::properties_set::const_iterator p = i->second.begin (); p != i->second.end (); ++p) { if (p->first == net_name_id) { std::string nn = p->second.to_string (); - if (jn_pattern.match (nn)) { - prop_by_name [nn].insert (db::prop_id_to_attr (i->first)); + for (std::list::const_iterator jp = jn_pattern.begin (); jp != jn_pattern.end (); ++jp) { + if (jp->match (nn)) { + prop_by_name [nn].insert (db::prop_id_to_attr (i->first)); + } } } } @@ -70,16 +81,20 @@ build_net_name_equivalence (const db::Layout *layout, const db::Connectivity &co // include pseudo-attributes for global nets to implement "join_with" for global nets for (size_t gid = 0; gid < conn.global_nets (); ++gid) { const std::string &gn = conn.global_net_name (gid); - if (jn_pattern.match (gn)) { - prop_by_name [gn].insert (db::global_net_id_to_attr (gid)); + for (std::list::const_iterator jp = jn_pattern.begin (); jp != jn_pattern.end (); ++jp) { + if (jp->match (gn)) { + prop_by_name [gn].insert (db::global_net_id_to_attr (gid)); + } } } const db::repository &text_repository = layout->shape_repository ().repository (db::object_tag ()); for (db::repository::iterator t = text_repository.begin (); t != text_repository.end (); ++t) { std::string nn = t->string (); - if (jn_pattern.match (nn)) { - prop_by_name [nn].insert (db::text_ref_to_attr (t.operator-> ())); + for (std::list::const_iterator jp = jn_pattern.begin (); jp != jn_pattern.end (); ++jp) { + if (jp->match (nn)) { + prop_by_name [nn].insert (db::text_ref_to_attr (t.operator-> ())); + } } } @@ -93,6 +108,58 @@ build_net_name_equivalence (const db::Layout *layout, const db::Connectivity &co } } +static void +build_net_name_equivalence_for_explicit_connections (const db::Layout *layout, const db::Connectivity &conn, db::property_names_id_type net_name_id, const std::set &nets_to_join, tl::equivalence_clusters &eq) +{ + std::map > prop_by_name; + + for (db::PropertiesRepository::iterator i = layout->properties_repository ().begin (); i != layout->properties_repository ().end (); ++i) { + for (db::PropertiesRepository::properties_set::const_iterator p = i->second.begin (); p != i->second.end (); ++p) { + if (p->first == net_name_id) { + std::string nn = p->second.to_string (); + if (nets_to_join.find (nn) != nets_to_join.end ()) { + prop_by_name [nn].insert (db::prop_id_to_attr (i->first)); + } + } + } + } + + // include pseudo-attributes for global nets to implement "join_with" for global nets + for (size_t gid = 0; gid < conn.global_nets (); ++gid) { + const std::string &gn = conn.global_net_name (gid); + if (nets_to_join.find (gn) != nets_to_join.end ()) { + prop_by_name [gn].insert (db::global_net_id_to_attr (gid)); + } + } + + const db::repository &text_repository = layout->shape_repository ().repository (db::object_tag ()); + for (db::repository::iterator t = text_repository.begin (); t != text_repository.end (); ++t) { + std::string nn = t->string (); + if (nets_to_join.find (nn) != nets_to_join.end ()) { + prop_by_name [nn].insert (db::text_ref_to_attr (t.operator-> ())); + } + } + + // first inter-name equivalence (this implies implicit connections for all n1 and n2 labels) + for (std::map >::const_iterator pn = prop_by_name.begin (); pn != prop_by_name.end (); ++pn) { + std::set::const_iterator p = pn->second.begin (); + std::set::const_iterator p0 = p; + while (p != pn->second.end ()) { + eq.same (*p0, *p); + ++p; + } + } + + // second intra-name equivalence + for (std::map >::const_iterator pn1 = prop_by_name.begin (); pn1 != prop_by_name.end (); ++pn1) { + std::map >::const_iterator pn2 = pn1; + ++pn2; + for ( ; pn2 != prop_by_name.end (); ++pn2) { + eq.same (*pn1->second.begin (), *pn2->second.begin ()); + } + } +} + void NetlistExtractor::extract_nets (const db::DeepShapeStore &dss, unsigned int layout_index, const db::Connectivity &conn, db::Netlist &nl, hier_clusters_type &clusters) { @@ -114,15 +181,31 @@ NetlistExtractor::extract_nets (const db::DeepShapeStore &dss, unsigned int layo std::map > net_name_equivalence; if (m_text_annot_name_id.first) { + if (! m_joined_net_names.empty ()) { build_net_name_equivalence (mp_layout, conn, m_text_annot_name_id.second, m_joined_net_names, net_name_equivalence [hier_clusters_type::top_cell_index]); } - for (std::list >::const_iterator m = m_joined_net_names_per_cell.begin (); m != m_joined_net_names_per_cell.end (); ++m) { + for (std::list > >::const_iterator m = m_joined_net_names_per_cell.begin (); m != m_joined_net_names_per_cell.end (); ++m) { std::pair cp = mp_layout->cell_by_name (m->first.c_str ()); if (cp.first) { build_net_name_equivalence (mp_layout, conn, m_text_annot_name_id.second, m->second, net_name_equivalence [cp.second]); } } + + if (! m_joined_nets.empty ()) { + for (std::list >::const_iterator n = m_joined_nets.begin (); n != m_joined_nets.end (); ++n) { + build_net_name_equivalence_for_explicit_connections (mp_layout, conn, m_text_annot_name_id.second, *n, net_name_equivalence [hier_clusters_type::top_cell_index]); + } + } + for (std::list > > >::const_iterator m = m_joined_nets_per_cell.begin (); m != m_joined_nets_per_cell.end (); ++m) { + std::pair cp = mp_layout->cell_by_name (m->first.c_str ()); + if (cp.first) { + for (std::list >::const_iterator n = m->second.begin (); n != m->second.end (); ++n) { + build_net_name_equivalence_for_explicit_connections (mp_layout, conn, m_text_annot_name_id.second, *n, net_name_equivalence [cp.second]); + } + } + } + } // the big part: actually extract the nets diff --git a/src/db/db/dbNetlistExtractor.h b/src/db/db/dbNetlistExtractor.h index a2be88166..06c8d4d29 100644 --- a/src/db/db/dbNetlistExtractor.h +++ b/src/db/db/dbNetlistExtractor.h @@ -26,6 +26,7 @@ #include "dbCommon.h" #include "dbHierNetworkProcessor.h" #include "dbNetShape.h" +#include "tlGlobPattern.h" #include #include @@ -104,23 +105,30 @@ public: * @brief Sets the joined net names attribute * This is a glob expression rendering net names where partial nets with the * same name are joined even without explicit connection. + * The cell-less version applies to top level cells only. */ - void set_joined_net_names (const std::string &jnn); + void set_joined_net_names (const std::list &jnn); /** * @brief Sets the joined net names attribute for a given cell name * While the single-parameter set_joined_net_names only acts on the top cell, this * version will act on the cell with the given name. */ - void set_joined_net_names (const std::string &cell_name, const std::string &jnn); + void set_joined_net_names (const std::string &cell_name, const std::list &jnn); /** - * @brief Gets the joined net names expression + * @brief Sets the joined nets attribute + * This specifies a list of net names to join. Each join group is a set of names which specifies the net + * names that are to be connected. Multiple such groups can be specified. Each net name listed in a + * group implies implicit joining of the corresponding labels into one net. + * The cell-less version applies to top level cells only. */ - const std::string &joined_net_names () const - { - return m_joined_net_names; - } + void set_joined_nets (const std::list > &jnn); + + /** + * @brief Sets the joined nets attribute per cell + */ + void set_joined_nets (const std::string &cell_name, const std::list > &jnn); /** * @brief Extract the nets @@ -135,8 +143,10 @@ private: std::pair m_text_annot_name_id; std::pair m_device_annot_name_id; std::pair m_terminal_annot_name_id; - std::string m_joined_net_names; - std::list > m_joined_net_names_per_cell; + std::list m_joined_net_names; + std::list > > m_joined_net_names_per_cell; + std::list > m_joined_nets; + std::list > > > m_joined_nets_per_cell; bool m_include_floating_subcircuits; bool instance_is_device (db::properties_id_type prop_id) const; diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index 5b53b0095..468be697c 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -140,6 +140,68 @@ static db::Region antenna_check (db::LayoutToNetlist *l2n, const db::Region &pol return antenna_check3 (l2n, poly, 1, 0, metal, 1, 0, ratio, diodes); } +static void join_net_names (db::LayoutToNetlist *l2n, const std::string &s) +{ + l2n->join_net_names (tl::GlobPattern (s)); +} + +static std::string dump_joined_net_names (const db::LayoutToNetlist *l2n) +{ + const std::list &jn = l2n->joined_net_names (); + std::vector s; + for (std::list::const_iterator j = jn.begin (); j != jn.end (); ++j) { + s.push_back (j->pattern ()); + } + return tl::join (s, ","); +} + +static void join_net_names2 (db::LayoutToNetlist *l2n, const std::string &c, const std::string &s) +{ + l2n->join_net_names (tl::GlobPattern (c), tl::GlobPattern (s)); +} + +static std::string dump_joined_net_names_per_cell (const db::LayoutToNetlist *l2n) +{ + const std::list > &jn = l2n->joined_net_names_per_cell (); + std::vector s; + for (std::list >::const_iterator i = jn.begin (); i != jn.end (); ++i) { + s.push_back (i->first.pattern () + ":" + i->second.pattern ()); + } + return tl::join (s, ","); +} + +static void join_nets (db::LayoutToNetlist *l2n, const std::set &s) +{ + l2n->join_nets (s); +} + +static std::string dump_joined_nets (const db::LayoutToNetlist *l2n) +{ + const std::list > &jn = l2n->joined_nets (); + std::vector s; + for (std::list >::const_iterator j = jn.begin (); j != jn.end (); ++j) { + std::vector t (j->begin (), j->end ()); + s.push_back (tl::join (t, "+")); + } + return tl::join (s, ","); +} + +static void join_nets2 (db::LayoutToNetlist *l2n, const std::string &c, const std::set &s) +{ + l2n->join_nets (tl::GlobPattern (c), s); +} + +static std::string dump_joined_nets_per_cell (const db::LayoutToNetlist *l2n) +{ + const std::list > > &jn = l2n->joined_nets_per_cell (); + std::vector s; + for (std::list > >::const_iterator i = jn.begin (); i != jn.end (); ++i) { + std::vector t (i->second.begin (), i->second.end ()); + s.push_back (i->first.pattern () + ":" + tl::join (t, "+")); + } + return tl::join (s, ","); +} + Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", gsi::constructor ("new", &make_l2n, gsi::arg ("iter"), "@brief Creates a new extractor connected to an original layout\n" @@ -334,7 +396,7 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", ) + gsi::method ("connect", (void (db::LayoutToNetlist::*) (const db::Region &)) &db::LayoutToNetlist::connect, gsi::arg ("l"), "@brief Defines an intra-layer connection for the given layer.\n" - "The layer is either an original layer created with \\make_layer and it's variants or\n" + "The layer is either an original layer created with \\make_incluidelayer and it's variants or\n" "a derived layer. Certain limitations apply. It's safe to use\n" "boolean operations for deriving layers. Other operations are applicable as long as they are\n" "capable of delivering hierarchical layers.\n" @@ -372,13 +434,38 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", gsi::method ("global_net_name", &db::LayoutToNetlist::global_net_name, gsi::arg ("global_net_id"), "@brief Gets the global net name for the given global net ID." ) + - gsi::method ("extract_netlist", &db::LayoutToNetlist::extract_netlist, gsi::arg ("join_net_names", std::string (), "\"\""), gsi::arg ("include_floating_subcircuits", false), - "@brief Runs the netlist extraction\n" - "'join_net_names' is a glob expression for labels. Nets on top level carrying the same label which matches this glob " - "expression will be connected implicitly even if there is no physical connection. This feature is useful to simulate a connection " - "which will be made later when integrating the component.\n" + gsi::method ("include_floating_subcircuits=", &db::LayoutToNetlist::set_include_floating_subcircuits, gsi::arg ("flag"), + "@brief Sets a flag indicating whether to include floating subcircuits in the netlist.\n" "\n" - "Valid glob expressions are:\n" + "With 'include_floating_subcircuits' set to true, subcircuits with no connection to their parent " + "circuit are still included in the circuit as floating subcircuits. Specifically on flattening this " + "means that these subcircuits are properly propagated to their parent instead of appearing as " + "additional top circuits.\n" + "\n" + "This attribute has been introduced in version 0.27 and replaces the arguments of \\extract_netlist." + ) + + gsi::method ("include_floating_subcircuits", &db::LayoutToNetlist::include_floating_subcircuits, + "@brief Gets a flag indicating whether to include floating subcircuits in the netlist.\n" + "See \\include_floating_subcircuits= for details.\n" + "\n" + "This attribute has been introduced in version 0.27.\n" + ) + + gsi::method ("clear_join_net_names", &db::LayoutToNetlist::clear_join_net_names, + "@brief Clears all implicit net joining expressions.\n" + "See \\extract_netlist for more details about this feature.\n" + "\n" + "This method has been introduced in version 0.27 and replaces the arguments of \\extract_netlist." + ) + + gsi::method_ext ("join_net_names", &join_net_names, gsi::arg ("pattern"), + "@brief Specifies another pattern for implicit joining of nets for the top level cell.\n" + "Use this method to register a pattern for net labels considered in implicit net joining. Implicit net joining " + "allows connecting multiple parts of the same nets (e.g. supply rails) without need for a physical connection. " + "The pattern specifies labels to look for. When parts are labelled with a name matching the expression, " + "the parts carrying the same name are joined.\n" + "\n" + "This method adds a new pattern. Use \\clear_join_net_names to clear the registered pattern.\n" + "\n" + "Each pattern is a glob expression. Valid glob expressions are:\n" "@ul\n" "@li \"\" no implicit connections.@/li\n" "@li \"*\" to make all labels candidates for implicit connections.@/li\n" @@ -389,27 +476,45 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "\n" "Label matching is case sensitive.\n" "\n" - "With 'include_floating_subcircuits' set to true, subcircuits with no connection to their parent " - "circuit are still included in the circuit as floating subcircuits. Specifically on flattening this " - "means that these subcircuits are property propagated to their parent instead of appearing as " - "additional top circuits.\n" + "This method has been introduced in version 0.27 and replaces the arguments of \\extract_netlist." + ) + + gsi::method_ext ("join_net_names", &join_net_names2, gsi::arg ("cell_pattern"), gsi::arg ("pattern"), + "@brief Specifies another pattern for implicit joining of nets for the cells from the given cell pattern.\n" + "This method allows applying implicit net joining for specific cells, not only for the top cell.\n" + "\n" + "This method adds a new pattern. Use \\clear_join_net_names to clear the registered pattern.\n" + "\n" + "This method has been introduced in version 0.27 and replaces the arguments of \\extract_netlist." + ) + + gsi::method ("clear_join_nets", &db::LayoutToNetlist::clear_join_nets, + "@brief Clears all explicit net joining expressions.\n" + "See \\extract_netlist for more details about this feature.\n" + "\n" + "Explicit net joining has been introduced in version 0.27." + ) + + gsi::method_ext ("join_nets", &join_nets, gsi::arg ("net_names"), + "@brief Specifies another name list for explicit joining of nets for the top level cell.\n" + "Use this method to join nets from the set of net names. All these nets will be connected together forming a single net.\n" + "Explicit joining will imply implicit joining for the involved nets - partial nets involved will be connected too (intra-net joining).\n" + "\n" + "This method adds a new name list. Use \\clear_join_nets to clear the registered pattern.\n" + "\n" + "Explicit net joining has been introduced in version 0.27." + ) + + gsi::method_ext ("join_nets", &join_nets2, gsi::arg ("cell_pattern"), gsi::arg ("net_names"), + "@brief Specifies another name list for explicit joining of nets for the cells from the given cell pattern.\n" + "This method allows applying explicit net joining for specific cells, not only for the top cell.\n" + "\n" + "This method adds a new name list. Use \\clear_join_nets to clear the registered pattern.\n" + "\n" + "Explicit net joining has been introduced in version 0.27." + ) + + gsi::method ("extract_netlist", &db::LayoutToNetlist::extract_netlist, + "@brief Runs the netlist extraction\n" "\n" "See the class description for more details.\n" "\n" - "The 'include_floating_subcircuits' argument has been introduced in version 0.26.2." - ) + - gsi::method ("extract_netlist", &db::LayoutToNetlist::extract_netlist, gsi::arg ("join_net_names"), gsi::arg ("join_net_names_per_cell"), gsi::arg ("include_floating_subcircuits", false), - "@brief Runs the netlist extraction\n" - "This method runs the netlist extraction like the two-parameter version. In addition to the latter, this method " - "can be given a per-cell net label joining specification in 'join_net_names_per_cell'. The keys of this array " - "are cell names or cell names or cell name match expressions (glob style). The values are label match expressions.\n" - "\n" - "If not an empty string, the 'join_net_names' label match expression is applied to the top cell. For all non-top cells " - "the per-cell label match expression is applied and determines what labels are joined into single nets. " - "As the keys of 'join_net_names_per_cell' are glob expressions, a single cell may fall into more than one category. In this " - "case, the label match pattern are combined. In any case, the 'join_net_names' has priority for the top cell.\n" - "\n" - "This variant of 'extract_netlist' has been introduced in version 0.26.2." + "This method has been made parameter-less in version 0.27. Use \\include_floating_subcircuits= and \\join_net_names as substitutes for the arguments of previous versions." ) + gsi::method_ext ("internal_layout", &l2n_internal_layout, "@brief Gets the internal layout\n" @@ -658,7 +763,13 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "considered.\n" "\n" "This variant has been introduced in version 0.26.6.\n" - ), + ) + + // test API + gsi::method_ext ("dump_joined_net_names", &dump_joined_net_names, "@hide") + + gsi::method_ext ("dump_joined_net_names_per_cell", &dump_joined_net_names_per_cell, "@hide") + + gsi::method_ext ("dump_joined_nets", &dump_joined_nets, "@hide") + + gsi::method_ext ("dump_joined_nets_per_cell", &dump_joined_nets_per_cell, "@hide") + , "@brief A generic framework for extracting netlists from layouts\n" "\n" "This class wraps various concepts from db::NetlistExtractor and db::NetlistDeviceExtractor\n" @@ -674,7 +785,7 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", " hierarchy and the layout taken as input.\n" "@/li\n" "@li Preparation\n" - " In this step, the device recognitions and extraction layers are drawn from\n" + " In this step, the device recognition and extraction layers are drawn from\n" " the framework. Derived can now be computed using boolean operations.\n" " Methods to use in this step are \\make_layer and it's variants.\n" " Layer preparation is not necessarily required to happen before all\n" diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 4718c6a2b..3e05c7e97 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -3156,7 +3156,7 @@ TEST(12_FlattenCircuitDoesFlattenLayout) db::compare_layouts (_this, ly, au); } -TEST(13_JoinNets) +TEST(13_JoinNetNames) { db::Layout ly; db::LayerMap lmap; @@ -3299,7 +3299,8 @@ TEST(13_JoinNets) l2n.connect_global (*rbulk, "VSS"); // Extract with joining VSS and VDD - l2n.extract_netlist ("{VSS,VDD}"); + l2n.join_net_names (tl::GlobPattern ("{VSS,VDD}")); + l2n.extract_netlist (); // debug layers produced for nets // 201/0 -> Well @@ -3385,3 +3386,249 @@ TEST(13_JoinNets) db::compare_layouts (_this, ly, au); } + +TEST(14_JoinNets) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int nwell = define_layer (ly, lmap, 1); + unsigned int nwell_lbl = define_layer (ly, lmap, 1, 1); + unsigned int active = define_layer (ly, lmap, 2); + unsigned int pplus = define_layer (ly, lmap, 10); + unsigned int nplus = define_layer (ly, lmap, 11); + unsigned int poly = define_layer (ly, lmap, 3); + unsigned int poly_lbl = define_layer (ly, lmap, 3, 1); + unsigned int diff_cont = define_layer (ly, lmap, 4); + unsigned int poly_cont = define_layer (ly, lmap, 5); + unsigned int metal1 = define_layer (ly, lmap, 6); + unsigned int metal1_lbl = define_layer (ly, lmap, 6, 1); + unsigned int via1 = define_layer (ly, lmap, 7); + unsigned int metal2 = define_layer (ly, lmap, 8); + unsigned int metal2_lbl = define_layer (ly, lmap, 8, 1); + + { + db::LoadLayoutOptions options; + options.get_options ().layer_map = lmap; + options.get_options ().create_other_layers = false; + + std::string fn (tl::testsrc ()); + fn = tl::combine_path (fn, "testdata"); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "device_extract_l14.gds"); + + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly, options); + } + + db::Cell &tc = ly.cell (*ly.begin_top_down ()); + db::LayoutToNetlist l2n (db::RecursiveShapeIterator (ly, tc, std::set ())); + + std::unique_ptr rbulk (l2n.make_layer ("bulk")); + std::unique_ptr rnwell (l2n.make_layer (nwell, "nwell")); + std::unique_ptr rnwell_lbl (l2n.make_layer (nwell_lbl, "nwell_lbl")); + std::unique_ptr ractive (l2n.make_layer (active, "active")); + std::unique_ptr rpplus (l2n.make_layer (pplus, "pplus")); + std::unique_ptr rnplus (l2n.make_layer (nplus, "nplus")); + std::unique_ptr rpoly (l2n.make_polygon_layer (poly, "poly")); + std::unique_ptr rpoly_lbl (l2n.make_text_layer (poly_lbl, "poly_lbl")); + std::unique_ptr rdiff_cont (l2n.make_polygon_layer (diff_cont, "diff_cont")); + std::unique_ptr rpoly_cont (l2n.make_polygon_layer (poly_cont, "poly_cont")); + std::unique_ptr rmetal1 (l2n.make_polygon_layer (metal1, "metal1")); + std::unique_ptr rmetal1_lbl (l2n.make_text_layer (metal1_lbl, "metal1_lbl")); + std::unique_ptr rvia1 (l2n.make_polygon_layer (via1, "via1")); + std::unique_ptr rmetal2 (l2n.make_polygon_layer (metal2, "metal2")); + std::unique_ptr rmetal2_lbl (l2n.make_text_layer (metal2_lbl, "metal2_lbl")); + + // derived regions + + db::Region ractive_in_nwell = *ractive & *rnwell; + db::Region rpactive = ractive_in_nwell & *rpplus; + db::Region rntie = ractive_in_nwell & *rnplus; + db::Region rpgate = rpactive & *rpoly; + db::Region rpsd = rpactive - rpgate; + + db::Region ractive_outside_nwell = *ractive - *rnwell; + db::Region rnactive = ractive_outside_nwell & *rnplus; + db::Region rptie = ractive_outside_nwell & *rpplus; + db::Region rngate = rnactive & *rpoly; + db::Region rnsd = rnactive - rngate; + + // return the computed layers into the original layout and write it for debugging purposes + + unsigned int lgate = ly.insert_layer (db::LayerProperties (20, 0)); // 20/0 -> Gate + unsigned int lsd = ly.insert_layer (db::LayerProperties (21, 0)); // 21/0 -> Source/Drain + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (22, 0)); // 22/0 -> P Diffusion + unsigned int lndiff = ly.insert_layer (db::LayerProperties (23, 0)); // 23/0 -> N Diffusion + unsigned int lptie = ly.insert_layer (db::LayerProperties (24, 0)); // 24/0 -> P Tie + unsigned int lntie = ly.insert_layer (db::LayerProperties (25, 0)); // 25/0 -> N Tie + + rpgate.insert_into (&ly, tc.cell_index (), lgate); + rngate.insert_into (&ly, tc.cell_index (), lgate); + rpsd.insert_into (&ly, tc.cell_index (), lsd); + rnsd.insert_into (&ly, tc.cell_index (), lsd); + rpsd.insert_into (&ly, tc.cell_index (), lpdiff); + rnsd.insert_into (&ly, tc.cell_index (), lndiff); + rpsd.insert_into (&ly, tc.cell_index (), lptie); + rnsd.insert_into (&ly, tc.cell_index (), lntie); + + db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS"); + db::NetlistDeviceExtractorMOS4Transistor nmos_ex ("NMOS"); + + // device extraction + + db::NetlistDeviceExtractor::input_layers dl; + + dl["SD"] = &rpsd; + dl["G"] = &rpgate; + dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes + dl["W"] = rnwell.get (); + l2n.extract_devices (pmos_ex, dl); + + dl["SD"] = &rnsd; + dl["G"] = &rngate; + dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes + dl["W"] = rbulk.get (); + l2n.extract_devices (nmos_ex, dl); + + // net extraction + + l2n.register_layer (rpsd, "psd"); + l2n.register_layer (rnsd, "nsd"); + l2n.register_layer (rptie, "ptie"); + l2n.register_layer (rntie, "ntie"); + + // Intra-layer + l2n.connect (rpsd); + l2n.connect (rnsd); + l2n.connect (*rnwell); + l2n.connect (*rpoly); + l2n.connect (*rdiff_cont); + l2n.connect (*rpoly_cont); + l2n.connect (*rmetal1); + l2n.connect (*rvia1); + l2n.connect (*rmetal2); + l2n.connect (rptie); + l2n.connect (rntie); + // Inter-layer + l2n.connect (rpsd, *rdiff_cont); + l2n.connect (rnsd, *rdiff_cont); + l2n.connect (rntie, *rnwell); + l2n.connect (*rpoly, *rpoly_cont); + l2n.connect (*rpoly_cont, *rmetal1); + l2n.connect (*rdiff_cont, *rmetal1); + l2n.connect (*rdiff_cont, rptie); + l2n.connect (*rdiff_cont, rntie); + l2n.connect (*rnwell, rntie); + l2n.connect (*rmetal1, *rvia1); + l2n.connect (*rvia1, *rmetal2); + l2n.connect (*rnwell, *rnwell_lbl); // attaches labels + l2n.connect (*rpoly, *rpoly_lbl); // attaches labels + l2n.connect (*rmetal1, *rmetal1_lbl); // attaches labels + l2n.connect (*rmetal2, *rmetal2_lbl); // attaches labels + // Global + l2n.connect_global (*rbulk, "BULK"); + l2n.connect_global (rptie, "BULK"); + + // Extract while joining VSS with BULK and VDD with NWELL + std::set jn; + jn.insert ("VDD"); + jn.insert ("NWELL"); + l2n.join_nets (tl::GlobPattern ("INV2"), jn); + jn.clear (); + jn.insert ("VSS"); + jn.insert ("BULK"); + l2n.join_nets (tl::GlobPattern ("INV2"), jn); + + // This will trigger an implicit connection on top level (side effect of explicit connections) + jn.clear (); + jn.insert ("VDD"); + l2n.join_nets (jn); + + l2n.extract_netlist (); + + // debug layers produced for nets + // 201/0 -> Well + // 203/0 -> Poly + // 204/0 -> Diffusion contacts + // 205/0 -> Poly contacts + // 206/0 -> Metal1 + // 207/0 -> Via1 + // 208/0 -> Metal2 + // 210/0 -> N source/drain + // 211/0 -> P source/drain + // 212/0 -> N tie + // 213/0 -> P tie + std::map dump_map; + dump_map [&rpsd ] = ly.insert_layer (db::LayerProperties (210, 0)); + dump_map [&rnsd ] = ly.insert_layer (db::LayerProperties (211, 0)); + dump_map [&rptie ] = ly.insert_layer (db::LayerProperties (212, 0)); + dump_map [&rntie ] = ly.insert_layer (db::LayerProperties (213, 0)); + dump_map [rbulk.get () ] = ly.insert_layer (db::LayerProperties (214, 0)); + dump_map [rnwell.get () ] = ly.insert_layer (db::LayerProperties (201, 0)); + dump_map [rpoly.get () ] = ly.insert_layer (db::LayerProperties (203, 0)); + dump_map [rdiff_cont.get ()] = ly.insert_layer (db::LayerProperties (204, 0)); + dump_map [rpoly_cont.get ()] = ly.insert_layer (db::LayerProperties (205, 0)); + dump_map [rmetal1.get () ] = ly.insert_layer (db::LayerProperties (206, 0)); + dump_map [rvia1.get () ] = ly.insert_layer (db::LayerProperties (207, 0)); + dump_map [rmetal2.get () ] = ly.insert_layer (db::LayerProperties (208, 0)); + + // write nets to layout + db::CellMapping cm = l2n.cell_mapping_into (ly, tc); + dump_nets_to_layout (l2n, ly, dump_map, cm); + + dump_map.clear (); + dump_map [&rpsd ] = ly.insert_layer (db::LayerProperties (310, 0)); + dump_map [&rnsd ] = ly.insert_layer (db::LayerProperties (311, 0)); + dump_map [&rptie ] = ly.insert_layer (db::LayerProperties (312, 0)); + dump_map [&rntie ] = ly.insert_layer (db::LayerProperties (313, 0)); + dump_map [rbulk.get () ] = ly.insert_layer (db::LayerProperties (314, 0)); + dump_map [rnwell.get () ] = ly.insert_layer (db::LayerProperties (301, 0)); + dump_map [rpoly.get () ] = ly.insert_layer (db::LayerProperties (303, 0)); + dump_map [rdiff_cont.get ()] = ly.insert_layer (db::LayerProperties (304, 0)); + dump_map [rpoly_cont.get ()] = ly.insert_layer (db::LayerProperties (305, 0)); + dump_map [rmetal1.get () ] = ly.insert_layer (db::LayerProperties (306, 0)); + dump_map [rvia1.get () ] = ly.insert_layer (db::LayerProperties (307, 0)); + dump_map [rmetal2.get () ] = ly.insert_layer (db::LayerProperties (308, 0)); + + dump_recursive_nets_to_layout (l2n, ly, dump_map, cm); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, *l2n.netlist (), + "circuit RINGO ();\n" + " subcircuit INV2 $1 ('NWELL,VDD'=VDD,IN=$I15,$3=FB,OUT=OSC,VSS=VSS);\n" + " subcircuit INV2 $2 ('NWELL,VDD'=VDD,IN=FB,$3=$I26,OUT=$I25,VSS=VSS);\n" + " subcircuit INV2 $3 ('NWELL,VDD'=VDD,IN=$3,$3=$I30,OUT=$I12,VSS=VSS);\n" + " subcircuit INV2 $4 ('NWELL,VDD'=VDD,IN=$I10,$3=$I29,OUT=$3,VSS=VSS);\n" + " subcircuit INV2 $5 ('NWELL,VDD'=VDD,IN=$I12,$3=$I31,OUT=$I13,VSS=VSS);\n" + " subcircuit INV2 $6 ('NWELL,VDD'=VDD,IN=$I13,$3=$I32,OUT=$I14,VSS=VSS);\n" + " subcircuit INV2 $7 ('NWELL,VDD'=VDD,IN=$I14,$3=$I33,OUT=$I15,VSS=VSS);\n" + " subcircuit INV2 $8 ('NWELL,VDD'=VDD,IN=$I25,$3=$I27,OUT=$I9,VSS=VSS);\n" + " subcircuit INV2 $9 ('NWELL,VDD'=VDD,IN=$I9,$3=$I28,OUT=$I10,VSS=VSS);\n" + "end;\n" + "circuit INV2 ('NWELL,VDD'='NWELL,VDD',IN=IN,$3=$3,OUT=OUT,VSS=VSS);\n" + " device PMOS $1 (S=$3,G=IN,D='NWELL,VDD',B='NWELL,VDD') (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device PMOS $2 (S='NWELL,VDD',G=$3,D=OUT,B='NWELL,VDD') (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " device NMOS $3 (S=$3,G=IN,D=VSS,B=VSS) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device NMOS $4 (S=VSS,G=$3,D=OUT,B=VSS) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " subcircuit TRANS $1 ($1=$3,$2='NWELL,VDD',$3=IN);\n" + " subcircuit TRANS $2 ($1='NWELL,VDD',$2=OUT,$3=$3);\n" + " subcircuit TRANS $3 ($1=$3,$2=VSS,$3=IN);\n" + " subcircuit TRANS $4 ($1=VSS,$2=OUT,$3=$3);\n" + "end;\n" + "circuit TRANS ($1=$1,$2=$2,$3=$3);\n" + "end;\n" + ); + + // compare the collected test data + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au14_circuits.gds"); + + db::compare_layouts (_this, ly, au); +} + diff --git a/src/db/unit_tests/dbNetlistExtractorTests.cc b/src/db/unit_tests/dbNetlistExtractorTests.cc index 5ee34afc5..6f0bbf378 100644 --- a/src/db/unit_tests/dbNetlistExtractorTests.cc +++ b/src/db/unit_tests/dbNetlistExtractorTests.cc @@ -958,19 +958,38 @@ TEST(3_DeviceAndNetExtractionWithImplicitConnections) // extract the nets + std::list gp; + db::Netlist nl2 = nl; - net_ex.set_joined_net_names ("{VDDZ,VSSZ,NEXT,FB}"); + gp.clear (); + gp.push_back (tl::GlobPattern ("{VDDZ,VSSZ,NEXT,FB}")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl2, cl); EXPECT_EQ (all_net_names_unique (nl2), true); nl2 = nl; - net_ex.set_joined_net_names ("{VDDZ,VSSZ,NEXT}"); + gp.clear (); + gp.push_back (tl::GlobPattern ("VDDZ")); + gp.push_back (tl::GlobPattern ("VSSZ")); + gp.push_back (tl::GlobPattern ("NEXT")); + gp.push_back (tl::GlobPattern ("FB")); + net_ex.set_joined_net_names (gp); + net_ex.extract_nets (dss, 0, conn, nl2, cl); + + EXPECT_EQ (all_net_names_unique (nl2), true); + + nl2 = nl; + gp.clear (); + gp.push_back (tl::GlobPattern ("{VDDZ,VSSZ,NEXT}")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl2, cl); EXPECT_EQ (all_net_names_unique (nl2), false); - net_ex.set_joined_net_names ("*"); + gp.clear (); + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); EXPECT_EQ (all_net_names_unique (nl), true); @@ -1233,7 +1252,9 @@ TEST(4_ResAndCapExtraction) // extract the nets - net_ex.set_joined_net_names ("*"); + std::list gp; + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); // Flatten device circuits @@ -1507,7 +1528,9 @@ TEST(5_ResAndCapWithBulkExtraction) // extract the nets - net_ex.set_joined_net_names ("*"); + std::list gp; + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); // Flatten device circuits @@ -1749,7 +1772,9 @@ TEST(6_BJT3TransistorExtraction) // extract the nets - net_ex.set_joined_net_names ("*"); + std::list gp; + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); // Flatten device circuits @@ -1915,7 +1940,9 @@ TEST(7_DiodeExtraction) // extract the nets - net_ex.set_joined_net_names ("*"); + std::list gp; + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); // cleanup + completion @@ -2048,7 +2075,9 @@ TEST(8_DiodeExtractionScaled) // extract the nets - net_ex.set_joined_net_names ("*"); + std::list gp; + gp.push_back (tl::GlobPattern ("*")); + net_ex.set_joined_net_names (gp); net_ex.extract_nets (dss, 0, conn, nl, cl); // cleanup + completion @@ -2922,3 +2951,255 @@ TEST(13_RemoveDummyPins) ); } +TEST(14_JoinNets) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int nwell = define_layer (ly, lmap, 1); + unsigned int active = define_layer (ly, lmap, 2); + unsigned int poly = define_layer (ly, lmap, 3); + unsigned int poly_lbl = define_layer (ly, lmap, 3, 1); + unsigned int diff_cont = define_layer (ly, lmap, 4); + unsigned int poly_cont = define_layer (ly, lmap, 5); + unsigned int metal1 = define_layer (ly, lmap, 6); + unsigned int metal1_lbl = define_layer (ly, lmap, 6, 1); + unsigned int via1 = define_layer (ly, lmap, 7); + unsigned int metal2 = define_layer (ly, lmap, 8); + unsigned int metal2_lbl = define_layer (ly, lmap, 8, 1); + + { + db::LoadLayoutOptions options; + options.get_options ().layer_map = lmap; + options.get_options ().create_other_layers = false; + + std::string fn (tl::testsrc ()); + fn = tl::combine_path (fn, "testdata"); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "device_extract_l1_join_nets.gds"); + + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly, options); + } + + db::Cell &tc = ly.cell (*ly.begin_top_down ()); + + db::DeepShapeStore dss; + dss.set_text_enlargement (1); + dss.set_text_property_name (tl::Variant ("LABEL")); + + // original layers + db::Region rnwell (db::RecursiveShapeIterator (ly, tc, nwell), dss); + db::Region ractive (db::RecursiveShapeIterator (ly, tc, active), dss); + db::Region rpoly (db::RecursiveShapeIterator (ly, tc, poly), dss); + db::Region rpoly_lbl (db::RecursiveShapeIterator (ly, tc, poly_lbl), dss); + db::Region rdiff_cont (db::RecursiveShapeIterator (ly, tc, diff_cont), dss); + db::Region rpoly_cont (db::RecursiveShapeIterator (ly, tc, poly_cont), dss); + db::Region rmetal1 (db::RecursiveShapeIterator (ly, tc, metal1), dss); + db::Region rmetal1_lbl (db::RecursiveShapeIterator (ly, tc, metal1_lbl), dss); + db::Region rvia1 (db::RecursiveShapeIterator (ly, tc, via1), dss); + db::Region rmetal2 (db::RecursiveShapeIterator (ly, tc, metal2), dss); + db::Region rmetal2_lbl (db::RecursiveShapeIterator (ly, tc, metal2_lbl), dss); + + // derived regions + + db::Region rpactive = ractive & rnwell; + db::Region rpgate = rpactive & rpoly; + db::Region rpsd = rpactive - rpgate; + + db::Region rnactive = ractive - rnwell; + db::Region rngate = rnactive & rpoly; + db::Region rnsd = rnactive - rngate; + + // Global + + db::Region bulk (dss); + + // return the computed layers into the original layout and write it for debugging purposes + + unsigned int lgate = ly.insert_layer (db::LayerProperties (10, 0)); // 10/0 -> Gate + unsigned int lsd = ly.insert_layer (db::LayerProperties (11, 0)); // 11/0 -> Source/Drain + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (12, 0)); // 12/0 -> P Diffusion + unsigned int lndiff = ly.insert_layer (db::LayerProperties (13, 0)); // 13/0 -> N Diffusion + + rpgate.insert_into (&ly, tc.cell_index (), lgate); + rngate.insert_into (&ly, tc.cell_index (), lgate); + rpsd.insert_into (&ly, tc.cell_index (), lsd); + rnsd.insert_into (&ly, tc.cell_index (), lsd); + rpsd.insert_into (&ly, tc.cell_index (), lpdiff); + rnsd.insert_into (&ly, tc.cell_index (), lndiff); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS"); + db::NetlistDeviceExtractorMOS4Transistor nmos_ex ("NMOS"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["SD"] = &rpsd; + dl["G"] = &rpgate; + dl["W"] = &rnwell; + dl["P"] = &rpoly; // not needed for extraction but to return terminal shapes + pmos_ex.extract (dss, 0, dl, nl, cl); + + dl["SD"] = &rnsd; + dl["G"] = &rngate; + dl["W"] = &bulk; + dl["P"] = &rpoly; // not needed for extraction but to return terminal shapes + nmos_ex.extract (dss, 0, dl, nl, cl); + + // perform the net extraction + + db::NetlistExtractor net_ex; + + db::Connectivity conn; + + // Global nets + conn.connect_global (bulk, "BULK"); + conn.connect_global (rnwell, "NWELL"); + + // Intra-layer + conn.connect (rpsd); + conn.connect (rnsd); + conn.connect (rpoly); + conn.connect (rdiff_cont); + conn.connect (rpoly_cont); + conn.connect (rmetal1); + conn.connect (rvia1); + conn.connect (rmetal2); + // Inter-layer + conn.connect (rpsd, rdiff_cont); + conn.connect (rnsd, rdiff_cont); + conn.connect (rpoly, rpoly_cont); + conn.connect (rpoly_cont, rmetal1); + conn.connect (rdiff_cont, rmetal1); + conn.connect (rmetal1, rvia1); + conn.connect (rvia1, rmetal2); + conn.connect (rpoly, rpoly_lbl); // attaches labels + conn.connect (rmetal1, rmetal1_lbl); // attaches labels + conn.connect (rmetal2, rmetal2_lbl); // attaches labels + + // extract the nets + + std::list > jn; + + jn.push_back (std::set ()); + jn.back ().insert ("BULK"); + jn.back ().insert ("VSS"); + + jn.push_back (std::set ()); + jn.back ().insert ("NWELL"); + jn.back ().insert ("VDD"); + + net_ex.set_joined_nets ("INV2", jn); + + std::list gp; + gp.push_back (tl::GlobPattern ("NEXT")); + gp.push_back (tl::GlobPattern ("FB")); + net_ex.set_joined_net_names (gp); + + net_ex.extract_nets (dss, 0, conn, nl, cl); + + EXPECT_EQ (all_net_names_unique (nl), true); + + // debug layers produced for nets + // 202/0 -> Active + // 203/0 -> Poly + // 204/0 -> Diffusion contacts + // 205/0 -> Poly contacts + // 206/0 -> Metal1 + // 207/0 -> Via1 + // 208/0 -> Metal2 + // 210/0 -> N source/drain + // 211/0 -> P source/drain + // 212/0 -> Bulk + // 213/0 -> NWell + std::map dump_map; + dump_map [layer_of (bulk) ] = ly.insert_layer (db::LayerProperties (212, 0)); + dump_map [layer_of (rnwell) ] = ly.insert_layer (db::LayerProperties (213, 0)); + dump_map [layer_of (rpsd) ] = ly.insert_layer (db::LayerProperties (210, 0)); + dump_map [layer_of (rnsd) ] = ly.insert_layer (db::LayerProperties (211, 0)); + dump_map [layer_of (rpoly) ] = ly.insert_layer (db::LayerProperties (203, 0)); + dump_map [layer_of (rdiff_cont)] = ly.insert_layer (db::LayerProperties (204, 0)); + dump_map [layer_of (rpoly_cont)] = ly.insert_layer (db::LayerProperties (205, 0)); + dump_map [layer_of (rmetal1) ] = ly.insert_layer (db::LayerProperties (206, 0)); + dump_map [layer_of (rvia1) ] = ly.insert_layer (db::LayerProperties (207, 0)); + dump_map [layer_of (rmetal2) ] = ly.insert_layer (db::LayerProperties (208, 0)); + + // write nets to layout + db::CellMapping cm = dss.cell_mapping_to_original (0, &ly, tc.cell_index ()); + dump_nets_to_layout (nl, cl, ly, dump_map, cm, true); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, nl, + "circuit RINGO ();\n" + " subcircuit INV2 $1 (IN=$I8,$2=FB,OUT=OSC,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $2 (IN=FB,$2=$I20,OUT=$I19,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $3 (IN=NEXT,$2=$I25,OUT=$I5,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $4 (IN=$I3,$2=$I24,OUT=NEXT,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $5 (IN=$I5,$2=$I26,OUT=$I6,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $6 (IN=$I6,$2=$I27,OUT=$I7,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $7 (IN=$I7,$2=$I28,OUT=$I8,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $8 (IN=$I19,$2=$I21,OUT=$I1,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $9 (IN=$I1,$2=$I22,OUT=$I2,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $10 (IN=$I2,$2=$I23,OUT=$I3,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + "end;\n" + "circuit INV2 (IN=IN,$2=$2,OUT=OUT,VDD=VDD,VSS=VSS);\n" + " device PMOS $1 (S=$2,G=IN,D=VDD,B=VDD) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device PMOS $2 (S=VDD,G=$2,D=OUT,B=VDD) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " device NMOS $3 (S=$2,G=IN,D=VSS,B=VSS) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device NMOS $4 (S=VSS,G=$2,D=OUT,B=VSS) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " subcircuit TRANS $1 ($1=$2,$2=VSS,$3=IN);\n" + " subcircuit TRANS $2 ($1=$2,$2=VDD,$3=IN);\n" + " subcircuit TRANS $3 ($1=VDD,$2=OUT,$3=$2);\n" + " subcircuit TRANS $4 ($1=VSS,$2=OUT,$3=$2);\n" + "end;\n" + "circuit TRANS ($1=$1,$2=$2,$3=$3);\n" + "end;\n" + ); + + // doesn't do anything here, but we test that this does not destroy anything: + nl.combine_devices (); + + // make pins for named nets of top-level circuits - this way they are not purged + nl.make_top_level_pins (); + nl.purge (); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, nl, + "circuit RINGO (FB=FB,OSC=OSC,NEXT=NEXT,'VDD,VDDZ'='VDD,VDDZ','VSS,VSSZ'='VSS,VSSZ');\n" + " subcircuit INV2 $1 (IN=$I8,$2=FB,OUT=OSC,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $2 (IN=FB,$2=$I20,OUT=$I19,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $3 (IN=NEXT,$2=$I25,OUT=$I5,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $4 (IN=$I3,$2=$I24,OUT=NEXT,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $5 (IN=$I5,$2=$I26,OUT=$I6,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $6 (IN=$I6,$2=$I27,OUT=$I7,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $7 (IN=$I7,$2=$I28,OUT=$I8,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $8 (IN=$I19,$2=$I21,OUT=$I1,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $9 (IN=$I1,$2=$I22,OUT=$I2,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + " subcircuit INV2 $10 (IN=$I2,$2=$I23,OUT=$I3,VDD='VDD,VDDZ',VSS='VSS,VSSZ');\n" + "end;\n" + "circuit INV2 (IN=IN,$2=$2,OUT=OUT,VDD=VDD,VSS=VSS);\n" + " device PMOS $1 (S=$2,G=IN,D=VDD,B=VDD) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device PMOS $2 (S=VDD,G=$2,D=OUT,B=VDD) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " device NMOS $3 (S=$2,G=IN,D=VSS,B=VSS) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device NMOS $4 (S=VSS,G=$2,D=OUT,B=VSS) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + "end;\n" + ); + + // compare the collected test data + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au1_join_nets.gds"); + + db::compare_layouts (_this, ly, au); +} + diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc index 8c1421bf7..d701b3774 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc @@ -1704,7 +1704,12 @@ NetTracerDialog::trace_all_nets (db::LayoutToNetlist *l2ndb, const lay::CellView tracer_data.configure_l2n (*l2ndb); - l2ndb->extract_netlist (std::string (), flat /*include floating subcircuits for netlist to flatten*/); + l2ndb->clear_join_nets (); + l2ndb->clear_join_net_names (); + + // include floating subcircuits for netlist to flatten + l2ndb->set_include_floating_subcircuits (flat); + l2ndb->extract_netlist (); if (flat) { l2ndb->netlist ()->flatten (); diff --git a/src/tl/tl/tlGlobPattern.h b/src/tl/tl/tlGlobPattern.h index 6e9dd22b4..308390c2b 100644 --- a/src/tl/tl/tlGlobPattern.h +++ b/src/tl/tl/tlGlobPattern.h @@ -68,6 +68,30 @@ public: */ GlobPattern &operator= (const std::string &s); + /** + * @brief Equality + */ + bool operator== (const tl::GlobPattern &other) const + { + return m_p == other.m_p; + } + + /** + * @brief Less + */ + bool operator< (const tl::GlobPattern &other) const + { + return m_p < other.m_p; + } + + /** + * @brief Pattern is empty + */ + bool empty () const + { + return m_p.empty (); + } + /** * @brief Sets a value indicating whether to treat the match case sensitive */ diff --git a/testdata/algo/device_extract_au14_circuits.gds b/testdata/algo/device_extract_au14_circuits.gds new file mode 100644 index 0000000000000000000000000000000000000000..7cf932fc888b37e5cb428c4c8a31894415616f6a GIT binary patch literal 48570 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 O?dG0v>U?wTFk=$ literal 0 HcmV?d00001 diff --git a/testdata/algo/device_extract_au1_join_nets.gds b/testdata/algo/device_extract_au1_join_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..0de18dc616528fb56b372aea1a30477c48b59f1a GIT binary patch literal 10712 zcmd5>Ylu}<6yEnS=iWPWwQ(HH$0(L#7>tIGj1WZaTFBIlI^$@fD5%koUQnn-MWB%n zf}nnxkR{a*EA54bSr8GSy(ohq3ICM!Lm23nGdibl?X}lEd!PF{=Zt6r!*}mG`#XE@ zwbovHt#y^B(&_c8BbCnmsWNJ+YF7)@RQt2ar@i}EE?25O)3jvSzPq}=d3W&c(#bmx zzrLYgbu`uHwR&F9ivG?0YnJu&ZSGv4vU#PvF==&Hd1KQ`sjk+H@B6!b|H=hIr7|-h zeNIs1gep5usccTgm~Vc0zVCwhluF&|`y*!<{da&PnA@PpfqFi(-Vh&a+(3N%FCu<` zA_v<1AME+ndCb2D?>G$5dG@pLI_F3J{y?AUUL1Ks zf7SN@xJBV2_BCYVEWP5eZ_v!<{&ZsO$R&;(BbWHZr^XA4Tt@)={`}ECP5LyR;+X7z zid`b+3XT~0$SEiCg!tfeo7ldB_?X|I$W^z)|H+M$O}j7^IH}(zj!C}1x4Pn*x`qK6ziYM`!9aP`xg{>VQy{y@T0KoY4|4Lm%zs@ z6!~f9$MDhKwf)*hbu3HlXQPOO##by`BS#xUwq_(7XQ!~O2}K^VwH42Z2S1s`e9HNV z$0%}*c07}MU}I~>)H>~qR1aj{1K*(6kM}-;qRC+4DLg-ibs5Ydr*3M8uNy?|#x+!X zY}<6s$I?d~14dyRxgQfoki<7!rBA@$|03Hn7SEz~`ImfxQRKCKf_P+e=2IO(d`6L1 z_=HslP`ts}_Xw5BpJCr(6uCy*ryM@jmYIc!8^G)#@KIw5iX3S3zYp#>%!}GK^9*Bl z0Eik_P~-@m+(VgqB2>TIl4P~?amww)|H zgZCEr2N`q7y@#F$-=N67gOT)%dQ3?;gC! ztk}unho8g>*FiVHX{!+fkP8cnykniSV%IZfgI@$6IPDPlCj%6DM={chO`XU7eDCt| zdMyoA-UUNn^Ibq#<1TRcuGN=@Hs@M++K_%0~&oetm8 z_vKfy&I8y7F!v(ne>p&rdk1Rq5wkufdofPKDNMuT1Vt|P%3)}o-!1Z ztt*3UApF#=h|N<}(=Q-}r2C|@GgEzx^&WFm>RuA%Fm-NIJ%)1mMq{WJYcvJ{sPP!| zwyM1+$dmAtGZSk^b_J-})p`j!Lo2fpS0!_>tBf*Ro}06rvhSt@{Q$`bx@h$Y@-&MN zo5#=R9e#p*-r`rrhI)Bov78Z2Osf>xmh&I1Nh*$#|or5nrFRwE{*`uTCtTPvA9ZA4$oUVBN&=4Q`>8AIS;)igL|IvsY~=;d0uaRj79z_eSDfx(dd~AukA&uB6nO=| z#om8{{+-3=`DmZK??8{NS9$L2-{B|d6fhq?x?}3!;V01l76V-zr2r#`gizodW8-0R`g(Wjeux@ z{z|@$vsm2Vh$Q?c8I<#^d6j-x^;!Gj+RvsB)_!*T(yGr)tn6r7{)v8W^3>T_!OZjQ zqt?o6jGhi^^wHyw#~EGN5`8r8%vNW#$7j7Bu z7&IO!9g&KWPVQItenSQS0g9-<>3-s>e;mB7S{@YcQP!(Vojj=S{HPB*bsmSDlqzDH zJvtkweLbqvU6j!#Vb3{?>5f<3yOY}_sA(-e;&F_c*67~ibMHcW71Fy96uH#IhVSG? z)Vq*gMWY@@oZN=$vCfkww?U7~vRsiEC7%BCg;;qqw4?xWpwSB$A9#Lm)YN5d?#XsE7-=AP93dhb(X^aV@ln-I(vv* zpU5;i5Yeg``5^dnDhMhom_)fxM9y`k+L`3ejP-6(UvaQLh7TA#2EW8qdu1$3 zk4*YlkGPvyA5-nrm!^mBQfS76!DFz(@r7znkIy=eFU)OX9X=7&UK8IW_y1=U@h{{2 zMxi^4*vpk_XIl9u^oadATnAZ)^KVtEJ=PcG{_;_LvxINapPt8enVM!WDF}X#Gl}wx zC8|f6_2C=S*4ev`+#*qNmexuG*GA<;_@MCar(P{iNDOP$%+FmQ!V|EQTPaVd@zLi* zJJb_O>@atX>owdJ(K{65Af6o2Lt$gLq$~`joi|Qr753H7Bq|Ky-tH*X&dkt{Bi05v zW%s_^ms0Kd4VGT-OZx36YQ!M!Ph1`M%@3v8nQ3~Q=dZ*%1WgtJuqHqPo*cwKKEyxUUw`H#`%) zQtgEnOP{mjbDfBO+DFkV)s8w!&bzu;k6f4H{LTp7UJ&y_sdg|$k8^u~^BWd=n7!rm z$S0!O&9J5S;`q4ROE{lb!r(dX@f?0fRD0!BmL8gR)+6`5tdFU7YR}deo{GNsNc2jz z7oKM8>$i!%VWa4kYOmiW`iAPfo9j@HCyMkP?#DD^YFS>G8R}J?27a^YI#j|1CEi}e zCXh5s+<+bEphwezq1M&=(gW&S1fM9W6lPw0&jwcxi>?OF44@&NQZF+V^Lx%Odx zD%EawS{0j^zeEEoSubSW;7idn)n22w=FjA9_}xH!(7S^9bu*^g%?&GlLQiybJ?n+u z739B~^+L60=?6}Vp2@nw<1twuttY$UpB}=g52J^^PuW9Lc|XLw`kL9FVQ&mfAQzwg qx_b6RJyO%h)gF==wr{u9zZ$#hg<8y-3ROS+e`3GtJV1ZDF8u|HH-|s~ literal 0 HcmV?d00001 diff --git a/testdata/algo/device_extract_l1_join_nets.gds b/testdata/algo/device_extract_l1_join_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..72ddfa900a1b912fca6ee4bb1e7ea6a5245f6914 GIT binary patch literal 3404 zcmbtWO-NKx6h8B2=FX2(nMq-(lvx;*jkq#^YZf+#wYLr+E0N$T$Ffz zB8-on^f4cK>zVJO#HlZckGWOgOlY*90`r_-qr`*rGmrCYjMOs^AA=Gv$gjNTpBqR1 z8>rtnc$bm8T2SJQzWsan$o<_J2Qm-!Zx)oe%TMY4MNNLSg}azs6UF}=l@>FSZU32M zB#Lg_>A*c5XkEW+|4&SKS7`aA-6>yTf0V@>D zk#)=or9z$)MozI_ol_Bgh8PF1vcUv|jnk4~>U>bWQJq?xtDli5bsl}YFDP-w5dI`` z?a@_^J<|OWlz4QHk1zew_iY*)QOG-u*73dhEhuruAU-O(&KUY`P zy~(}>B_6i_nSO{cYuk(o);=^MC~=kb?LXGKhb1p9u`dIK-=}7Qcxu%Z+FJME&xh3~6B-a7rh`dtDmO<~0^eDaR&L&nPrm|m~r4*h#agXZB^ Nqwb_x4-OlP(O(B0s!;#{ literal 0 HcmV?d00001 diff --git a/testdata/ruby/dbLayoutToNetlist.rb b/testdata/ruby/dbLayoutToNetlist.rb index ab41460aa..7b179a732 100644 --- a/testdata/ruby/dbLayoutToNetlist.rb +++ b/testdata/ruby/dbLayoutToNetlist.rb @@ -99,6 +99,46 @@ class DBLayoutToNetlist_TestClass < TestBase end assert_equal(map.select { |i| i }.join(","), "RINGO=>TOP,INV2=>INV2") + # extended attributes for extract_netlist + + l2n = RBA::LayoutToNetlist::new + l2n.include_floating_subcircuits = true + assert_equal(l2n.include_floating_subcircuits, true) + l2n.include_floating_subcircuits = false + assert_equal(l2n.include_floating_subcircuits, false) + + assert_equal(l2n.dump_joined_nets, "") + l2n.join_nets([ "VDD", "NWELL" ]) + l2n.join_nets([ "VSS", "BULK" ]) + assert_equal(l2n.dump_joined_nets, "NWELL+VDD,BULK+VSS") + l2n.clear_join_nets + assert_equal(l2n.dump_joined_nets, "") + assert_equal(l2n.dump_joined_nets_per_cell, "") + + l2n.join_nets("INV*", [ "VDD", "NWELL" ]) + l2n.join_nets("ND2*", [ "VSS", "BULK" ]) + assert_equal(l2n.dump_joined_nets_per_cell, "INV*:NWELL+VDD,ND2*:BULK+VSS") + + l2n.clear_join_nets + assert_equal(l2n.dump_joined_nets, "") + assert_equal(l2n.dump_joined_nets_per_cell, "") + + assert_equal(l2n.dump_joined_net_names, "") + l2n.join_net_names("VDD") + l2n.join_net_names("VSS") + assert_equal(l2n.dump_joined_net_names, "VDD,VSS") + l2n.clear_join_net_names + assert_equal(l2n.dump_joined_net_names, "") + assert_equal(l2n.dump_joined_net_names_per_cell, "") + + l2n.join_net_names("INV*", "VDD") + l2n.join_net_names("ND2*", "VSS") + assert_equal(l2n.dump_joined_net_names_per_cell, "INV*:VDD,ND2*:VSS") + + l2n.clear_join_net_names + assert_equal(l2n.dump_joined_net_names, "") + assert_equal(l2n.dump_joined_net_names_per_cell, "") + end def test_2_ShapesFromNet From 6ceac2c6ba89fd57bc3f77edfee5b155bfabfd6c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Mar 2021 23:14:33 +0100 Subject: [PATCH 10/16] Updated a test. --- src/drc/drc/built-in-macros/_drc_engine.rb | 9 ++ src/drc/drc/built-in-macros/_drc_netter.rb | 104 ++++++++++++++---- .../unit_tests/layNetlistBrowserModelTests.cc | 8 +- src/lvs/unit_tests/lvsTests.cc | 6 + 4 files changed, 104 insertions(+), 23 deletions(-) diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 65c76b590..d5d419e76 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -1790,6 +1790,14 @@ CODE # @synopsis connect_implicit(cell_pattern, label_pattern) # See \Netter#connect_implicit for a description of that function. + # %DRC% + # @name connect_explicit + # @brief Specifies explicit net connections + # @synopsis connect_explicit(net_names) + # @synopsis connect_explicit(cell_pattern, net_names) + # See \Netter#connect_explicit for a description of that function. + # Net names is an array (use square brackets to list the net names). + # %DRC% # @name antenna_check # @brief Performs an antenna check @@ -1828,6 +1836,7 @@ CODE connect connect_global connect_implicit + connect_explicit device_scaling extract_devices l2n_data diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb index 747179495..05b7fb6c2 100644 --- a/src/drc/drc/built-in-macros/_drc_netter.rb +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -67,6 +67,8 @@ module DRC @netlisted = false @connect_implicit = [] @connect_implicit_per_cell = {} + @connect_explicit = [] + @connect_explicit_per_cell = {} @l2n = nil @lnum = 0 @device_scaling = 1.0 @@ -241,6 +243,8 @@ module DRC @netlisted = false @connect_implicit = [] @connect_implicit_per_cell = {} + @connect_explicit = [] + @connect_explicit_per_cell = {} _clear_data end @@ -286,6 +290,67 @@ module DRC end + # %DRC% + # @name connect_explicit + # @brief Specifies a list of net names for nets to connect explicitly + # @synopsis connect_explicit(net_names) + # @synopsis connect_explicit(cell_pattern, net_names) + # Use this method to explicitly connect nets even if there is no physical connection. + # As this breaks with the concept of physical verification, this feature should be used + # with care. + # + # The first version of this function will connect all nets listed in the "net_names" array + # in the top level cell. The second version takes a cell name pattern and connects all nets listed + # in "net_names" for cells matching this pattern. + # + # A use case for this method is the following: consider a set of standard cells. These do not have a bulk + # or n-well pin in the schematics. They also do not have build in tie-down diodes for the + # substrate connections. In this case there is a build-in discrepancy between the + # schematics and the layout: bulk and VSS are separate nets within the layout, but the + # schematic does not list them as separate. The solution is to make an explicit connection + # between VDD and n-well and VSS and bulk, provided VDD and VSS are properly labelled as "VDD" and "VSS" + # and n-well and bulk are accessible as named nets (for bulk you can use "connect_global"). + # + # The following code will establish an explicit connection for all cells called "INV.." between + # BULK and VSS nets: + # + # @code + # connect_global(bulk, "BULK") + # ... + # connect_explicit("INV*", [ "BULK", "VSS" ]) + # @/code + # + # Explicit connections also imply implicit connections between different parts of + # one of the nets. In the example before, "VSS" pieces without a physical connection + # will also be connected. + # + # When you use explicit connections you should make sure by other ways that the connection + # is made physically. For example, for the bulk/n-well pin example above, by enforcing at least one + # tie-down diode per n-well island and in the substrate by means of a DRC rule. + # + # The explicit connections are applied on the next net extraction and cleared + # on "clear_connections". + + def connect_explicit(arg1, arg2 = nil) + + @engine._context("connect_explicit") do + + cleanup + if arg2 + arg2.is_a?(Array) || raise("The second argument has to be an array of strings") + arg2.find { |a| !a.is_a?(String) } && raise("The second argument has to be an array of strings") + arg1.is_a?(String) || raise("The first argument has to be a string") + @connect_explicit_per_cell[arg1] ||= [] + @connect_explicit_per_cell[arg1] << arg2 + else + arg1.is_a?(String) || raise("The argument has to be a string") + @connect_explicit << arg1 + end + + end + + end + # %DRC% # @brief Performs an antenna check # @name antenna_check @@ -481,16 +546,29 @@ module DRC # run extraction in a timed environment if ! @netlisted - # build a glob expression from the parts - expr = _join_glob_pattern(@connect_implicit) - - # build cell-pattern specific glob expressions from the parts - per_cell_expr = {} + # configure implicit net connections + @l2n.clear_join_net_names + @connect_implicit.each do |label_pattern| + @l2n.join_net_names(label_pattern) + end @connect_implicit_per_cell.each do |cell_pattern,label_pattern| - per_cell_expr[cell_pattern] = _join_glob_pattern(label_pattern) + label_pattern.each do |lp| + @l2n.join_net_names(cell_pattern, lp) + end end - @engine._cmd(@l2n, :extract_netlist, expr, per_cell_expr) + # configure explicit net connections + @l2n.clear_join_nets + @connect_explicit.each do |names| + @l2n.join_nets(names) + end + @connect_explicit_per_cell.each do |cell_pattern,name_lists| + name_lists.each do |names| + @l2n.join_nets(cell_pattern, names) + end + end + + @engine._cmd(@l2n, :extract_netlist) @netlisted = true end @@ -548,18 +626,6 @@ module DRC end end - def _join_glob_pattern(exprs) - - if exprs.size > 1 - expr = "{" + exprs.join(",") + "}" - else - expr = exprs[0] || "" - end - - expr - - end - def _make_data if @engine._dss diff --git a/src/laybasic/unit_tests/layNetlistBrowserModelTests.cc b/src/laybasic/unit_tests/layNetlistBrowserModelTests.cc index 83a7d059c..d57531088 100644 --- a/src/laybasic/unit_tests/layNetlistBrowserModelTests.cc +++ b/src/laybasic/unit_tests/layNetlistBrowserModelTests.cc @@ -330,10 +330,10 @@ TEST (2) EXPECT_EQ (model->rowCount (inv2Net0TerminalIndex), 4); EXPECT_EQ (model->parent (inv2Net0TerminalIndex) == inv2Net0Index, true); // .. whose second terminal is gate - EXPECT_EQ (tl::to_string (model->data (model->index (1, 0, inv2Net0TerminalIndex), Qt::UserRole).toString ()), "G|G|IN|2"); - EXPECT_EQ (tl::to_string (model->data (model->index (1, 0, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "G"); - EXPECT_EQ (tl::to_string (model->data (model->index (1, 2, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "IN (3)"); - EXPECT_EQ (tl::to_string (model->data (model->index (1, 3, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "2 (3)"); + EXPECT_EQ (tl::to_string (model->data (model->index (1, 0, inv2Net0TerminalIndex), Qt::UserRole).toString ()), "D|D|VDD|5"); + EXPECT_EQ (tl::to_string (model->data (model->index (1, 0, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "D"); + EXPECT_EQ (tl::to_string (model->data (model->index (1, 2, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "VDD (2)"); + EXPECT_EQ (tl::to_string (model->data (model->index (1, 3, inv2Net0TerminalIndex), Qt::DisplayRole).toString ()), "5 (2)"); // The Pin EXPECT_EQ (tl::to_string (model->data (model->index (1, 0, inv2Net0Index), Qt::UserRole).toString ()), ""); diff --git a/src/lvs/unit_tests/lvsTests.cc b/src/lvs/unit_tests/lvsTests.cc index aa147204c..f4938f530 100644 --- a/src/lvs/unit_tests/lvsTests.cc +++ b/src/lvs/unit_tests/lvsTests.cc @@ -161,3 +161,9 @@ TEST(18_private) run_test (_this, "test_18.lvs", "test_18.cir.gz", "test_18.gds.gz", true); } +TEST(19_private) +{ + // test_is_long_runner (); + run_test (_this, "test_19.lvs", "test_19.cir.gz", "test_19.gds.gz", true); +} + From de4454a0d60283603dd1c80e48755e508c523144 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Mar 2021 23:56:53 +0100 Subject: [PATCH 11/16] Updated documentation --- src/lay/lay/doc/about/drc_ref_drc.xml | 6 +- src/lay/lay/doc/about/drc_ref_global.xml | 11 +++ src/lay/lay/doc/about/drc_ref_netter.xml | 44 +++++++++++ src/lay/lay/doc/about/lvs_ref_netter.xml | 43 ++++++++--- src/lay/lay/doc/manual/bjt3_schematic.png | Bin 3931 -> 3910 bytes src/lay/lay/doc/manual/bjt4_schematic.png | Bin 4945 -> 4936 bytes src/lay/lay/doc/manual/bjt_lateral.png | Bin 8640 -> 8640 bytes src/lay/lay/doc/manual/bjt_vertical.png | Bin 8080 -> 8076 bytes src/lay/lay/doc/manual/cap_schematic.png | Bin 3419 -> 3271 bytes .../doc/manual/cap_with_bulk_schematic.png | Bin 5000 -> 4907 bytes src/lay/lay/doc/manual/diode_schematic.png | Bin 3135 -> 3062 bytes src/lay/lay/doc/manual/inv_explicit.png | Bin 0 -> 18920 bytes src/lay/lay/doc/manual/inv_schematic.png | Bin 24433 -> 24349 bytes src/lay/lay/doc/manual/inv_schematic2.png | Bin 14965 -> 14990 bytes src/lay/lay/doc/manual/lvs_connect.xml | 72 ++++++++++++++++++ src/lay/lay/doc/manual/metal_connections.png | Bin 13184 -> 12706 bytes src/lay/lay/doc/manual/mos3_schematic.png | Bin 3907 -> 3863 bytes src/lay/lay/doc/manual/mos4_schematic.png | Bin 4342 -> 4271 bytes src/lay/lay/doc/manual/net_graph.png | Bin 27017 -> 26114 bytes src/lay/lay/doc/manual/res_schematic.png | Bin 3194 -> 3083 bytes .../doc/manual/res_with_bulk_schematic.png | Bin 4729 -> 4578 bytes .../lay/doc/manual/substrate_connections.png | Bin 0 -> 4519 bytes src/lay/lay/doc/manual/symm_nodes.png | Bin 0 -> 36042 bytes src/lay/lay/layHelpResources.qrc | 1 + 24 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 src/lay/lay/doc/manual/inv_explicit.png create mode 100644 src/lay/lay/doc/manual/substrate_connections.png create mode 100644 src/lay/lay/doc/manual/symm_nodes.png diff --git a/src/lay/lay/doc/about/drc_ref_drc.xml b/src/lay/lay/doc/about/drc_ref_drc.xml index dc30e7fe4..9b7471ea4 100644 --- a/src/lay/lay/doc/about/drc_ref_drc.xml +++ b/src/lay/lay/doc/about/drc_ref_drc.xml @@ -887,10 +887,12 @@ The plain function is equivalent to "primary.sized".

Usage:

    -
  • expression.smoothed(d)
  • +
  • expression.smoothed(d [, keep_hv ])

-This operation acts on polygons and applies polygon smoothing with the tolerance d. See Layer#smoothed for more details. +This operation acts on polygons and applies polygon smoothing with the tolerance d. 'keep_hv' indicates +whether horizontal and vertical edges are maintained. Default is 'no' which means such edges may be distorted. +See Layer#smoothed for more details.

The "smoothed" method is available as a plain function or as a method on DRC expressions. The plain function is equivalent to "primary.smoothed". diff --git a/src/lay/lay/doc/about/drc_ref_global.xml b/src/lay/lay/doc/about/drc_ref_global.xml index 48877b955..5e1739598 100644 --- a/src/lay/lay/doc/about/drc_ref_global.xml +++ b/src/lay/lay/doc/about/drc_ref_global.xml @@ -312,6 +312,17 @@ l1 = input(1, 0)

See Netter#connect for a description of that function.

+

"connect_explicit" - Specifies explicit net connections

+ +

Usage:

+
    +
  • connect_explicit(net_names)
  • +
  • connect_explicit(cell_pattern, net_names)
  • +
+

+See Netter#connect_explicit for a description of that function. +Net names is an array (use square brackets to list the net names). +

"connect_global" - Specifies a connection to a global net

Usage:

diff --git a/src/lay/lay/doc/about/drc_ref_netter.xml b/src/lay/lay/doc/about/drc_ref_netter.xml index f57ed7a06..25d84a583 100644 --- a/src/lay/lay/doc/about/drc_ref_netter.xml +++ b/src/lay/lay/doc/about/drc_ref_netter.xml @@ -219,6 +219,50 @@ joins. Connections are accumulated. The connections defined so far can be cleared with
clear_connections.

+

"connect_explicit" - Specifies a list of net names for nets to connect explicitly

+ +

Usage:

+
    +
  • connect_explicit(net_names)
  • +
  • connect_explicit(cell_pattern, net_names)
  • +
+

+Use this method to explicitly connect nets even if there is no physical connection. +As this breaks with the concept of physical verification, this feature should be used +with care. +

+The first version of this function will connect all nets listed in the "net_names" array +in the top level cell. The second version takes a cell name pattern and connects all nets listed +in "net_names" for cells matching this pattern. +

+A use case for this method is the following: consider a set of standard cells. These do not have a bulk +or n-well pin in the schematics. They also do not have build in tie-down diodes for the +substrate connections. In this case there is a build-in discrepancy between the +schematics and the layout: bulk and VSS are separate nets within the layout, but the +schematic does not list them as separate. The solution is to make an explicit connection +between VDD and n-well and VSS and bulk, provided VDD and VSS are properly labelled as "VDD" and "VSS" +and n-well and bulk are accessible as named nets (for bulk you can use "connect_global"). +

+The following code will establish an explicit connection for all cells called "INV.." between +BULK and VSS nets: +

+

+connect_global(bulk, "BULK")
+...
+connect_explicit("INV*", [ "BULK", "VSS" ])
+
+

+Explicit connections also imply implicit connections between different parts of +one of the nets. In the example before, "VSS" pieces without a physical connection +will also be connected. +

+When you use explicit connections you should make sure by other ways that the connection +is made physically. For example, for the bulk/n-well pin example above, by enforcing at least one +tie-down diode per n-well island and in the substrate by means of a DRC rule. +

+The explicit connections are applied on the next net extraction and cleared +on "clear_connections". +

"connect_global" - Connects a layer with a global net

Usage:

diff --git a/src/lay/lay/doc/about/lvs_ref_netter.xml b/src/lay/lay/doc/about/lvs_ref_netter.xml index 91e11b964..04244ff71 100644 --- a/src/lay/lay/doc/about/lvs_ref_netter.xml +++ b/src/lay/lay/doc/about/lvs_ref_netter.xml @@ -114,13 +114,15 @@ netter.equivalent_pins("NAND2", 0, 1)

The circuit argument is either a circuit name (a string) or a Circuit object -from the schematic netlist. +from the schematic netlist. +

+Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists.

The pin arguments are zero-based pin numbers, where 0 is the first number, 1 the second etc. -If the netlist provides named pins, names can be used instead of numbers. +If the netlist provides named pins, names can be used instead of numbers. Again, use upper +case pin names for SPICE netlists.

-Before this method can be used, a schematic netlist needs to be loaded with -schematic. +Use this method andwhere in the script before the compare call.

"join_symmetric_nets" - Joins symmetric nets of selected circuits on the extracted netlist

@@ -174,6 +176,12 @@ with inherent ambiguity such as decoders, the complexity can be increased at the expense of potentially larger runtimes. The runtime penality is roughly proportional to the branch complexity. +

+By default, the branch complexity is unlimited, but it may +be reduced in order to limit the compare runtimes at the cost +of a less elaborate compare attempt. The preferred solution +however is to use labels for net name hints which also reduces +the depth.

"max_depth" - Configures the maximum search depth for net match deduction

@@ -191,6 +199,12 @@ the next net. With higher values for the depth, the algorithm pursues this "deduction path" in greater depth while with smaller values, the algorithm prefers picking nets in a random fashion as the seeds for this deduction path. The default value is 8. +

+By default, the depth is unlimited, but it may +be reduced in order to limit the compare runtimes at the cost +of a less elaborate compare attempt. The preferred solution +however is to use labels for net name hints which also reduces +the branch complexity.

"max_res" - Ignores resistors with a resistance above a certain value

@@ -223,11 +237,13 @@ This method will force an equivalence between the two circuits. By default, circuits are identified by name. If names are different, this method allows establishing an explicit correspondence.

+circuit_a is for the layout netlist, circuit_b for the schematic netlist. +Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. +

One of the circuits may be nil. In this case, the corresponding other circuit is mapped to "nothing", i.e. ignored.

-Before this method can be used, a schematic netlist needs to be loaded with -schematic. +Use this method andwhere in the script before the compare call.

"same_device_classes" - Establishes an equivalence between the device classes

@@ -244,6 +260,9 @@ method allows establishing an explicit correspondence. Before this method can be used, a schematic netlist needs to be loaded with
schematic.

+class_a is for the layout netlist, class_b for the schematic netlist. +Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. +

One of the device classes may be "nil". In this case, the corresponding other device class is mapped to "nothing", i.e. ignored.

@@ -260,7 +279,11 @@ schematic side.

Once a device class is mentioned with "same_device_classes", matching by name is disabled for this class. So after using 'same_device_classes("A", "B")' -"A" is no longer equivalent to "A" on the other side. +"A" is no longer equivalent to "A" on the other side. If you want "A" to +stay equivalent to "A" too, you need to use 'same_device_classes("A", "A")' +in addition. +

+Use this method andwhere in the script before the compare call.

"same_nets" - Establishes an equivalence between the nets

@@ -281,8 +304,10 @@ After using this function, the compare algorithm will consider these nets equiva Use this method to provide hints for the comparer in cases which are difficult to resolve otherwise.

-Before this method can be used, a schematic netlist needs to be loaded with -schematic. +circuit_a and net_a are for the layout netlist, circuit_b and net_b for the schematic netlist. +Names are case sensitive for layout-derived netlists and case-insensitive for SPICE schematic netlists. +

+Use this method andwhere in the script before the compare call.

"schematic" - Gets, sets or reads the reference netlist

diff --git a/src/lay/lay/doc/manual/bjt3_schematic.png b/src/lay/lay/doc/manual/bjt3_schematic.png index 3873fb729be1a63fc659436f68eeb29d67e0c659..503657f86f57b8f798688b12006af57b122c9515 100644 GIT binary patch literal 3910 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 k%>Nth{hJkyyuJrL literal 3931 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 E2a#oDHvj+t literal 4945 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 diff --git a/src/lay/lay/doc/manual/bjt_lateral.png b/src/lay/lay/doc/manual/bjt_lateral.png index 37e812d25fb7be798929258c13223c61b54e7dd9..6ce72b9a918171cf96aef18520b5721936e62cdd 100644 GIT binary patch literal 8640 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 diff --git a/src/lay/lay/doc/manual/bjt_vertical.png b/src/lay/lay/doc/manual/bjt_vertical.png index 4084da77f3b99588f90045e8a2ceb806219e445a..c92fa82505547dc39cb37acdddc204df29c46d6c 100644 GIT binary patch literal 8076 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 NT55W#wJI;){0FDs>hS;o literal 8080 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 GkN*qFEb+Af diff --git a/src/lay/lay/doc/manual/cap_schematic.png b/src/lay/lay/doc/manual/cap_schematic.png index de10904f2e3483c19b3c84de1e507872ed8c1fbc..a57ac7515458e6fabef03962a3466b9f634d5631 100644 GIT binary patch literal 3271 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 N0M^;Vslg#E{eP0^22%h4 literal 3419 zcmcIn`9IX_7oP<~w&`WPLX4#=OHKBjBqrY?ktOTM*wSF^%a}pbT<&-?XypXdB?p4a=N*j%&VKOu1f1OoA+EHBxD zKw!7y_$N2lu@_G+N;p0^a2HW_+{Y`H`!?zL&J$$mgad&DsDE$pBV~b8$KY?F=8mB^ zu$a&Y&k%1=L_~y|-`xP5muHZ-8aBk|$*O?_2*fvrx@2k>`E+G6zNN%V3fS20dPN>M zF`k=QA?s!S!%E23^`wJwMZJ~6qnRFiR28b0EG*LZl2LX2Y-w5jNa2OE$l(!1v&(os zE7j>alXJ{L=enwPO~V5i}tEaDAJ#FK#bdZF9#SfFIKGn zHY0?EQklgFEyzP~IAVv2OA7_deo(M2|D!6fu^fT?? zmT4>}T9itYG0H`Q(ehLc5|7l^BLo$ex>5-jn<)``JJZNXEr6@bVrKr(ZBxYrkQ!w0 zIVWyN^3>l9-sd1$I2+H`OHnV7iu9l26cV-F_=9Ds!*DUPwo0>pUYWt(-F?WkILT8A z-I3>|X4R!?I0~1!*4MqD+uaeYJGYV+Clu0w+iH)Dn~3`LaWqbhWF=F3Z^!nHwQ6u( zV8C3Ml-sD*t{&}PiveRhEcLs6u<<#|9(H5BHNG7j?00e^{50Bvqy)#ws9jlmk{9zv zl)5b&P8d^So&y|IvaJd0;=>v~FZ9l9Csl`zoKbxPP7U%U9 zEn2qpddPr6jEtD*;8b1}F_C;k)9e_F(4i*4msl%-vh?ARK?Lc0+KT#CfNBy3eR@c{ zq-jB}EKIE}^R&3>Xlas;*z{g=T+b}pg+Av#v+a~jZ(U^0e0^4lXrFRAwdRhhSX#KOg|78*p0+Ta7-L6hA3Jk$EX3&1|GE(Sliq({O+ABT~g5}#Ic z!ZXUJ`+SE2q{s6Ffsl$rn>&n5+-ZK5B!7Hz_~pi?2E+BYKk)O0N1+~jeYjX#H;#V4 z32m`Ry;n|szY)9vg9^MZx2~^9=)Ad~2Q|pU>zU85D0$`7J2cwwOqZc6+?y}i6e6lM zx$*Qu!X(2U@|CZn&OUI^TYJ$0@qlJSc))SsaSocGLsD(3^!6h`LVVC+A@!L>B^|JT-1;E^90BpqVa$;CN z?R&RQaNYF<<>;Tj^i`#KL&49_32CKzWZ!b}9Il}->CeZH%g4QKY4wefnY_3-o)_IL zMp=oE1ktMTuY2^F=o*dsKm7m&n{SzdE&|JREli7;AJ-f4qiPnkfnY{7B-R#JLyvf9swS<8!>ALk=_@sn+2AO<6^s@1!pP8J8Kqusd((; zaK)@7o4#-ih<(w3GPdnE1a0jMcoh>p_$~T}0|-kP)0n%FMYq6}F47LNlkVv#Sl?9X z6Z$P<7L|Q6FxV$6PxGprD#LjsA{*w2>xmLQTSmo z{hR3g?q37T2DeX6K)zeHzh{J|jH}FT>70jE#f$d1U6i=BeA~#5!fFWy(uhxNPrWo- z{KOC#F2VKd0o9i6%WMK;`+&E`J_T$mxFxb1b|6}%5ZDu;cr{iPQj19MTtmHKX_Mc5 zv>>PwdC0}fYtQ`M+bm+b!x|fzps}sH%c$3G!eq2Nyd& z|PoSru@m$h@I%52;XP~zr+K^MAa~#rO0S=6I{^K`6 z6{SwboGb`itB91;wg9*RcY!hh!pyy_ZtEaQXFY(vhcGwGe!TeHpua8F(fe?U*#EQ9Wmgqay$ruQ0CVz J)n4?t_kZ-SSKI&q diff --git a/src/lay/lay/doc/manual/cap_with_bulk_schematic.png b/src/lay/lay/doc/manual/cap_with_bulk_schematic.png index 8390a4b3aaa5d194df501ba99300d9499b288b96..7013d757c269b3bf235ab17c34fd206de078b0aa 100644 GIT binary patch literal 4907 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 JPDHLkV1kA|3J?GQ delta 2669 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 U_J$;7ls%P+p}7H6?^)9S07)Nz%m4rY literal 24433 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 K={4xQNc?{>YZV~? diff --git a/src/lay/lay/doc/manual/inv_schematic2.png b/src/lay/lay/doc/manual/inv_schematic2.png index 2867dea7a1a7b510bfaed50c65949f456515ccc0..ec5ffae410143f8b93103e5f08efaefda1651971 100644 GIT binary patch literal 14990 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 statements.

+

Explicit connections

+ +

+ Explicit connections can be useful to enforce a connection in the layout + which is made in the schematic, but not physically on the level of the cell. + For example consider the following layout for an inverter: +

+ +

+ +

+ +

+ In the layout there are no tie-down diodes, hence there is no physical + connection to the n-well region and no physical connection to the bulk + substrate. This saves space, but these diodes need to be added by other + ways. + Usually this is done when the standard cells are combined into + macros. Filler cells will be added which include these substrate and + well contacts. +

+ +

+ On the inverter level however, there is no such connection. Therefore + the inverter has separate bulk and n-well pins. The schematic sometimes + is a simplified version which does not offer these pins. Hence there is + an intrinsic mismatch between layout and schematic. +

+ +

+ +

+ +

+ To align layout and schematics, bulk and VSS pins can be connected + explicitly. Same for n-well and VDD. + There is a certain risk to forget making these connections later. + But this risk can be mitigated by implementing DRC rules which + demand at least one tie-down diode for each isolated n-well island + or the bulk. +

+ +

+ To establish an explicit connection, make sure that n-well and + bulk have proper names. For the n-well this can be done by creating + labels on the n-well islands giving them a proper name - e.g. "NWELL". + The bulk isn't a real layout layer with polygons on it. Using "connect_global" + will both connect everthing on this layer and give it a name. +

+ +

+ The following code will connect the bulk net with "VSS" inside the cell "INV": +

+ +
connect_global(bulk, "BULK")
+...
+connect_explicit("INV", [ "BULK", "VSS" ])
+
+ +

+ The cell name can be a pattern. For example "INV*" will apply this rule on all + cells starting with "INV". + The cell is not mandatory: if it is omitted, the rule is applied to top level only + to avoid introducing rules in subcells where they would mask layout errors. +

+ +

+ An explicit connection will also imply implicit connections on the nets + listed in the net names. So in the example above, different pieces of "VSS" + are connected even if they are not physically connected. +

+ diff --git a/src/lay/lay/doc/manual/metal_connections.png b/src/lay/lay/doc/manual/metal_connections.png index a5e184dfd7919ed37a7ef4419b63073263eedb9e..707ed7677139e53e7b0ee34921dce4788ae27e30 100644 GIT binary patch literal 12706 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 literal 13184 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 Jl@>he{{XdVa%TVl diff --git a/src/lay/lay/doc/manual/mos3_schematic.png b/src/lay/lay/doc/manual/mos3_schematic.png index 7070986ccf4b8dd8ffead10a262ba25ecabfcfaf..e27e5935ee439b54c685ff4e90ca3cbe65c2b92b 100644 GIT binary patch delta 3341 zcmZu!cU05Mx=nxtX-O2094P@TSWu8Fy%!50paM!yI117X5Fiqy{D=y~f}rux>oLlq zgd!3GQoYW6oC)~glGy#m2z{}d+Yx3)|)l6*8XGWTi>_${$|#6A#;#f5x`T& z)3zS?!XM)aPyK`9dqjdhg%EAcxH=np%Ka4j%WOuf%ATiownue!uT%=pfkXWRE+M`J zg`F7-)5*CKHuK~PrmPyWH@d>67j#+IDMX5JsJ_{TcKXO3ZK+*T^BMy4zGBj;cXyp9 z+77QLv&_P;3zmxGtmg_>H?Iq}_<3M3Ndz+^X@a2ubGGzy9C-98H->H!vgJR3+msR3 zwAO)qCMz3MDk82e=mzv7dL+-yr4G@%=n8|kk`*KI)&W&6ukQ^bO_Xybs=<7zniSSs zr)OwT8$Fa>W0TRTItLa=wM$Wy7h5XmhY3|>H~`h88f6uAq+&&tuy63b6S@OZ2yT_c z1CU&}AB-}046_2Vf~!eYOU!~N(^!=*i$4!8-#f3FtRP||*mj!|TnO>NLd}h7Mx>2} zRJ}a+DpAkocxtY+evAFDeF*C#sOvi_v$oY~Yd3<%3tRc_@$m`7GlWJ5j`l3<9Bm~u(3k@C}DRXk*CQ|&4pm2(~S~lZr##M-i6fy7*|wT+Osor ziD=aJz^0Xjhs;C-(Nptgtp#6#$o|BGA^^B3V2ldCOS5dt1YwOBSBdq18kNt{k?eI` zmVxu=9p&g=C_^Sbo{dYe*FPjUbHY{Y<%)ij{idg{wcy=UoL|s*1tR#XO%c1SxJa}3 zE^RkAPSXl3@{(mY0+^d=_+#l)(r!ETySN%Umh$?L6X7Y05x-6q% z9yf}>MkE(5=`ddm>EnIZy8tYzJu5aBHKKT5adG0~&3Is)yq(|=5*eG8nyZhZ&1{6I zT>HirN*~#BMijp;K|DM+0cH@S09WI_+x)dU+pSLPqHMvamhGRvRi`WplECjDwejA zOiYd4^=lt9kd?16yhfl)4&I`;eQ2}636@h}%QrkL=D?*oO(PDJ=ZJ=sQ(6E%=WB6x>{OQ>`ROn4;ai0zq(*4+~ zFsVsB1%O7|P*DdU-J$Cjm%RcKI1372)>h<^xkU>j^?;>CUP;&4`*2A)0x(aH=9JT8 zw#Hteq#uFWLC#X!ie6CccMxW!@|EeM<#AitaRD{;AQ?1SPwB2Fvje?@5^C*u*F%b2 zH*B#+^G-y?v9JzlA8FFWhaJea>Q(k5y;SlPQ{>U*xL&tb+L{<|){1;po8&kbYrPBG zU!bu7$5&1{5nqq7GA;n=Uy~FsVfQgC?|hCvV}&-QVzzR$tVYYkGP@f3!9~+loHc>A z;zG3~7<4YX?YJx5X`DA70wMf)VS>2IskB&?GBnzK@fR*|LB}<1xTDKR**?(_0Q_#1 z{2K4T@aLu_t&K7KFViP<&)PaexA*4CP|_FGnKaHHeVLmbDFeZRi96(a~GT}*;y;{A1Yp%f@!<(6?m^~lq+hqIgb{r`tYA$Akl9Mu)V!VhcHELRkbRSwsWi7y#6XEzcvq-t-jt36q zyZG-CSO5U^E6()W2(wVan-8uQBc=Rg^y`_cSaOW<(5E9$($Ic(EwG|`R0L} z
4Nh)KelF%fNB=sypb6QyGcs-g>KFqmTrty^JL-$gY^{BX_Wvn`{6@!N!vxSh^* z)1CXVBcqv`s6+l*ALG;oEOPkW1V>Numvi~0zaG4=&7>-!m~N-P%Y83Wz5>pKpPT|D zL!sLjnaI)c!CO@O0xUf}<#5@u*I1JD<7D${(wq3q<`#;*!#OKU$s0TnCy5;MkK|B; zZ%we0aN5AzC~LSNb{nTwhwynYMr}}{>8u*6<6%DVuSU-Z@Y$YPDSkRj5wn#Eg&i)E z<_~I5NFNsnX-Db%LoD6B;{s~t_n+awLMR~#?{ZryZ4L?F;kLXS_2MNha^Ks_WXFL+ zlC35rF#H(sgle#zGxaz~DN0s|4G8A2>Bl)Lnqk%F2fpGTr`Z|{C8BhN`kX{&$H|>4 zm2kHh+%*2N(daZCO{JKt(x_=ZpA#P`hyH#X6tQ33&gl-e)!_5D7P&#Fx2zwi5b7+& z0`)I?RuoO2QPF-43Vy8Zctzp@N&5oXRZWk1vH$=jmi#1)*3bS=7|9>7LhwV3FK}|6 zS$g!1jvL7oMgF5sPY4yhp)M;+)Jm8u)jGJ$ic{s9^?4qRSWpE*l3-1(5gP3Gk+d(311QN5`ofqydG!?YEZX zOR+v*d0g5oCdb45_Uab-_plrdwzfYiJ_DydF*Jl5XWUaf>|9`k2~TN`nQMO; zJJ0;Bt*x#6vllzNrb2x5QpABDj*lqS=5Sj2hHJW-Rr2QAMBb_3v#)(4nt?;znHrOF zeglI$?F$!2LqqaSP-%au_B>Ge3(_j#_l4U29zHl+Q&SVvaDBi>FScrGY-p%aVr>zz z!Rv-Iqe8j(eU%aNsE-djTjc6~t9ymnQZ@X{3|_GM_5Bm>oqmt}FlYE$X3z)ARW)hn z)|qD#^bi2e5bXGh@QWH=6oqZC>utlaW6cvoopslRVwh#~rylGp{OPq`Y}Y=DX! zT23YdoTD6DfU~X=-cr@nwEB(YYBt~ENYc(Iu}a!7+TDq18mESdxNq8YJZmiWnY`@Y zpa!~#yCTc*5dPQZ!O47X&BVmSJ-NNdO|Ev{h6o0Dd$X>;@Vq8uQ9{MCKY0O(RHQ&7 z280;0$iDF9C5yNb8 zAs3&$Z_)vMFH{y924y&%xsOS8F3#qkVf6-XPbjrk|gM3VP!#ga_;X=Gz(UH$kZc zIFCcVofuOwfvhP-=^~c1pQZ6s^~$+o!}caJQ-u!*LpFL*wndPNk7{}fGkO;v9#605 z|EpA)kWRR~{O7Z@m{y1XT$&^VTvhzXkoleYVtL2wsNO&MT2(ZVcsQPMJzZlLaQokb Cp@=j9 delta 3375 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 delta 3794 zcmZ8idpOgN_utHA%w-C)HyqJ#pH<4?q zm=+ous+8#pmxrp~*c$%Kd=kD}C+@VQ^U z*!uR>5X2sD8eCe*lQ4Tnvd(exOU6 zVbcfcNvD#m>mSP*Xl&@3dzUA4K;|IE-~zb=&aijy4 zj-l_bjvwV0nw&*gWeC(MCy+r(Gm%i7tN7A*KDM+m;b1`zyc0YV4G$*UD7oYlB4*~` zvHj=>s-97LqYA?;Jwe08Uv1`0WRd}eHN0l++^We4fTAr?-^9y>*~nthObqnI){R%W zlIyAr6)2dI2q6I1?%&7np`@qJBH^(f)_hACZso4QwIJ31zj;@FkUtgY!=T?7r>x^0w$aB!TLC|B?a^42(%Ar z;2-uxA3B9Mp#&mPpc$voLQz!H0Se$L>Q5JhL0y5m=3kLf`*wO!;~j6_y|HQqZ5saB z2`^0(xFCrQHw59Rm(Nos!{7&54KSx{_F~K8IVjatcQ`Z^M%jrBx-9S*Usesy#AwM; zoV@xDITLN)Jk>Z^R_(UOfaUlt7TS~?6Y-?EBoxLQ^xWQ<2qpN|L_~1^6R$`K<^FAr z(g)I#FFd}0q=9D?p~g4yJ}OnlnADvMoms4FZmS;phJ-%UoA+}gK+?!fkHT;(jxXGq z>QR?ew;LW3pa1noyKceJ*8Hli^N?$L_R8YO=ZCH-nNOc!>nUx33RUyZsxBvZsTG=+ z>DgA=XpSL>w)JBfA|zX6tJqpZ+$sOvd_zP(AII#^qL0RGT#yU(H#PxM$1{jNyGEN5 zYO5?7(Vo4Hj~JUTUN$;9dlk+p4lZkF`XN#eJOGBBI*IL6^Yp}O_uLD& z?%HbfRphwRq)5ym-Z%RZaJ^=zWs^De@a+B&Kh8F;vM_Uby$xKbE)PwCdz*tgoOhM_ znv4XG)MV5|KA_?PI^_UH>KkKrbl|2;zcCHd;E?u62zI?Fs#TlOko-Iv8CMm;Lm2OqFXZr8l*HGA zT9c82Y+fB3&Ef%Q%n4{jIRg7~;O>5{B^Lasv$)F$-S^23n5ty=oN|Xe)umxz+@4xD zc)0dN@nPE2_*0lKAI*Z2CyTWBJA&H)W5$gB7!Lh5B_Mg8!f1zOQE3SS)7LZXpU{A3 z198kGj<2`YTSEy?5W_`Np^Dt4K7 zfEs>xb4&}?31#Fn17C*2ank|nGau?=8hG2-WoNIiq;f~3N?AJB3$zP9xMR(k>sixe ze4xtDS-6)~OqGj)$SMuLPq@-D%hUjGUOiw5q>Go}M(*1?oh&!5C1YebS7Kcw%J#K` zMY;&fJY*W{*(+3}SXbDEW)TpmyFZGYX|ud#MrY!-I}aAUv9a!0FN@~y-h#w0e5xL! zJ5v8d=GFEK3%Q;j7WaB)TB&cd+xk2`^Cl(Po0{Y>xSQ5y$}aT#>&} zHgMC&F`6vP@W2@DH&BgXA9M6}HqG(x2Gg(8m*VtQAv++}z1e8^u_6QT?d^l$X^nip z4&2YQ2}*9&+_T;#y*nVIMQJ2s8{S6f^54hw3z6z=;=+61Tb<--3aD37Vy$v0DUFoE z?Xk-PgDn8HURhY{%NTI6N-=c)_y!sm0b$8KW43=bm{;>e}R2 z9)3}F=F9}lA67gaDmg$}B`v)A-{SB=C87>dvk-;??}iNSZrWw5wvNsb|MmJ^lcNy( zMV($pmoUBI?ha!z6ZiZ7&&%J|TBf>(UGA1r&qFp&{JgK>m#3j&>a1qjLE7=Ls57eV z&5B;EcN#iSjXH8mf@`wpenDZE8(gVAY!0^fPJ?u{%WfV>=t1EASooay8Hv4}_&e=J zEnbfpCF`Pq=6^Z5=#J?jS#b0KTyfJXM7qkhzKmclurM1WN#(h_#$opfFfh zI2g`{_q%H|e!P~AJjR$V5Kf7@l7I0o8AJGBBTy{lx^e3kdt@F$&y?{x241AhGE*T zAN|9S_l&MXIzf7ZP(p_3-j+1%Cuq7>5$z-@LS?IHUuo9IvSWn)B3p~){a3&B+axdl zt*wo4CBXhBq$|z2!!{hX_dRy^&BEFV`-LC0pAoFGc|Z zO(DT>*Bnl${!Qw4OXc^!2+tLTC5kbYmX_87rH&nbe`|MBD=Naa2ndp~V0{T}%4@VM zKj5S)XPizs`N#cD0Mr`DD6%1U9``TfJ zil}{u*Le+q>7D*#h-rR_I?6Z7?Qzv_qDSV;!yT$)%46-5SCY4EmNXdMAjGeA?oE}! zI9$euRrx7?Z{kFh|M0WC9VerHJw2MY)7z*stxkqcJbWtFwNlCm=+IP>QD9=k%j z)SgB!tJ!3W?Ut`_05oa!L~Z2N-vn1l5oT?&8t;*{>0A=;a=+BCc8;W<@_c>2P~qhT z=@t9r>D1K=6La#oy>VP-K3(+BI`o}-1B-bJ^5JqRIIi%s3tH#EplPD`^teTBNiq+R zj2vB(;Ws)Y_0YHqH*hMs zy(9)e#a$W`iA>^N{K}3Y{?Ul|{I5KJPI@IO3bxHodD&I3p&k2$F7tvaIPQ`RD%dMH z#HrVeZl{)&eI2VgQ#D1KD^0mmic0!f4VNQKCC%M(%hpbaPfWaCK>Su;fnW~$<{Cfs z_oNN&*Zr;iI-TfDg65=ZvHJ$7r&bgFL)TZ*5PXB$ha2{L)A}1UbYtV!l9K}<-H?20 z|Ha%VHILG#^<`OaWOUeu!)t!e?0pWx(wt*xShW>o_~G6L=AJ`1-LW4scAsYsos|T- zZ5(-G=lUinD6hN{lP6H#?e_1Dk=`r57ErSwJ2T?t0O*{}snJ?Tf6?_x1gpI$xpEL4 zjgdtKFDy4`G;flny$^`AjPZ-o!#-H*n(zpbv10~>c|v82e=gSl4W)mP6&vmewSdn< z=3zQdbADQe1g)+5a1PKtLlPpjY95WrqL>`)ZjGwNC6bVSiKD>L<-C4%%V)SzR%7O< z8g$m*2!?`826#C{?Oo~86E@`8wy?;?;GN*W{GjZjTnTQ|7Fy0?T*$6Q!gzYM*|^M( z{FW^)s9N0aQSUPFUP@I~oqET@B;=20>zj8kRxGY`<%9_AI`c?6Qs?l(w96|m%IXkKhd=tOVFoHyaNqsk-=Q+po{_y?(h@YRIn5!oo>F9vCFXrX#lCz=A4uSAMv^CUC{Bzf419GkIo}6usH~Ez^ zTIyKns6XdXa#Nqw5Q<;RiQ~P0*W|c!sEYZ{YkL=l>)-?C-~?@_t!6veIeV0!`XWC&9|w^4ZOy&d;WUy}dVnhsXT? z&)*ujRrH$!VI#g;N#YrkZ$LJe$R!RlBeIL}uzQfRP9|eX4?Zc-L$NR9J+9qIIq&hoO}1OGkxXOVxn_4O z2SbD{K$o}N2mgEDkgYP&5eNFG(l8!|-n#7%%_6ssF}PPCUOm8i{M0dLF({&eq-}Ue zcA9D8PhWiW^BpXS2l^RYtaz>BjZHUXH`hl~(bP8YFI^YI*5%w2-CQPtBp zxy~irJ{P0oB_57-Kfk$cFRi}Az!u2yxdNws=bLK1AE9zYO3Z9k1np8eXpN-Zs%LH* z8p7{=;(nW6fSZFI(;QH$mW9SMrD3d4hOHOcl@7kSA*RAE;?l3-_9}J3px6}zKYX!f8h{5OTO7lf~iO{XjCf{ z6jE*c*!Zt^-#^Lcdvp|9f8st@9~M)wD!i~ROVlXt)m+6gQCjt+VS3|$SA|AR)!&?Z zI_?(9E8LTqn9;O8d_MQ=^v#r_?R>kr>6;-3bn;vUuZVJuIwc_+B%vHC^N<76*xo;8 zc@Yg6`Z8iQW!0P5F(kB6D64wmMHbhbXKmrSqQygVV#e_vS>36 zjg?qf>CGWI$lluhW}BbcpB%2Jw7x6(S;;y{ymsfCWPaz6F%7O;>K=Ux!{r4g`jkNF z^!(osgE%l>T9wt*#qNgge%jSlzUgOxROqEkVF0#=n&1nJiQ)Uj6u301LdD8uHBYRU zkUs>m6~PB@E~{|c^gLwNT3^{wX{d^!lHzYMDp%PWfv9M2+Fgu`G11+ZpZJPaukkq| zTJ5P9)-s;MS4t&~`>W$#Yb#QTj& zwhYKPO=lD4V^G@zx}@5y?H2RgMEAMAXMZ~w{=lg;}jVhol3IrXV+|fiFC~snv6-HkWg8k5g^wwFi5y zF&F4L7s_fGe2tyk$;5dQ&7?Hk_L6Jg&BSq$iuA|7xic)n%Y+#{6OekcBbV?(Znle~ zQrN+cKE=C*Y6iAOeJo-rd*0sx(kp*qLNTySQG)A$L1i=Lj8y;UGV4Wz=^Vc1U@+W3 ztM3AWJ=7f7DPM6_{;X$4Tf2aXK~)Uj#r6hF%{Twad;3B==A^ll2|pL*r76}}!upGr zOBLz0!vp7GIRF4ZnmlCRtA?ouf}#Y2;F4@rvP=z0*I_>;>^-Hb3P$ zjW~)xi=wSgE-YU(ZA+;*ua?o+%sbQnARCPAp2k~WvlDjdR!uJh;dG?9tt&8V`}(V6 zEZFf+HOc?%{Hs^_TL_2W@Rz4J!Xb9+hHc+i#Ux4f%&g@$Kd-1*H_+~tHkZooA4C6Er<(~r{PnUb z`;jtlqTZhlMd0hz?y^CxlFp_J%peJiOikibWqR!WUcq``W6~Z))-ss8Z zv{IR~MlBQE*G%9QbD5e~uCZhQ5sM!whEz&ATeC*>TELOoy~gTd zR9%ZG8PHBso9xnX%pVyCTs=Hg#uOUPwtw`|)?vhS74vm!F=Dh`I0^f4lPkL;#f$W= zS9Sa~HHu4%M}A#OkbL>X!5?eklDBI+yk!z!=lf7BQ;AhxC{I63>6gC-d+Z-2vH>O1 zF3)J9FJh*^WQ;vHIXSDKWJ=3}+Al|r1UQp#72P4Mkj%w&oe<{6DFwer0%-vvzx-2E zKRGQCozFvt>6aKy^Mi(${xpn@r_j}t?$Pt~@@kg`%`!eYIYZP)J&hbFQnrN%Czpnd zn?tO3nMCAAtRbQ6Sl69Sh989Acf5E4PQp|vJ__BZx{*Q}pI=gHX=pG=KKbA0V#4oV zd%j}IxSUC09P?C*up}m9cSJ=D#h6Ozg#Kk1&tBM?7g}G@yJ*DzXBn8Th)BwtWLVX?mbDd3v6z@L*Rf_3lOu6{Hq~v}C&pBrfN26zOKSZ%nSuDD#$=dU}mR4^cFcG7^ z(Jre3k#7FHAgM<3&7GeYd5x|zWxS#yjF%tiznYd<_rp_=*NEQGAvOo)0*oO!HR&aN z$;dB!IOk)Y2|H=q7{*dAg2gg|_lUrIzY4^tz~eda$WC~SCM*TpqbnkYHbClOIfB$u zuT0E&*$9tHe;yxzM+?H7vOwdqhn`f8qUZ|QbG@I#gS-(*W2*`R5jnJ;bfvN0tD5ac zG26*^13-TqZVeKV(pU_4%WBgrp;x zq0S-pa#)Gu&4SG{XaR`;bQ*F-x?hGWs8t3U%5i%s5y=r7rg9dUKfc3-*1#4|^qfJ1 zY2?|?rcN6SqI9Lcz)w$FvUHo4k|7OU=&tg1cpDe6wzB@5^R?TFKbJR&Mz%3kuwe27vTE3up>o)gda(HI+I_?EV`RDizJF3@m zTc(}%pOVkx!L7jW-(&({$udy{V|MkZ1CT#o&X_`o(JBnhzthYtfrnPH^ zt>3V4_k@k~ntRngF{mtp>aIMSuZkN#jy~YCkrQNACm3KMb>W`JeB*2fCzs0Jl?DH4 zX!7HSA?`?$Hm#q{=z{RJdeNRs=sWNxTp`rD;K(DzF1?M)WPSBE*f|{J zETs~<;Su37D8UXD+Ku-33){vk@IWPOO=#tBHQ0IF_%RdJpo|)`xu1RW*pQ6f5I9J0 ziLRU;O1yW7SVYx^>!S9UpR9K;dtptZc`dAZ#{o@Lb z!=4Ld&eFl+dNb{~MMyLECi6rOv^#jfUlzff50-M`tf7T>-g zI5wov(3z}Tei`V*pw2L>vxRg?N+M`6n(^IUZI|i6*6#1PrciH2zkxG$d5-=OP50@7 z4Ls9f{i5m>zk%K}RikT!Ktlce&7=zfV?7*REr+&2>oOOxmCYNLvSFvk-MSef3R1tY zSNdcY+T#Mb_2x9!WV^#S_i&%>44VvKqBuAY zv_jbRwktU6%408~S#%n+`M#NM*{S=gJs|4&abo>(5B#;y zPX091nwk1qJS83;9|)Ui!`jAWR56Gii6fG+!arvy#ZC(zkE*bQ=P;a1i^KL=8-35% z*>E+g+^wM$Erv}I=vh8k;*>VH-t1edewY z@$JBu^Uj)nT8o-gD;fh&inPpRrQ~}XMP^5aPKiLNQJL9mJuQ4d+hxUo}Wg z(j+yzl78rVCH+~ByF;ALLq7q;fR|Ms{?j#ADzD#bJY1`~LF;wnA5Uecf;L;DRdz>> zSE;`Rz`xd^RSw?9+L5#+Rf(LJT;5R@_Y+`a{B*LONzU^(OEz2;8$oRaYG=9!49Q;$nimQ;LA>i8`r2AOJC99dEPJa7jn&Q z-*&+T2JE-4RyO-~7^u#^3EF6KQK+{6XV9Wgte19Ho_9gM*dfc;^jU8M*N`FSuSbX0 z_SSU7M{lY%ZXg|4xe5lHUus4Qg+6_BEkbL&#h-ddyYix5*j6`-TFZ|NtH4=oX^!W& zADMQxNmZ4@KPyS}i9Pic=~7#unB6Qa@FKzoj$LPV-Zk4kygr0f*_H^v88508TQGkwrpQGct zy&Gv8(IbP0+pM82h-Jv2)tax&hYA{N(%f?bRW6@A!rM{ntP*+H)0{bBmpW9o@vmE& z?6>_27juCix*SA|BOh!3OvXkH8u4ZLI=E3AL`z{bEhNjaCg*u{jW&j(kI;# zqw^>vvT5c?Qc}_j#C*(EiA3P$vVAMBD@u@*Q(6|g^HzBF-|@suR-};AWrE`?_2dUj z#se=eGY4`Db%8X6Ay%Z7N!C3={P0u<+>#%^lyou-SM`YMUAz%%+sMm%!$dJpb(~e2 z5FVJAUq8flN+@%gOB0+zKSGQ^rG7=^BJ4#cqtWJ{M@jHla>s$9%!LpZMEzrOr7jw| z!HcDXuOEZ?9S(o}y2XyDMsDOjQf&1C!!qg;hNYut${;xU@8ZaBl>xb}?F*3`LqOrp zAjA-g0`BL(yyib%-rLu&w3|R9Dr_(Mwy3BmhaiXq!U)4(ZG2&2-kEo9!wA6?T!381 zN~J4>dKd$71PS7>n};xFAm04`@KuFk!xb?eaTLtfdqjlbLMUXl{KCw9#cn|S5OZ%6 zi|(rlBXZXZeWv%l3;R%M9U(;t=4vp{D~*C^6%tSO##THgOg3&I>A`F_c2D>uI0A$Y z#kK^H#MOG&$o!j*Jp(QXYr;d;4`~R9?QN!OB<(Ms*fxMQLj`|`9_hbZlRnjwqncSp z--e#jtij4{4z45_RR}>v4ws5XaS!nSk*J62bZd6ak^D1*=<)EP4r?~iW|Bto3HmHK z^x|!Z07UKQ{fKO|CsvlomLv)0{Jr9g6hV)m#NEce2-Ww$2z+vL>|0d#XE8J@R;@#0 zq^s!&3QI`z2%^qv`YLOTnnuqOFZDq4YZ(a9qf70itD!?2H$Ue;w{lfu#{_jBYOg&a zl~b6N4ve{#_@=0nL1{7Qk=IWPCvSTiBII52UL{6r&0fxp z-2B1GgH&rohA3}b#hTi)BR1n4lPLq4FZ+Dd(lm#&G`xFv;R12T+9So2y)RAbMk}a5 zg7w_uWX@ov5!w9xwG}d`nOr99xe^N5c+%4Qk*Jr0vSo5e!HZayl%`j({YN0ooXolr zxN*TEOJio%UIMVia9!MDj2Db+OKxP>l}%>CwFGRMH-D@)Mp0B`i$X%c>#$k{HE?-G zH;7eLdp$Jaa`nLLYih0COOD4>l(gXiO;55GGrr9mP|h}*YVPl2j}!&WYNZTw>)>rQ=*!YtXW@%H7uT=w{1+s$t}&=__|EY<=miDjwrSjGmmZj#KAv=J>u00X&8 zFpy3@&TDzKHT7R7%>imB1x}JH5+YPxMQ(nTv-cVy2g<5jAr$>e{9G;`SWT>!{k_&% z&#Mx_-3}xLYv;oQruPfqhKiGI661B;=fxM7_h#!|@@JsE%B1tU?%^!xmiz`!d0bgb zA`2szqT$GUyO(bO^5GQHu3ut1Hp7_@fSBCF%OZv%X)rFXCyOG)eyaZv`B7)JD8vjD z&h7vH{#y%8*`3$N_MEfv^^OxuaN)poYq^zau(Av|N%$VJ5A*OEMX$W7)?k=IY3`_v zs5c2ft)PNt2g*JMpn)r4iC6RpX3vTQW>1#ygR!WyHRpwvp0|6rUfS|&@XCUNee!U$ z9hE3%s=LX0!yXGjpObZP2)`fzW4AcVx;h!iC95Ku-o(V+|B>s+oMq(f!ul4hPAc$u zG>Y{{T^-0%m|jUl@(}{4Tb!>JI~jO|2^Jj{j+$MU z98GnEld#N2AZNgq6lfh`G~5wma^1>mx@@rN_oTb;>pIhbSd%9d1^|kB)dQfCgV3#b zy98G^H%qz9z6y^7Ht?4^f%GPa%#Vg`Sefsa;6lDKPVR~{$y})ko<;6r44-jmAhx&=5_2R1G>Fo-z2#d@?4mzYhOu8No~-%R`}`9ko{S| zOl$8?{CU6o^yL?xEY)Ew;ZCP-C8*5co}RY+IRll@s|w2)NEC^>M5jW7qG2cXBf;6( z*aJoMd-Ubt`*m_ClEJ2i$2|TrnSHH9E%y=v6sWq)%Io6E@SB#ag<<q;ttm3nVZ-49UYJ7 zFRtbWO|nW4vEp-)p0;9{LG?{dzb)YFk2S(#DO_D$Cwb02p6tnb>iOkqNAZ7Ta{32^ zSSsQ0@DL7%%2^@f(sFL%4uAU=$t5J(-(DAc$+5o{C{h8dfNd%*Ee+_)5Df}=wfkVd z$FiAOqVmlfII@tIM4irkA$OX=oXMZrzf(a@@Z96x=J)THav%1vdGWE~?PHd5+NN2N z?!eMG0gjfaPVN=pUheTo78Cc8XC<`AZ;;bC8M$r=mm6@dRe~u&?I86e-sr`iEQN#b zYg<41GS!0iCSBSKoXU4M-i_#6IZjkNSP@OLIjSz_9$h%y<>>cXl|=&Bx(-W>hw+ft|4ylY3EA zwD^PIM3w%fS2}aJmXD9m0I^urSd(u>9UMH|mgre?NSK^eXpq4@mvolJT>~57BoXHm zcb@LVhAL@{;6-&6xylUwIM5zN zwKb+1pB=%3P3!@Jt;Mf0URmec3ZQQ~1W&ab?z%0*&Zec#V<((BJV#MU$&b7X7yh$r z;3cX#+zLR~@7P&0hC}ko8KM?awCx0q3QYX3Yw%sHyJOx7pnTb!lTyhg0Hhx&V12RN zlkR)HGXMO%#iYUQ~mR=l2A zX0vQ&pUvHM$!XBpk2{@KTpVzYyu>l(%qO9y!sawdM*A0_;#lDySvvO=a z@bx8d-pW=R00g~ocsYJ)PX#JZkp;8nidv|{OqGOe3Jo`ZN)t>r_ZhoO3j^aZH!?Kx zrG4tJ*l98@8}-Ky{)8>I=oA>RNBm{u4CZjTcbGQIjzz-pcWrq_2<;NCn}^ICcZ3p^ zg&20EEQWJKkcpBk#OerL{@xO^L-y33qW{=!aTKsyaJX=~Bq)4ht({QtQ)i7bgIiac z+_>63r-5a(_gS8a1gAjraV~Q3x5TdrjiI=kPI zJeq$y7K6cfgxj$??jZ;B=0`^#r7+lOKmKdR#OCk_U^*dKGqhTl#Bq9^um14E*qt8B zqb7G*I7*z&aaf)1Se=Bg_}O2Anpgm=Ld=2HmRJPz`}C6&m$srn$~_Bs!_W_J7Wrk< zPd~f`eI7lU$$X?Ib@i{6D@%Q~wmFHmMZ1CnUI)#8A>)=1;kpMgQU-FP5*(2u6?K%U zG&<~7fHeS;TMZzi;}3FCqpbLwtWncWQrYd#oy=A;;c)m75`00dUd@v;pqi@xlGZ@+ zva9>bf2^eJ`G~%fN1{Vl)Cv0phrGVD_^nGiB}Ph6W0YX0#BmsA$}cTRn?0G}1iO8C zDrDS4!y&u&(6PT4pmc0iPG;ozyDT%a)S;l~ug;D*E5I(n<$0 zb{aNuYJe`Ecx!{>H-V}W>65%|T?-?vmX&}V^g#xjQ%$bOw6Xt))^v-s#q-vEl6se_ z*7xNwq(t%?t!;FiyQ4u0 zj*(b23+_gI2GI~v80>ViH}rhCP0_L$q-wWj{liaXh|99*8LfQG zPb7_ujgX%or@ics8~X(8m5#?TkS^5FFg}o^k(IyY#@AA^UrEA}QIFZO(o z_E&szI0x>`r3!W-F3e7zxRG-Lrk3kkJ+S_xv49AZoUcY5Y;K=pCQO&ihvmZB1aCh} z%x|Y$yHH_A82ocPuL7 z%fa?xg4QR>G^oG;EliW>Ai$}ZzzW(f9%F#!jHyGs>yN+bI!ubM`3MQ3} zWDhyggTVBASH?Qfc3Z7^U4^}h=rJ^kyyvb^$VppW)(+``0Y2=5U>!DE%KGtU2WR>n z>tXquU+_@@@+ywMHTc-t`-4K2+g#-4jj-myr%;?@U|Y@8i-i~p18e9p?=PN1I_&u} zdJ;@x3}$R<{@sO>leWyea=C@&(VM6s<@#Wn(|tCx-I!u}n`CaTktYRdfPheQQDweO zg=Xe!pKtuHfFbs%_TC#&|5!G=u2W!}54L2aNgJy;*X$|GIm}y=ci(*T@_l9&Y5v*) zc^pR7YQZ5No&T^R-6?*?Q*X45+iK3{wYQDN=SniAT*ZY`%Ru^~Fp^RcoI7Ap`Txb@rMcvuZ5wmCTA6!ywGq zUb0j91#^w4*INNY%R99mr-xgup8V8hm_YxId4)4&+;8ZH3z^!}bYe99&JJ%+h{l#& z$7n>r!Mv-J`06~F{)>8NcXP<2Z(djX8u(S@u7;l>m^Lfy((?1|Q%-{u3Mz`I3vj1V zE5Xwjc~vldL8m%ZRsob-ivecw>w-6~RqWy(7d&3mIc{wHLEg)d*+tuQ`D)3>X!OHT z(`~cLz~hi+oE@A~cW7c!;MitwpD4cYJ|1VR^J6+a-?dL8Zp-SpuA2GOnT@`z@|Y(3 z`sUq-x<&O>3$erDZjuWmYAJXP(^Jd$%rgne>mI?^+mr)C9by^BH0UT z*NW@!3NNV86g<&G@4wGuR+0UA|LElw61OqccUU}|aHja(@2jJVo7`^>s@=UK)mUx@ zBxSW^o69~PT@)}q6D6*(MRj_!s|meva0UL5a*Mn3s$?Uhw^%6aK-9rSgH3*pq>ulk z+WmZS2lb|L!-Lt3hq=sQto&WQP-y?M=+0C3){AbL(DCYTlyVW=YE$z(JH_9BvTa(x z?N-gMx1O|#*Lpj|HU<@Gh0T>N589?Qn&HmEI1}3!_I@~Z(X$zCZ#Mt*vfels7$vpi z14G)$!6O2rSB>fz=1x;1H4mx=@UwRI*)g|752;lbI1M)C>f%0PgQPZpXPme0Y&hq58@B~l4%^r}RehI~XgRQAtOX9tTSe0}VMEt0!Wqr?m0ss-S)} zh@(ci?WC_3_9|Zw)u5|@iZ{{+5Z1<1j>dCp^?Ub@6O?RfJd$8-tSaqE`3jTGCmdn( zUtXgnuLbQOXFl!B;KG8MQ1PRbx;_-PqZEXj2~Z#QWEP@j8-Gf2o_X{|9k2z4CK`lu zhO%}Fv|d;a+fI7OjgvXnQ{%u!->`n%?m7+iT8L+2>RRI5j}P&>l3ob1^eOCD?|*(; zbNlw~aWZv@PnJgr0F0VEtMfgw7V|R%zPsFz{9=L}Sv8!c&1_%)ICnKjBe^jWZHCGW zR2XzUuqgKJ6f6zfyx-i7Hhe(Q`M$7B=5wvfsJ?+jl-rcF+e<;#h>!nBAeZ`S_a_{a zX}z0f3ifA^Lq7FWZg1FVRd(*E=#gKR;=xRnlAjki?pN@WM1*Q-X-SS~_J~5o;-_{| zK6s2MB$W1o_WX@R)gD^iKo&K9KPq92us_>@G>#LS0{u|W8ETCS`g#h}Ub_F!j@)?e z&cOe;YbNG|j16#)=2)e#oO=wcu|OZVK^?}j|9YP=x8gi)?Gui({r{)9;zTHDe1KS7DqHO$li2&&xe3-ko=8vKJP`Y+qR!U z22zY$a)_5)k$O;Gw+I44TGS#6IOAduz+7K4ECc^>5v9~6F*9bVRK>9T&wu&;!~l-t zMR$~Ek&!!l7ep7i6BK zUarSFrSFHBo0nHuo6&N@Qc1o?f_7b9-AS3za>{=Vq5(35FVR~7$T&Z!xLO{Uak&md zGvr(w{q1~OUicdjb#p5zDxN0x9DYV;fiy+D@ebKov#VldV1LH zy}doWaDbSr@?Y!w`5*n4LonZSkL}OgcHaX)VQpnaagY@$(v~{vgRTQkyXw=JR(LC`Sa?r*lp-Gky zzIn^fj(@6_ph_Orqcw^vEr!w2vvvM2WDl&2_xWoTGt86%`T`dGr~o($jdV}0g74Zy z1&I4p2Ac>0yQz0>&1e$fvn3`9<03qxPYpq)K7gnSTh=?0N=xPgJx6yQ-+OTK(*-Eo-D`dsi}=yiuuz-nlK*}x%G7dQc`H9}tB(0T# zzoj=#_S(TrQ5Su8a|ymzcL~RZBgQ?ET-xF}mk-IW!}#*E!Ha4zvOt>^36ce{dgNdT z4J&Oqx$=XAChni7bVe-cpWdq-C@d$r{)+-vEcw?=%EH~4N?-9fR?LM3FhmNaGVI!WIw!C#=T4uSV4c6DJjhy0FG1nY;V&K7 z0!~tCfO4`CcPOEGmuGnG4aG98MrUS@^Xq)I8QS}X!cOsHIXO96;NoRx1*r3SuGNS_ zgD)MiV;<5G<%6pE>PS`1I&XZWGuq~!5YVk4*AA3uE83rRM;qii)BnQair+Gb>KW<^ z+-coe*(GvmDl9xX z|7_e6UXjujP0J0Xg5C7-^_69Fyh*11g({(c9^B3|3Jq?jZ+XtCb_!Pi&HxCQJ^gUSq3BUZ~lw@sQ|7{uED1G@EazYw_pFs zJ>&uA0}>TG*rfK-sB9hVpJGNpeSDLL)Ed`)$W#{gCmnL@zgd6uY%za`xUM}}pI#RV zj1h>O#e)DCd4s}7@HGD388NT}Y*CFkhYHU72~Cpk0B^cykB0APray&A({d|x9yLuGUFz>R#iEQ}C*+{|cAawb;EX&5za0_5YD z;xitQw8^CG!ui_|ypqt&^M0fGY0(jz58anT6o@bS+d&3lVq|G%AJLrD8#D;AWzlJQ z%e~r``$ZjjWE+MEWXB1~j0;8XPj9RfV*c_a%uskU(aZ&eK}0j#%v`{#8b`MuS)(gf zjI_b-*YcaT|B!sks-N)vE9>)r@ppy3rXO%@iNQiCoKS4o`paAXEJWks*-Tcau7JQ; z+;V?DHsgojyD-=uG!#|k*`SD^ZLBj@&dnw}?kO0mkitpKDxx3p5bMX>%ch;l&BP@q z-OHw);pq0h)Tm1u+O`7!FA8ywFedg^UNu$4YsZWiyGL=OiG(Er>#e(M2IxCfgD9md zUKVt_kGL#4w|wK18t$TeL=6po86h}O(%2 zH&V!#)EiB$60b+ouWnxnc!73g`NViV@zqR--$IbFU+=zvTDISYSqDf&hs~)v7VXWZ z)4I%&ZIHHEUN863^q8ZgTtw%dTiToRlWu^~!lE+~gRk#^-68WQ4XcsITslGLPus5* z@u)={w)KMQHuz@m%)IVNb!ANc0f&f2anyaVV}M5x1=9TFE_ptfAno4KXtQw1HnWlMxU-?F z&@hqeb%*kUwq3tty2#)+L?=7UImsP1H15<(_YoxZ4>^b$AXQK;jTBhi?H)2VQ50#c(!sHu`HwF4CIOYM>gEw6e1D9_&cKQ6U^OLMsau1{vQN zEaJoPtRf?T$zsH^0@zRI5BPlj&Wf^Wmq<+YFF=tzSs{3?$UtuvBKUror8Har#mMod zitw;+E}HVi1x_sXR5<#q>yq4z@d4^{0c|83SrdDB)HF)f`4xf#dLHK(4S!6bd#eHf z{fjwC&Zcy1sZ&!^`nOArjEg!MwJ<*~yxNB#QS%2Dbp6v`AK5tm3VU$&&Rw+qak6sQQWPNu(P-Wc6Z$VySUNS>t#NHJR$z39Q&e>m^Yz6X znm=Uw_ODyoQpu}kI`&os{NIT|6DupLi1|C;LrE?~8eA7PpY3A{9@^lrPx|GxdK08w zk`aS>=c5)XpTW)C@}jPS!Y!zebygwcI-2)bk1YKv`2>Z0Q|EG-0%4jr|8R#4Uq%Y) z*ejxLpmO3wzTJ5pjZ)F!4mB(Q*1Ym(&s;2~p)`K_v4v)-@`2~PPTs##-LI?&UHoA^ z(E&%il5{Yc!nhoiT^Ipp@qcJk4~`#VhaV7F4^-+t1l~e8FppM3{fLwXh&u9Ca&x$u zO1~VTb^v#+c;lCvJblUw!iwG+2BWj12s`#-zh$#BRaaNI4cJ8nX-Qgcx&&nnHl1XO z+l9WWtAC7Sj|Yr5Les(j>&i6ZAf$48vWx-k>dxKK;<-Q73@=Qxt{=VtL@gkhPU6Gc zSYN<0a{pcWb-TrTLX5aUsj;ZAzU+NX+|`U1#-J&n3ll0jbSf-h{&ZQ?m6PJ$Pze=F zRZ!_e>VK}0U0&6#fVjaSeUZua@(n;uXC%DvitxfAoxFTt8!cVC^a0g#wuuYkyyDy0ZVbds~#X(<}@ZCaW>l}WeGVt#`>PCZ!x(i5b!Y!qC z!e@bEE4gFumkglw75nhElroTx7;!!RAT19faDzGm;V<{8L_!gKRKU*2qSXUGu z5Q3PG%{L%!@YxAYpV-R@4`QFGz}^&Kcxw{VOgW>A^-XZzlxLkWLY!HA+uGRm;t>`t zIJe<}*hE>$Sjw`^DAmZdhuxG+ObS({vZk4tBS~s@XeZfhRo2L`(|GotLRa{ z4st(dJ{GGA+sMJ5l59{T3fK=6j+`qv({)O??mEQvfQUWN4G>?bpMxKp0M+uWoZs)(v1AO5_C8F=X4oz^9g)w%zGujLI&2|9!8B(ItlJA8Vi z_CV(?>0*7{X=^)&)hQ=}>?xv!vv83q%dPJ=@xFW`8Jd$*GzaGCHk4DqLAt?vR$AZL zR&^xz`SEftP304guo;&kbH6yzdD@xfqUBky(CDQKJJ`&11Pj%1?#Z8XCtar%kW>;c z0p~MCr^L1fW7?Sc#e<{Owjo(`zJ2-GG2g<8+uUKwR~cVh@@0{wXYK6;@Y7DBaR*Tw z{3V>q0#nlxM`F~~Th!9J9o9sp*A#U3*^UvZXHfhN37fk#`d8rpUD*)WmLbCT!N9}M z`srvTbf&;>?uWeqDxlgWdz!+W#s6KV;~ddi7dqO$5f29nh?h|L_cd+sN2XiP&gdGm zDQxhG;4~b4qXyAMH5A!mH+ub2Y4k|DNez$cy=FGBG|5~+>4=u~aIKC;)%qr)32HT^ zj|JhX^*$WXGaT*6v~@``*6SLZboHG9BkZ-~L}4Vl_1s2-@V*$!j?|r+D!IE^g_0)i z)lYuYojv2C?kCy5zVl3MY4yy=Zlv29A4VzwneVnc1HvVD3wFcqG>->g0vC0ivjT#e zZgcE}$8s*@MmO>xXKJr!Xk_|iqDav}C(`HaNK4Kgp^RWg2z_b$R=OdD@cXXPb_eSZ z$7^rTaNtQ#mwL}2rxQoMp3=cX_z|>Z$c5^oU=jw~KP3`otxM_>&5JK5`#{XPA918U z-wqEy#`$Uwoh zhvUc6pje^G7|Z&nYGN((-&GSOJz2D9J?j8B7QE#<<@ePCHD8Cedbh@sEv`I262`pp z&R3x6cRsMqY7B$=Evd|jRwu&Zem~oC$BIY{G1{*CehB;Wp8VkEsPsqRjrjU!8AeX%66_?1%?!Vy3Vm5!MB+T_< zHTz36FKlwg9X|Wo|G^-_Ec}Q8p(H_~Cu#i1r=aSZcv@5W{_IRFTHU^l%Sw{vEb*q- zQ3$)5ozIK1VNx5w5f;zW_hMx#otU?@vM+BPjXUmS$kr?U3>c=gsFRKh>{eTRA)&&{ zvM?H`zTICX5+}C!2B_%W#wA)(d>x_s!b>b@+UxwWOch0u<{es4lEYJbPM%Djb4L5C z&72*XFWFoE3pjc#CI5^b!^2BkH{6_IX9*jS%~1ELKK$?wyc`LT^sY`b^Jg@a9Kew7ob;drUIyrdN=v=eI7{My94&wlLy8pnYU8hW#_}#Xp)ohUS1|?w( z2j|WTPA3l}Yvr(l0o<>qagN~)y_wGk8;Jeu(5jn@o7mq_6Ljbk+&MVXTSF8Y>2psPATO> zvd%^_>BkdGe&?!j%FF1Q_zcn_Nt(9qXUzwXI!4@1)_tbWPUhB5#PzuCwYp%QB4!Yj zuy{P*6rWYI^`fSs(YF9Fg$z2N)@6vRC%0@RBU{=fw6Lmb^VM(s@@C*?p_d{ZZyR)r zro>UvS_irF%!ihbx{SpADRqYZw>Hu0ccyEaqsw;Bi`LAu({_lVu~_U#JhSK&w>&0N z=w>nsg}QNcc&IuxKi@uoKCi5S;Ue|yeC;hx5Y zPR63XbK)YBI=i#s%PmsAuI7r=vMOS(irOnN7PZdn`c!+9a3^M&JGJf(vBL1WDJ3o6 zN*mXmw~=P4qmp8JU#(iLfR{H*<fqa!C%8-zd%=?ta zdMiF5J?|em#GW@$e)xn1v{56pF>93Qj`_7qaJH67Zl#Wj)}5Z7?h_f;MhcCRz+kYK zySux$S2s7+3gswuZxt2PUKPnB&Ea}8JiwFTvi4MgQ_NtPb-9yqR=N%JIW<1NO7Xya z>5J_b1v2yYh~B)Er^4DEK*xi+&Fl5kLx+clgU=)#dmPQ|Lh91+erWJ{fxZ?NcE%-` z@Bgi?!NWK;0fd8t3U;MJJhQGbryCK@zg39{jKb-hn$Uj`muvWQx~Q$wtQ87{O|4n2^R zkU%`JvzymCIy%}%GRe4pV#VpC25uCm=?6k4+D+etgxcHNSJ{rg9CsdgRu=>&CWRo_ zS1VA;yLZyqbiPCiF+F-~5K<_Er5k=FImc3p0HLW8LH?r8{x(^iga|;A&{A>H!+`>< z$osxXYyuDRZU*L(GaXbF)10D-0Lwn7l_v*2Xbw88`4^C?Uh(C4+%ucTPM5>f)lvk(ghXi(u;`FqzVEO5D*d2Loh>;AawoO|ltXYcR3XP=Jw=&UvZ zYjB%&4qY3e57L^cHt3mK z69X(f7QkNqk-)oH@)$~Cr@9k^_^R5oH!v**OPqw`Ea+bNGw$(fl?6qwE*^l1=l?7+ z+5-xlP^@5AZ@hkZM_}3K@Sd4$(p{JqTP4Rxic<;14_PgA-4rKPpz|tR;i(h2PW^rD zrFGAT*BiTUNNGgWC;I>-WIP<2YaC?TcySonfqb~2Fnsd`!R!o4^7K1sdQF80LXC0-rGIfn2MtqNM2j<8DfEK8w=j(e&1^#X^%E)tF%tr@>qtS6# zH87{iq(OzatC0YW<-oFiud=!*3V+<=_qYGap?br?mrp2gossFL(I(M{&9o<+P zQS)`mGB7Ot!AGxj?>Q$5e9J}WircgXe`6;&(}7hC_*EqE(?!j zN^z~T1+lLSp8aNEHF?wdqG)P}=pKW{*#yJG-9+HO{~^#2vbfo{9}fCwSp8pN8olc} z44E78?MPIt&FQcSa)3Tc>?^2?Uftd&N^LvV*JghG5 z+^`z^Rk7%c+A~88q9)PT8NSzet)FCbuM))y+w)7g_95>`g4tvX;-d7vJkY07WTLAQ z4*H{EdU?BZCvzX1eXQ&Wl^06L`(?O&n=DVZ@H(RlAf3AL2J3>k^SQ$r&WjL4ilqu> z<-_6-d_I8RS8qygWsa(LQuZQZYF&Wv-~u6f#`=3$~~g99U6 zlVqG#mdRQR*3&J7sKVkn6X8>x*XRs+ugSqtLt^Y!cGFxUFte*wiS@D3>r~}7nKI{< zcihLOhl~S_fjE_t@XF$)k1ust!%RYeo);wV6irh5Igp&pyt0Sl2JF#@3sv!op&IG% zV3Pqeb&?G$qe&S1)@~48lW3e*Enc?9>%sTV^7jD#`4`Tq(?ndhOTeGCDcmABtk!O* zedFkPFlF-9uVwn69tjr+P*(wDH7h9nZsS;O1&g2>csc0obGG{^+uzGGm8wHCgm^F8 zrr1L0|E_B9BH5H|?p5`5bf^i+E873pCf6}r^tlHZtHSP{O0{fORk6O#WF9g$392r{ zzKtUG1xAU^NTVgaBv|wQ|9L7apY$a>pB%bkPxtj5N(?AdR|IfByWHs!JpHqA?p+SP z>*@}qfKoEt`sC>sS*M|q*hk2aw-zV*H?ZALGrMx|u=mw_JBP zaDR8d0-EnPG^a<(&B~Ks%Pyh$ajZ4Jfut!6#kyBbW=$6162M$a=r<6i#*kb4-O8v; zaGl4<&qlM{924k;LyY5&%;cXcT`WzQq_vlklM#Mg2ShsP<^(}Z@VZZ!iXM}Hnkz8&*h6UbLj=iwE_bjNLs5o7j03*^!Q=Fw0s zcZ)~aXuoxggYBvi=j>(-N3`^VRR7kM7O6OQxKdGvm}%?}CYab5SZ`ZXQHjNHF| z@gPx3%{j~Z6+486yLt8u%zq^&khkB1=R3?FKY5jo&kf-%a7GxS1U2U5H~I)m?zPBK z^={5I#O9eMqX!`}VHp2QUhh|IgDEScF`YsO$e=g~xkyUwd}-i}I$~|on$RevhLL%Y z|7)qYAm0FkX71sh8#h_do@_B5J5n%cx>EL}sF;o;GNLVMHi{=q{; z5zNEjROV1_%7fd8T62)l(_oBD{LGGJ5t#dqR9Ax{|FAM*a2qLle#gwW)9mIRH<_XU zeq#@(ifC%KP?j%}>F0Peqr82_M@&l?CkEQ}T%UMR3S5SDkEL`#ho&8=#`Y`>n$Z;zWE!eF ztlv4=O1zL#4MA((Bf4%(pr3 zC*abLTh8Gz>Y|+M(&gLuwhWw6S4rdXK+{lJ^7yzM$F@v%CsJ4bpv3uJXXLl3_EhqI z{CpcIF+q6Sw4;`?H-_gvupem%B<}6D=jcnkvtH1pI}_*Xtj}81Rto~#wdqhZ@{5Yc zc((W=|8FWyrlA7E7{h^fp#x6sp87g@ruTdV<#oleZYlK;a6hs40q22IT4(o!5cq&) za6JW>kfEk@=f;s>2;Cxul5Z|$3 zFHI+uFI4oZe$2_~(iXNI810RBCwpC2k`8&M_*B~}`)YcQpiqd|R>;}o%Ya?|c^p$W zU1p2Z{&1BY!kDTrW2ef?`%kK=SM$VwkOx=8qO^9_2=enLZGWH25Lbv{mdirMH2Bqh ze99;C=Y%IdTUiR>T*B9vHhlvK?U%PsZA+izV$tR4{77NVo+=$O!)>1G0@=c{ANVyU zO4mbr>AU3$GijFOxYOEX1BTaDR}!u=n>P*8;}K=z=tPLq3^my9FuSconznfSp3%>8 zN^Bo3tgVFfuf{xeb!8mxs&;(9scq2}W*)K59+c}Od~WBDerSH;;vW_TYQ-U>o9pOtc}w%iDVe-i#2y!+dlrJ%M-G*l7| z*s0nS8P{%M6#l6wf^YjW<^1UAZ11-9e+20OKsjr4B+dR_-4*!6ISU{RP{1mJK~tR) zTOSk>owgz%XsYr?XAaLE3}Zd}boJa$1d|nzsi)L=eP3g^ukh2E zXh-|~qFaw?pQKW?1og8Lh5d_@_4uP9vPpF-y(f!3b<4V5cf-~Aqp9+~Ez!3cJCWF~ z@lKuTTMI3sUWQGv7tJ1TUlB4AVvl6(mL85LGEAbmum%3-CQDBJ9x?xS4@P@7&qq+z z=>V?@p@@5JIzz)95;1H{m9!xvW9|cwEarb2oBw(<6TcmNsYe#9d^o|VooQS93{5{c zYu_GMb7%T6UIoB%xSrm1A{ad{D6X-7rFLaC0wlJTVDy8=5q7D*r;Boac&RmhqH>p= zD%h))=OdtGTCY_Rw)L8|*a}Nw=jA=Ccth6CQ>xEI&y}pl-R=#`zuYMnf9vx}2p6uO zGb*cuIlzADJ}=HM8xz{mNWqTuK>jP4Y_8KFyT3Nh=y)^VQTtW|m9TeQsk7n8IcuaM zjjY#*(Qg3jZ`g6pO4Q%(T{F4?$VPiPvTY6)cfQ5WfW1o;5+lArMGpPG;d5wyC|;Z{ zxh6}e&WDqk&JIH%z0NUF!k+rZau#HwiuM%dnu{568nRAje=U| z!7aQ?9=GlqA?iF5LN%30Sj_fPoRb(i*ywYImk#yGOYd7QttMYoaxz8Crx4dnVA!jG zg@F_VlOcBDXq_j;4@nc%bg}Y~rvkU0cxM0O8hW8vnd7g<1;(5l#r?(cQRL|`qqZJA zVW?>XGd$}z7TD7bcz3(`tX{jx4>z}J6{9VKik&BBtqE-+hoOBQ*bT;W>d^-Z5qM^i|JYL{mgyA-e+TMtqyIk45)H# zI=L;QdOwnmLmb;Dnq2&;v2UL39XVUIm9#jC-`D!K>%EV)uqvE$S#Qt%b)7GLVlYyo zm34xRf}C7X%5hAjy%qPl`MU*Q?FD9p_#6Q%g;0w^do@S%87D>Hq9AI9$raf|O{)-x z)q3}xA@|)?-f!#un!WFMR9vgxDR&tEerC(3 z6gjUqxgbZh+FiD^`z`qr^q_2e>8zJT-3H>bP5yA(Sg_`fR@iEn^DuEU%0jU!MR3=Yerw4S0F5Q%T>4yN> zCKHAyxAkO`js!)MFC=ip!~F*`x~jP?6Q|b@CpKveqzF@RK(lgV$&r>n(EDi5 zbd-PjsX%%0NBR+F;TI^6|%fqX%B2WeL z&~Av{k7)J$;q){Rb7@d+>FrGV1-56xhdm7Wl{Le(F4IyM;L}7{7XkI;^WP4dK@DZA zdp5iVLw4vi9?l99d+@F;DRS)!2)OLJcZXJ)$UNkxgK{%F2Af^5s}P`WN`03w=WHe> zqu|M+pyl0YiBPV^WKj~Rq*IZquV09}$+g+plQ4J6;LL%6cKDH`*Xx;QAlI@6f7pe{ z-SASy$V*d3RBCDbIcVJ&Vtnl_q61win*4Pt#=Wz$iSs21-capqN{HIXS~dzsiPP6c z(oJ%W^E=D}MVtO=c0REIgGOS}u|+F%hULh<&J;avbfoO?Epyh#-2*u0xbS!KcoRz{1nAxu*ReSdsl z;5u{TbC`0(qGFFnLz@6}b<#(7qxk7-E{tsD%sS~jo|cuO)_jagX9p9o!_3VL*WYHI zO=rALE33PV=X6R$sXNd3_$cns)CLlx8r{W4Yr*a&>F0TH`0FY+q7bfW`3DzoG} zr2GhimeTIS{79VWC8l&~Tk?gxxzW3Cv#*(BU7gm`u6%4XW^NC)t#l%}vgEpNYC;T=cI(t%p$XJyv&E>6t5_so50 z26c~7W`tgfi&VWmvz`;|_B4GZ&&twW&ckku)VViC5f_P4U8}W4zjZsM+29mC!^}-$=i#o#+?ZrF&S^$d6CUG$z!dw5R*f zD^~Nb9n$@`=?|=?>U@xwgv=8&#}J#K2@z<)k8R~0*V)UeC)dJ^XWaia=Kl>gkK9nR z-Q5mqw|>lA=j~~KA!WPJ=U-*M)AhoYle@iqcEDF8O)Jl~(RssooubBkQja>QCm^+?#8T=$dZVPN_ zx7kLV&hE~r8@!JElK*4i}$oTJJz=qe(qE)AP{H;Vq;j^9M2Jm>z zy-NW>@VGHE_JmQa^>3T{qYQ`vMBA&J#B^ zoQyKtF8(TeZVm4^8)@G7^|vn6ZmYutwJQ~=b$4g9`k;k zA3?6Q{(5!mq~=PsUma~)fhowhGvu)E)`A;%0|x5agBVR8{B@g46uJ%H7VyTpcuBM# z1)1oUmW1z$41dGE+SwyAm#2u0T@ZPmxN+KfXv~3sBlFq(ZeYZGyrk^Wp+`p9jn*%L zxjJ08_@gJ34CmdraYE|`Fpmnyyp=#3~6h^E=>P;WOU<>5QlRH&03;q(v@(Ol4o5U z;$R6#t-P;?SakAQt%IhD4{IU>)4LIT3yMT5Y*2lVGC~|_lA1PZ)(a6cwM>Zn6mx$| z3jr!0$WAk}ZpN+O;+h;uB}2LA^~MG))wjN!>pY(##QEcy{1qTg7-dQ#{Sw}Z_&8>H zEVN7<)=B79KWc1;`SY8mVXrrd4x9YEF)!JXcRqF3`k&_37GP8%O>$k&YN*jL zDN29a2VWawSZAi_d9EWIrBAk$ee7)`Z<&&&3HJr!)QAZt-F0%JExn<$coB63Ly+D+Q zGK*eN3QCE}Eeq=wXR#s=8ZHl~L|iO|gl0DP1C029e*mrz*3hdP9A0fTzH6DZbOx{9 z5$kP$YLiq$ABYvLlI06Re*J)prgn_8yuxLWZC?fpXtHiGfyb%Jw3_7?EVz;k<`uqX zl{NT^X!~vJS?a%7MBJpsl9zLaUR?ga#iYiRI7VQ>@Cl*n3j2+K^tQ*EJ3dIiJ?s@y zfo{WuScZb?h#-#B;Ouw)2Mw|Vc;XHL%zk1R?(zo{%loQF2&SNlPzg(%%4|^l#~|Lx ze_1Swj==h9V>{^scR}3w??Mlx3oIic(bq%S>#qXn&!tB(*?%aVtS;;d;e#asbLzkV z1q!Cw?u^2q?GHa+Vev86Feobr{`W=CmY}$dePB=>+iQ-5Gsav&{Ewz`SV-I=fsa5- z4@qWY;|Be?BLI>*)Sk?OWTAS#>f&{_&;S^X{^P82PQqul&g3Dw?AP3~@ zYcOXB@0Fpx>U;gt%4~7ob%Ffy*iJn<3*RK&^OhJskC8qn&?4*`(FaO)?C-;CsngV0 zG)Z6~$w4`3s`2DsM-Hm`jlo_DYJ@@9Cg75;Nh|YEbnQUVW~YO=j?2rW-PoLMSLLdb zG;vvF9+UaU5x+D%KMU>70UID5`vw;cBnr&AY6bm}Pn}cQq9ufzJ#2r=C5T6JYd6o=eJ*g{nv!?J8-Vm;iQyOj=A+lBfrJUKXvuYNZ0HwVJO%l0I#=h~6w=E?e3UB`05j+( z`v;Q498;Qe@=2@m$%IWyzFj-TfXzGTiCTj}jQZ<{utvNypzsDEIe7H1y_beIYMwGI z3b<|*^fWNH%JY2b^X9&|R9Hig=B zVoUmp>X}=$gO{CQaeQSSt=U-o-0iPOdSzB*qB2j4?puNQ`t$|khYgS@vi#2YbSq)N z5_Tk_{VSN$>&4%$eM_s)yKY1)w>agM30z|_iFfns!00s)yr3Zfy&jCKyJq_q~jaYk4^PLX2La`~%bS zu((X%58=8;Qce~7Dk~mrP$M7w#NI1pN-@(x&UL)*nVTn*Li{tbYiKNan#0t8@{*F1?$9&4vYxspT%m{{WTCQWOWubcz2Xq?8#tckGq@RDnzFGz1 z$J!Q#s2q}o!CAzm31YL@x%v8?zZPOz*v z@`1st1rPJDYz@MpXNNb)PrM47XAtnnPVQOzJ2%j0Lyd)_=guBiRN;m={5T-o34pIf z?Yf~{!e2XWYQ8G{VWpl(k<$nZl>T4hjA`!p;#HR+Qu*_0QgCMOMEtAggs9Xvl57qk zQ_L^xhmiq4pTZv!BYg)JO>E9)1?FARG_Ec!h5$$#ZkZ1E;2kWAEUQdO6fTAEjC@*} zxbcWAOh(?XvvH-J*)jK?VB1wAU9pQqe$Vp%f=6B;K67+#3x*FMV8^~>c6l<&@r|Ao zR=;|;!;+R_ML-c)+DWlH!5s}%-a=*>)|xkq-@@@CU2xJYsj7NR5}*Bag3pTWF%M+z z0um$tQyKoBZ1j>bi*e?kbL{nVW&IZ?_J@4=f1K{GC$h(X5a80i&0fX2uZ0a@L>m-jz$a_Z^5lRwl0Yp3D+=HhNpUY0IHvU1w+jS;X!{BOL}{Y5?7)Ytk9BMx zTl%>_4si{*4+;qhk$C9q73k*be_z5cz$0fzjSU3i0U2oDv_e@47LvWo1$$FZVn20+jafvG7}>1e``OI7OT4-%5@}OAGmcCLH;Nq7I^93$)k8owBj~DKvV@wEiAa8 zbp&tGau7MvRg)YJt6~_?xUhjUUKRI(_$EifrSH+}f_Fhnu|5}c5aoL3{kTwf3FKVQV`bY`89F^4eTB>IG zmOzs^aANT_s?b#`2pyzq9WxB?D%{40DO^|F#$;m1{!UU~M+BWV8-v_mU&pw?K8_up zCnS8SjY_ifq8}jKgVh^iXKD2GzNwcI)zRfw?C8##qQC;l-z*HkDbpI0YXMv@o-otvl_gU&uiCL> zcmLUab5{Pw#0rQEiIAz5KI& zo_WHkd>>|{_sL%SJXOO~cLx3`^Ulj`!Aqb$Vk&H-iPPVQe+!rG$`V=$Cq~^FlaDgh zUkKE6?Qg}fiP{Bf+W{ufz9;X+R7%By?dKUij(B{gifj*?H!D_TQCA2~0&84S%MKZu zggW*Jp`jhf?gKb$3}hPSi-z4qI1qmQyyELRjaCRNdM0_5Ga}4;DQ=$=d&okg_9|on zL&fpk1v$S0(yI50$q7#dH#um3F^&@g7Cyx>1$UO&O&iaVhdYb!rrj6_K#PWH7C86I zgLXjYLgD8O=sg{0y}v^ck*n#jh(lAl$$dFoO&-1ca#X1;B!i{!OYRw0H4E9E>F!Oy zYPCMBDeEqv@vn}3lcgJ~1B}5UB^s`g(w#cLb#vE{wy|(8US9FKo)lItzFSR;MVjo$ ziqeN6g-;lLnIT12_9g1z(P{S7w?=i%8xq!0hl6yN!xA~Lj&alUDSUt*zMFX>f7SLX zLj~gMx|1c&A7bSleMw3^Tq6f{qtVuHRh`?RFDSfJ{Y?N3gUAzNXVz*c#aEE}%_SG! z_*kp2b%g<9{tvvD;K-wyMDC^?t89E0B&3}(ECobSbdF%h%j!#YrQ(b! z%RAxWdd5A>^V5wg9|F*GG84^FsJ;95TSpx5TmpRe%V0KSe^~q2KaIuIkzQr$);i;) z3fCzPw*Hnw{o{6c8D%w?%%rTq^(8QuefJY5 zVGit*C(aBNdBboq-pnEG`$?$d_fX}Ob1+IAuDX}hR++rHe-*2O*6&O7Ro|Wqfd$9u zlQvOD#!~ZxP2ARvM)#j@#w*!q7c;+vbx;=7EhQlqFR7`t3)ycOI>XK<2(h2{4EVC1R;!wjNG1$S^Hh_64mm>KfJxE~K1A z#%(h)uqJF$di@rp^ z2{~eX;{Wr6aj8yR&Qv?i>bP;wOZ~3D7op)zGc*}-Z=?&~ZK(}%FkJ0gQpp+C$TTbw zoNgEA`v50)29_wIrly7ouBn0<*+)rM2SpfO#?zJ&E4CmIzRsJ6v2v<}@hzr1ac;SR ziaE0a+uPecwLuYI1X2ebX&iI)ulZ_Vasw60gCexEv;JPu0d<-(>wOrGSy)I)N_y$F zm>=-~sd=++vVNll;YJU>+;5z90o4hm4qAFGW*R>9^V9q=bj71_ z)(#cXCHc|{rUIB}qU5m(^j)B@g-n)OZ$v>=P{j4muLpk)BFTVp#_bh#i&;eoDWLuP z)Ku{EEY2p+$;uC%I8k6|!#z};pS7+~IDVg^2Vs{f7k4(Z548-OpHJahEFP z$6s4jvhKtQ7?$)i^L;cYti+vn{fw*cfWuoD&qYv0IkZquxh{kb{Wa8tftAeO!9hrn zF)nPR?akDjc(T=XdCpd;jISoYwzeFt05_SCO~fh)j-sE8K!yE~5oY`J46Jej>E+bG zgjv-H-*Jl?shesS^AYYGY<6EJ>jJQNwz*1PH^q#6iatm2Wl3$S?-an&Sj96MS;2uj zr#9xi1?=!zaRQaPaU$AeZ(%mt+Fwk8LnGku1W2F2p<$eW=Pklv>EA;+90kOH%O6Tk99#%aqpEUH8wwjIA&fT5fY=j&S8dP=G+^K(jUp|e#snQ zZcZvQZH-akj(4A^(*)<}HhaDx$uf3Vz|x>UO-)S=do6O+--3I<%izjL znR=HeiLr-f303ol?5$j9iWnO+y7;Ap;uzXO65WKNEitatZT{+x7@x1ZF03Ibz5E@bnrz*vuBqH_x9&!K{#7crZ^ z(`I`0z^V)A1PwdTO;7fvy~AX34N^z)E!`f!D3C8LmdC3*i|&&GQW$OTKW5?VtKhHX@3%FR@gb+J zBO6PmoJ22dW%9=M$A7XKqi$<4BM{s=BW?4#NgQEMaotZ`HgR4?5GQo|I+z!It`%xB zpl6i<%c0v*L4&k%|7^drc^JV++}G`#ItrHjK94aY@%=KdnUCfD(_YR|J~~RqRd}S) zbEOJ*+(CJv5$HD1&7slMQ3c1t4=Mr*{HzG8#pT1D)@ZY|?))~OPj}HZSY8cY(#D%aY)4O;+8LXdeqSmN9!;8v8$Ah_{y_xhucvF7jnE@ClStzd%c{N zB^noWXwIpvJw=*tm?h9oaXYlz6-Xy3;d0W~GoJSylBx2AZ`t%8XpDIJxs&4AX09qJ zggaNhfQvtm+RKh$QhI~+s|R`C%=CJoO7r6<22Mv;)BOYRlMm&O?ozenVzW`8P~t7t z`U~Ru`}9JRg{{;3kkhlM7!A#;Sd|}KWC#+6kOyULiWCHlHT`{~?$l5w8NqnxADe8~}Ybop2f}SfjC=9Hfw6~!(NF!suVM2(w z$Z|ApE2UXx9gE_zcrr+@)bpYAs#r-lA^1}mN7_ zTQdqD5}z`aGB?rq_Z?OrXG?e#Xwt2d;1+n2IQ5lJ&I-#%%><0i*~&++p5Ri5?SXtR z^5Dz*SW(?v#$LH?+#m!-faq%*(Z5Sk5T zno%EOJx7OvBZ!Y4xOnJ@YHgYQuCk>zbiNx0PO~li{Wv(d`;B2qY1XXMugBpA5fP`` zX?2s`v>YTm^)pGwNEAM|MP7r56!DJHaFwHJgQ zXmAUOTce21#hwl)j#R{+^)qsEasu)A7RV@Pz}~tzRBKRTL2k`!@VN*n;Ob--rPV4D z7}B>v+!M#y5xB1yEwe0t0i>)Q@)cL~n?eW2)=@v{8wE?Gp;Qa;yw-=pSa5wbaR!iN zddSXfwq~4qr^;GJN8?5q9t!j8CUfD(O%xaJL629sFCG->B}cJ8;O;+&+<(A>FmEb9 zY=n$GfN@eBMmDM23r$9m97@eZ)<^V4xiktIJ+meD*4>ZpVccS*H)$CL>Z3b_P zIt;64zvO)pkQqV_anMnmV-DYcbhTCev*)1L^9j_g-ZfYk5#q@!&Yk&v39TL-XZ$6k z`_nuF(nf=31j5v~#A0-Mr^;WX1-N_lG{dgjdHu}6JTr^S;OZI*{3my8uS%-|@2))l z@qP$il{xRxQ1Hu>NH}}XmQ=mwm6`MV_3O{?%(oY4EDd-{9@19{G_qJkl3Mh$5+o@=+hHNyl@7?%|poH z%`m4D0G_$yU}-DvO}!IAAYR$;h{=k&_dq2)H#f)i(97%8y=Iy znena{t+bkkn-?sVsvHd7erR~>S0rm)GbZYETM;6$gU2mJ|4cR0uxn&4yb~bquQ+oJ z?E)JGhiyi-??n_`ZP&bUkH!Ps_~C^R4zgO=zz~zO6h-BGb9JL432>7vW1zmtHWo~6 z6V5aBV-qM>o?5pb75!KxVFw6I)n;}b3+;xaS)<(654SlKay1SeR6Q<%w zj$09&X+En`$)9KHLSbxpjgcHk@Rp50r5&J<($!IkBk?H--R~GVP zLT#n~qk*I9xE=A9g>ej#A}W;sRyWAYanqEPlG0H3+`;( zZbJQW8fkN@N(D;vKg`(lPr#6DH$T69Z@It6A9LL2bgMk1eqfTpf$oSDyNmv#o9@lG zOoouu85BOx>26$)q+2*MY_Tey-6v6TKP&;Dl&a?8PyexSr83U$6|-=gO!31B~RiJ{d1OBGes7aySfvdq2*K>2;7z>)+zDxP*1po|F;WUK+lAzK1d%fa*Mj2*9 zV_sn2BV4|ypV0z<1+R1o1^Yb~AkkS4Z&{!NQKugPTqu|Rf3X#-7y<8m&<2VxPgzyn zL6TxypgPu&S{#Z>H7wB}fK=+|KUe7e27$G;hvvYD_5+_oeLLiYI-c3xg?VBR{3d3`5aYLq` zZaelJ1d~dDB*;C&=99@gIJX+Z<`D^qJZH2@LBgTQPEp}kcp#Eil8h_RdOz);{LSt$**mc19wF876jv7Yd$l*?s8K`x&*7n#(xi$_Yn|GES>h|~V@ z_QXWYwF7)X#R1+48ACY0*x_cxmtKT+L1F5!l~W}Mtv_hy1p~lMlR}~&E(cOcaz(~Ue&dC;Dj!El*Y+f+9WGo%pH~jBt zf2E(R5hqUoh%*twe*WzMKul4Yz7Z!7ER?X0`qnYHisYo)spP8-VZpMrb`!7Ok9mu; zg=j*Qt4Py*ue4I4yQ$SQ{eyPo(au`mG^~s$cR!G0oFp&pT=Ne+sn}yfvueF^x(dMI zjXd4ZoO$&(is*y;o$0Yw`T#^9A2Kx)OpO3MBHT<&njw^FA>w{JDj&HJb*Kut;`$8k z0~3Xt7*z*yxvKQtUL-xEZYA1cKr8fpKJ1*ta9!P(yS?=$IUbAZSp<-pc#8gzQQ3E+ z7o#+<$0_9cMESxIJA+RRwmX+mQ!?^rhGLF?7gaBnBQp za$k~ZZR(RP-xSIcIkmI6OT91+OQwHMg-iK(ic%$yM=>bz?KT9%-E-}Bi|{x%f{JAY%97Oa`cwZ+HaR( zxmA9ykz(N^nwD=ttwjVJU8F!6+E8q56U%6+uTCIe#!1+e8v5O0QoeM3NcNR(^`N|Y zFF}=J0UnlILRyPcsUJg5wtq)L->qBM`MUKrCCntkCD^@o;pp^(ofbD_H;bwV@3LUK z&q?hFf;?EzIIi3X2=fC1zoP%t3){~a0^S2EpHI(B5Yn!mTGILNmLLs$i2I+b3_z}z z#{yhFI(l$mIsB00dN!#VWUj#7~2jctb-buuxLmG~=@<9jMjqFKgVlQVUe53PbiUX;bgQk%M6OJ@i}h*IB3vzi1OdIrq{hq-phD{?C8$#UJ00k7R#hWlOM%)g#%z> z`7#k2K0caqIHS>oma6M)Z4%Y=o)4t4{7n=P`xDuJEtd*#SdT~Y@{I-QvXleeyM)rq ziEtWNWpp~R(X+JgF0cw{GKZ1IE`Gkv(U;BN9*C3LsqK_y9m4W~A0qo1<$pL6hfHS! zXE!G)k1=}6=mTRn|4pMe|A<$17j!%Ws=|%Jb zKh7r5PZJHF%*1%d6#?6O66hw29!G^7JSjFDVDy@&5*aFD4i67yhrAytFL1Mr8mg;WRndP`)mZ|1VoB#`N9&)eq) zy1|-ZL3cAaXcA(5Hu;M8hg*n4mxfTL*BBSw5!DzaS%-Z6Yw0@k=|o?LytA^zjjl1) z0V-%uI4TSyF238N{lW4sRfST?6^X?E`o=2+0wJ3l=(4l1ku9?@HFtnY9&~Ta$(mh5 zx&wP|qoYJiMWC-5@9+)@gafO9y^duwhh*tr)1Z)n-ydlU320mBKwifzINS)4$%?G1 zs@lYRD|GPnpnwX;?f8C%(^Oi|_8Uu6)`sQh>`5a_>BfT7tkQS;?PtA%p>ID9O_A#Y z#9Pp?w|XIreVwvBg7|kL{5O&XB7&u=?;mnDxyC5D7SMJTb~QKfB|wT^LY=mJJ>Iq7 zExf=fBdd)C1I=sZ`G<9g{2oSDdk1S+$Lo_8fB(fly!GUsOq)$(ZL_M|rvy4+*Y<(? z{NZu{r^2q)v)GdClfWf^2DFs`W;beYZ+9ll$zm5@Y2Vz#3=q6dPEISj2`tj@Iyxd! zwfUq+h7|bk`OH(#_78z5qM4AUH)umAlyOSD zN)eAIEAL$DzQFJH-1&-7Xm1?Vh`^`t>-k=aTu^Q)%G%aH!kN;acX1PQeO?W~g*<+& zX7l~im3OlmM+Vy>Xz~9#l}M`Y>2+Ln{SP2C~}C4hr}T{FJh;y zZldYkhXhiHaxN-~o^2L2-EG%#*3f-M1YBPfGrHjIv`Z%c6|6e**-^~)~ zz{V|_lb4*vXF-NKm0nz2#1Y#}Y}$9APH8$iI?9wVr7D<1g~n}ok1}SBi0*Mn$kGzE zOPM17F8&1&X3)t_=KuBr_#%DX5m&JWXalsfYz}ZKs9%>UKTx{k9r+pVE32t*WJ=dLg? z!ya(L*&57;OZDW~guoqZ*$#m$eQrhb_{R#nCkWl zjEaV|2kuBg^0S;!J(hmrniDBof1##pqllslfq`G8Br1$ z7kRC|VCr=EuFZaQss7T4+TKvmv~$Gv2Y4zhnWzUW0cbjVqD~<|&{8ijKp~E{zfOvk z|2@ekxG2kA{L9OoQG6L+Iq$lcfw@VeeBEv2?h+GMM4R`eFv03TdwGEw*BIj*t}9D+ zjYIXblZjE|N4B$Dk=4v8DnGio3EuW3a7PZaFz+60Ad}T;zZX!#Rw!^-3N8b0X3ZvB z*E)uMB)#g0^{tr~Ec}a$6jZrIb&6?o!`8Akli1`pG;;$zKsaK}Az;RyixRBefSOST z40=HxN!H2(xS8$u2gT|CvdP4^in9FnshI{hGf?*>HIgQva@6x&5KEzJYHr>JMGh$A z>|{GyOG;KPNV_dAQH2_Rc5iec>n1-_&N7Y~jkpM{nHLzpx{FVm z^_~Jps9?r{8azPY)hkbnf5qs1K!uCAmO5wzPje5p7fcOno$_I172Cy2v^nlD$~uT< ze9fDhojsnmZ!vgIPTE=WyW#!pv8@Q-9mWzoUZbAdKUgsJm=hxJo31bI2yEq_hTOAf z^MK@y3Ej<@Xg42UU~Kk<#m4SS=9zM*47*?K`H+1<=D2e!ZkKqIymLC)Uod+ySyK80=1z3~V5EVY^G^ zK{>_84`KkMMN0^gzRxNA+(9E;OxWr%SqYzYO#>P{@IXeoA0v@>~>r zsKK`U-}IQGD@Cg0OEGZ4mdQSk5c0pfA4%#@_HCMTO%_fA>wqoMy+g?ZO55P1zNVOk z%V%NFr*}n%mqlZaYo9_Ro00OS37mU=_z9fW;~|0j54&Px-&6z5BJ>Ou z_0gb=j|fiE1R@ks)YW<1PoCN^1?-FQ96>Twk6u#V}x zmU*Raeb<1{2IDTzrUYzmp{%V}LPh;9Fz_;pqG)vlaWGl!%lzH((F5+6d7>Xa5;Z$c6CQkn^` zUQzBz+NnyftMenDIGxEkSCy!aajoZIMqzGg=~A6FV~km{`e=^2&Ru?dcMfqNMP1t| z1>8%uo(}u?@sK3!5`6E^$f=ksYE{2)9XUI8*bGl*Bwh$9^2sQKg($HZg+4tM^A}zQ zk1u)6@i7n`@%nr2pQ)qCv=SPm{aKcw%Yn!OilvcGOSpoX?lL5tuijw)%%HD4aKY&E zgX9V1B9cAk;-~li84U#Y;Nk_PVwl?ChEd5P(POK+tfvZz*up5ya4~_-WBDrGQ)sl_ z5n4{;Bn+zMAMS;ct|MBC%fkgh?!Y&<^+TaMc8~(OQV)uYvQgSUM@+xETzyEeE$hd1 zmpH0&rnD=)G{a9ZcOe}!=RvTGuc~W(#MA3T^A{CF8y);AwO+5bbFxCV^T@-J-q4XiO0LTm`@7Ia6!+YZq5D@K*8W zk=%u7pCTE3sf;5ThyL%qD(&!Uup_;?=Ib9leat}LgfD6@m6E^X*cvpz_=67S_w6*X zgqrCfJ@kj+6h}~%5TzDRvo9RFse8Jyz0|}E#&`FW`v#KTXM!+eA9$ax#4LGOOgdZ} zUT5nQ{!M{)koSH0*&iX|TH`|zjq}@GX>Iuh9c43U9k}&8CA{O|_H+#8Y6R07g!Y+a zgx6FPL%p-+=`^60T(N#9K3OlnOn1vgfiK~)c%7ER8Ba3%)il442wZU`N3#B}_c6*( zGZ=j#gSSI>@?)*0%6@S^X!n7+`H_wcs!Db{zLRSRXHVQZ-Z-uXC^(I8E(#h&7qHeV z&dFa6+~y;qtZ(zJ{xRy?e8`E`^jx@-vg{dv+8ZEOQi#6zsgeA;)XI;)69lxkT<6&6 z_jNfht>(R%*{E2XjNg?$%7L);YW3~PwI}2Kh{`NoL>0t%7xe3)-lvFt)Hl%bX3fz9 zJlTE{$xG^cq}ZjH3Cvttm&35XtZi zaVFM;3&BpfuXbjV0N-SqCXZ|;U9P&(lLJL{^X%K|WS#WsSf$q|oki#FT30ekVqlSX z_RlEL>1>E93%1`l^&BaxXi3-muOeW{47x=Tj>fh|2jP7!;OJ}B?|*-|j<94~vOxb? zKW2@gvzg5NF2UD(dnMD`O7QC7#q!fQ!UbFFDX_P;}dUpNgfHJf^%xCD0nbsl2vL)ugjwD=%B)W01KQcB$U03IDu2GP!=p z?EJI7>hfkBT=3nrA<(6he{k3wB_KDq+d=Vw6P-CrEs?e&x{sO_WyEnT=RElpGaaYF z5@%(04!sH<{_2#0s-VU%A{Um1=iZJjKewuZg}j9s!n`5#5JgjT%eB3|4=oo-Ei=>>M%-=*}YdNS_!S@Zmovj4?;y^BD+~Oks83FU8@#fB%uuOJ6 ztzSEtSZBbg@Ww*BlQ$!C>f2T)$7=NCwol3>}pho#u!GFPb{XcN=N5)8-G*C8O+r?`F#X?E|Aim7^Du*azttB!} z13WxD+NK(ubLjUaijGodnkhxwu2J@GsN%UL4vhbW?}A#bt{((xEuG#0bhgmlG!a9# zbX=6NM21n$>~HtOMSx;2U|egBx6*F5xb!ks|F>A`;F(gEu`gW$Q2v_%C5L4v?u1<; z@e@F%@&mZW4PZ_p0mfosZ0t7^^OoVWhcN_R{-64j%%Wn#cchIwD@(TVv7X+GbiLQ@ z(^FH2v)X(qL;?R)d2gqDUN7JJ*A%ybDF%^XozNzjg^>CA`Q7TEBJCf5-bCv^5(WXI zBLGI8O|DS%PU*iY-A|pkI0CMC6)@73{dHAERYk>DkLcM%I>1sE$TwO;aszACQ}oYJ z5p`+0#e)ATbeVf2dMa?|9|N5CV)>KweF0#m2lgQ5hZzA(LK^Dy@@lnSu_o#?2!Pkg zfg^eD?p2I&{iJ3Z$3O2(I|iB{h7R6z(HR8(3vSA2qfTo8@T7)@84K>&3Ty!2?KFG# z0<5u}dm6@%Plao$VkCgZubvLZAfQQW5Xm`sFF(4_W(0zT-AoCL~o8vx?r-nDhh$VsFbZZGGm_6{Y1xMDT*@qQ(m`eC~R z1T1~Yacpet(IbJ)*K7ZXHUBsT$*;#Je2{4X`rU5Ub?U<91c!tapmSBzUm0wupyvRK z*)X3Oy4C_p#^^nnV;xylj?k;EGFiqWl~Zc=yZFz*I?H0MfLedlb3){Qu{sYO0VqaV zon|bUTzSa3g*xQ~a6TP*_j)csk#*1>EeRN<4|Q$5|Jq(Td!<4W;BWx%m6TPbJ|f1J zW;tPUI}}m}hw1_X!%qf315mliI{ek$ripeUKzzw0h&sTcngFob;%z0r4eE3NJ^Afb93Hu z?rDJhTD#G|oz+6{ARO0*(-@naY&POcnIX@lGA42+b63Tz0r)V?{!g`Z=&q2?cjiOE zGnXie%u7RLJEnrEbkpA{JOPk3J|q#F&i&oX*zn)LGXZeZ0a!C6H zX9kB3gGWqA4?&DbaE$69xBfLoy3k#hDj%a4Ef3yADay_#*2@g|-k?3I${G$H88#41 zO`foXyBKDVOTa8X&Z(~BQ4Nr>%S`lchVdsaE1VXNMq~n{uOo~aJt!827VOKC0qA#H(<)n zqecIUkcUceOAeC2)sS)?s0Bv8$fT z+PM6hK>91ge;dJ0-o)74%vAkgqFG_?LD3_CTg?Hu07~Q$KrS0F-|*FDwgsr3|Ipsd zSlar(tak9oeagL;9Ys)Xsm8RCHr(C$so;^sczF-?K9xiEOuKPqMKaq?~i3La*oCylw>%E|2kd8U-c3;6yv>IMj+k~s!HQTHVl~Q zyIIu905<7A^fZ^e=R`YFA7X4{V*?;~Z@v^3qJ$Aml(OQNp(%j7*1v}0C#gS92i8qO z)8Z+~Zgm$HH^@vk2Km-n^0*+uATy$V>x)8KEq8Q1(j zq6CA;i~s4?jeX)&AxBVBY2G$A0#A2jXF0txEu#aNL2bhnK!DGlEkR=5=$KUA{HsF& zscaAa%l$A1$m_oW2QOv;S_rC!w=#7NYWWh){)!!g_hcI3ejr7F>i^sHOs(VPwW*C3 z#4KceDHNP6CJ4v9_hhoF;4hit`iFsj=MgeMtNsiL-5pTE_$o9S5ym5QZ0m!+{I6E$ zBb5feAOSVkIdO00PXfQqOJ#1)lm1;iOjlpRdGAA-fFdW zN$!8CBPq)U(L6fZY#D?s{8w71`7%Yc35p=u5~@*~KE<|-E5t8x5 zM7+Cu1E?9Us$c|xp_qWE@%%>(`oB%oIwzdVx}7edCF1u;?|Ps3PE-lfuNN2Hl}hJr1eCJh`Vf;{(xrnKVn~Ej?oz zhJh-1uAGzBwn@)W)w{(%*c^k{)u)8bzlLNy-R8tcKCr@-h7CkY&ml&?ZC2+4zIX3> zT6k}SX2p~b!x+$`tk3iS-Ot?OwZ!ZZE(C+EmM|1E0h~rTNaU8h}w6k9AMa8O*Y^~&ig?*cv!0yrS0D9J_(g2ZT&4J~xWhwoK>j46CsySv&{|*2U?sKsqaN*cziU!;W<|R=4k&2v zHO~IOQle$10ENrkU>Nz|>BAV%H9klHQbasdjX*Xl$&qG?rS8U#%C zkEoe3HJ29tS0ev#11wZ3tgBkjxL)qBzC@<*@(a)lAW%F;NW-jxY;w{%3=5}749ua> ze8%wjl2p*}L)u{zf*As?sg3v8PEq@!3l)=E7{ipv4Xwiy#o+X~dXt|ER zaRNLJF^=6QHw7B2JYj%(?ggAUd;{1?K1;22xd6pH9r>XlptMy2!dEw-KIFN&lh@cc zZ%FMz7q4~FvtIw(9F>bZ-v(e2jgmOP?%Ql0>O%q4rKf@+w7t9#ewi(&LO4xkfzk3a z2*~Q$iNwwovQxwe(V3}+Z3o@?y>V9uMsL6RqN4XD?LLC z1FHz(nu)C+1e#*U4pYVuzmd0)y8v{nf*(VesurA6N@XH}z7k%->vuy{OnVzcbRs`ft1``@5*N{EB$(^L5i-}d-zANxi z;w(@3+m#v{ZU;qiE0N4}J%MV_ zaL`2-j-5uz64bq&cgNsS`!L!}az~PdOPve2cL+-Vt5V!Y2AAgq@3=A{eaW4P&WSo{ zgHaob612aB*T{-;zPf+A8Zp`5#Df6+gm!B1@AbIC4KyW#({fulepk zG@v1v38< zCL;sjXs)N7bTC@V6%c9y0A8Ja8JR?kLB95?>=C!B7x{Q-YvM>cg`Ps?NMOO-Ge#ey zUp4l?@MiGs0UcO?z$C0eDTq`Og0S zF|^3%f(yUEgyp(?aQw^sG#@dM32%RQUCH1yihD`^PoeIrF7YPhz50OVN zm?ijVa&4jcYH=0o;iSa3Rj+LML86n1^~ez*Z)-oKm)BFgP3Uk<@D4jZ#2<$jllog4J|6Ivqy;GQj3bK08?_^A$6O%%Q+;@ zN0UO#{(R>tEW{plet>i)>!^=a!6nq%@PL|EkX)b$IjU;TneXNWz|$}eLK}MxtH)a= zCuiS$a8L0~v4|)Y&rL+b_V+(0k)uof6=A1}3Fl-`zx5zm&FpcWprIO2h~09S&7pMP zLTV)Fa{_5xH?=R^DsH*yxm{9CR(>Us+Qh+e}rXd2kg?Sw+i`tUS# zn8{PFRzKKj#I8aPbpq)hM9rG@+e{K#Ga^55UNwTNxudP%5WI64y~el6@d{YfjO#+_ zp=6<)N2m?%p9?PdoY`Za!!pI}N zWAx%_AN0JG=;VwYnWr9XG@>uJYT_P!`+!V` zY|>Vg@cQTJ+TJg(J&BYB_JafW%l=nDIaJ`SkHqNF82Ru$?g*Q*EDKlli;J-{ z6;m>IVoh=tYt4x$%HRkTzrr>MmUzSyy7Mvu?q38v^jLSIF@oS^^nksI>!*Y_l{NcYLvg${R4Sn0mo|u}@pwu|UB`|8wf{&)2uie-4Dx zll0S@Q}8fIn~#O^#PNPf+NOO0yUIr? zHKyv8mUq6(&daK+?uGR@?`>?*-zgv+xz<@9Qbyh0vEl z>S*%^okIBtQ!-~K{RmcuZwPVWSrp^j)GJ*Z{?d~D3ps` zwo#lzkx#OX_YYrmjbbopXbrSfJBT%Wes1QXOMPbsA%{6@KwfCA zB*h1w2lXpoh@)dm)G#&%`P0BX@~F(?6O=Hc<+C2Mr$vlpov#H8t77d@(AZ^|r<|d# zGs_QGLocW2-kW^r!aqSZPOw1Ohj+gZXb;=0-|zm2@>D@IY1~X;&7N3Ou>aOhS^kR% zHAfsOi|wWTf|wv9BkP`7TIz9DkpAd9>>l{WE!R`6n8}EmnmS>xpRwi^eDQ5Vi0ela zq=W$L+_8dy@4>}9dYWB;Uy^$4mvS_(boKq&a}RJ2h<4MzAP5sV{3@&F1AO~kL-gxz z=*Vd0V2i*Q^(L6}jn9`xgQvxm<-b|5VQ7j6P`S-je28{x+Q*_9v^;eUD;IpKQd|7% zGvuAIL+r)5LiOh+PJdCke75D0T2d5cjpWCx_FireI6)kSkKY^ku z*x3x{sp7EKRY4&N($AKJn5LCX#LRysBf0up8ys{ZW~RE411u++T{KOHPo~P^k_K&L z+ka1H9Ok+tDq$pfvadW281cJw7RJn^d&UrYwW*s$)A3?A_k!ovQo;7?!1Kt$ijNii z3rlbA(bN^kZO9@Uk*>%`+GcA7GVQ&Kqx|7F{G7%`&kXKDjtd6{h16!LL)k(v-|B%y z4ZUI$=27{TtN5o-K_Wpi^86`_{9UrKulNUl+@kYY`Y&$v)4@*TxG7vXF$_lbP$+cQ zp)>Ti@@zjdH`f5P>J}c4cL7ZmDEQ3zc4djzJz{M-1Zn7;J#d5{UEuk?aRqEW+iJcQ zD@a5K5&{cpt+bhM?W;j}qU026C9A7cFx>o_7)2o!UBBLMf#+gxy~nf0^7ne4PtC*m zW6k;JFlQj_s+jp{?uFDb5JLX%iDFG?LVt_d zhc?mSQ&e= zMK?puhN#Gcb&?7^tUL;V^w7T0vZtz9jT`YRGkylS`gYb^qXFnlQimt2NPp}f16=fAJ-)YkP)5(JSZpn&ueK%|K@=~oCsq&RGH1>%tL=66TEDaQ2`x9( z$j->CaZuc(?Ly6;0o(=`?*ifb>S4GOLvqizK;A%SHlK-a#{(8vIk@oW{U1q~X`o1N z&`(uhK0`NRS2Er4S$9y2;>aUl5`yUWc@k-_7$EJ}7w2^2tgiBfF%wnR6!d7#?tXRh z?Zw&Ws{^_O(z0Ee=3I^H4eY{uXmV0qT%2HB6dTX!xkz9UmQTm-tGWcFFI@biv3;+LMN0hkCD}IlyZ@ ztm>6i_;fb1E?}4#+uBFnc|>D6ozD3Skp0V(laphCFf9no9eDh~P zN-O6?-Pn_;Rstc0~wZCuNmnjYaSlc)oRywB)@IgRR_g=sdRK?9dpk&Xa;GTK_mVn!1%~Ah* z9r5q5-nEuy3qWkQf1UguYVMU9K7Lc{H2DhPnS%H< zc6gyW5g32CeYo!gS|*d#zY^Y}Xoxc_c7I#gU5Em@!tFaI3U8sI@YJlcE3~v4Kqne% z{sfp43eOrn6BJst>ND@=mBtsE`saZFfTl^`)znI33f=wfQFaCb)n)bmslA+7qt)w8o$2OQ0Pv znV(z%^Zh+Ql!?_wQzAdLJu^jzpGl%{E|W#)HsfoJpp4Zi`Quc!N~b89u#4f2<|ppx zK$mNFF}JXO;#)m0eesNqQl>cmjIoE4QbDNPaVWg^rDvQ<-Eu!NYeK6Ej2%deVbsYj zk8r9zn$7a0T|O?`m5)XRU)@e*O+`u-p) zMXd`mHyt;BX?#9Yo~qHOM};_F4U19^u*Bk7mG2t9Pas0tHjIb)mH(a1eyO2h9b6|j z-10gSnmAG$S*8-VTDPpkLkT5`8ilo{>sG=U7pY)p)t3|#0mEcK$?lbt-+cQAM3gpv zsAoIPq!pGeJchATRNY47_udz3vh4Foe4qWBU-rY#C?W|}%yO|U*U3B(Mwi-h=2!>l~W_ z5prC{T7%CvE;4K0y16-Zu_H6x z#5;7)-1#(WcgoGRjRQMUxe!$vUk?z%eKFNYUK-pTxABzappfVB1BgiPupT=eD@|!4 z&e}~S5CyuM47(uigMiwO*&t%A@oi8%zIqWMt)hGZN;@kib7w= z3;N-=j9^<1>hD&65jBN<3XJMC=AsLkWe{tc9MsVU?%xOccC~(5)e_3OE9i}evA7-N zLiDDTpEz$3ViQigGi+6C> z1A5#;w7h`We6%$~J5yg{meGvcbivl4YCh9my(YTaW)aZ_V8lb(7QD)={ zvz&BQ6r@oi>|{;8|4Tw(nI_DkVQ;kX?OB7_C+(NgnLP$eASqzcJHuu&9oWcNuYw5i zEiNWz>_}VzT9xl2q(Sfq5OCRQw23O5VP89+`FE1(@D50uD|c1|>?h`-cC9!J65x}z za)?&_mz$|%e)nhQQ|4a4f3>O`H_FD|$pZq~_|N zM3#JcYSDp|@`;~z|7$4((6TTu^PJn`|p9v{h60dzD(+;EE0EY(kKyXVttiFkqUvLn>y^ zrC{3?Q39Rsaks87q6O1v!bXgoxi`gnpZD>2J~FLhz0o1Cgz2^kcjVpq{;Wlj2(7=ob+UAz8yJkHyjgZ&5K?Da4!=^Z9Uv9Z zf1Y-Hu2GwQjXd zdhd2zxlYv@Ew~6pHiY@QR8h=wJeflf5kN@uYZ!Zbo$W_fy)M^(Ooyx4LOTb*&<};CKKL$)$X561%hy> zF(0l)s9O1H^HUxz`d#65hUZfGW`ZC&w$xyz>VE!o<6rM%21RgSy`gUu3&=RLvw7fa zflZzG&D^~dhEC63D%0F>5yilfpe|MEDLB+p9NY_vF~obC(%al0=U*95BYrIC(vRQo z!-B_!9?rc-7AsAI1&6~3Z+@s_3+tv1)avT9d6qZt#fMhmK-uO6ymH~BnZ^Hnbz$WAW_dGZc91U~4`m%Bm zJ83~T+dZ<%&xDMI*wY`T?M|V6EoHKF969A>->r$0r`!4LvQDhXH3pN@%ahoZi6Ybn zW$ARp8>}y_Kd)Za9df=c1Z|()XsU|&vO4jcSansDo$T4JxnqFFM&%tltR{Z*?AERr z5Ss$ykXXEmH!U}(sJTwMpSL||U6x6d#6#Re4Y~ImsT|b!*w;q0=P$a3&qlm%46ei! zeO7RYxhL*Fh45`CXVaI75p{skH&|nB(l)dy^;mr_*Jw;B1A@@X*!aq8)p3;?XBxM9 z<>*8w&9~0y^rG5U3fSiHAgAT-={nS@d1R`8a$5spb7{BXjaOYuxVa_?a+3Pt6qAIa z5)4Z@htDcP{N$JXWBjA@4jF>)|2PWGf3tVtM`C8~c%kwzM<}K{kMQEi%}%496ab=b z5=WN!`aZ>lO}a0+offa)}bF(Q^ID z7ErbbncG2TecR<6*?vM}=i6!(2o-}r=XpBka+~ofh4`Z_A(9e{J2nhNK<0u=#x$g+ z%LU^eHpcGd!mf`go>EbZP3jSIt;P5nsQ>I$b|vY#0@a)Nob%tOJAd~q@xNABvV_c0 zkXaPIt}0(k>2*=ppPp$<_(TSrXYU?2$m`Hf&zLduaVxse!Gy7O8@V1v-zhoQnYYzw_rR6D+>35@zL$&jt736Lz*=Z_yY()g|F01Xyf%v4? zcpnX}fNx*AMh3zTH zrqq|iA#=&Kq+w!tsB^>EIs7N?+**5L8yoxUadB`4@9%HUr`_S15KwmGxj6 z}vTV$~+rg)P`0Nd(rMFYe(Sa`ibqmdvxlMupjBrp3>~L)grM~0gajT z5?+f4lQ*1Cf^T2a{Bbk z1)`4^3cZ$8_9~ahM`jG_mo|HL_Et&YRZ%(v;&} z`pGLS-_{@6w?*gJ7kt&qd-S`Z?6-n(}rwJMopAjf$q*=i^g64QYe>C6^DxPepx{L*IQs zz>=eyB}&Vg!UUe~i5+tl%Jx`zFD|m=Cv(Fn9VULVz&7nM=AC*0!f#YHP z`y`(*WAb;dC8uQkpKbVUwsx`B#xC9z2gT-10vTx}N`%z)}+#i10{Ycb- zjl92R137i5QDnb9BC1j)LonQ%zn7wNWJ>FK5p`ib?NLe;jNQ|T@CLhh&6yxQ>HOPv z8q+HYb4j_8gtB)=616KR(R^WScQw#MF;cy2HGYQ9%~4EG^DZlZTJ7q5&BdAUi>oqA z1hLidaZa*&EE{eq=qmtNn)59u)4F{U0aO%qa;wMfHL^xwBueV$ojr!sH_y zYozE?1T0F`wM4**ur8p8Zv?n5T}_AQCt( z5sQf)KtIl!>o^4Fqw*Ep`{(YhR41wy{=VeOIni(O-rqBwE~n~QZO_Hk6{jr*e_ zA^9J7uk?8HB#H1#pSr`uPKTFv>QTl{(MXRQ7xe?jKSaJHTX=NgQw|qp9ro_hRzx^C z>W&Y(pG}E0MU@Y7hg|!mV{(KkIUvE));GmC_Vk{-yiOK&+2d|#mf?=0-h!FKbMK>< zH6F+0^7q@mmHf?S<)FsAFQ)9%&(xky{?ML2GKiY0Xv*S7)JDAEH(GGaAKx8m53`oh zrQ&)|w3M@IERENB$zHCp{GLZt1{^D@HKBHymo+s746qoItMgWS6Q`mBA@PkiPK*y!7CZqP&sYEk#OP>-bf zz3tc$RB#HWFKv49bsBt-URUbZ<`B5!#d`BjCr?PYy3Eu=XOqC&pU@&CzBv7mMvQ~D zNh*`yx4DzRMn9bfZ<7)2he_z=1?eag;|{xfZ#@w4yVnqziE^GzddEi?B|Dd*sPeU^ zitw~vEk10;-8u_vB#652U>5l3Gy111?OeX^eBI?WK4@<`Pkf1`>L8c!%2e)(I^&#a z;6OHf=5@>83;t2?)X9fLzxLTTRglw^fdP6pa=@5pNP-L@`Xi_Njd_FvwKXeEkCwKdw8wFBi3y`-faN^It8Y4oK5_1C zaXxdbWbJY;QndB{>ap)qD#`56_Q_pZ-XBp3E$5vd;lVbwj(nqO{bm}x;}v$e8)4Qj zrqLvSleDD8GQI|q{vA)>pyoAk8n))XVQ)iG=i6XKZ&7d2om`Q08UL#66@rRgCF>K7 za;3cD3y`@v1hs+Pcy{kVzU=zvY1G&$_J>zXYPc+@^pK2(47tC=@(-fkMa zz8uaz6#RLFS{8V??R0He-gqWTfYZPDaDNz)Fye7F{Y?yCq5A-DOGYFtasGPb>T1pR z)>ePhW6oE>4iw&l)4(jzUny$Bew*l}oj==^bgnv^eyuZ?*0Qa75cp5_jld`0M`Juv z>bg1!^5KAkUZBRhTpBOZ{YnzBQ#L(j|6O!^-z`7++FH7WR&)Rk$gRqyUChb{MGYtkd^xbd@*jYp%#(Ui~w!@_6hT*HESCsb$1%gzAjx>tW#8; zS-4N+)RJ#oUJ`ycXBk#8@A#rh(@6jkOMgbId6$IX=uJz%X!rm-Yxrk9)=SVyZD!H4 zJBB=(Ojb^t>i0QBzD|1V%yQ(8jTDbJM&HcNQ0B;Yim*%UU2Cr~I8us{u?vwKnqhV` zn>W8h^MAh~`YNoj@>({cVqlFeFSDea`FCJG?>|P4Vg!rPrApIRMxj>^awtj2DN=rC zSII9O^AKO zraZh39H4UG<0=E5=ILyZ+ER9dW%Ca4hOQf&G?gWcOcFN5UWci3T|AbEI4*!^HD!+f ziiYM`KaH$QJt9Eo_N0f({DUOAP9LIIM$x|hcLI;R2fyz{yD{ddp}cnx@RQmB;`7v< zPb48jt%iVHZ|MCcJkl=GA?WQoGR-3EosoFh(iO z-H>@#=#M!nU90jM@;~;ah=%Z%t2I~FJ5ZSRdRLg6AQQYRSW-^V7qeF!p#bvc%Wit>_yI4kZ zrj=#&p5#ep_1)8~k)|fM3wQ5wmQwHe7+S{`s0Pjl!Nlywq;Wq%whc0^&FF9=dovwD9y){D2=PJQuzx{ujLPFs9?%7GD6 z{OK<~W#Vf#W8j#19(ZLQd-}YGSy3NZdJv`iJ0?^qxBmjv92W8xV;I!-tU&?V`t!7A z%igXjJ~kC8+lG??*K{JE?{m97S@;jb&KPz+%z zDJrNb`VAYY$5*ojKKqKqdn$jT<(ALbyf2Sm;Z$+waBSnQQxmLe4jSl{l2CxUjhJiCA zG{8Se0)e*$khUy&XG_)Rwq`r>X={`n{a0bAv&@oW)8D0^olMw9Z0ot6vJ#9`llj*d zrq1qEhC_xta7Oh-dMGcM7X1LBq16vY^|?`|DLq$#l#{}H99M4V$MDVhlvFMg>({vF z02b13=GM2M5W9msxwaiAhvX9bb=&pL|7CWn@JTO3kfDkxm=VkLU>$+#0MacZA{r46 zmk>1jO47MEP}w@60I_QQEhg7*Xw?uffuZ%nQP|8KfLW+Q5hd24+gm%4?!edw5@@;V z!*_-*qE*jraOfC{>^pJb`6l=?So*}WG&b0CSmfcuBGd7-;M6nlW*CZ-t&Tmv&kGTK zxNkqLi`FrX)kuB}>dXLQ5e}a-l(J~CTe~3Y!p$<^(*R(q%##QzGZv&MUpOgb%NKfK zUwDYc&&8EDKw)bUO&I`Vna7!&!8%McjE)RCPF88@JPm_MaY?nLaf$!Z(z)U`FmW!R zl#l?Qi=fkNuX5Axd_z**1D)QgS5ov_w8~d;wgRFS#-Ue>DVLWU8EhN!h0ssV zl>h6!Zdd$_HS(XQP`^q$+|`I2xN7Re+w+dA5KHLVI72j~Je?9F5+G%zf>#b;u!0=B z5Dm!mgkzm_UG>-$ZqQZ(EO{I3YDyL+{vw8;rlz*#dX6N>KQ?|pB8%h~hl#ox=8x~O z67|P~aA!=sQR((p(paZq^)bGC;`wQNUio&0Lok zbmkt2TPzRSw*_ClncCfyU8eV%D!qm^aR!nA2{7~LnU+#(*iSkF@S>)uxVO+UYo!4Z zQO~9RD=$Xwq!I3?0*4Ks#`>a&&3(Szv?;aeLy@u0-${=S8ZIMF_!@EXH_Gj-nc!0Y z8o>MDLq32xL&12fm9E_2>EdiQk}tGzYsb6`S_xmxwu z=~Ek}rQQQ5;M-W}xrgrf8(2YQHd70R-jc0ka!4{sApSL-|Tu81XVuGGWIObo|a{PAIj|;qxQi z_E0QNW-XfONbWl{>!v(NQF>|~Tf)?u>*bHP#wG_AH|(l@BlKaPUk$jRJZueYsE-CouS3ZRrOK@Zk3^GDYq2(6 z!@@4MF$TNg0$Y`)eKWg`-Mc3jHXhW*K%5amuR>C6;pi82I@OTsb5_k-x%`vlTVzhQ zJP%w?G4aJD%?QTe@K}rMmET(m-7o*e-ljp5Z=4;>e48D1kq)PckEnOi%Hj2NCrqhS z>y`b>R&5UTIzxfr{N4TCmp3X|d{Px$k|cFa0FmSrs^Nnu5mGyE&Vbne6H;B_*TD}* s-=liAS9_4VSpMGw4a0x`IRzc)i`#K{bbJOro5TP&vVc_@xc>Kl0N>(tg8%>k diff --git a/src/lay/lay/doc/manual/res_schematic.png b/src/lay/lay/doc/manual/res_schematic.png index 9f6dd0c599b4e959dad9d3d3c585da93b7142334..76505460e999d7b672587a098ba1c9e23005fbeb 100644 GIT binary patch delta 2612 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 ZJvAbt_5%k~9RDx5u9o5LDhb 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

8`6C;Wq`MHNF!c3L=9YdAV z-6EV(YP(`dajZFl#Ul$v$b;jQ(bne}1#MoQLk>EM(gOvM0%dlJAQfN(M3Z*{p$*&e z10z4D+20q^DCqgto)PS^Whq6la}O_M!8a(Ot)h8J#*wHL2B5Dv+YLboD;7_d>aqh; z(j({S;(5~yrKpC*wV4pgguim10EL5Qi7iHdH$(XYD2dbM^hlc-1F27>8qQxr+A|qp zXHf6-`s9w>Zc#PJv6EM}zgenVxUq6p+e{@aDkd5kHP#@gVvwEna5Wv)nKRyR7Eh4- zm4hBYSog(C4^q0m9~1l|-L$aFDni6&PDD20PH!U{hu36s6f*vnV}Nk8A!hSPtsmYw zxVp0q+j|-9;#Mbudn+0v6XCQ>XZG`m_|7n1=Dxib`G0`6b&CmV9`Evdor2maW2`y` z`Rg&Ck|%DlJ)rz3+HBx`PfpQ$^vgEMzwVbl_HkThH`2i4PP>8Sx_;t&roeUsD!+d~ zX>;4A!9GZFv1(NdW?cHom*KS{lR`c_Osnq&rWmO-?+V>m#j!K|h0{nuf2T3#30CW7 zchw9=4nZD>VoFPR#6A{Pf?I{)$nly2y>Pqc-HA z0`ome?>O2Vzo;JxK@w-k183h{*V3^0AuV_DoQ%o)TeJcP&LUV*xvhm3wu5W_#Njin zK$4eP00o2=ZIgK;!2A495CVQEyC@&jsw+)VxUpth1W{=1z>)_U>krHk3-3;c#i!6` zE?0Vx2oJG@+mXpxv&QSbZ@^tzx%XAFRakhxDib8Q?gsW#uXOBy!XqaHQBc1kB#`hI zlv+$Sp{Mp6)4|m|BZ)dkmu{aFNKX79{bmF*G=WO8!#Wh3=STK=IjEIW14p$^REB*m zO&j0&h|_L6nM!SY<=auWpqaXoZ!TTNpLUP~`>~iY3al5IcjxSl*>?(g(-?ppDTQc* z9mN6(J}H_hIMyNi1`LF;_qC1`l}&|d0%SJ)_>!_M9)6pjRY!~R26f#B0ZF5^u2maZ zU~`n+mYxN4E>A&QV)V~@9SNm$w=R+0XELsjU~FGasQDsYk- zCist?Nbk}WeM}Iu8{ueoRg0E9M)nQpX4wjlKFInybNrk z#I6CP|BOzw;Cvm1;yNtLO3w3yj2FG2j3=N+{}7Nt@u1vC)GN=!_IHI z^5hy`h>tW(K5Qy%40#%V33!3SJif&GXas%yf)rC$ApsD6dS}v_XN*&nL9xb_S$g$^ z5*u(X)90gX10xj3aYQnr>gfhG1Jg>{s|`f=$FXg}x}&u8E#IocJ^Jn*lWpbIpg+8(a5e-Mn% zWIk7x!+J{-DQSdODU8D`fbHhUv-gVFq!lfxyFHYEi)?<-^>)xC=5%1+RmIKPQz1SM z#}PEbhupjO2GT@#Ii!W{br2UuH;XbW7yh8-rYN2gWu|^jTcvqq{)&;^?{@0L#dtHM z3`rg)s{lT+SD5N6<^Ax|XL0-T7gqJTz7?mL#7ASHu8is5Mz$5OhMDI|q$Nk78d2O! z@8`5CEjJBMhc}NS6De!2T459XrFrcSvhUR)vLGWoU;nTW8AHlPCy({<5%90EG^>Bz zY-bG`=MX%gX zFHX((4}K9R>~W&Kx*x$lD=Qz?IH;_10U>K$!kT!v06uu+i`M@A#4=^QmM4|;cwI8O znJ%j{tCh6UFyc(mE0pY9D`(q&k@sOs%2pA^NW#AWzi*#s<|x6E0o4!~4jNU?A6GXk zmE$SbEj6h$$Uzlejr=cxbO0~#Q~8L=4eMYldUDnR@ilP?4NuhilRkN|Or?~DQ=5oK zFR8E70w`&Jcb+_9PZdAJ#9C~bMwm7Mm8AwZ7{v$1ka1Ik2sT4z~z|+0s{#z;=V2c}i;xWt1LG;n;=R*`2 zm(rCA;-6ZF&37_TX5pp(PgY_GWF;-f06;&R76#lwsWgzA{m;2_0C)d?m|xO!Xi=rc z>hL2)W=71(dr9WNdVodKxa>Tp`nxj#C?8&{ zg|sVZ1Y8Wc()uQZE9LRw(O5SFM1MB46GaMmR@e_9lM}NC61X{gKeN1`kbLHiK1Z)v zeawQHLdW6j0J z$Ho^I1q8*0uXkX!N#Pe|-w}oL0g{z``@p)`cET_cHd0=eZ>9v&)_Ern9>>zI5juY>avbt23TaPVZ-p0Z+*969oq+4jr7V6jB}t z^k#dWFYkTT7)%4y?EkF=KyF+*W2qX~@MVUMsJ+;r{%e#p{cdLf!)`7hhX3>aUz0#| z_`vXgtp)i1q4b8BuI~T;z7RiFzdrTzi_ePOF|0fn-X##B47`tf zAu4-#){W7cMBmeZn@<5*{e%UfAZa^N9VTVtWn9J=)f-9L^7x|}FXSHi!I_DTj*YF- zuj_-i26G8Y&~K2|=wvwUt{y(oJwc1xoV_{L*;|0da>hufL+PeW?nzTN_#0%HtF`A^ zcisKyyhL|@Eqe>@-t_Vv^KYY_EBy@#fXkEWhtu&+%$_Wekx4@ylW%A~(WN@@A?N6)SMr#}PVY-{fd34+4D2 zu9KEX)M?bPa0)XN1s+^l>Ef5eJYWPf&S`SI6Tj}q@8utV7y%w;wao+LiShkgH#Jcy zLL|{z>kjN3X0ktWb;1*dW$6to$Q6JyUpw+DYGQraO%7X&AzG97CaBgJ0I*vf-4#_@ zN*>t%3~S1Hj69Vxh^xgC#*SGzc?AeW22fkhHxhdA5yxnuS|hJ-_ovf;y?&0h+5Llk zy+KkMmR}y)uDgPSVZCn!Y4(r#?|_~bHjA)X_xsyLHAETi5^M3D@QdCl zn07a4RjZ_E&tE^ZLr%?IZ{~CG4DEqkMUGjMtbTZ)=BH3nuV-W7%8)AO5MNZGw#;4< zbj8UNUn1AU){6&k_#jIn#qI;|jGx@O0@!(CpZ*i85V!M2{ai3oi zFC5$0{@zlc0J(y*KuhbB;qG`&Jldc5dT|Xsi?Dzk;9&P=yHNkJCl3x3NJn!V!Vy!*x8ObZjIWA__l2o%*hkgExMIKAk`sVol(GUnVVolO&WVkYSfY zf5nv#KcNc9Sl~a@*f_`jmOBet(g7MvW7+XzMi(T-)ymT`j|mZ0J8#o50EEHP zbU!oHo;^XbAbYs!p;&;Jr3$|MQAWltnuRLCx0oj*z+I{szjrIF)EzB~NPN_yZ+ zAE?3UKv*0QH&rL3{WW8Ge%$rLp>~mNk>!zz6M=J3ZWaip6f*= zf+$1^vxAUdX`_?oOQ@I*zbD7Nx8|26_80WmFp`!m+<5y@{i3#Z+Ru3Mbcphh7{;aJ zJA)4Vn3-r+_b;bVlYQaV4U06lCZo>$!;}|VeZmH2Vn1}b%Br+ETza(DJtLsM`%Gj_ z=VP7!C?q!T6%5zu?jIbL9R|=SLdMn8d@hgcHc|@FaVhpG?t1lZDLbw8mG_?~GV>4I zJy*g7`!eC;%MhR9QF1-OSj*kw=jJ{p0oKYpHMJTn_Tn+j4LdI7H+x+i@(CHYN`}bXzPK1H#eP%HT#?FQnPXLXpli%!AKh1kDic z0H#d* z3aq%Xa}7@Z-U8Wp1EX&lDej(sFPq5YQ(_$<>Jtsbyw^~>B#yj-%k&1&?{*yQy4$|&TtR3WFwneK^tb7@f*kgB$;LB0GiA!SjsrO^hUsOx5**u@ zF166zbca;?(3>ZkW378Io`iL(xh~p(XRSK211BJT%XMw^zy-nL%atu`VC?bOq;xsd zE&xAS=C_2DuOEw8ji41L*XnTL@)iy8f}CIEW~l#Nv=0)+KgPyKKLxx3J?vMUT6;30 z!O9gUyVS%>y&!%Vr+iO7@5JrGeVtL6aGO}S;(##=l;@0BJTTJD&LxoI7C>}-|GY=f zNxSgNthYRe}pN@TbwX_-fu*GG8(ZDWuUc2(;=WXl(_f5GUCoC((Mi86HfN;r_=gt z1I0>G9K6Hlg!Zz5F1n5 z&O+6@yr`(L{)rwQ#0utea;f=IlRnpZ-1y#iITP_lsTT4 z$)T7g8lfY${!|a;%}(auB$LKS0#LFc+G3;ZQoO=n626>uS3MQ{l#H zaIhuW{qwLc&?da03N||$sPyJ|A^Z1Dt?V1wFsK6+HR;m=WMAy^aez{arp8Hq0kX_~ zt5I}!iudR|A1JuUrlRIqZR8}zNy>3lCrf16gDze+b4y0A!He?8W>B>e@qXph0qQzp zqn=irUGgIi_i!eqqb^U8BRzJ=pcqYxP5I=D@P+#XJ0HdF6T!yXVqHCRTO6FXb}kt8 z+5VWI*NwS-o`2)*>N%CQKD&B7x);igmxEA9W5Wt>4dxV-p>C*V@~J@Gxi?ga^D0L3;BhU>DXW6e2l;0gn9u5`Mdt;L;(;GO`6)IqYrgTB)S$B(TjCl0g+m!8N<@e1-<;U%LAO##ICwE*e~=ZLcy?{bk+9x5Z(f_*b6E zub)Shj=QNsja{mBkX&MxJ@gd(5pA>qKYl_klvKYhwkzW*W^}fB%^W?reiNDIWUv>Q z@oD!-=MB0|QHx%tmeei$`3TwJT)c#cdU^ZDZ^=E=?lUzgD`+R5eVF1EUiu*rNPX6p zAKr)GXQtTHwz&JzQhT;`Uf~$=$y*xts)IoPbz!SR0UavN?4L$GZ>59mH0Cr&ku!}?usVML7jn`^G4RWew zabf73mTyF9(Kys~4$sAvG|Ajd+?>j&<)ybDD4p}v(&d(@l3nE5;-R;#ZsH75Mt&+B zV~n_IO+{S=w2*LW*`hvqa*->*npNBh;(L~M5s&6tFJ_WEa<~EMu6>r5BO-n^J-L8;Nme_VuRa-b^S`&YTB;?K)W5p$CfBYPcME^wM9#e zXJi=C2sIS7K#<4Nx1`NUTh)T?4d}S;l&f_o$I$^TFg^&AZjh!SO^4q8V`FQpf*2`j zL#F-m$ilVX0}eWJoFbb+O0ms8^Uv2c#9F)WVFs|TuM*!+UOhvX(ZXCmi(%Q$eUPt~ zZsPwgAM!=A(+Tk(@-iPnnQ}IZu?qpu7d;!u=JeG};KzY{UbfNa+MK+KIT{8pEhJ1k z6P*vMCRDJ5Wz0b4IU3YQ;0fC1+J2v>x;p#(4^XVVX>S|X7tX}Gp=1MJZyxhOrj&T9 zd9M9~a&HCU$?>k3k(&vsa8J{hC76IfAm4kKl5yqpNdYXA;OwpFQ<78W_Fz^asjy1& zb;)-hx*F5&+6Pdk>YrggVJS|;>>AwS(IX`OdP0dr{~l_8H2z8pEALD4V+~P86MdM` zWGi+e&Ld_;1-l1v?VUk~$*V$T#RHX;G((N!M^3{6c>6v}F7o4j3^#O%J)Ywk<*6;J z_8ZA?JT1XhWeF=bUZp;Bx)NmUBp2_SS=!wm*8VlqQlnSW@O=Xmye+kHHJGcZL4ac2 z_$!-Nc1Lh6wdtSOZ+Pq89k$Z+IL^Mj%lFM+uQ)`D;Z3G2*OY&Z=-ah9FB+PS9$0sL z!YWDU=#=JlaX*&igbkCA?RB-%7IVo`yU3b-bdlAbx>jet<-54Ba|w(6{v?=xz$Bqr zCBvUX6cSSG3jsq>JaysIb7@Mnxc3e@S^~yArOg*Q>Nj7c|B_^nf(oy6ZmrIraMFMWl zJ(+iDDsmPsouUbp*T>qy_uFv-$mB0OA$vlS<3d`#wf_QiMJ28)LWGa0#bbA1P&Q*a zJL+kDsZnTwBv6ISW=<#mnW`yABc{;f?_cVTMBd*_E=@y77SDJ&8W~a((!faPU(OP^ zK$;UckVeO;lvw_9Dd~#n2g=i0fA(S)klnojparmQQSFb=9?=2kp#%&S@>5GWZ{jEO zsPp7iAgyhk(tu5r`ZDC!Ax@+BYi?DB?I^0n<;$m?%)1u4uDb1?hlkPz-mfunnG3?2 z#3%e_q$mwZZF}mjYB4$gKx(X1TTIQLs3+jH8EG@hd3(Nkz1IIz{Pm0js6=mE*xD|B zheTbH#g}+5@b*n^S@?4F1X54FEBPRkJX(jjiXDZa(3 zH3uAe%;Q`F$`nv>iU@DALuQH!l6IJZ8?%Z8Ti2sd+0ld7BBUAWp${g+6II(X?bW%> zUuF;<9K>1pJ{)4|#|PcFvR@k+?L|iYq)N7An5(F`IXj6tx=&pR&Qq^sQ6U%TN;>N_ zrgu6Kc&QpBkE7Uh$8ckh=h@-aiQJtL{?OK(%QtuB!^Dt!X=Gn##e3Wa z5L_8Aq%9C9xtW<}O@g}%3ej-mY2-=QKBu=MtfM3~#A} zC-x#`n)E8oGn$_$cSbF->XM`H&Ae&Wnh>Uo(ki{k$>T(27*3z$<~U0)K2mkTa$$S7 z+7Qc!(L$`Q@{1OuFB3R~M}PLw1~gLfy){cgun;>y1~y0xx0-HyCzT&fr%WF>3O=fr zG0eMHPJ0!@LOZbdhH2K)H(EbPoQ{`#H?k*zlQ*0WR5T@Ss%{}Lu`AcH{25eHZx*Y#lXGn zND6PZ&xAi#W6rxpy4d42VShCR3&`$g0Z2gDl^tS_F}5hJuF)#qZ-d%;I!N2LXuF=e z#M6CCkacJ*udJjbymRbLZp5N(&+qVD`g5OwrPrSTL2Mj0QC;M$fCCOEg7-s1 zZ1J<+I#_rj9Rka{B`N2*`(gBXAMNTYDJ&py6%kuliS&LjI4)*zQ$~=o0u-7yJ?G;O z6as89S>PY`-b;MOY`)Y`D=5?V+<_}oIZJ(crVsF>_ZPtP1NZ^Cu7|$*UwS^mq|uI+ zq!(l-%)3d%$2q!1M0S#B!vaeo=9OBJf~OmuTC0hISkiUO8 zAk*SJ$|v{O7InYPAoh52)?92sE5`_`vugYY=lD&@#Vfxwx!Un7FF?2sn;B-uOlamG z4>1>9FV~;dF`mlX_@T>3ne|fz8Dt2<)J0U(fVXg8_3!kHJO;2@5sci0i0}p@VDX?QSA;}@+6zkB9}|{$e##$ zoa+Ilz~eXif6l~WGrlegyv->+qrg~49U!w5I`B4VW^Qaompo#8-$Tzfkt;mWlSX=J zoR4=$9@YFyjNk=oWp3#>$Su8^-<#b^Vm9{`s(e4a2!B_6Q-J zLL@?>H-@OqR;4!Gd9+$6ZSuAxb9_fHqdBeCW-X6s8nkaxvX)0JUed_T&z|Q4Dzxeqsy07da&EkV0BHvjj_EEdaQhC)xQh8xjc3_oRU)CXP>7VZX z#f&Wd`%`wmyp4VHx<2lX)^Iv|g*F7eoHh9%)AM=6f~oKc&(I_1{lKvmmawy>okS-Emf63g0)q!D^0g zIr~6=Ff5U6+c4|+WHzjmlE`$=PU;ca1buL>tUaBxU@Soa&-38l)w0e%Qj(wOJ<}RO zH=l>f?pn3+28aW%av`>;wkNUM^cZJmU0KVm^~i1vwu0;j;%M#8LbTbb^26IlW_7$G zoEwiM&}=th>}r4Xmh_olJf(+psr-aFeO6?4baWicCwp&IpVIkAe5gUP_({YkMATiO z=hS10YXKrP+?u3vAfYlN?N-eqwnX~V*uRgDgB;32`s&7TVcX!%*q69R0^6ocx|h8Q z=N}#fJB<41R}#y97XfBRx%GsoB&AiVLu>4mn}UH}Xk3ne#^_O9x1nuqi^KDInV636 zqmyBgML5u$zG|)=N2)o?^U4(WulYn*s90=jk67V-_(aNFl+O2;ufy~`C%$lv;L-=@ zR=fFU&OZ1Rbyi{y)774P_fAhm#bjn?8YU}$2)#;^VG#Pq(8SpMZqVRcBgwyNrhZG} zi#vzyp@bFcdr^J2>Pw#UWNTbX>Tz{F6lY4E5y5|VWDSt?qA=qOk!W4$C1(CvIvb`$ zdZIsgq@`c&(Bl4byq14lhf?C(w{K-VT$UNUt0h@9U}*?uc}A+%*(@S#l0ZKe{Va=SHL?`;-J>N$bg>RKqC{Z^CXK&U`K zp7P@;9|cfXSag5pf2{?eYi@cpv7;@%QwJ!Oc-^`PiQ$vVmHCIy9~*yo{dKtb$f)6; zq?xvd(~GBRMp^opKIfSHA$^;_Z?v@x+GBCk+#w&HdzRx&B*e#8oWG5!-u0>q&h;Av zrf*gdfQys&M0>N3Dcn#PB5{7bN+vG+_^BG$INxx~v(Q12+>{YAoA|P@HSX(=44iXE zVj^zAuc5->$K7M+4AX`Ugb`uxtKUU>brLS5ccP6{B*ni@n_&=KM_06!<}}vXHIVP5 zfzh9T1gELlkxZi=-U`So20sRo^f#&=2NAxWTP-TtHfsDvJRFLalhu_LAP% zOirPbMCX7(XH^uHTm6A;l97ZBwW=RXXQ&L6)UIrDuqChW<*G10rqCw>wg=uA_3Uici@Rs+5D zlUbLsafM{(-(g3LfXx~?6&OCLRuTWwj--DhN9gzK2v+B15%W>sBF;$nl66uzdjEZ9 zrfJ-@eXDJKPeD@gH7du#3ma8@e%*&CFokr|eW%cAaRw}j>BV|VreT5AT^sd}cLX0< zu0U|#cn2(&pW;9rnnOZX9&P9e!r-kuwCo`}y7_c|=-W4eVL){a3v_=4O#cmADDY;| zkcV^;9OVa-{JCV5DL`C?6r{THz3Sg6=-yrU@S%Y53Zg5#&j@IRqtAzDj$?P<=hlV{ z`pi9-30tC;c(SD}(6sOA^OvG4!W{ok{sWWYaGbk2J75wpsVdCD=F5*aqyWogBw=_?{>~_HoE?Lx0$L%lx+33*RY54 zg_^;iaE3{ilNt%tD#YCP!1yGct&Du(waXc9g*&g|)AK_WFh|FI@ei&i#}&v{GFd8tw`7L{#KDX&D`Aj#co_hvEm?7ZWMILcBU=l*s$Y` z30Jjh8}hyur3kC(?4Z5}%SShJhPgzI+XB&<4dIIVBS(LL*dXpw`1Z$( zw*0@-Xp@2iwgR^A{`>n$7PrSfp&Xb)7JVPIv z4a=(`^BSz;gp;2T2j4ie9u!Dxp0CGsww2{xYcS6hNDAC1b#M|8l|wrt4E{HRdL(J9 z`a!uR_tjxfaX_(@s38WYPfd#I=d%{pTQ-@%MbBS z-lxZ;q-J8`F{aKKKS}0A9}h?qY7{oWRp zkeTw)s~jSa7WIsP#DLQs&Cq>pNahT53chnq>3*gu^5*g#in{Tg8xmjDX5r4bHX7uX z9>J=?2klgUa^?^1kaY_F{=r`)KScDJNp9XipU?8SwZ+W7U5L-r>q)zE)$hIJ7)6=r z#1vrMAK#+p-8pbt`?~M)H#|=5c+u39~@GC?I8zZ8sF7lyrw;hrTkfgsa zNetSz%=T~o3&oqRAKf-$i5t7j^2H=#A;5g9mCLY}GuZGDaCu6X%9U;ywP!ep@gi&7103je+8fPDEg)S%+aXh zM_)%&Fv&+F{FyoEjl@!OtUh=D&5~|AhqqJ`j!#;=J^9e4jLG|RS7pB*^i;TKsQ-+f zB~DT@eLI=%9-{NJU)M97MhKVKzu!nN=iU1Y(STu+Xh#3;w|9icY!F(r zT|%BZt(Q}D`$e{c^HW8R6IK|bhdObewU)I<`o$GJ=0i*iT`JT0ePgB_kEJkk_ouWz zzA58Za`W!}=brqqYF`3-d^2T*!y9A3jO+z_hOU2jH6a{+a*yb?xV%|?#6?*Nui2Bt zQ=*tMQDP{JHO(*Dya37S5LR&{w@}oMlxQm>NKD8PfYCM5>P|N2%4nn-#f&#T-xY;_ zp1RF9?1b(fx?86JY)y-(z2_OJcKw(MUBf)IZb0~KbX^}Y0(v5iRthq+3b7)2F)uE; z3OdIlKnoonXYdYbxJYqUjquln>#&hNiRTB1J=BRSbCcuZvuS?6kI`EDWjReRQ_a?s zc0>RNKrI9(Wh!(;;3AzDVTAWVv(O*VGS+&LGLYshdG7cd~{u$L_CV zmK}L_;+2(#MMs$4{cZ^Da(`$~1H;xDeZ{R%NB(23STnqxEWhvhvfHZpCDr$_wC3hu zQ0kMTgkZJoXEL+&ttjR<#o?pXt^>H?_K;16bi)YX&&kMa0**bSn1@y3X7e`!Dd7|Q z@^-q;x0y8IEO>ZchL5A**%ea1+QMAoaCoJ^f-7W)>8VMvOvrUR>PZfwBmV%G!{O) za{qS5qg?RWL#nG%{%eQD_X-;$bptjrXN+^UoI3$bYtQOI=gWOQtMQyHjQ|eBmzqaA zF7q|Iq%R(Z`6={!?Mi@c6@0ng5AIa7d!ziR`p=yo!1B0nYZi8Qty0BEki(dHDcl~^ z&4a8q#knsUc|T0j@u}6vX{PC}yq6r?+5VS3x9Lu#mtBiHhy?&WeyT z9p93-e%}oxlWTyfB0W0rly2sx&P8^JmMQ6@&_m>x651|GpTx6f`2u`;r8FI}nI{5} zm!pM}t9N}BjGpfrXN(4fS6jsHD)g^aUCXbt*N==#m6#TRdu51?#k8|t=&qMj)`YsJ z@k??D_bK?<)MdR$(cPoau^uD#?oCnu&0Pg? zO{*Z6jG$_gyH+5+x4=imcb|HVCdOgtd)>fWFTHfbf6<6-Bfgaowd(&o_kn|vsKAbC zq{sNX1A64=2bdH&{W7(z^uC==Zg}Sj$h((IKH^CmfAWDOTMsMs$L`hV|1FffIi zOyh>oMti${q%niEdN;z@DV1tGNX zPxECDci8;a{u0KMiNtoavi_4eyjQwlQ|H&~CE^zkaT$td6e~*kT3!)b`_mX^1ee+` z`6zf(`Ta)@LPWwM>b0?z);+Sk#xGzb;E~Ld}=%KMWP-DZ?M}PF?R$6pu=e`Kg*CkzL1~dHY3T4*(h13_aEGlOR5MWc4GbJj*}4v&O=s z(W1M(j+)J2RX;>4QT!I^@4wQfQu2#0G}3%1GLaG4$^*IWv2z_yl>(4gUIOLMy5*@1 z)4E$eu9UHqit1XMQs;VJCfq;@p-;lciM++N@*WM0hrN^-{!&3S((rxdKH#y~jBTUT z{Zc_=S|<~M`1D`l5xF!05wGCKXZoe|?W#ZJQe86K_HZw}e%KZfY*je3B2H~BfhDl;l6N(Bgr$Cc_L>>+$;VXw zjgt4o@s)wcw&nJ>_6={=M^GC)7PmiMX=&jd%*U=Dce0@tc#Hn)KC*F7#^OP}l?|sS zC6xAKJml}Ut4w{Bfep3E^aKl{k3uy%aauAw*zY5`Z;+;nnd;ywxw1>Dbua9(1w{z? z0NCROazH?2Sd!(>*6TyNl+%l$_QpJsb#$-qlz`93bid9+A`&#V#8XkQ#+<{?o@bibrpUaZEe*Rbc&lY`UZJD-eqH&$XPB2>lgp?} z6n#|6!wizY)rl z!|AecQCu&{6r8xa4#t{Gi@m`aSMuAh&;yShN>rWjzsvRqSnsWYO@KZRq=Z ze01%qy&vJm%z4{Y1aL#0c2;h%3?;wAfn%i#80iOp*q5uoQFCG5AqwhFU=3;!0#--l ziX-mfaMvuQAiW#JyZ={3wMZwtmt|3n{^)+yOYEG*^RvGep$m@*=*>r#?v;5kQSJQAMvJ)ct3EU?T!~jlzth$3sP9fX5R~Uf6vsT{pybW$Vo)UCS zanLq?uVD8s?tW|jfuXeeH}koq8@S_cb`?Ky?Dx$a-FlgbpH>MvOFsgZCvCg>2C$`S zA?ERkpUn|?nF^fpoZesW4csp?csm&>hxd}Nk2%$eNLQ|wjWt6mIMazcag{w0{E@*a z)m3VLXFFW0>gpDh;cLc#_e|sOC2k%bM*zi$l~Z4TryrpkaF&+v^BKX_y%a}P`H%Br z^naXRY{~OPIxBGcwjzx2w*EZlDSNS>uMXIBflu3y#k+)TgWIi>DQ{j##Z(GE8<$PH z!@#TR6;ro)zqPx&tM^8Nw325_e9n86Nb70%K~Dnf%UQ3I#^Ey?iXR~1M^imXq#0^0 z);XRSbX)^@E>KxO-o`}|o0W6_(UvZ+q?e#BL#q@3_{ILX@wB!dz_dnygYm9{sQ+}Q9T)qqlC=f^zAuqxiYsDK86HKK39DN%pm}xmc0Nn zDMXc6Y_P70<;~dmKpWP$V$qR#dzcE(q}m_9S~7#4RnkeHH}iYn6B05VqJk1C zG`Pj+9s9?_;jt^)*jwdvBYS2~O|6j@s_id_t@-#w#bGDZy0Mod4mAA$@YX$1sdi{V zH4*)_h@^r)(56K3Uk37}sys~9#2P@3%&FCLB^Tl?Bv04mm|Q7f0?+ernPr=20v?(| zUVowb;z#83DjUKs-GRb*^GTN?!2A)TB{f{~dWBlZ)-sMv;br5_`Rh*5hn|L3JJ zaQQS9-Z_p(K)ABi3yKVhoQ#az~beDMqM zqzVVBM`vKKegnQx^}7LMmaA|XK-mC7=vT@1_p(kD%Q%9jUBpR)gFaH+J>A5|q_VpD*L38|dnuR5n2`az5`TlZk5ozRGk^thN=Qdgq0gpCBa! z)X09b*DL1qusP!k@BN}s(;Uqm!jc!3cLz%43It7Pk>rx%O%G|`fk9RMm53#&FJRBl7V0@-cl-ehv0kTX{13&ZbP-RGO)|GTn#H!N1T2Ggy zNIA%)UcvG~qSOWn_IL5-ccS>ez!X0Qc7|q?<>H_P(JtS}x$n6NFIH=ZvlcUP}sSB*Dz|X)%~IAR-n>SPT!1?*tnE^xypjOWuspnUKx% zG(JK05>z_yz(2R}padQMEVLX!kpeM15}yp8pMbxoSRYsyECATn{ZypsRwJYk;*k;C zItvo-DZ3^yHZ%(b{tc7@DFS0oUYCV?Xw}L#K(7M2X7rs7uUtt-|IH`^B^KdYzTDfW zL;E{FD4%$WARL?ma_OoRym@*AC&{}%0nwrTNi71IURc3dYrHae^jBJ^tRSR)cbc3L zUq7rJw3T9(cC&eNdm1o$n5V{n0CYt62c9xmi%=p7NZ&HTa=)!uks1!W>q?(E(4SL% zQ#HK!J9HyF%WsBfa=u*mJsSPc_LcDydn-3l5^aeRRW4%%#&C^jmVxk3=d6`Qksf*c zx1+Wt{S>Ok#eG5IG7Au;-l0h#D54pxk_|15$$~Vo?+X33p~ZS)>9Ix@$iKb-Oj~LC zY=^X1!%dW~8gHD0E@brLRD|e7WoJ4n&dLlp8Rzo%(SXIo0iR!$Un;mL?Jy})_PQz( zT&a-b2y}5&(K*AX*Ne?ZJa2^q9wCAfX>B?t$eIrtaCiwok#~!4kF!^97GdhLM=p4I z$&5!7srrfdsV;qugqCXPF+kXS49`5nFpmw>_t#Ju82Z1-5YodnuK@P)JPJ$QSSH^!(>K!@x-kfS_f`hw@{Y1iA9kW(C9@RQ1W^hejUOih5y z9(^L%k7E9IC2ZSSiYOSShgWQ|&b{YC`=)pc$ZWSZ#Ihw)Te4Ao*-ji62Q}E~J*hPM z>`^!a7LG47-U4Ono zMWe0mE=7f{r77RG^_-5zp+#QA5DsUF0vZmBApO6PuG`nEZNZoa&<$677%>m}tM@3> z5UK*Um0I<-1NW);urR=Oe|zK?y`GA;xn4hW;5=;HN`&SrCE4@7TF2d5ykw$ ztYY|A_%i7gd4_0z2m|`pd<1Kt!KPSd$3mUz?rBbnMeg(vhu8E24cyr?^1xIUC;$;nA2g`q9;w?+DT@LJuB6XF9R9N*VM~6K_sxJ<*_wK^GaGkD9uw<(03}j%&p1 zpPdq%(ykHkI_gLMDt~daSF<2Hth{%QGIq+OG8D8Ofgp$M@u`dk{q*A@iV78HxZDT! zH944q3GtI>vJ2dePmKm(xM4Qn$+hCgJ8wh!ghGPU4J)A%kjO|eL+aN(!gQohs|HU9 z?Y%a3yH>=DW}Ix+8BO zDN;P}SUum;JX21EOE#6iY@6H8EC4?Nh=QLZmYwgzN}k5n^;tH2grI&63|B6VGH9m@ z5eVOe;>)ZYB)a76LU=3$;U3)fl(9J=y9o1?t`;ZuD$Lv;xUa;jdCao9ZPM52Zq>4E zAo@*q(iF2{Q?i z*$1OEYnGlYAn2W%VToKQnD!;u%I-fDLCdX{KEDautD^29JsOAaw!W3ziRg5Eq4^AHDcYay%3j&JvK?AYWCee0o9$Y>4T# zpBKT7=6p@0#oCOfUlZPJZ2r%RO+j8dCS%wR(~g4+pd#|bHc@8mUPP8Q|Nbv7K+mRM zFuPaJvd1lQ5?+o`CoCz}uzcosl|{dcX&!nT60VT?3Vna!fiZ>d(uI!(-Vtvj5!H{tjb=Z`^jM)~HHvODzylx8o<8uZ^iK=KH`%G`|JCinuB?0-wFpf%9;rHu%kE zP}>!OM!PEoH48Y-=C`JP`$#Bou)DH*hB!pxiRWPti7NPt<=;+9W3OMp!*n>4B6R~E z{mtmwrH)|Shub4Jb_-XoXVK%8fBgnn*iAXli4I|MXLyu;>kLB6KG>=d8|#d|YYsoA z%%{d2++>2RM{YV)F_&yMI2TKXp8Tv0;*DG3V18*%QV~FZtW}WhfNHi5y|xHc+6|UK zu5&ZQWez7K1=@{rDL4X<1jshZCh0aKUU-C!C%HD^*y^`NFw>LkgQ~yj_`vDVlYP(> zXs@LDxb8xue8Y#FAtTdHzeSrA=_5S(S$awp@ooer{-8`@!UIWGCIw}?k z8WO4WcGj@8+u(U;V5}a4n@G@CVKtc?Ea`x>G(uVysQnIGeLntZbr!hY=ZwcM>*&Sm zEVDoUE0g7bWGt#Z$C(gkz?JO!kcfXC$iF5uQ!0B>8$3-pDFO-@5K|T>s4bf8cgodD zEjDtBTI=^W)=M9QjWIPS>{`n&jo*Rl8c;0h_@8Yf^=S{z7Q{ro`#s|>lJKTy(|$J1 zob2kUHPtn6)dpK0yPeuM_{;&4FqG=(2qQGCxb&!!vG)nE)!|n9lTVGlH zAb#&ra%OO&0zK%-!#>AgH1`YU_R0#G2Kj6JLU4+#*h9-y$2gwP7rq_lS_@mULi=B5 zGcXxsOORCBN@hYYKKU9EJS7`5LW!T?f>XaPdN6CDgG0xX?KVES_C}z&{z$Yb)eMf!Z@&_IxadOm_$s!or7Gv$J zvgLE{ZY5`ULzWJPtmLwaZ0zKzzVBv?gwGK9-6n`rW~%EMJ-6nG`7qsoJJLNPCnglR8GQBs2yP9Gb^4>pul5%lVAb zoPI_0zwc%qaFuIh`sX_E-g_!^rOX^=4=|+9K;BzV7#b&XKf+HYZu8mBpv1#myGjw} zTH=2s7iJf=IHsvJZ8I3VAnK8JQSAGh2#zZW)4tc>NV4m#&$?e|(?|EDRv_*fzfaGC zHf2mZ3g|24sCOhluED;uiiOf{{AX{qi7Iwf9~{dSTM2f+vYl70v2W&TlyXuPci==W zlBRE%ZeK@&Biy!|Q&wb3Wlk&=S|u0cVx%>Kb?-#S-IloLM~f(?Z8&PnRzn7F*?F=2 zBkzRTcSJK;l*>@>S+^{#oc(C!6TvG2jRkEQwDM(i`6@UVrL1>UZidhN%@$bv7_s5ijX0FV~Pxzaa~kZ)$5z?GB1#^ zGp6Ouwtw%?+_I3(vdngn=AqhELX2GBd#I6S$hLcNJa-Hp_Sn5^Wy!$Mn?S-#^U3ij zOzi58H{J)vN}C|vg@?%a>*(zHse9gE8eX;fbt0{Y7#3^GQc0E~vm&Sb^mh)m6zPY& zn0>yj5-4Sm4@|aJ;IbGQ4oja(#$Ls$b=A`?nJREzK#-s#+>V=6ibH{)e=XcTw2=95 z%Zs2s(V^$$Jg#yx7LoNJTe^wu5E7L2l&pza#!?Xt`e>Tic)XANf{2G30b>Y0WB5$) zX}9U?7F;snlSf0k@!O6aH}F~A^|QXio>B*uM*R- zd*)8t$E=q#Mxj?)*MV0lk_(cyQVqgUK{l2}Y-bEQFOCFr^AF{G`yL^7G=aLzbv&8* zZX0~r(YhIBFtLYTmt$%=e(2&x#5A(+ z>&P*iuIZu|tgvT{P*5=8{Ri`kRl+@0fm+&JjjPZ4>+Vwp*-E~iye{*>5&G{RSWTb0 zf0!JPx$Ko9KADPCbMzq;CB3*+vFI&-xr=xD)VAFbnRm&FnJjS-c;ocJl={>Y$#SXf zWOqhuKjS{F*rWJ{#%C}jIB`?7U(OY^{gM4IO(G94L_6?nfTH3%9+9j9OEB0Rxm zfPGqF8!%9NcQVZ>rpL0{ALo%hat(2SlX6YNZ|(?=@J%~zjJ(+X<3-eDWR$LvZOL1I{EBiQUzl=W{Oe&0cTxIG?(%ok zU_naO?){N`Sh!?_EGt2dv7^3=G z`QAq>ZL^Om!9Pz>dwC@*}d)wjZRrDf<`6*E9Nlz1i^~ zM{D%#hS;#8OQ`Q5A9db8ppUlW=jzAp%4Z)pFt0i9`F|D)9<-nTrjArRpvbQBap%?A z6YS1kz6_cQm;GpflzRd55~X_$P3+cT4sTadmWpjkGVQwz4l;5(N0Uzj!RN;{T}M{g zOTIK`;BZ`R3UlEQcL`_WrUg_ZCvxwoNV4I<7Y2q>X*VySnjFtDvOYOes7JaY?!$qf zo5hAlM+Wvis%up!&M^E3eS}4vHQ3p#6+=~0T!Y|;?K@o+I&Srl!(U$v z?q*6V%ar|`dQ}_S-;hEXStJv1&xo+YNBWta=O6o?lMnott(BT;z23w`EqnC-;E_Lz z=5O6ep@g4ii`tHuQQ$J66(!%Q7{{5&y&n<;KCnt>x#sow?BHd02O(U1yWuQ$M>oA` zfdP>yvnWZG+(U+gyKIE&#MMSO4K(W(GSqC-a-s1?TFgzjt1WN zCOp=W-iWRdlBq-u&UDe8K8u+XGQdfbclXfa$@`>Yff{##i~TJfqt7(33EQs&Xrlk? zG7-)K|8Td4GpDIgw&I*cS9{Bh7c>3e?7(pBCg8s%4sL&vqKB^yJrtLl4w;cgrvAGMyw}XVe?W-Wh>D=}r zQdVJ{yf-e$xKB}x&VG}PUUI>X#^BwI z|JcS>{XHzSq{J9f12@+pf77Un6GAS>WW(kfBEXrQd+<&X%IaqyJejBW#ZVnB;*6OGWCVv1tND72?cGHE9M0?n#Rv{^Fd#&F^LP*fT)_VV40kXvD_ zOl3GaCxPC%eh^RNY`-BI-BLM(6hLb~E5pGfKLl|%O6i>$Qce)Ov#*Zka z6T}scmt=+mp3|Se=olxDd5vjmSr`0L*v4v5Q|sX#{DqRuBjr11T4a;wpT_t^K1e+K zSlHU~(VJpY0_+n7^OJ{2gc+RIl+)VXOpMmEJ}`hsr$(_4AW=IK*rK^vT$<#AwLi*P zDx|UnI2k1QZ^oFcTlNpuI6>OfP|g9t!1Cdocj&vZthp!!OU*!h7(qtdg`>rHU8*0R z_o!~=Gc%?7vu}Y!KH-8sEg0e1-tIe;m1^IDgMjR~Xtf|>#1|?Zrs?1YRODPuQj6nX zmacD%c@EEruD&rrnFz1N=^Bh6i0!{|Z0AV;fKlM0LyPHFTvL^_Gp3|^`T=i>I;D<5 z6E)W3xDl5!BkW^b zws6R{Cg!zQZg|>aqE&>&kJmC2qWq)F$9|MUp3_3{iBabRpmW|Z%Bc8EpG3`fPin?o zXo;R*g@N036Mq2MayMYVaPSaTT#!qe@_-tZFSb%e;VZ;WG7Ez3>AYyOy$zADgM)Oh zNQKqcJP2r$5G2J8#IGR6ejL5dQcc&em6PrIXmg4fai%eR&P@>U1Jq!PLasQIKe%3x zT#xdIq6z9S`gT3@^SosKp~nHw7?sQLsCWkH!X>3y%A_ zFH+(~;o2zn_4AHOZv+1q1p)q_m^1va*EY>HGhh|D1|ANGbznzbnqeII9<#1$eeeS! z>G`5_hD83TDKVA{k=cgVXC$Q6Mnq7fud$t(*?wF9G_d!vA>ck6phfDb43p7X*;PqO zB=I?;1Dndg_Q$zn(KRk%0j7$@DCo>>2}+`B^F7g50Wv~ca?~3_B{&-71r9OHGeqCB z#*3e+?yXqpnnt(KT9fs~b2^pEeu@=N&9O$W6EM|a(`7n$JX2zmmxkWh-Ej-H@BM+m zlxX?kZg}N4Av4`zXOzu=wEKdez@G>PL56YaOuB}bg)e~bsem*s#vm!umLk63N+})< z>e91AIGO_{Q}VuR{}EE|iX`T2SnO!(y#;K%wRK?pqO|)+JZp3eyxhIY0IBVMINsnpYAwH8d8{!&@-2?$uk|El6EtIq3Bcke@M z=DblBZF;QR;5iNMbEVNW-j*w$cXQGg!76L4-Xp$<+w*2}29L6OkL+yvc}kydDdhQ& z1>#)fiSPIjUHEnN;aA|*&Snk=AGF<0%8;;^=7};0nR@%>2K~Q@epa`lR)K@GOtM*x zjok`~?YZn92fm>=ChV9d%?E{p_~kfExCr!b>ysp_@Dq>PdYsn?RfL1mwniC`$sVO*vo6VoHrCk)uP{Q)2dp`dO}Avn3$~D#m-N zmvQF`ehff3=vJdb1N4)1V=y?#j7_w^8U}@b{n5e)Fz!D6R57`RgF*+9lO0UgT*_qPj z(QB^pgRa1$LAtoQB_S6GmTvfbM{K8$CeDlxds`!o=`+5Of=7DSs@ZT6wu6(TX;z;4 zupDo&FTKl~x8I$C>T@>-+Utj30AlI*7IwP?$4}t%8C)DlN^WKlgYRVLRu4xm%Vm$5 z++R(}@L3W|^+Z)5w_9sh8pY^)B|;CHPiN7?rC!Tus>+qK$qekW`$6M11ghIMPSF!r zL3gPCGBKaJa|?qaQ3g7ufD9Q2MCv@cp5USdVET4)x#Wu zAH;e{-o(mMxg9zfO_RpW?EEw7Ug2y^QR}wPV!Hs4**x8w{UX(grdR?`K zU;o@*u57hf?RAzm^TTqneeythyW<#s!p&30-vUs~bbk%0Rc@m2-TS0DyO|e-j^3Cw zsh#p14i!C=L8x{-hh<@9pGn59Xd>?KSon=4%gw2`syq=0df4+2`+!%>PEBJ=B|8{& zP~*X>C{jx^{r4F(jwN;FM8_ik_uJvg()%A$qaC_=;M42RhQs)NhFEvY6+NQxeI1w> zY`9-gVngXy(eJ(b^6Gd3h(6E*(e4V;F?4zCIugn!B%@9fDZM~zX!|Zz6X`MV9$xSZ zvB2h45G8#e8Bi3RK9h2C5-|g>SY@`5|7E9Sf9Z_8zr(WjCtIeugLRJ8XyTtNmev9` zTW%OtU1HJi%3C(BYmuxV?ue|_t#4kV946j-wecQr_4p6aw$OiVyQUrdlpYERE#cYW zib>a0wm$U8Zeb+-dBu35_cnFRPX;Mj>Tnl#MZES(DE2I%A?)MrTxH*28d|TXW~l>< z`1RMYig5{$GN@OI^E?2dD(jffTb6MU%eVyiY>xxC{oc zZ(9Jd_5WG&Q!@Jfohnvj%f%IP8g4Oa1ntw9x|f^(v7(O$1iIo+gQ2?bIt-4Cx{qz2 z>^v-X?-I|$yT+@fcIy?!@|J5)Dr1{4_$9bEQh8yjP;ropjY>1|Fu8W^R~$M1jKY%F zy>afNsnYCp3F-o;A1-!XITE^r^}||nd5Q5YbBi=8Pvzemvm8{t1?UJUAcZ1KgreZ3 zH$ySQ4mA;czMwk~6a^7Mx-A`0VTrK9z}IQT*EF@CRrXEJ0r9w|G|f&98&xb>x&L9I zMc{fzXTc}&dv0VW51tdn4guf)(&k#1z8M{z!)%B+^a?gPN>4|A!Cl!$0I1jR?Vk7# z?;*)C&LuX}s3O1Ftc@mj!Q)W2K2FjN2&3ZVeWoN`rSBly<^OUR-Zq; z!uVl|MLPo2fb&{i9?-&7@Sl;6&BB+y^Yu4Oz9?y+ET*=X+^FI53u|#t_Z%WS!KO7^&k--tCCG6BomZC( zJ62s+1ZhlBENUqtx1hqz>BCvgCg%=wuvE0+=bG$!CLscCP(4MY5;3nwkRxNtxX<0^ zEN0iSf|NzAMqC6TX(yR0TYxzGCkeYYiSe$=AP1OkL!+J3Qx(x!(3&awKn4W1EXN|JpZ} z!0EuyB1+U(6Hi>R_oe*>Kx_^IX8lIa^H8~wW}bgYeV=NBkh3fJeKh2!6JpbL3+DOB zo8WQfHSFZ6*)57nCb|?$jr?eAi9SB%$C9RItYhXp7FT&SG9u4L@Cu`8UGoENmv_=pyD z!I|9T?Ka~F@NhWhf!J%oP!u9_?}LVv4;0Gq2JDLG3Kpa2kY(eXeNqXq)qeN#syfZQ zE8gQI9g2*UEx5+snn)RYp;j4!zx}I zAamzt06tRJTW0Lcjw87}>8a;rLC{UA$`2Kpf?kG<@(2ClirX37lne;pDX7JLL0)A8 z`cHefB#v44H?NbA(-B!o`^oQL#TXt~^L{?<1s5?NBAjfTxGHM7y48D&`m3X=aY#%w zzncdWX@~6v_|X5U%xYHKCag;mK1;nKHVg1({tTD9Tksd`BQZ665lQ!a6pAt_iA29O zs7E9~lph}-SK}+p~K)fdl-4;EVrL@He#%nVMGo=B|q{f<$64?Dt_v<2B;)Hn3sUs$PEN}ma}Muxd3 zHkvfCBEqgOz+Ja$q}ta7N@ktP8$0|KJz#N?oVk`^i=6a6jj!BoKXe^xNiV$18hCDD z8CeL2!zh`eq#C4CfBQ753fXeoKeFOfA?sI7jP&3cC5##)ZIZBE{4O-CTE-w#sPKq= zYymbCTh;^YYNh3f4LE8$w2I8-o9pX>LJ&M6Rxg!YxJlty_4Sf^Xv z&OG+mlXrjc;K@DL+nI!N0lfrx z!FZ1ll6#!6?qnyCHEudWFFy|nLvXL;g4s7qYXDkiHwn%#=1BB7?whM!K$!W6*Wm4b zoksjMVI5ZIDMhVc(tDrwNe9#$f#d~BuU}#AAFwdr~7I>u%T6z=By_l zy`I3W2R^L7_vwmx^QB3|J*~+n=NZ z;l1)Ib|l~Xr9BOti+(rrj#w!B2GUNl&c2ap(L?T=DHTmxSBLu?qdLkr7`Gg>kRfSB zQbYe;GImd?x3jw{(=KkZcNSyK_-b;wp6&QwSccqrI{mh$A{qx%5&TG(I(7@9D2%}rv(-K`Hvl$R7!I?mS<5Y%u8eXk0P~-QAG%+>_ zLYfK5YKW9>57F6t3Ah(jJ*UG)J405$a2&EfqOE=!d;{Fa{$RYm~ETB>z71a3gizuoNj5E~kpkK0ab<1Z**LvakWd`V4*3JCzYR9c#% z@<5LO`V^t4&b*@v{t?@*kae@FAEjy3m^{Z!QN;T~_RKlVYDq_c-+AlyHV@lZO=^JE zeRDR8*z^3~xQqJVxV%qFewuo2k(}z41;9B<-$z;;Y2Uh|9ifHM=Ahh-H!aI-llXX1 z8HSN@)py*Q0&&IRfYb&wzZGyOs5x=p0CNHV4=1kYWr4V7ziHl8C7vo{w}=e#Xjh4A z#XoDg*@gV!6rH&l0RKWv3&pjU?!X&_7{5Y}$nHqnb~g5KvdefU0=1p(o60eg^p+0; zaJzvHn(!;8md1I~bAny*`xyXwR*Gea?k@q94XO;)i1F<&Z#2KLJAs(T55wCMT83N@ z&-Vtm^Hcz&5Gaavq>2(@+UjT6e<}H)|AVb72$<& ziH%sf#R4DP&9V?6mA;L718xLAvuPIk4`s`5ak)ifooWoX~w|8M>ys{Z1Jv@XAXS2xfDBtm< zDK`6xd(J@!_>@3{e6tU!v}MgfQEAQ7uUZFUDjd~%@pc?OOa^0qW&irA{T|YQ>Omle z(&%x|t-1eOrsh4a?8UF&>M0l-zSR?m(TY`00@DTiM~qNT)}ficEPIeg99N^Exc`D* zWhAmZ!gA{xB%my?gzAPXm98jPY6l`pK1QoTBC*G+;^E)uWaqW(3fEvkQYey6zUikw zX}z#NzGyAcJKEN*=jaJ&F;wlJm`3ju>bJDpiB84w5~!LOM* zl2-C~Tictr&txNaEKt}rg2mftq;dCQsneB!L`0Q$-b`OM6a>=m&-JkVRAZFr`?|^Q z!@1!#5|=f>U&=`S{;1Sae0Qy~WLriH0s<9wYKR%u5`zAB`QIb(zenJIkHG&AMj$R3 Yl%*hZv9fpJe$$&*aw@Wg(#HP(2e1idoc/manual/lvs_symm_nodes.png doc/manual/lvs_connect.xml doc/manual/inv.png + doc/manual/inv_explicit.png doc/manual/inv_no_transistors.png doc/manual/inv_transistors.png doc/manual/inv_with_diodes.png From 6df333a05efb1ea4b09643ffb9affa04957013bd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Mar 2021 22:33:24 +0200 Subject: [PATCH 12/16] Avoid turning DeepRegions into flat ones as this spoils hierarchical processing. --- src/db/db/db.pro | 2 + src/db/db/dbDeepRegion.cc | 80 ++++++++++++++++++++++++--- src/db/db/dbDeepRegion.h | 13 ++++- src/db/db/dbFlatRegion.cc | 66 ++--------------------- src/db/db/dbFlatRegion.h | 62 ++++++++------------- src/db/db/dbMutableRegion.cc | 90 +++++++++++++++++++++++++++++++ src/db/db/dbMutableRegion.h | 101 +++++++++++++++++++++++++++++++++++ src/db/db/dbRegion.cc | 38 ++++++++----- src/db/db/dbRegion.h | 10 ++-- 9 files changed, 332 insertions(+), 130 deletions(-) create mode 100644 src/db/db/dbMutableRegion.cc create mode 100644 src/db/db/dbMutableRegion.h diff --git a/src/db/db/db.pro b/src/db/db/db.pro index bd6371526..02e4e20ad 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -50,6 +50,7 @@ SOURCES = \ dbManager.cc \ dbMatrix.cc \ dbMemStatistics.cc \ + dbMutableRegion.cc \ dbObject.cc \ dbPath.cc \ dbPCellDeclaration.cc \ @@ -258,6 +259,7 @@ HEADERS = \ dbMatrix.h \ dbMemStatistics.h \ dbMetaInfo.h \ + dbMutableRegion.h \ dbObject.h \ dbObjectTag.h \ dbObjectWithProperties.h \ diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index db74b18ff..2d6aa848d 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -127,14 +127,14 @@ private: // DeepRegion implementation DeepRegion::DeepRegion (const RecursiveShapeIterator &si, DeepShapeStore &dss, double area_ratio, size_t max_vertex_count) - : AsIfFlatRegion (), m_merged_polygons () + : MutableRegion (), m_merged_polygons () { set_deep_layer (dss.create_polygon_layer (si, area_ratio, max_vertex_count)); init (); } DeepRegion::DeepRegion (const RecursiveShapeIterator &si, DeepShapeStore &dss, const db::ICplxTrans &trans, bool merged_semantics, double area_ratio, size_t max_vertex_count) - : AsIfFlatRegion (), m_merged_polygons () + : MutableRegion (), m_merged_polygons () { set_deep_layer (dss.create_polygon_layer (si, area_ratio, max_vertex_count, trans)); init (); @@ -142,7 +142,7 @@ DeepRegion::DeepRegion (const RecursiveShapeIterator &si, DeepShapeStore &dss, c } DeepRegion::DeepRegion (const db::Region &other, DeepShapeStore &dss) - : AsIfFlatRegion (), m_merged_polygons () + : MutableRegion (), m_merged_polygons () { set_deep_layer (dss.create_from_flat (other, false)); @@ -151,13 +151,13 @@ DeepRegion::DeepRegion (const db::Region &other, DeepShapeStore &dss) } DeepRegion::DeepRegion () - : AsIfFlatRegion () + : MutableRegion () { init (); } DeepRegion::DeepRegion (const DeepLayer &dl) - : AsIfFlatRegion () + : MutableRegion () { set_deep_layer (dl); init (); @@ -169,7 +169,7 @@ DeepRegion::~DeepRegion () } DeepRegion::DeepRegion (const DeepRegion &other) - : AsIfFlatRegion (other), DeepShapeCollectionDelegateBase (other), + : MutableRegion (other), DeepShapeCollectionDelegateBase (other), m_merged_polygons_valid (other.m_merged_polygons_valid), m_is_merged (other.m_is_merged) { @@ -220,6 +220,74 @@ void DeepRegion::min_coherence_changed () set_is_merged (false); } +void DeepRegion::do_insert (const db::Polygon &polygon) +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + top_cell.shapes (deep_layer ().layer ()).insert (db::PolygonRef (polygon, layout.shape_repository ())); + } + + invalidate_bbox (); + set_is_merged (false); +} + +template +static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) +{ + // TODO: this is a pretty cheap implementation. At least a plain move can be done with orientation variants. + + db::Layout &layout = deep_layer.layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ().transformed (t)); + } + + layout.clear_layer (deep_layer.layer ()); + top_cell.shapes (deep_layer.layer ()).swap (flat_shapes); + + } +} + +void DeepRegion::do_transform (const db::Trans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepRegion::do_transform (const db::ICplxTrans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepRegion::reserve (size_t) +{ + // Not implemented for deep regions +} + +void DeepRegion::flatten () +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ()); + } + + layout.clear_layer (deep_layer ().layer ()); + top_cell.shapes (deep_layer ().layer ()).swap (flat_shapes); + + } +} + RegionIteratorDelegate * DeepRegion::begin () const { diff --git a/src/db/db/dbDeepRegion.h b/src/db/db/dbDeepRegion.h index c15913f2c..a2c0f06ef 100644 --- a/src/db/db/dbDeepRegion.h +++ b/src/db/db/dbDeepRegion.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatRegion.h" +#include "dbMutableRegion.h" #include "dbDeepShapeStore.h" namespace db { @@ -35,7 +35,7 @@ namespace db { * @brief A deep, polygon-set delegate */ class DB_PUBLIC DeepRegion - : public AsIfFlatRegion, public DeepShapeCollectionDelegateBase + : public MutableRegion, public DeepShapeCollectionDelegateBase { public: typedef db::layer polygon_layer_type; @@ -53,6 +53,15 @@ public: RegionDelegate *clone () const; + virtual void do_insert (const db::Polygon &polygon); + + virtual void do_transform (const db::Trans &t); + virtual void do_transform (const db::ICplxTrans &t); + + virtual void flatten (); + + virtual void reserve (size_t); + virtual RegionIteratorDelegate *begin () const; virtual RegionIteratorDelegate *begin_merged () const; diff --git a/src/db/db/dbFlatRegion.cc b/src/db/db/dbFlatRegion.cc index 01f3a954a..71e240cd3 100644 --- a/src/db/db/dbFlatRegion.cc +++ b/src/db/db/dbFlatRegion.cc @@ -33,7 +33,7 @@ namespace db // FlatRegion implementation FlatRegion::FlatRegion () - : AsIfFlatRegion (), mp_polygons (new db::Shapes (false)), mp_merged_polygons (new db::Shapes (false)) + : MutableRegion (), mp_polygons (new db::Shapes (false)), mp_merged_polygons (new db::Shapes (false)) { init (); } @@ -44,7 +44,7 @@ FlatRegion::~FlatRegion () } FlatRegion::FlatRegion (const FlatRegion &other) - : AsIfFlatRegion (other), mp_polygons (other.mp_polygons), mp_merged_polygons (other.mp_merged_polygons) + : MutableRegion (other), mp_polygons (other.mp_polygons), mp_merged_polygons (other.mp_merged_polygons) { init (); @@ -53,7 +53,7 @@ FlatRegion::FlatRegion (const FlatRegion &other) } FlatRegion::FlatRegion (const db::Shapes &polygons, bool is_merged) - : AsIfFlatRegion (), mp_polygons (new db::Shapes (polygons)), mp_merged_polygons (new db::Shapes (false)) + : MutableRegion (), mp_polygons (new db::Shapes (polygons)), mp_merged_polygons (new db::Shapes (false)) { init (); @@ -61,7 +61,7 @@ FlatRegion::FlatRegion (const db::Shapes &polygons, bool is_merged) } FlatRegion::FlatRegion (bool is_merged) - : AsIfFlatRegion (), mp_polygons (new db::Shapes (false)), mp_merged_polygons (new db::Shapes (false)) + : MutableRegion (), mp_polygons (new db::Shapes (false)), mp_merged_polygons (new db::Shapes (false)) { init (); @@ -410,39 +410,7 @@ void FlatRegion::insert_into (Layout *layout, db::cell_index_type into_cell, uns } void -FlatRegion::insert (const db::Box &box) -{ - if (! box.empty () && box.width () > 0 && box.height () > 0) { - - if (empty ()) { - - mp_polygons->insert (db::Polygon (box)); - m_is_merged = true; - update_bbox (box); - - } else { - - mp_polygons->insert (db::Polygon (box)); - m_is_merged = false; - invalidate_cache (); - - } - - } -} - -void -FlatRegion::insert (const db::Path &path) -{ - if (path.points () > 0) { - mp_polygons->insert (path.polygon ()); - m_is_merged = false; - invalidate_cache (); - } -} - -void -FlatRegion::insert (const db::Polygon &polygon) +FlatRegion::do_insert (const db::Polygon &polygon) { if (polygon.holes () > 0 || polygon.vertices () > 0) { mp_polygons->insert (polygon); @@ -451,29 +419,5 @@ FlatRegion::insert (const db::Polygon &polygon) } } -void -FlatRegion::insert (const db::SimplePolygon &polygon) -{ - if (polygon.vertices () > 0) { - db::Polygon poly; - poly.assign_hull (polygon.begin_hull (), polygon.end_hull ()); - mp_polygons->insert (poly); - m_is_merged = false; - invalidate_cache (); - } -} - -void -FlatRegion::insert (const db::Shape &shape) -{ - if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - db::Polygon poly; - shape.polygon (poly); - mp_polygons->insert (poly); - m_is_merged = false; - invalidate_cache (); - } -} - } diff --git a/src/db/db/dbFlatRegion.h b/src/db/db/dbFlatRegion.h index 87342eb13..09095cc0b 100644 --- a/src/db/db/dbFlatRegion.h +++ b/src/db/db/dbFlatRegion.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatRegion.h" +#include "dbMutableRegion.h" #include "dbShapes.h" #include "dbShapes2.h" #include "tlCopyOnWrite.h" @@ -42,7 +42,7 @@ typedef generic_shapes_iterator_delegate FlatRegionIterator; * @brief A flat, polygon-set delegate */ class DB_PUBLIC FlatRegion - : public AsIfFlatRegion + : public MutableRegion { public: typedef db::Polygon value_type; @@ -62,7 +62,7 @@ public: return new FlatRegion (*this); } - void reserve (size_t); + virtual void reserve (size_t); virtual RegionIteratorDelegate *begin () const; virtual RegionIteratorDelegate *begin_merged () const; @@ -97,51 +97,19 @@ public: virtual const db::RecursiveShapeIterator *iter () const; - void insert (const db::Box &box); - void insert (const db::Path &path); - void insert (const db::SimplePolygon &polygon); - void insert (const db::Polygon &polygon); - void insert (const db::Shape &shape); + void do_insert (const db::Polygon &polygon); - template - void insert (const db::Shape &shape, const T &trans) + void do_transform (const db::Trans &t) { - if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - db::Polygon poly; - shape.polygon (poly); - poly.transform (trans); - insert (poly); - } + transform_generic (t); } - template - void insert (const Iter &b, const Iter &e) + void do_transform (const db::ICplxTrans &t) { - reserve (count () + (e - b)); - for (Iter i = b; i != e; ++i) { - insert (*i); - } + transform_generic (t); } - template - void insert_seq (const Iter &seq) - { - for (Iter i = seq; ! i.at_end (); ++i) { - insert (*i); - } - } - - template - void transform (const Trans &trans) - { - if (! trans.is_unity ()) { - db::Shapes &polygons = *mp_polygons; - for (polygon_iterator_type p = polygons.get_layer ().begin (); p != polygons.get_layer ().end (); ++p) { - polygons.get_layer ().replace (p, p->transformed (trans)); - } - invalidate_cache (); - } - } + void flatten () { } db::Shapes &raw_polygons () { return *mp_polygons; } const db::Shapes &raw_polygons () const { return *mp_polygons; } @@ -166,6 +134,18 @@ private: void init (); void ensure_merged_polygons_valid () const; + + template + void transform_generic (const Trans &trans) + { + if (! trans.is_unity ()) { + db::Shapes &polygons = *mp_polygons; + for (polygon_iterator_type p = polygons.get_layer ().begin (); p != polygons.get_layer ().end (); ++p) { + polygons.get_layer ().replace (p, p->transformed (trans)); + } + invalidate_cache (); + } + } }; } diff --git a/src/db/db/dbMutableRegion.cc b/src/db/db/dbMutableRegion.cc new file mode 100644 index 000000000..8f55558f5 --- /dev/null +++ b/src/db/db/dbMutableRegion.cc @@ -0,0 +1,90 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbMutableRegion.h" + +namespace db +{ + +// ------------------------------------------------------------------------------------------------------------- +// FlatRegion implementation + +MutableRegion::MutableRegion () + : AsIfFlatRegion () +{ + // .. nothing yet .. +} + +MutableRegion::MutableRegion (const MutableRegion &other) + : AsIfFlatRegion (other) +{ + // .. nothing yet .. +} + +MutableRegion::~MutableRegion () +{ + // .. nothing yet .. +} + +void +MutableRegion::insert (const db::Box &box) +{ + if (! box.empty () && box.width () > 0 && box.height () > 0) { + do_insert (db::Polygon (box)); + } +} + +void +MutableRegion::insert (const db::Path &path) +{ + if (path.points () > 0) { + do_insert (path.polygon ()); + } +} + +void +MutableRegion::insert (const db::SimplePolygon &polygon) +{ + if (polygon.vertices () > 0) { + db::Polygon poly; + poly.assign_hull (polygon.begin_hull (), polygon.end_hull ()); + do_insert (poly); + } +} + +void +MutableRegion::insert (const db::Shape &shape) +{ + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { + db::Polygon poly; + shape.polygon (poly); + insert (poly); + } else if (shape.is_path ()) { + insert (shape.path ()); + } else if (shape.is_box ()) { + insert (shape.box ()); + } +} + +} + diff --git a/src/db/db/dbMutableRegion.h b/src/db/db/dbMutableRegion.h new file mode 100644 index 000000000..ea00278cf --- /dev/null +++ b/src/db/db/dbMutableRegion.h @@ -0,0 +1,101 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_dbMutableRegion +#define HDR_dbMutableRegion + +#include "dbCommon.h" + +#include "dbAsIfFlatRegion.h" + +#include + +namespace db { + +/** + * @brief An interface representing mutable regions + * + * Mutable Regions offer insert, transform, flatten and other manipulation functions. + */ +class DB_PUBLIC MutableRegion + : public AsIfFlatRegion +{ +public: + MutableRegion (); + MutableRegion (const MutableRegion &other); + virtual ~MutableRegion (); + + virtual void do_insert (const db::Polygon &polygon) = 0; + + void transform (const db::UnitTrans &) { } + void transform (const db::Disp &t) { do_transform (db::Trans (t)); } + void transform (const db::Trans &t) { do_transform (t); } + void transform (const db::ICplxTrans &t) { do_transform (t); } + + virtual void do_transform (const db::Trans &t) = 0; + virtual void do_transform (const db::ICplxTrans &t) = 0; + + virtual void flatten () = 0; + + virtual void reserve (size_t n) = 0; + + void insert (const db::Polygon &polygon) { do_insert (polygon); } + void insert (const db::Box &box); + void insert (const db::Path &path); + void insert (const db::SimplePolygon &polygon); + + void insert (const db::Shape &shape); + + template + void insert (const db::Shape &shape, const T &trans) + { + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { + db::Polygon poly; + shape.polygon (poly); + poly.transform (trans); + insert (poly); + } + } + + template + void insert (const Iter &b, const Iter &e) + { + reserve (count () + (e - b)); + for (Iter i = b; i != e; ++i) { + insert (*i); + } + } + + template + void insert_seq (const Iter &seq) + { + for (Iter i = seq; ! i.at_end (); ++i) { + insert (*i); + } + } +}; + +} + +#endif + diff --git a/src/db/db/dbRegion.cc b/src/db/db/dbRegion.cc index 605fb1671..1cacb17f3 100644 --- a/src/db/db/dbRegion.cc +++ b/src/db/db/dbRegion.cc @@ -131,13 +131,13 @@ Region::clear () void Region::reserve (size_t n) { - flat_region ()->reserve (n); + mutable_region ()->reserve (n); } template Region &Region::transform (const T &trans) { - flat_region ()->transform (trans); + mutable_region ()->transform (trans); return *this; } @@ -149,7 +149,7 @@ template DB_PUBLIC Region &Region::transform (const db::Disp &); template void Region::insert (const Sh &shape) { - flat_region ()->insert (shape); + mutable_region ()->insert (shape); } template DB_PUBLIC void Region::insert (const db::Box &); @@ -159,31 +159,36 @@ template DB_PUBLIC void Region::insert (const db::Path &); void Region::insert (const db::Shape &shape) { - flat_region ()->insert (shape); + mutable_region ()->insert (shape); } template void Region::insert (const db::Shape &shape, const T &trans) { - flat_region ()->insert (shape, trans); + mutable_region ()->insert (shape, trans); } template DB_PUBLIC void Region::insert (const db::Shape &, const db::ICplxTrans &); template DB_PUBLIC void Region::insert (const db::Shape &, const db::Trans &); template DB_PUBLIC void Region::insert (const db::Shape &, const db::Disp &); -FlatRegion * -Region::flat_region () +MutableRegion * +Region::mutable_region () { - FlatRegion *region = dynamic_cast (mp_delegate); + MutableRegion *region = dynamic_cast (mp_delegate); if (! region) { - region = new FlatRegion (); + + FlatRegion *flat_region = new FlatRegion (); + region = flat_region; + if (mp_delegate) { - region->RegionDelegate::operator= (*mp_delegate); // copy basic flags - region->insert_seq (begin ()); - region->set_is_merged (mp_delegate->is_merged ()); + flat_region->RegionDelegate::operator= (*mp_delegate); // copy basic flags + flat_region->insert_seq (begin ()); + flat_region->set_is_merged (mp_delegate->is_merged ()); } - set_delegate (region); + + set_delegate (flat_region); + } return region; @@ -274,6 +279,13 @@ Region::smoothed (coord_type d, bool keep_hv) const return processed (SmoothingProcessor (d, keep_hv)); } +db::Region & +Region::flatten () +{ + mutable_region ()->flatten (); + return *this; +} + void Region::snap (db::Coord gx, db::Coord gy) { diff --git a/src/db/db/dbRegion.h b/src/db/db/dbRegion.h index fea1d8e8b..a14761c42 100644 --- a/src/db/db/dbRegion.h +++ b/src/db/db/dbRegion.h @@ -37,7 +37,7 @@ namespace db { class EdgeFilterBase; -class FlatRegion; +class MutableRegion; class EmptyRegion; class DeepShapeStore; class TransformationReducer; @@ -1644,11 +1644,7 @@ public: * * This method will turn any region into a flat shape collection. */ - db::Region &flatten () - { - flat_region (); - return *this; - } + db::Region &flatten (); /** * @brief Returns true, if the region has valid polygons stored within itself @@ -1776,7 +1772,7 @@ private: RegionDelegate *mp_delegate; void set_delegate (RegionDelegate *delegate, bool keep_attributes = true); - FlatRegion *flat_region (); + db::MutableRegion *mutable_region(); }; /** From abd96c8400f3da804128965c800c73eecaa7915d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Mar 2021 23:19:14 +0200 Subject: [PATCH 13/16] Deep mode: avoid flattening by FlatRegion/FlatEdges poisoning --- src/db/db/db.pro | 6 ++ src/db/db/dbDeepEdgePairs.cc | 78 ++++++++++++++++++++-- src/db/db/dbDeepEdgePairs.h | 13 +++- src/db/db/dbDeepEdges.cc | 80 +++++++++++++++++++++-- src/db/db/dbDeepEdges.h | 13 +++- src/db/db/dbDeepTexts.cc | 80 +++++++++++++++++++++-- src/db/db/dbDeepTexts.h | 13 +++- src/db/db/dbEdgePairs.cc | 19 ++++-- src/db/db/dbEdgePairs.h | 9 +-- src/db/db/dbEdges.cc | 24 ++++--- src/db/db/dbEdges.h | 9 +-- src/db/db/dbFlatEdgePairs.cc | 20 ++---- src/db/db/dbFlatEdgePairs.h | 50 ++++++--------- src/db/db/dbFlatEdges.cc | 90 ++------------------------ src/db/db/dbFlatEdges.h | 71 ++++++--------------- src/db/db/dbFlatTexts.cc | 20 ++---- src/db/db/dbFlatTexts.h | 52 ++++++--------- src/db/db/dbMutableEdgePairs.cc | 58 +++++++++++++++++ src/db/db/dbMutableEdgePairs.h | 96 ++++++++++++++++++++++++++++ src/db/db/dbMutableEdges.cc | 107 +++++++++++++++++++++++++++++++ src/db/db/dbMutableEdges.h | 110 ++++++++++++++++++++++++++++++++ src/db/db/dbMutableRegion.h | 2 +- src/db/db/dbMutableTexts.cc | 58 +++++++++++++++++ src/db/db/dbMutableTexts.h | 96 ++++++++++++++++++++++++++++ src/db/db/dbTexts.cc | 19 ++++-- src/db/db/dbTexts.h | 9 +-- 26 files changed, 909 insertions(+), 293 deletions(-) create mode 100644 src/db/db/dbMutableEdgePairs.cc create mode 100644 src/db/db/dbMutableEdgePairs.h create mode 100644 src/db/db/dbMutableEdges.cc create mode 100644 src/db/db/dbMutableEdges.h create mode 100644 src/db/db/dbMutableTexts.cc create mode 100644 src/db/db/dbMutableTexts.h diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 02e4e20ad..b22167cff 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -50,7 +50,10 @@ SOURCES = \ dbManager.cc \ dbMatrix.cc \ dbMemStatistics.cc \ + dbMutableEdgePairs.cc \ + dbMutableEdges.cc \ dbMutableRegion.cc \ + dbMutableTexts.cc \ dbObject.cc \ dbPath.cc \ dbPCellDeclaration.cc \ @@ -259,7 +262,10 @@ HEADERS = \ dbMatrix.h \ dbMemStatistics.h \ dbMetaInfo.h \ + dbMutableEdgePairs.h \ + dbMutableEdges.h \ dbMutableRegion.h \ + dbMutableTexts.h \ dbObject.h \ dbObjectTag.h \ dbObjectWithProperties.h \ diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index 96316416a..12d89df30 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -112,31 +112,31 @@ private: DeepEdgePairs::DeepEdgePairs () - : AsIfFlatEdgePairs () + : MutableEdgePairs () { // .. nothing yet .. } DeepEdgePairs::DeepEdgePairs (const RecursiveShapeIterator &si, DeepShapeStore &dss) - : AsIfFlatEdgePairs () + : MutableEdgePairs () { set_deep_layer (dss.create_edge_pair_layer (si)); } DeepEdgePairs::DeepEdgePairs (const RecursiveShapeIterator &si, DeepShapeStore &dss, const db::ICplxTrans &trans) - : AsIfFlatEdgePairs () + : MutableEdgePairs () { set_deep_layer (dss.create_edge_pair_layer (si, trans)); } DeepEdgePairs::DeepEdgePairs (const DeepEdgePairs &other) - : AsIfFlatEdgePairs (other), db::DeepShapeCollectionDelegateBase (other) + : MutableEdgePairs (other), db::DeepShapeCollectionDelegateBase (other) { // .. nothing yet .. } DeepEdgePairs::DeepEdgePairs (const DeepLayer &dl) - : AsIfFlatEdgePairs () + : MutableEdgePairs () { set_deep_layer (dl); } @@ -151,6 +151,74 @@ EdgePairsDelegate *DeepEdgePairs::clone () const return new DeepEdgePairs (*this); } +void DeepEdgePairs::do_insert (const db::EdgePair &edge_pair) +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + top_cell.shapes (deep_layer ().layer ()).insert (edge_pair); + } + + invalidate_bbox (); + set_is_merged (false); +} + +template +static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) +{ + // TODO: this is a pretty cheap implementation. At least a plain move can be done with orientation variants. + + db::Layout &layout = deep_layer.layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ().transformed (t)); + } + + layout.clear_layer (deep_layer.layer ()); + top_cell.shapes (deep_layer.layer ()).swap (flat_shapes); + + } +} + +void DeepEdgePairs::do_transform (const db::Trans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdgePairs::do_transform (const db::ICplxTrans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdgePairs::reserve (size_t) +{ + // Not implemented for deep regions +} + +void DeepEdgePairs::flatten () +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ()); + } + + layout.clear_layer (deep_layer ().layer ()); + top_cell.shapes (deep_layer ().layer ()).swap (flat_shapes); + + } +} + EdgePairsIteratorDelegate *DeepEdgePairs::begin () const { return new DeepEdgePairsIterator (begin_iter ().first); diff --git a/src/db/db/dbDeepEdgePairs.h b/src/db/db/dbDeepEdgePairs.h index c423407b7..af2d3d42d 100644 --- a/src/db/db/dbDeepEdgePairs.h +++ b/src/db/db/dbDeepEdgePairs.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatEdgePairs.h" +#include "dbMutableEdgePairs.h" #include "dbDeepShapeStore.h" #include "dbEdgePairs.h" @@ -36,7 +36,7 @@ namespace db { * @brief Provides hierarchical edges implementation */ class DB_PUBLIC DeepEdgePairs - : public db::AsIfFlatEdgePairs, public db::DeepShapeCollectionDelegateBase + : public db::MutableEdgePairs, public db::DeepShapeCollectionDelegateBase { public: DeepEdgePairs (); @@ -50,6 +50,15 @@ public: EdgePairsDelegate *clone () const; + virtual void do_insert (const db::EdgePair &edge_pair); + + virtual void do_transform (const db::Trans &t); + virtual void do_transform (const db::ICplxTrans &t); + + virtual void flatten (); + + virtual void reserve (size_t n); + virtual EdgePairsIteratorDelegate *begin () const; virtual std::pair begin_iter () const; diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc index a6d9daea3..b2a299a9f 100644 --- a/src/db/db/dbDeepEdges.cc +++ b/src/db/db/dbDeepEdges.cc @@ -123,14 +123,14 @@ private: // DeepEdges implementation DeepEdges::DeepEdges (const RecursiveShapeIterator &si, DeepShapeStore &dss, bool as_edges) - : AsIfFlatEdges (), m_merged_edges () + : MutableEdges (), m_merged_edges () { set_deep_layer (dss.create_edge_layer (si, as_edges)); init (); } DeepEdges::DeepEdges (const RecursiveShapeIterator &si, DeepShapeStore &dss, const db::ICplxTrans &trans, bool as_edges, bool merged_semantics) - : AsIfFlatEdges (), m_merged_edges () + : MutableEdges (), m_merged_edges () { set_deep_layer (dss.create_edge_layer (si, as_edges, trans)); init (); @@ -138,7 +138,7 @@ DeepEdges::DeepEdges (const RecursiveShapeIterator &si, DeepShapeStore &dss, con } DeepEdges::DeepEdges (const db::Edges &other, DeepShapeStore &dss) - : AsIfFlatEdges (), m_merged_edges () + : MutableEdges (), m_merged_edges () { set_deep_layer (dss.create_from_flat (other)); @@ -147,13 +147,13 @@ DeepEdges::DeepEdges (const db::Edges &other, DeepShapeStore &dss) } DeepEdges::DeepEdges () - : AsIfFlatEdges () + : MutableEdges () { init (); } DeepEdges::DeepEdges (const DeepLayer &dl) - : AsIfFlatEdges () + : MutableEdges () { set_deep_layer (dl); init (); @@ -165,7 +165,7 @@ DeepEdges::~DeepEdges () } DeepEdges::DeepEdges (const DeepEdges &other) - : AsIfFlatEdges (other), DeepShapeCollectionDelegateBase (other), + : MutableEdges (other), DeepShapeCollectionDelegateBase (other), m_merged_edges_valid (other.m_merged_edges_valid), m_is_merged (other.m_is_merged) { @@ -211,6 +211,74 @@ void DeepEdges::merged_semantics_changed () // .. nothing yet .. } +void DeepEdges::do_insert (const db::Edge &edge) +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + top_cell.shapes (deep_layer ().layer ()).insert (edge); + } + + invalidate_bbox (); + set_is_merged (false); +} + +template +static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) +{ + // TODO: this is a pretty cheap implementation. At least a plain move can be done with orientation variants. + + db::Layout &layout = deep_layer.layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ().transformed (t)); + } + + layout.clear_layer (deep_layer.layer ()); + top_cell.shapes (deep_layer.layer ()).swap (flat_shapes); + + } +} + +void DeepEdges::do_transform (const db::Trans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdges::do_transform (const db::ICplxTrans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdges::reserve (size_t) +{ + // Not implemented for deep regions +} + +void DeepEdges::flatten () +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ()); + } + + layout.clear_layer (deep_layer ().layer ()); + top_cell.shapes (deep_layer ().layer ()).swap (flat_shapes); + + } +} + EdgesIteratorDelegate * DeepEdges::begin () const { diff --git a/src/db/db/dbDeepEdges.h b/src/db/db/dbDeepEdges.h index a9637a573..a0a01a429 100644 --- a/src/db/db/dbDeepEdges.h +++ b/src/db/db/dbDeepEdges.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatEdges.h" +#include "dbMutableEdges.h" #include "dbDeepShapeStore.h" #include "dbEdgeBoolean.h" #include "dbEdgePairs.h" @@ -40,7 +40,7 @@ class DeepRegion; * @brief Provides hierarchical edges implementation */ class DB_PUBLIC DeepEdges - : public db::AsIfFlatEdges, public db::DeepShapeCollectionDelegateBase + : public db::MutableEdges, public db::DeepShapeCollectionDelegateBase { public: DeepEdges (); @@ -53,6 +53,15 @@ public: virtual ~DeepEdges (); + virtual void do_transform (const db::Trans &t); + virtual void do_transform (const db::ICplxTrans &t); + + virtual void flatten (); + + virtual void reserve (size_t n); + + virtual void do_insert (const db::Edge &edge); + EdgesDelegate *clone () const; virtual EdgesIteratorDelegate *begin () const; diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index 1cffe1fdf..b46814961 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -116,31 +116,31 @@ private: }; DeepTexts::DeepTexts () - : AsIfFlatTexts () + : MutableTexts () { // .. nothing yet .. } DeepTexts::DeepTexts (const db::Texts &other, DeepShapeStore &dss) - : AsIfFlatTexts () + : MutableTexts () { set_deep_layer (dss.create_from_flat (other)); } DeepTexts::DeepTexts (const RecursiveShapeIterator &si, DeepShapeStore &dss) - : AsIfFlatTexts () + : MutableTexts () { set_deep_layer (dss.create_text_layer (si)); } DeepTexts::DeepTexts (const RecursiveShapeIterator &si, DeepShapeStore &dss, const db::ICplxTrans &trans) - : AsIfFlatTexts () + : MutableTexts () { set_deep_layer (dss.create_text_layer (si, trans)); } DeepTexts::DeepTexts (const DeepTexts &other) - : AsIfFlatTexts (other), DeepShapeCollectionDelegateBase (other) + : MutableTexts (other), DeepShapeCollectionDelegateBase (other) { // .. nothing yet .. } @@ -156,7 +156,7 @@ DeepTexts::operator= (const DeepTexts &other) } DeepTexts::DeepTexts (const DeepLayer &dl) - : AsIfFlatTexts () + : MutableTexts () { set_deep_layer (dl); } @@ -171,6 +171,74 @@ TextsDelegate *DeepTexts::clone () const return new DeepTexts (*this); } +void DeepTexts::do_insert (const db::Text &text) +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + top_cell.shapes (deep_layer ().layer ()).insert (db::TextRef (text, layout.shape_repository ())); + } + + invalidate_bbox (); + set_is_merged (false); +} + +template +static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) +{ + // TODO: this is a pretty cheap implementation. At least a plain move can be done with orientation variants. + + db::Layout &layout = deep_layer.layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ().transformed (t)); + } + + layout.clear_layer (deep_layer.layer ()); + top_cell.shapes (deep_layer.layer ()).swap (flat_shapes); + + } +} + +void DeepTexts::do_transform (const db::Trans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepTexts::do_transform (const db::ICplxTrans &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepTexts::reserve (size_t) +{ + // Not implemented for deep regions +} + +void DeepTexts::flatten () +{ + db::Layout &layout = deep_layer ().layout (); + if (layout.begin_top_down () != layout.end_top_down ()) { + + db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); + + db::Shapes flat_shapes; + for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { + flat_shapes.insert (iter->polygon ()); + } + + layout.clear_layer (deep_layer ().layer ()); + top_cell.shapes (deep_layer ().layer ()).swap (flat_shapes); + + } +} + TextsIteratorDelegate *DeepTexts::begin () const { return new DeepTextsIterator (begin_iter ().first); diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 8c1057bc0..009b032eb 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatTexts.h" +#include "dbMutableTexts.h" #include "dbDeepShapeStore.h" #include "dbTexts.h" @@ -36,7 +36,7 @@ namespace db { * @brief Provides hierarchical edges implementation */ class DB_PUBLIC DeepTexts - : public db::AsIfFlatTexts, public db::DeepShapeCollectionDelegateBase + : public db::MutableTexts, public db::DeepShapeCollectionDelegateBase { public: DeepTexts (); @@ -51,6 +51,15 @@ public: TextsDelegate *clone () const; + virtual void do_insert (const db::Text &text); + + virtual void do_transform (const db::Trans &t); + virtual void do_transform (const db::ICplxTrans &t); + + virtual void flatten (); + + virtual void reserve (size_t n); + virtual TextsIteratorDelegate *begin () const; virtual std::pair begin_iter () const; diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index 2125714b5..3c5e65d5c 100644 --- a/src/db/db/dbEdgePairs.cc +++ b/src/db/db/dbEdgePairs.cc @@ -93,20 +93,20 @@ EdgePairs::EdgePairs (const RecursiveShapeIterator &si, DeepShapeStore &dss, con template void EdgePairs::insert (const Sh &shape) { - flat_edge_pairs ()->insert (shape); + mutable_edge_pairs ()->insert (shape); } template DB_PUBLIC void EdgePairs::insert (const db::EdgePair &); void EdgePairs::insert (const db::Shape &shape) { - flat_edge_pairs ()->insert (shape); + mutable_edge_pairs ()->insert (shape); } template void EdgePairs::insert (const db::Shape &shape, const T &trans) { - flat_edge_pairs ()->insert (shape, trans); + mutable_edge_pairs ()->insert (shape, trans); } template DB_PUBLIC void EdgePairs::insert (const db::Shape &, const db::ICplxTrans &); @@ -120,13 +120,18 @@ void EdgePairs::clear () void EdgePairs::reserve (size_t n) { - flat_edge_pairs ()->reserve (n); + mutable_edge_pairs ()->reserve (n); +} + +void EdgePairs::flatten () +{ + mutable_edge_pairs ()->flatten (); } template EdgePairs &EdgePairs::transform (const T &trans) { - flat_edge_pairs ()->transform (trans); + mutable_edge_pairs ()->transform (trans); return *this; } @@ -181,9 +186,9 @@ void EdgePairs::set_delegate (EdgePairsDelegate *delegate) } } -FlatEdgePairs *EdgePairs::flat_edge_pairs () +MutableEdgePairs *EdgePairs::mutable_edge_pairs () { - FlatEdgePairs *edge_pairs = dynamic_cast (mp_delegate); + MutableEdgePairs *edge_pairs = dynamic_cast (mp_delegate); if (! edge_pairs) { edge_pairs = new FlatEdgePairs (); if (mp_delegate) { diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index c80d622d7..e2200e465 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -36,7 +36,7 @@ namespace db { class EdgePairFilterBase; -class FlatEdgePairs; +class MutableEdgePairs; class EmptyEdgePairs; class Edges; class Region; @@ -469,10 +469,7 @@ public: * * This method will turn any edge pair collection into a flat one. */ - void flatten () - { - flat_edge_pairs (); - } + void flatten (); /** * @brief Returns true, if the edge pair set has valid edges stored within itself @@ -615,7 +612,7 @@ private: EdgePairsDelegate *mp_delegate; void set_delegate (EdgePairsDelegate *delegate); - FlatEdgePairs *flat_edge_pairs (); + MutableEdgePairs *mutable_edge_pairs (); }; } diff --git a/src/db/db/dbEdges.cc b/src/db/db/dbEdges.cc index 07c0eece8..8143070c1 100644 --- a/src/db/db/dbEdges.cc +++ b/src/db/db/dbEdges.cc @@ -24,6 +24,7 @@ #include "dbDeepEdges.h" #include "dbOriginalLayerEdges.h" #include "dbEmptyEdges.h" +#include "dbMutableEdges.h" #include "dbFlatEdges.h" #include "dbEdgesUtils.h" #include "dbRegion.h" @@ -133,7 +134,7 @@ Edges::clear () void Edges::reserve (size_t n) { - flat_edges ()->reserve (n); + mutable_edges ()->reserve (n); } void Edges::processed (Region &output, const EdgeToPolygonProcessorBase &filter) const @@ -166,10 +167,16 @@ Edges Edges::centers (length_type length, double fraction) const return Edges (mp_delegate->processed (EdgeSegmentSelector (0, length, fraction))); } +void +Edges::flatten () +{ + mutable_edges ()->flatten (); +} + template Edges &Edges::transform (const T &trans) { - flat_edges ()->transform (trans); + mutable_edges ()->transform (trans); return *this; } @@ -181,7 +188,7 @@ template DB_PUBLIC Edges &Edges::transform (const db::Disp &); template void Edges::insert (const Sh &shape) { - flat_edges ()->insert (shape); + mutable_edges ()->insert (shape); } template DB_PUBLIC void Edges::insert (const db::Box &); @@ -192,24 +199,25 @@ template DB_PUBLIC void Edges::insert (const db::Edge &); void Edges::insert (const db::Shape &shape) { - flat_edges ()->insert (shape); + mutable_edges ()->insert (shape); } template void Edges::insert (const db::Shape &shape, const T &trans) { - flat_edges ()->insert (shape, trans); + mutable_edges ()->insert (shape, trans); } template DB_PUBLIC void Edges::insert (const db::Shape &, const db::ICplxTrans &); template DB_PUBLIC void Edges::insert (const db::Shape &, const db::Trans &); template DB_PUBLIC void Edges::insert (const db::Shape &, const db::Disp &); -FlatEdges * -Edges::flat_edges () +MutableEdges * +Edges::mutable_edges () { - FlatEdges *edges = dynamic_cast (mp_delegate); + MutableEdges *edges = dynamic_cast (mp_delegate); if (! edges) { + edges = new FlatEdges (); if (mp_delegate) { edges->EdgesDelegate::operator= (*mp_delegate); diff --git a/src/db/db/dbEdges.h b/src/db/db/dbEdges.h index 561a5d9a0..a4cb05ae7 100644 --- a/src/db/db/dbEdges.h +++ b/src/db/db/dbEdges.h @@ -35,7 +35,7 @@ namespace db { class EdgeFilterBase; -class FlatEdges; +class MutableEdges; class EmptyEdges; class DeepShapeStore; @@ -1112,10 +1112,7 @@ public: * * This method will turn any edge collection into a flat one. */ - void flatten () - { - flat_edges (); - } + void flatten (); /** * @brief Returns true, if the edge set has valid edges stored within itself @@ -1208,7 +1205,7 @@ private: EdgesDelegate *mp_delegate; void set_delegate (EdgesDelegate *delegate, bool keep_attributes = true); - FlatEdges *flat_edges (); + MutableEdges *mutable_edges(); }; } // namespace db diff --git a/src/db/db/dbFlatEdgePairs.cc b/src/db/db/dbFlatEdgePairs.cc index 60a79f167..561aa4829 100644 --- a/src/db/db/dbFlatEdgePairs.cc +++ b/src/db/db/dbFlatEdgePairs.cc @@ -32,7 +32,7 @@ namespace db // FlatEdgePairs implementation FlatEdgePairs::FlatEdgePairs () - : AsIfFlatEdgePairs (), mp_edge_pairs (new db::Shapes (false)) + : MutableEdgePairs (), mp_edge_pairs (new db::Shapes (false)) { // .. nothing yet .. } @@ -43,13 +43,13 @@ FlatEdgePairs::~FlatEdgePairs () } FlatEdgePairs::FlatEdgePairs (const FlatEdgePairs &other) - : AsIfFlatEdgePairs (other), mp_edge_pairs (other.mp_edge_pairs) + : MutableEdgePairs (other), mp_edge_pairs (other.mp_edge_pairs) { // .. nothing yet .. } FlatEdgePairs::FlatEdgePairs (const db::Shapes &edge_pairs) - : AsIfFlatEdgePairs (), mp_edge_pairs (new db::Shapes (edge_pairs)) + : MutableEdgePairs (), mp_edge_pairs (new db::Shapes (edge_pairs)) { // .. nothing yet .. } @@ -205,23 +205,11 @@ FlatEdgePairs::insert_into (Layout *layout, db::cell_index_type into_cell, unsig } void -FlatEdgePairs::insert (const db::EdgePair &ep) +FlatEdgePairs::do_insert (const db::EdgePair &ep) { mp_edge_pairs->insert (ep); invalidate_cache (); } -void -FlatEdgePairs::insert (const db::Shape &shape) -{ - if (shape.is_edge_pair ()) { - - db::EdgePair ep; - shape.edge_pair (ep); - insert (ep); - - } -} - } diff --git a/src/db/db/dbFlatEdgePairs.h b/src/db/db/dbFlatEdgePairs.h index 9b31fb0f2..93db999a4 100644 --- a/src/db/db/dbFlatEdgePairs.h +++ b/src/db/db/dbFlatEdgePairs.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatEdgePairs.h" +#include "dbMutableEdgePairs.h" #include "dbShapes.h" #include "dbGenericShapeIterator.h" #include "tlCopyOnWrite.h" @@ -42,7 +42,7 @@ typedef generic_shapes_iterator_delegate FlatEdgePairsIterator; * @brief The delegate for the actual edge pair set implementation */ class DB_PUBLIC FlatEdgePairs - : public AsIfFlatEdgePairs + : public MutableEdgePairs { public: typedef db::layer edge_pair_layer_type; @@ -82,41 +82,19 @@ public: virtual void insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const; virtual void insert_into_as_polygons (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer, db::Coord enl) const; - void insert (const db::EdgePair &edge_pair); - void insert (const db::Shape &shape); + virtual void do_insert (const db::EdgePair &edge_pair); - template - void insert (const db::Shape &shape, const T &trans) + virtual void do_transform (const db::Trans &t) { - if (shape.is_edge_pair ()) { - - db::EdgePair ep; - shape.edge_pair (ep); - ep.transform (trans); - insert (ep); - - } + transform_generic (t); } - template - void insert_seq (const Iter &seq) + virtual void do_transform (const db::ICplxTrans &t) { - for (Iter i = seq; ! i.at_end (); ++i) { - insert (*i); - } + transform_generic (t); } - template - void transform (const Trans &trans) - { - if (! trans.is_unity ()) { - db::Shapes &ep = *mp_edge_pairs; - for (edge_pair_iterator_type p = ep.template get_layer ().begin (); p != ep.template get_layer ().end (); ++p) { - ep.get_layer ().replace (p, p->transformed (trans)); - } - invalidate_cache (); - } - } + virtual void flatten () { } db::Shapes &raw_edge_pairs () { return *mp_edge_pairs; } const db::Shapes &raw_edge_pairs () const { return *mp_edge_pairs; } @@ -131,6 +109,18 @@ private: FlatEdgePairs &operator= (const FlatEdgePairs &other); mutable tl::copy_on_write_ptr mp_edge_pairs; + + template + void transform_generic (const Trans &trans) + { + if (! trans.is_unity ()) { + db::Shapes &ep = *mp_edge_pairs; + for (edge_pair_iterator_type p = ep.template get_layer ().begin (); p != ep.template get_layer ().end (); ++p) { + ep.get_layer ().replace (p, p->transformed (trans)); + } + invalidate_cache (); + } + } }; } diff --git a/src/db/db/dbFlatEdges.cc b/src/db/db/dbFlatEdges.cc index 689cc5e93..1421f1353 100644 --- a/src/db/db/dbFlatEdges.cc +++ b/src/db/db/dbFlatEdges.cc @@ -33,7 +33,7 @@ namespace db // FlatEdges implementation FlatEdges::FlatEdges () - : AsIfFlatEdges (), mp_edges (new db::Shapes (false)), mp_merged_edges (new db::Shapes (false)) + : MutableEdges (), mp_edges (new db::Shapes (false)), mp_merged_edges (new db::Shapes (false)) { init (); } @@ -44,7 +44,7 @@ FlatEdges::~FlatEdges () } FlatEdges::FlatEdges (const FlatEdges &other) - : AsIfFlatEdges (other), mp_edges (other.mp_edges), mp_merged_edges (other.mp_merged_edges) + : MutableEdges (other), mp_edges (other.mp_edges), mp_merged_edges (other.mp_merged_edges) { init (); @@ -53,7 +53,7 @@ FlatEdges::FlatEdges (const FlatEdges &other) } FlatEdges::FlatEdges (const db::Shapes &edges, bool is_merged) - : AsIfFlatEdges (), mp_edges (new db::Shapes (edges)), mp_merged_edges (new db::Shapes (false)) + : MutableEdges (), mp_edges (new db::Shapes (edges)), mp_merged_edges (new db::Shapes (false)) { init (); @@ -61,7 +61,7 @@ FlatEdges::FlatEdges (const db::Shapes &edges, bool is_merged) } FlatEdges::FlatEdges (bool is_merged) - : AsIfFlatEdges (), mp_edges (new db::Shapes (false)), mp_merged_edges (new db::Shapes (false)) + : MutableEdges (), mp_edges (new db::Shapes (false)), mp_merged_edges (new db::Shapes (false)) { init (); @@ -320,69 +320,7 @@ const db::RecursiveShapeIterator *FlatEdges::iter () const } void -FlatEdges::insert (const db::Box &box) -{ - if (! box.empty () && box.width () > 0 && box.height () > 0) { - - bool was_empty = empty (); - - db::Shapes &e = *mp_edges; - e.insert (db::Edge (box.lower_left (), box.upper_left ())); - e.insert (db::Edge (box.upper_left (), box.upper_right ())); - e.insert (db::Edge (box.upper_right (), box.lower_right ())); - e.insert (db::Edge (box.lower_right (), box.lower_left ())); - - if (was_empty) { - - m_is_merged = true; - update_bbox (box); - - } else { - - m_is_merged = false; - invalidate_cache (); - - } - - } -} - -void -FlatEdges::insert (const db::Path &path) -{ - if (path.points () > 0) { - insert (path.polygon ()); - } -} - -void -FlatEdges::insert (const db::Polygon &polygon) -{ - if (polygon.holes () > 0 || polygon.vertices () > 0) { - db::Shapes &edges = *mp_edges; - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - edges.insert (*e); - } - m_is_merged = false; - invalidate_cache (); - } -} - -void -FlatEdges::insert (const db::SimplePolygon &polygon) -{ - if (polygon.vertices () > 0) { - db::Shapes &edges = *mp_edges; - for (db::SimplePolygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { - edges.insert (*e); - } - m_is_merged = false; - invalidate_cache (); - } -} - -void -FlatEdges::insert (const db::Edge &edge) +FlatEdges::do_insert (const db::Edge &edge) { if (! empty ()) { m_is_merged = false; @@ -392,23 +330,5 @@ FlatEdges::insert (const db::Edge &edge) invalidate_cache (); } -void -FlatEdges::insert (const db::Shape &shape) -{ - if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - - db::Polygon poly; - shape.polygon (poly); - insert (poly); - - } else if (shape.is_edge ()) { - - db::Edge edge; - shape.edge (edge); - insert (edge); - - } -} - } diff --git a/src/db/db/dbFlatEdges.h b/src/db/db/dbFlatEdges.h index 353511179..c93e926db 100644 --- a/src/db/db/dbFlatEdges.h +++ b/src/db/db/dbFlatEdges.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatEdges.h" +#include "dbMutableEdges.h" #include "dbShapes.h" #include "dbShapes2.h" #include "dbGenericShapeIterator.h" @@ -43,7 +43,7 @@ typedef generic_shapes_iterator_delegate FlatEdgesIterator; * @brief A flat, edge-set delegate */ class DB_PUBLIC FlatEdges - : public AsIfFlatEdges + : public MutableEdges { public: typedef db::Edge value_type; @@ -65,6 +65,7 @@ public: } void reserve (size_t); + void flatten () { } virtual EdgesIteratorDelegate *begin () const; virtual EdgesIteratorDelegate *begin_merged () const; @@ -91,60 +92,16 @@ public: virtual const db::RecursiveShapeIterator *iter () const; - void insert (const db::Box &box); - void insert (const db::Path &path); - void insert (const db::SimplePolygon &polygon); - void insert (const db::Polygon &polygon); - void insert (const db::Edge &edge); - void insert (const db::Shape &shape); + void do_insert (const db::Edge &edge); - template - void insert (const db::Shape &shape, const T &trans) + void do_transform (const db::Trans &t) { - if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { - - db::Polygon poly; - shape.polygon (poly); - poly.transform (trans); - insert (poly); - - } else if (shape.is_edge ()) { - - db::Edge edge; - shape.edge (edge); - edge.transform (trans); - insert (edge); - - } + transform_generic (t); } - template - void insert (const Iter &b, const Iter &e) + void do_transform(const db::ICplxTrans &t) { - reserve (count () + (e - b)); - for (Iter i = b; i != e; ++i) { - insert (*i); - } - } - - template - void insert_seq (const Iter &seq) - { - for (Iter i = seq; ! i.at_end (); ++i) { - insert (*i); - } - } - - template - void transform (const Trans &trans) - { - if (! trans.is_unity ()) { - db::Shapes &e = *mp_edges; - for (edge_iterator_type p = e.template get_layer ().begin (); p != e.get_layer ().end (); ++p) { - e.get_layer ().replace (p, p->transformed (trans)); - } - invalidate_cache (); - } + transform_generic (t); } db::Shapes &raw_edges () { return *mp_edges; } @@ -168,6 +125,18 @@ private: void init (); void ensure_merged_edges_valid () const; + + template + void transform_generic (const Trans &trans) + { + if (! trans.is_unity ()) { + db::Shapes &e = *mp_edges; + for (edge_iterator_type p = e.template get_layer ().begin (); p != e.get_layer ().end (); ++p) { + e.get_layer ().replace (p, p->transformed (trans)); + } + invalidate_cache (); + } + } }; } diff --git a/src/db/db/dbFlatTexts.cc b/src/db/db/dbFlatTexts.cc index 8fc167004..59d0ec2c9 100644 --- a/src/db/db/dbFlatTexts.cc +++ b/src/db/db/dbFlatTexts.cc @@ -32,7 +32,7 @@ namespace db // FlatTexts implementation FlatTexts::FlatTexts () - : AsIfFlatTexts (), mp_texts (new db::Shapes (false)) + : MutableTexts (), mp_texts (new db::Shapes (false)) { // .. nothing yet .. } @@ -43,13 +43,13 @@ FlatTexts::~FlatTexts () } FlatTexts::FlatTexts (const FlatTexts &other) - : AsIfFlatTexts (other), mp_texts (other.mp_texts) + : MutableTexts (other), mp_texts (other.mp_texts) { // .. nothing yet .. } FlatTexts::FlatTexts (const db::Shapes &texts) - : AsIfFlatTexts (), mp_texts (new db::Shapes (texts)) + : MutableTexts (), mp_texts (new db::Shapes (texts)) { // .. nothing yet .. } @@ -207,23 +207,11 @@ FlatTexts::insert_into (Layout *layout, db::cell_index_type into_cell, unsigned } void -FlatTexts::insert (const db::Text &t) +FlatTexts::do_insert (const db::Text &t) { mp_texts->insert (t); invalidate_cache (); } -void -FlatTexts::insert (const db::Shape &shape) -{ - if (shape.is_text ()) { - - db::Text t; - shape.text (t); - insert (t); - - } -} - } diff --git a/src/db/db/dbFlatTexts.h b/src/db/db/dbFlatTexts.h index 90b2d7bbd..ab7378d71 100644 --- a/src/db/db/dbFlatTexts.h +++ b/src/db/db/dbFlatTexts.h @@ -26,7 +26,7 @@ #include "dbCommon.h" -#include "dbAsIfFlatTexts.h" +#include "dbMutableTexts.h" #include "dbShapes.h" #include "tlCopyOnWrite.h" @@ -41,7 +41,7 @@ typedef generic_shapes_iterator_delegate FlatTextsIterator; * @brief The delegate for the actual text set implementation */ class DB_PUBLIC FlatTexts - : public AsIfFlatTexts + : public MutableTexts { public: typedef db::Text value_type; @@ -83,40 +83,18 @@ public: virtual void insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const; virtual void insert_into_as_polygons (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer, db::Coord enl) const; - void insert (const db::Text &text); - void insert (const db::Shape &shape); + virtual void flatten () { } - template - void insert (const db::Shape &shape, const T &trans) + void do_insert (const db::Text &text); + + virtual void do_transform (const db::Trans &t) { - if (shape.is_edge_pair ()) { - - db::Text t; - shape.text (t); - t.transform (trans); - insert (t); - - } + transform_generic (t); } - template - void insert_seq (const Iter &seq) + virtual void do_transform (const db::ICplxTrans &t) { - for (Iter i = seq; ! i.at_end (); ++i) { - insert (*i); - } - } - - template - void transform (const Trans &trans) - { - if (! trans.is_unity ()) { - db::Shapes &texts = *mp_texts; - for (text_iterator_type p = texts.template get_layer ().begin (); p != texts.template get_layer ().end (); ++p) { - texts.get_layer ().replace (p, p->transformed (trans)); - } - invalidate_cache (); - } + transform_generic (t); } db::Shapes &raw_texts () { return *mp_texts; } @@ -132,6 +110,18 @@ private: FlatTexts &operator= (const FlatTexts &other); mutable tl::copy_on_write_ptr mp_texts; + + template + void transform_generic (const Trans &trans) + { + if (! trans.is_unity ()) { + db::Shapes &texts = *mp_texts; + for (text_iterator_type p = texts.template get_layer ().begin (); p != texts.template get_layer ().end (); ++p) { + texts.get_layer ().replace (p, p->transformed (trans)); + } + invalidate_cache (); + } + } }; } diff --git a/src/db/db/dbMutableEdgePairs.cc b/src/db/db/dbMutableEdgePairs.cc new file mode 100644 index 000000000..876d609c9 --- /dev/null +++ b/src/db/db/dbMutableEdgePairs.cc @@ -0,0 +1,58 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbMutableEdgePairs.h" + +namespace db +{ + +// ------------------------------------------------------------------------------------------------------------- +// MutableEdgePairs implementation + +MutableEdgePairs::MutableEdgePairs () + : AsIfFlatEdgePairs () +{ + // .. nothing yet .. +} + +MutableEdgePairs::MutableEdgePairs (const MutableEdgePairs &other) + : AsIfFlatEdgePairs (other) +{ + // .. nothing yet .. +} + +MutableEdgePairs::~MutableEdgePairs () +{ + // .. nothing yet .. +} + +void +MutableEdgePairs::insert (const db::Shape &shape) +{ + if (shape.is_edge_pair ()) { + insert (shape.edge_pair ()); + } +} + +} + diff --git a/src/db/db/dbMutableEdgePairs.h b/src/db/db/dbMutableEdgePairs.h new file mode 100644 index 000000000..2b5785155 --- /dev/null +++ b/src/db/db/dbMutableEdgePairs.h @@ -0,0 +1,96 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_dbMutableEdgePairs +#define HDR_dbMutableEdgePairs + +#include "dbCommon.h" + +#include "dbAsIfFlatEdgePairs.h" + +#include + +namespace db { + +/** + * @brief An interface representing mutable edge pair collections + * + * Mutable edge pair collections offer insert, transform, flatten and other manipulation functions. + */ +class DB_PUBLIC MutableEdgePairs + : public AsIfFlatEdgePairs +{ +public: + MutableEdgePairs (); + MutableEdgePairs (const MutableEdgePairs &other); + virtual ~MutableEdgePairs (); + + virtual void do_insert (const db::EdgePair &edge_pair) = 0; + + virtual void do_transform (const db::Trans &t) = 0; + virtual void do_transform (const db::ICplxTrans &t) = 0; + + virtual void flatten () = 0; + + virtual void reserve (size_t n) = 0; + + void transform (const db::UnitTrans &) { } + void transform (const db::Disp &t) { do_transform (db::Trans (t)); } + void transform (const db::Trans &t) { do_transform (t); } + void transform (const db::ICplxTrans &t) { do_transform (t); } + + void insert (const db::EdgePair &edge_pair) { do_insert (edge_pair); } + void insert (const db::Shape &shape); + + template + void insert (const db::Shape &shape, const T &trans) + { + if (shape.is_edge_pair ()) { + db::EdgePair ep = shape.edge_pair (); + ep.transform (trans); + insert (ep); + } + } + + template + void insert (const Iter &b, const Iter &e) + { + reserve (count () + (e - b)); + for (Iter i = b; i != e; ++i) { + insert (*i); + } + } + + template + void insert_seq (const Iter &seq) + { + for (Iter i = seq; ! i.at_end (); ++i) { + insert (*i); + } + } +}; + +} + +#endif + diff --git a/src/db/db/dbMutableEdges.cc b/src/db/db/dbMutableEdges.cc new file mode 100644 index 000000000..b0859ac1f --- /dev/null +++ b/src/db/db/dbMutableEdges.cc @@ -0,0 +1,107 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbMutableEdges.h" + +namespace db +{ + +// ------------------------------------------------------------------------------------------------------------- +// MutableEdges implementation + +MutableEdges::MutableEdges () + : AsIfFlatEdges () +{ + // .. nothing yet .. +} + +MutableEdges::MutableEdges (const MutableEdges &other) + : AsIfFlatEdges (other) +{ + // .. nothing yet .. +} + +MutableEdges::~MutableEdges () +{ + // .. nothing yet .. +} + +void +MutableEdges::insert (const db::Box &box) +{ + if (! box.empty () && box.width () > 0 && box.height () > 0) { + do_insert (db::Edge (box.lower_left (), box.upper_left ())); + do_insert (db::Edge (box.upper_left (), box.upper_right ())); + do_insert (db::Edge (box.upper_right (), box.lower_right ())); + do_insert (db::Edge (box.lower_right (), box.lower_left ())); + } +} + +void +MutableEdges::insert (const db::Path &path) +{ + if (path.points () > 0) { + insert (path.polygon ()); + } +} + +void +MutableEdges::insert (const db::Polygon &polygon) +{ + if (polygon.holes () > 0 || polygon.vertices () > 0) { + for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + do_insert (*e); + } + } +} + +void +MutableEdges::insert (const db::SimplePolygon &polygon) +{ + if (polygon.vertices () > 0) { + for (db::SimplePolygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + do_insert (*e); + } + } +} + +void +MutableEdges::insert (const db::Shape &shape) +{ + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { + + db::Polygon poly; + shape.polygon (poly); + insert (poly); + + } else if (shape.is_edge ()) { + + db::Edge edge; + shape.edge (edge); + do_insert (edge); + + } +} + +} + diff --git a/src/db/db/dbMutableEdges.h b/src/db/db/dbMutableEdges.h new file mode 100644 index 000000000..2832901f1 --- /dev/null +++ b/src/db/db/dbMutableEdges.h @@ -0,0 +1,110 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_dbMutableEdges +#define HDR_dbMutableEdges + +#include "dbCommon.h" + +#include "dbAsIfFlatEdges.h" + +#include + +namespace db { + +/** + * @brief An interface representing mutable edge collections + * + * Mutable edge collections offer insert, transform, flatten and other manipulation functions. + */ +class DB_PUBLIC MutableEdges + : public AsIfFlatEdges +{ +public: + MutableEdges (); + MutableEdges (const MutableEdges &other); + virtual ~MutableEdges (); + + virtual void do_transform (const db::Trans &t) = 0; + virtual void do_transform (const db::ICplxTrans &t) = 0; + + virtual void flatten () = 0; + + virtual void reserve (size_t n) = 0; + + virtual void do_insert (const db::Edge &edge) = 0; + + void transform (const db::UnitTrans &) { } + void transform (const db::Disp &t) { do_transform (db::Trans (t)); } + void transform (const db::Trans &t) { do_transform (t); } + void transform (const db::ICplxTrans &t) { do_transform (t); } + + void insert (const db::Edge &edge) { do_insert (edge); } + void insert (const db::Box &box); + void insert (const db::Path &path); + void insert (const db::SimplePolygon &polygon); + void insert (const db::Polygon &polygon); + void insert (const db::Shape &shape); + + template + void insert (const db::Shape &shape, const T &trans) + { + if (shape.is_polygon () || shape.is_path () || shape.is_box ()) { + + db::Polygon poly; + shape.polygon (poly); + poly.transform (trans); + insert (poly); + + } else if (shape.is_edge ()) { + + db::Edge edge; + shape.edge (edge); + edge.transform (trans); + insert (edge); + + } + } + + template + void insert (const Iter &b, const Iter &e) + { + reserve (count () + (e - b)); + for (Iter i = b; i != e; ++i) { + insert (*i); + } + } + + template + void insert_seq (const Iter &seq) + { + for (Iter i = seq; ! i.at_end (); ++i) { + insert (*i); + } + } +}; + +} + +#endif + diff --git a/src/db/db/dbMutableRegion.h b/src/db/db/dbMutableRegion.h index ea00278cf..3863e3f19 100644 --- a/src/db/db/dbMutableRegion.h +++ b/src/db/db/dbMutableRegion.h @@ -35,7 +35,7 @@ namespace db { /** * @brief An interface representing mutable regions * - * Mutable Regions offer insert, transform, flatten and other manipulation functions. + * Mutable regions offer insert, transform, flatten and other manipulation functions. */ class DB_PUBLIC MutableRegion : public AsIfFlatRegion diff --git a/src/db/db/dbMutableTexts.cc b/src/db/db/dbMutableTexts.cc new file mode 100644 index 000000000..8556fe4db --- /dev/null +++ b/src/db/db/dbMutableTexts.cc @@ -0,0 +1,58 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbMutableTexts.h" + +namespace db +{ + +// ------------------------------------------------------------------------------------------------------------- +// MutableTexts implementation + +MutableTexts::MutableTexts () + : AsIfFlatTexts () +{ + // .. nothing yet .. +} + +MutableTexts::MutableTexts (const MutableTexts &other) + : AsIfFlatTexts (other) +{ + // .. nothing yet .. +} + +MutableTexts::~MutableTexts () +{ + // .. nothing yet .. +} + +void +MutableTexts::insert (const db::Shape &shape) +{ + if (shape.is_text ()) { + insert (shape.text ()); + } +} + +} + diff --git a/src/db/db/dbMutableTexts.h b/src/db/db/dbMutableTexts.h new file mode 100644 index 000000000..a4f830970 --- /dev/null +++ b/src/db/db/dbMutableTexts.h @@ -0,0 +1,96 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2021 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_dbMutableTexts +#define HDR_dbMutableTexts + +#include "dbCommon.h" + +#include "dbAsIfFlatTexts.h" + +#include + +namespace db { + +/** + * @brief An interface representing mutable text collections + * + * Mutable text collections offer insert, transform, flatten and other manipulation functions. + */ +class DB_PUBLIC MutableTexts + : public AsIfFlatTexts +{ +public: + MutableTexts (); + MutableTexts (const MutableTexts &other); + virtual ~MutableTexts (); + + virtual void do_insert (const db::Text &text) = 0; + + virtual void do_transform (const db::Trans &t) = 0; + virtual void do_transform (const db::ICplxTrans &t) = 0; + + virtual void flatten () = 0; + + virtual void reserve (size_t n) = 0; + + void transform (const db::UnitTrans &) { } + void transform (const db::Disp &t) { do_transform (db::Trans (t)); } + void transform (const db::Trans &t) { do_transform (t); } + void transform (const db::ICplxTrans &t) { do_transform (t); } + + void insert (const db::Text &text) { do_insert (text); } + void insert (const db::Shape &shape); + + template + void insert (const db::Shape &shape, const T &trans) + { + if (shape.is_text ()) { + db::Text text = shape.text (); + text.transform (trans); + insert (text); + } + } + + template + void insert (const Iter &b, const Iter &e) + { + reserve (count () + (e - b)); + for (Iter i = b; i != e; ++i) { + insert (*i); + } + } + + template + void insert_seq (const Iter &seq) + { + for (Iter i = seq; ! i.at_end (); ++i) { + insert (*i); + } + } +}; + +} + +#endif + diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index f0e5d4ee8..33a564f0b 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -93,20 +93,20 @@ Texts::Texts (const RecursiveShapeIterator &si, DeepShapeStore &dss, const db::I template void Texts::insert (const Sh &shape) { - flat_texts ()->insert (shape); + mutable_texts ()->insert (shape); } template DB_PUBLIC void Texts::insert (const db::Text &); void Texts::insert (const db::Shape &shape) { - flat_texts ()->insert (shape); + mutable_texts ()->insert (shape); } template void Texts::insert (const db::Shape &shape, const T &trans) { - flat_texts ()->insert (shape, trans); + mutable_texts ()->insert (shape, trans); } template DB_PUBLIC void Texts::insert (const db::Shape &, const db::ICplxTrans &); @@ -120,13 +120,18 @@ void Texts::clear () void Texts::reserve (size_t n) { - flat_texts ()->reserve (n); + mutable_texts ()->reserve (n); +} + +void Texts::flatten () +{ + mutable_texts ()->flatten (); } template Texts &Texts::transform (const T &trans) { - flat_texts ()->transform (trans); + mutable_texts ()->transform (trans); return *this; } @@ -161,9 +166,9 @@ void Texts::set_delegate (TextsDelegate *delegate) } } -FlatTexts *Texts::flat_texts () +MutableTexts *Texts::mutable_texts () { - FlatTexts *texts = dynamic_cast (mp_delegate); + MutableTexts *texts = dynamic_cast (mp_delegate); if (! texts) { texts = new FlatTexts (); if (mp_delegate) { diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index 2f9266024..c5cf5eec3 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -36,7 +36,7 @@ namespace db { class TextFilterBase; -class FlatTexts; +class MutableTexts; class EmptyTexts; class Edges; class Region; @@ -491,10 +491,7 @@ public: * * This method will turn any edge pair collection into a flat one. */ - void flatten () - { - flat_texts (); - } + void flatten (); /** * @brief Returns true, if the text set has valid texts stored within itself @@ -616,7 +613,7 @@ private: TextsDelegate *mp_delegate; void set_delegate (TextsDelegate *delegate); - FlatTexts *flat_texts (); + MutableTexts *mutable_texts(); }; } From cefc2eeddb7079e9a1c2fe2e5b74f4c31d0aa42b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 29 Mar 2021 01:05:53 +0200 Subject: [PATCH 14/16] Bug fixes. --- src/db/db/dbDeepEdgePairs.cc | 8 ++++---- src/db/db/dbDeepEdges.cc | 8 ++++---- src/db/db/dbDeepRegion.cc | 12 ++++++++---- src/db/db/dbDeepTexts.cc | 12 ++++++++---- src/db/db/dbFlatRegion.cc | 7 ++++++- src/db/db/dbMutableTexts.cc | 4 +++- src/db/unit_tests/dbEdgesTests.cc | 2 +- testdata/drc/drcSimpleTests_au4.gds | Bin 10452 -> 10196 bytes testdata/ruby/dbEdgePairsTest.rb | 7 +++---- testdata/ruby/dbEdgesTest.rb | 7 +++---- testdata/ruby/dbRegionTest.rb | 8 ++++---- testdata/ruby/dbTextsTest.rb | 6 ++---- 12 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index 12d89df30..a0a53cf8f 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -173,9 +173,9 @@ static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ().transformed (t)); + flat_shapes.insert (iter->edge_pair ().transformed (iter.trans ()).transformed (t)); } layout.clear_layer (deep_layer.layer ()); @@ -208,9 +208,9 @@ void DeepEdgePairs::flatten () db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ()); + flat_shapes.insert (iter->edge_pair ().transformed (iter.trans ())); } layout.clear_layer (deep_layer ().layer ()); diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc index b2a299a9f..d93bef028 100644 --- a/src/db/db/dbDeepEdges.cc +++ b/src/db/db/dbDeepEdges.cc @@ -233,9 +233,9 @@ static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ().transformed (t)); + flat_shapes.insert (iter->edge ().transformed (iter.trans ()).transformed (t)); } layout.clear_layer (deep_layer.layer ()); @@ -268,9 +268,9 @@ void DeepEdges::flatten () db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ()); + flat_shapes.insert (iter->edge ().transformed (iter.trans ())); } layout.clear_layer (deep_layer ().layer ()); diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index 2d6aa848d..f126e08fd 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -242,9 +242,11 @@ static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ().transformed (t)); + db::Polygon poly; + iter->polygon (poly); + flat_shapes.insert (poly.transformed (iter.trans ()).transformed (t)); } layout.clear_layer (deep_layer.layer ()); @@ -277,9 +279,11 @@ void DeepRegion::flatten () db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ()); + db::Polygon poly; + iter->polygon (poly); + flat_shapes.insert (poly.transformed (iter.trans ())); } layout.clear_layer (deep_layer ().layer ()); diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index b46814961..85796f48b 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -193,9 +193,11 @@ static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer.layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ().transformed (t)); + db::Text text; + iter->text (text); + flat_shapes.insert (text.transformed (iter.trans ()).transformed (t)); } layout.clear_layer (deep_layer.layer ()); @@ -228,9 +230,11 @@ void DeepTexts::flatten () db::Cell &top_cell = layout.cell (*layout.begin_top_down ()); - db::Shapes flat_shapes; + db::Shapes flat_shapes (layout.is_editable ()); for (db::RecursiveShapeIterator iter (layout, top_cell, deep_layer ().layer ()); !iter.at_end (); ++iter) { - flat_shapes.insert (iter->polygon ()); + db::Text text; + iter->text (text); + flat_shapes.insert (db::TextRef (text.transformed (iter.trans ()), layout.shape_repository ())); } layout.clear_layer (deep_layer ().layer ()); diff --git a/src/db/db/dbFlatRegion.cc b/src/db/db/dbFlatRegion.cc index 71e240cd3..516fd8a31 100644 --- a/src/db/db/dbFlatRegion.cc +++ b/src/db/db/dbFlatRegion.cc @@ -413,9 +413,14 @@ void FlatRegion::do_insert (const db::Polygon &polygon) { if (polygon.holes () > 0 || polygon.vertices () > 0) { + + bool is_box = (empty () && polygon.is_box ()); + mp_polygons->insert (polygon); - m_is_merged = false; + set_is_merged (is_box); + invalidate_cache (); + } } diff --git a/src/db/db/dbMutableTexts.cc b/src/db/db/dbMutableTexts.cc index 8556fe4db..d8789e591 100644 --- a/src/db/db/dbMutableTexts.cc +++ b/src/db/db/dbMutableTexts.cc @@ -50,7 +50,9 @@ void MutableTexts::insert (const db::Shape &shape) { if (shape.is_text ()) { - insert (shape.text ()); + db::Text text; + shape.text (text); + insert (text); } } diff --git a/src/db/unit_tests/dbEdgesTests.cc b/src/db/unit_tests/dbEdgesTests.cc index 15740ccde..543b60704 100644 --- a/src/db/unit_tests/dbEdgesTests.cc +++ b/src/db/unit_tests/dbEdgesTests.cc @@ -55,7 +55,7 @@ TEST(1) EXPECT_EQ (r.bbox ().to_string (), "(0,0;100,200)"); EXPECT_EQ (r.transformed (db::Trans (db::Vector (1, 2))).bbox ().to_string (), "(1,2;101,202)"); EXPECT_EQ (r.empty (), false); - EXPECT_EQ (r.is_merged (), true); + EXPECT_EQ (r.is_merged (), false); EXPECT_EQ (r.begin ().at_end (), false); db::Edges r1 = r; diff --git a/testdata/drc/drcSimpleTests_au4.gds b/testdata/drc/drcSimpleTests_au4.gds index 843f2b33cb1a804446a586af9e2e2ab5c092b3a2..6920b9a33203d8bff74f49374c7f3a9c6241fead 100644 GIT binary patch delta 346 zcmZ|JF-yZh7zW_4m*g&kflaS;@IAcC|SiXf4SQc*Gme};pTi<|Je=@49; z#ihT%*&*&ha2NaoenAJ94iE3~!uxUMae0)HBq7eJ-4N=N9O@9!2F3s1QU6Bs=}tDmCkbI0;pWKo8{yZ0zX5p&0MZPnr~s&{Kr5xFk?0A(0l Ag8%>k delta 716 zcmZ{gze^lZ5XZmn?d@*Bizl93^j7b=_p1pf=t2m>vN=QuIZh1+%7J9bKcKc2IRYuP zQ|2md78_F}MX<3+n^ID2W#f5RB#edm2=-n?KrSgk>WqO8Ll zQuJ#?k%odXr22ChidKGJ0uqs^^K9?Qi!UcXpDv!w`bV`Y%;>$oPqnisz0~ID&={t; zX43`p>mB!*s=h-vx}Lme?x|_K8z+CdMDL9`3Z}~BPr2mrxi$TUJh4PSj3PDCW%AN4 z2~nbB;gV&!)DjN)Gkljrv}xHCFoR5mjxC404C@?CzALgeH8^kND)c(*kjSx~b;;*w z=4`6+xmu`@m3OF_XPv`qvrwdSp;4mX)+4{5JyZZZjz@s;E5MlN?^*^PypoK4l=|@7 zZe1M(R0GgrO20o0e~$o Date: Tue, 30 Mar 2021 23:20:39 +0200 Subject: [PATCH 15/16] Fixed some build issues that appeared during merge --- src/db/db/dbDeepEdgePairs.cc | 12 +++++++ src/db/db/dbDeepEdgePairs.h | 2 ++ src/db/db/dbDeepEdges.cc | 12 +++++++ src/db/db/dbDeepEdges.h | 2 ++ src/db/db/dbDeepRegion.cc | 12 +++++++ src/db/db/dbDeepRegion.h | 2 ++ src/db/db/dbDeepTexts.cc | 12 +++++++ src/db/db/dbDeepTexts.h | 2 ++ src/db/db/dbFlatEdgePairs.h | 10 ++++++ src/db/db/dbFlatEdges.h | 12 ++++++- src/db/db/dbFlatRegion.h | 10 ++++++ src/db/db/dbFlatTexts.h | 10 ++++++ src/db/db/dbGlyphs.h | 4 +-- src/db/db/dbMatrix.cc | 8 ++--- src/db/db/dbMatrix.h | 54 ++++++++++++++++++++++++++---- src/db/db/dbMutableEdgePairs.h | 4 +++ src/db/db/dbMutableEdges.h | 4 +++ src/db/db/dbMutableRegion.h | 4 +++ src/db/db/dbMutableTexts.h | 4 +++ src/db/db/dbText.h | 10 +++--- src/db/db/dbTexts.cc | 2 ++ src/db/db/dbTrans.h | 43 ++++++++++++++++++++++-- src/db/unit_tests/dbMatrixTests.cc | 8 ++--- 23 files changed, 218 insertions(+), 25 deletions(-) diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index a0a53cf8f..8792a6505 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -196,6 +196,18 @@ void DeepEdgePairs::do_transform (const db::ICplxTrans &t) invalidate_bbox (); } +void DeepEdgePairs::do_transform (const db::IMatrix2d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdgePairs::do_transform (const db::IMatrix3d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + void DeepEdgePairs::reserve (size_t) { // Not implemented for deep regions diff --git a/src/db/db/dbDeepEdgePairs.h b/src/db/db/dbDeepEdgePairs.h index af2d3d42d..75408ea94 100644 --- a/src/db/db/dbDeepEdgePairs.h +++ b/src/db/db/dbDeepEdgePairs.h @@ -54,6 +54,8 @@ public: virtual void do_transform (const db::Trans &t); virtual void do_transform (const db::ICplxTrans &t); + virtual void do_transform (const db::IMatrix2d &t); + virtual void do_transform (const db::IMatrix3d &t); virtual void flatten (); diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc index d93bef028..9660c10b9 100644 --- a/src/db/db/dbDeepEdges.cc +++ b/src/db/db/dbDeepEdges.cc @@ -256,6 +256,18 @@ void DeepEdges::do_transform (const db::ICplxTrans &t) invalidate_bbox (); } +void DeepEdges::do_transform (const db::IMatrix2d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepEdges::do_transform (const db::IMatrix3d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + void DeepEdges::reserve (size_t) { // Not implemented for deep regions diff --git a/src/db/db/dbDeepEdges.h b/src/db/db/dbDeepEdges.h index a0a01a429..08fbda231 100644 --- a/src/db/db/dbDeepEdges.h +++ b/src/db/db/dbDeepEdges.h @@ -55,6 +55,8 @@ public: virtual void do_transform (const db::Trans &t); virtual void do_transform (const db::ICplxTrans &t); + virtual void do_transform (const db::IMatrix2d &t); + virtual void do_transform (const db::IMatrix3d &t); virtual void flatten (); diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index f126e08fd..a4100ef19 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -267,6 +267,18 @@ void DeepRegion::do_transform (const db::ICplxTrans &t) invalidate_bbox (); } +void DeepRegion::do_transform (const db::IMatrix2d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepRegion::do_transform (const db::IMatrix3d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + void DeepRegion::reserve (size_t) { // Not implemented for deep regions diff --git a/src/db/db/dbDeepRegion.h b/src/db/db/dbDeepRegion.h index a2c0f06ef..38252620d 100644 --- a/src/db/db/dbDeepRegion.h +++ b/src/db/db/dbDeepRegion.h @@ -57,6 +57,8 @@ public: virtual void do_transform (const db::Trans &t); virtual void do_transform (const db::ICplxTrans &t); + virtual void do_transform (const db::IMatrix2d &t); + virtual void do_transform (const db::IMatrix3d &t); virtual void flatten (); diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index 85796f48b..9dd0361a9 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -218,6 +218,18 @@ void DeepTexts::do_transform (const db::ICplxTrans &t) invalidate_bbox (); } +void DeepTexts::do_transform (const db::IMatrix2d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + +void DeepTexts::do_transform (const db::IMatrix3d &t) +{ + transform_deep_layer (deep_layer (), t); + invalidate_bbox (); +} + void DeepTexts::reserve (size_t) { // Not implemented for deep regions diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 009b032eb..bda0cf452 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -55,6 +55,8 @@ public: virtual void do_transform (const db::Trans &t); virtual void do_transform (const db::ICplxTrans &t); + virtual void do_transform (const db::IMatrix2d &t); + virtual void do_transform (const db::IMatrix3d &t); virtual void flatten (); diff --git a/src/db/db/dbFlatEdgePairs.h b/src/db/db/dbFlatEdgePairs.h index 93db999a4..f5ef4ffdd 100644 --- a/src/db/db/dbFlatEdgePairs.h +++ b/src/db/db/dbFlatEdgePairs.h @@ -94,6 +94,16 @@ public: transform_generic (t); } + virtual void do_transform (const db::IMatrix2d &t) + { + transform_generic (t); + } + + virtual void do_transform (const db::IMatrix3d &t) + { + transform_generic (t); + } + virtual void flatten () { } db::Shapes &raw_edge_pairs () { return *mp_edge_pairs; } diff --git a/src/db/db/dbFlatEdges.h b/src/db/db/dbFlatEdges.h index c93e926db..384250641 100644 --- a/src/db/db/dbFlatEdges.h +++ b/src/db/db/dbFlatEdges.h @@ -99,7 +99,17 @@ public: transform_generic (t); } - void do_transform(const db::ICplxTrans &t) + void do_transform (const db::ICplxTrans &t) + { + transform_generic (t); + } + + void do_transform (const db::IMatrix2d &t) + { + transform_generic (t); + } + + void do_transform (const db::IMatrix3d &t) { transform_generic (t); } diff --git a/src/db/db/dbFlatRegion.h b/src/db/db/dbFlatRegion.h index 09095cc0b..1f0e2fc93 100644 --- a/src/db/db/dbFlatRegion.h +++ b/src/db/db/dbFlatRegion.h @@ -109,6 +109,16 @@ public: transform_generic (t); } + virtual void do_transform (const db::IMatrix2d &t) + { + transform_generic (t); + } + + virtual void do_transform (const db::IMatrix3d &t) + { + transform_generic (t); + } + void flatten () { } db::Shapes &raw_polygons () { return *mp_polygons; } diff --git a/src/db/db/dbFlatTexts.h b/src/db/db/dbFlatTexts.h index ab7378d71..0b4839226 100644 --- a/src/db/db/dbFlatTexts.h +++ b/src/db/db/dbFlatTexts.h @@ -97,6 +97,16 @@ public: transform_generic (t); } + virtual void do_transform (const db::IMatrix2d &t) + { + transform_generic (t); + } + + virtual void do_transform (const db::IMatrix3d &t) + { + transform_generic (t); + } + db::Shapes &raw_texts () { return *mp_texts; } const db::Shapes &raw_texts () const { return *mp_texts; } diff --git a/src/db/db/dbGlyphs.h b/src/db/db/dbGlyphs.h index 2fa6d69a2..acb1dd49c 100644 --- a/src/db/db/dbGlyphs.h +++ b/src/db/db/dbGlyphs.h @@ -121,13 +121,13 @@ public: * @param char_spacing Additional spacing between the lines in µm * @param The resulting polygons will be put here (the vector will be cleared before) */ - void text (const std::string &t, double target_dbu, double mag, bool inv, double bias, double char_spacing, double line_spacing, std::vector &polygons) const; + void text (const std::string &t, double target_dbu, double mag2, bool inv, double bias, double char_spacing, double line_spacing, std::vector &polygons) const; /** * @brief Creates the given text as a region * For the parameters see "text" */ - db::Region text_as_region (const std::string &t, double target_dbu, double mag, bool inv, double bias, double char_spacing, double line_spacing) const; + db::Region text_as_region (const std::string &t, double target_dbu, double mag2, bool inv, double bias, double char_spacing, double line_spacing) const; /** * @brief Gets the glyph for a given character diff --git a/src/db/db/dbMatrix.cc b/src/db/db/dbMatrix.cc index 5c78bb240..20c492d6f 100644 --- a/src/db/db/dbMatrix.cc +++ b/src/db/db/dbMatrix.cc @@ -46,7 +46,7 @@ matrix_2d::to_string () const template std::pair -matrix_2d::mag () const +matrix_2d::mag2 () const { double s1 = sqrt (m_m11 * m_m11 + m_m21 * m_m21); double s2 = sqrt (m_m12 * m_m12 + m_m22 * m_m22); @@ -65,7 +65,7 @@ template double matrix_2d::angle () const { - std::pair m = mag (); + std::pair m = mag2 (); double u1 = m.first; double u2 = is_mirror () ? -m.second : m.second; double n11 = m_m11 / u1; @@ -98,7 +98,7 @@ template bool matrix_2d::has_shear () const { - std::pair m = mag (); + std::pair m = mag2 (); double u1 = m.first; double u2 = is_mirror () ? -m.second : m.second; double n11 = m_m11 / u1; @@ -114,7 +114,7 @@ template double matrix_2d::shear_angle () const { - std::pair m = mag (); + std::pair m = mag2 (); double u1 = m.first; double u2 = is_mirror () ? -m.second : m.second; double n11 = m_m11 / u1; diff --git a/src/db/db/dbMatrix.h b/src/db/db/dbMatrix.h index 8e5ab74c2..835134228 100644 --- a/src/db/db/dbMatrix.h +++ b/src/db/db/dbMatrix.h @@ -309,7 +309,15 @@ public: * into the geometrical base transformations. This member returns the x and y magnification * components. The order of the execution is mirror, magnification, shear and rotation. */ - std::pair mag () const; + std::pair mag2 () const; + + /** + * @brief For compatibility with other transformations + */ + double mag () const + { + return mag2 ().first; + } /** * @brief Return the x magnification component of the matrix @@ -320,7 +328,7 @@ public: */ double mag_x () const { - return mag ().first; + return mag2 ().first; } /** @@ -332,7 +340,7 @@ public: */ double mag_y () const { - return mag ().second; + return mag2 ().second; } /** @@ -356,6 +364,22 @@ public: return matrix_2d (m, 0.0, 0.0, m); } + /** + * @brief A dummy displacement accessor (matrix2d does not have a displacement) + */ + db::vector disp () const + { + return db::vector (); + } + + /** + * @brief For compatibility with other transformations + */ + coord_type ctrans (coord_type c) const + { + return db::coord_traits::rounded (mag2 ().first * c); + } + /** * @brief Return the mirror component of the matrix * @@ -759,9 +783,25 @@ public: * into the geometrical base transformations. This member returns the magnification * component for both x and y direction (anisotropic magnification). The order of the execution is mirror, magnification, shear, rotation, perspective and displacement. */ - std::pair mag () const + std::pair mag2 () const { - return m2d ().mag (); + return m2d ().mag2 (); + } + + /** + * @brief For compatibility with other transformations + */ + double mag () const + { + return mag2 ().first; + } + + /** + * @brief For compatibility with other transformations + */ + coord_type ctrans (coord_type c) const + { + return db::coord_traits::rounded (mag2 ().first * c); } /** @@ -769,7 +809,7 @@ public: */ double mag_x () const { - return mag ().first; + return mag2 ().first; } /** @@ -777,7 +817,7 @@ public: */ double mag_y () const { - return mag ().second; + return mag2 ().second; } /** diff --git a/src/db/db/dbMutableEdgePairs.h b/src/db/db/dbMutableEdgePairs.h index 2b5785155..854d61eb0 100644 --- a/src/db/db/dbMutableEdgePairs.h +++ b/src/db/db/dbMutableEdgePairs.h @@ -49,6 +49,8 @@ public: virtual void do_transform (const db::Trans &t) = 0; virtual void do_transform (const db::ICplxTrans &t) = 0; + virtual void do_transform (const db::IMatrix2d &t) = 0; + virtual void do_transform (const db::IMatrix3d &t) = 0; virtual void flatten () = 0; @@ -58,6 +60,8 @@ public: void transform (const db::Disp &t) { do_transform (db::Trans (t)); } void transform (const db::Trans &t) { do_transform (t); } void transform (const db::ICplxTrans &t) { do_transform (t); } + void transform (const db::IMatrix2d &t) { do_transform (t); } + void transform (const db::IMatrix3d &t) { do_transform (t); } void insert (const db::EdgePair &edge_pair) { do_insert (edge_pair); } void insert (const db::Shape &shape); diff --git a/src/db/db/dbMutableEdges.h b/src/db/db/dbMutableEdges.h index 2832901f1..e24bd57e2 100644 --- a/src/db/db/dbMutableEdges.h +++ b/src/db/db/dbMutableEdges.h @@ -47,6 +47,8 @@ public: virtual void do_transform (const db::Trans &t) = 0; virtual void do_transform (const db::ICplxTrans &t) = 0; + virtual void do_transform (const db::IMatrix2d &t) = 0; + virtual void do_transform (const db::IMatrix3d &t) = 0; virtual void flatten () = 0; @@ -58,6 +60,8 @@ public: void transform (const db::Disp &t) { do_transform (db::Trans (t)); } void transform (const db::Trans &t) { do_transform (t); } void transform (const db::ICplxTrans &t) { do_transform (t); } + void transform (const db::IMatrix2d &t) { do_transform (t); } + void transform (const db::IMatrix3d &t) { do_transform (t); } void insert (const db::Edge &edge) { do_insert (edge); } void insert (const db::Box &box); diff --git a/src/db/db/dbMutableRegion.h b/src/db/db/dbMutableRegion.h index 3863e3f19..68c90a45b 100644 --- a/src/db/db/dbMutableRegion.h +++ b/src/db/db/dbMutableRegion.h @@ -51,9 +51,13 @@ public: void transform (const db::Disp &t) { do_transform (db::Trans (t)); } void transform (const db::Trans &t) { do_transform (t); } void transform (const db::ICplxTrans &t) { do_transform (t); } + void transform (const db::IMatrix2d &t) { do_transform (t); } + void transform (const db::IMatrix3d &t) { do_transform (t); } virtual void do_transform (const db::Trans &t) = 0; virtual void do_transform (const db::ICplxTrans &t) = 0; + virtual void do_transform (const db::IMatrix2d &t) = 0; + virtual void do_transform (const db::IMatrix3d &t) = 0; virtual void flatten () = 0; diff --git a/src/db/db/dbMutableTexts.h b/src/db/db/dbMutableTexts.h index a4f830970..eadd63129 100644 --- a/src/db/db/dbMutableTexts.h +++ b/src/db/db/dbMutableTexts.h @@ -49,6 +49,8 @@ public: virtual void do_transform (const db::Trans &t) = 0; virtual void do_transform (const db::ICplxTrans &t) = 0; + virtual void do_transform (const db::IMatrix3d &t) = 0; + virtual void do_transform (const db::IMatrix2d &t) = 0; virtual void flatten () = 0; @@ -58,6 +60,8 @@ public: void transform (const db::Disp &t) { do_transform (db::Trans (t)); } void transform (const db::Trans &t) { do_transform (t); } void transform (const db::ICplxTrans &t) { do_transform (t); } + void transform (const db::IMatrix3d &t) { do_transform (t); } + void transform (const db::IMatrix2d &t) { do_transform (t); } void insert (const db::Text &text) { do_insert (text); } void insert (const db::Shape &shape); diff --git a/src/db/db/dbText.h b/src/db/db/dbText.h index 20eab0e0f..5d11d9511 100644 --- a/src/db/db/dbText.h +++ b/src/db/db/dbText.h @@ -688,7 +688,8 @@ public: text &transform (const Tr &t) { typedef typename Tr::target_coord_type target_coord_type; - m_trans = simple_trans ((t.fp_trans () * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()); + fixpoint_trans fp (t); + m_trans = simple_trans ((fp * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()); m_size = t.ctrans (m_size); return *this; } @@ -708,13 +709,14 @@ public: text transformed (const Tr &t) const { typedef typename Tr::target_coord_type target_coord_type; + fixpoint_trans fp (t); size_t p = (size_t) mp_ptr; if (p & 1) { - return text (reinterpret_cast (p - 1), simple_trans ((t.fp_trans () * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); + return text (reinterpret_cast (p - 1), simple_trans ((fp * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); } else if (mp_ptr) { - return text (mp_ptr, simple_trans ((t.fp_trans () * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); + return text (mp_ptr, simple_trans ((fp * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); } else { - return text (simple_trans ((t.fp_trans () * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); + return text (simple_trans ((fp * m_trans.fp_trans ()).rot (), t (point_type () + m_trans.disp ()) - point ()), t.ctrans (m_size), m_font, m_halign, m_valign); } } diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 33a564f0b..8bf367a23 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -112,6 +112,8 @@ void Texts::insert (const db::Shape &shape, const T &trans) template DB_PUBLIC void Texts::insert (const db::Shape &, const db::ICplxTrans &); template DB_PUBLIC void Texts::insert (const db::Shape &, const db::Trans &); template DB_PUBLIC void Texts::insert (const db::Shape &, const db::Disp &); +template DB_PUBLIC void Texts::insert (const db::Shape &, const db::IMatrix2d &); +template DB_PUBLIC void Texts::insert (const db::Shape &, const db::IMatrix3d &); void Texts::clear () { diff --git a/src/db/db/dbTrans.h b/src/db/db/dbTrans.h index db83aa018..7b11e57ba 100644 --- a/src/db/db/dbTrans.h +++ b/src/db/db/dbTrans.h @@ -376,6 +376,32 @@ public: // .. nothing else .. } + /** + * @brief Reduction + */ + template + explicit fixpoint_trans (const T &t) + : m_f (0) + { + *this = t.fp_trans (); + } + + /** + * @brief Reduction from a matrix2d + */ + explicit fixpoint_trans (const db::matrix_2d &t) + { + m_f = ((int (floor (t.angle () / 90.0 + 0.5) + 4)) % 4) + (t.is_mirror () ? 4 : 0); + } + + /** + * @brief Reduction from a matrix3d + */ + explicit fixpoint_trans (const db::matrix_3d &t) + { + m_f = ((int (floor (t.angle () / 90.0 + 0.5) + 4)) % 4) + (t.is_mirror () ? 4 : 0); + } + /** * @brief Returns true, if the transformation is unity */ @@ -420,7 +446,18 @@ public: // .. nothing else .. } - /** + /** + * @brief The standard constructor using a code rather than angle and mirror and no displacement + * + * @param f The rotation/mirror code (r0 .. m135 constants) + */ + explicit fixpoint_trans (unsigned int f) + : m_f (f) + { + // .. nothing else .. + } + + /** * @brief The rotation/mirror codes */ static const int r0 = 0; // No rotation @@ -1651,7 +1688,7 @@ public: { tl_assert (! m.has_shear ()); tl_assert (! m.has_perspective ()); - std::pair mag = m.mag (); + std::pair mag = m.mag2 (); tl_assert (fabs (mag.first - mag.second) < 1e-10); double rot = m.angle () * M_PI / 180.0; m_mag = m.is_mirror () ? -mag.first : mag.first; @@ -1674,7 +1711,7 @@ public: : m_u (u) { tl_assert (! m.has_shear ()); - std::pair mag = m.mag (); + std::pair mag = m.mag2 (); tl_assert (fabs (mag.first - mag.second) < 1e-10); double rot = m.angle () * M_PI / 180.0; m_mag = m.is_mirror () ? -mag.first : mag.first; diff --git a/src/db/unit_tests/dbMatrixTests.cc b/src/db/unit_tests/dbMatrixTests.cc index 605b41f04..d64e249a2 100644 --- a/src/db/unit_tests/dbMatrixTests.cc +++ b/src/db/unit_tests/dbMatrixTests.cc @@ -114,10 +114,10 @@ TEST(1) EXPECT_EQ (tl::to_string (db::Matrix2d::shear (17).has_shear ()), "true"); EXPECT_EQ (tl::to_string (db::Matrix2d::shear (40).shear_angle ()), "40"); EXPECT_EQ (tl::to_string (db::Matrix2d::shear (-40).shear_angle ()), "-40"); - EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (17.5).inverted ().mag ().first), "17.5"); - EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (17.5).inverted ().mag ().second), "17.5"); - EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (27.5, 7.5).inverted ().mag ().first), "27.5"); - EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (27.5, 7.5).inverted ().mag ().second), "7.5"); + EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (17.5).inverted ().mag2 ().first), "17.5"); + EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (17.5).inverted ().mag2 ().second), "17.5"); + EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (27.5, 7.5).inverted ().mag2 ().first), "27.5"); + EXPECT_EQ (tl::to_string (1.0 / db::Matrix2d::mag (27.5, 7.5).inverted ().mag2 ().second), "7.5"); EXPECT_EQ (tl::to_string (db::Matrix2d::mirror (true).inverted ().is_mirror ()), "true"); EXPECT_EQ (tl::to_string (db::Matrix2d::mirror (false).inverted ().is_mirror ()), "false"); EXPECT_EQ (tl::to_string (db::Matrix2d::rotation (25).inverted ().angle ()), "-25"); From e66c8046db97a0e436c0b7af5f33016bfc74d200 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 31 Mar 2021 23:04:35 +0200 Subject: [PATCH 16/16] Limited lvs stack depth to 500 to prevent stack overflow. --- src/db/db/dbNetlistCompare.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 49fef7934..4efcd9871 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -2031,7 +2031,7 @@ NetGraph::derive_node_identities_for_edges (NetGraphNode::edge_iterator e, NetGr } - // propagate pairing in picky mode: this means we only accept exact a match if the node set + // propagate pairing in picky mode: this means we only accept a match if the node set // is exactly identical and no ambiguous nodes are present when ambiguous nodes are forbidden size_t bt_count = derive_node_identities_from_node_set (nodes, other_nodes, depth, n_branch, tentative, data); @@ -2828,7 +2828,13 @@ NetlistComparer::NetlistComparer (NetlistCompareLogger *logger) m_cap_threshold = -1.0; // not set m_res_threshold = -1.0; // not set - m_max_depth = std::numeric_limits::max (); + // NOTE: as the backtracking algorithm is recursive, we need to limit the number of steps to follow + // Long chains can happen in case of depth-first because the backtracking algorithm will follow + // each successful path further to the very end. Depending on the circuit's complexity a long chain of + // jumps is possible leading to a deep stack. A value of 500 is compatible with 4M stack depth on a + // 64bit machine which is considered acceptable for now. + m_max_depth = 500; + m_max_n_branch = std::numeric_limits::max (); m_depth_first = true;