From a6dce8c2ad69de958290c7bcdf3031692972f5f9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 2 Apr 2026 22:14:11 +0200 Subject: [PATCH] Improving Layout::cleanup to consolidate library and cold proxies and to establish proper cell names if possible --- src/db/db/dbColdProxy.cc | 22 ++ src/db/db/dbColdProxy.h | 10 + src/db/db/dbLayout.cc | 276 ++++++++++++++++++++++---- src/db/db/dbLayout.h | 28 ++- src/db/db/dbLibraryProxy.cc | 17 +- src/db/db/dbLibraryProxy.h | 4 +- src/db/unit_tests/dbLibrariesTests.cc | 16 +- testdata/libman/design_au1.gds | Bin 13144 -> 7124 bytes testdata/libman/design_au2.gds | Bin 9038 -> 6990 bytes testdata/libman/design_au3.gds | Bin 8300 -> 6990 bytes testdata/libman/design_au4.gds | Bin 8300 -> 6990 bytes testdata/libman/design_au5.gds | Bin 8300 -> 6990 bytes 12 files changed, 323 insertions(+), 50 deletions(-) diff --git a/src/db/db/dbColdProxy.cc b/src/db/db/dbColdProxy.cc index 486e84688..08da24b28 100644 --- a/src/db/db/dbColdProxy.cc +++ b/src/db/db/dbColdProxy.cc @@ -60,14 +60,36 @@ ColdProxy::ColdProxy (db::cell_index_type ci, db::Layout &layout, const LayoutOr } i->second->push_back (this); } + + layout.register_cold_proxy (this); } ColdProxy::~ColdProxy () { + if (layout ()) { + layout ()->unregister_cold_proxy (this); + } + delete mp_context_info; mp_context_info = 0; } +void +ColdProxy::unregister () +{ + if (layout ()) { + layout ()->unregister_cold_proxy (this); + } +} + +void +ColdProxy::reregister () +{ + if (layout ()) { + layout ()->register_cold_proxy (this); + } +} + Cell * ColdProxy::clone (Layout &layout) const { diff --git a/src/db/db/dbColdProxy.h b/src/db/db/dbColdProxy.h index 69fb84511..8d6d2fcd3 100644 --- a/src/db/db/dbColdProxy.h +++ b/src/db/db/dbColdProxy.h @@ -81,6 +81,16 @@ public: return true; } + /** + * @brief Reimplemented from Cell: unregisters the proxy at the layout + */ + virtual void unregister (); + + /** + * @brief Reimplemented from Cell: reregisters the proxy at the layout + */ + virtual void reregister (); + /** * @brief Gets a list of cold proxies for a given library name */ diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 9411dcef3..977aa82f7 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -337,6 +337,37 @@ private: // ----------------------------------------------------------------- // Implementation of the ProxyContextInfo class +bool +LayoutOrCellContextInfo::operator== (const LayoutOrCellContextInfo &other) const +{ + return lib_name == other.lib_name && + cell_name == other.cell_name && + pcell_name == other.pcell_name && + pcell_parameters == other.pcell_parameters && + meta_info == other.meta_info; +} + +bool +LayoutOrCellContextInfo::operator< (const LayoutOrCellContextInfo &other) const +{ + if (lib_name != other.lib_name) { + return lib_name < other.lib_name; + } + if (cell_name != other.cell_name) { + return cell_name < other.cell_name; + } + if (pcell_name != other.pcell_name) { + return pcell_name < other.pcell_name; + } + if (pcell_parameters != other.pcell_parameters) { + return pcell_parameters < other.pcell_parameters; + } + if (meta_info != other.meta_info) { + return meta_info < other.meta_info; + } + return false; +} + LayoutOrCellContextInfo LayoutOrCellContextInfo::deserialize (std::vector::const_iterator from, std::vector::const_iterator to) { @@ -540,6 +571,7 @@ Layout::clear () m_pcell_ids.clear (); m_lib_proxy_map.clear (); + m_cold_proxy_map.clear (); m_meta_info.clear (); } @@ -568,6 +600,7 @@ Layout::operator= (const Layout &d) } m_lib_proxy_map = d.m_lib_proxy_map; + m_cold_proxy_map = d.m_cold_proxy_map; m_cell_ptrs.resize (d.m_cell_ptrs.size (), 0); @@ -809,6 +842,7 @@ Layout::mem_stat (MemStatistics *stat, MemStatistics::purpose_t purpose, int cat db::mem_stat (stat, purpose, cat, m_pcells, true, (void *) this); db::mem_stat (stat, purpose, cat, m_pcell_ids, true, (void *) this); db::mem_stat (stat, purpose, cat, m_lib_proxy_map, true, (void *) this); + db::mem_stat (stat, purpose, cat, m_cold_proxy_map, true, (void *) this); db::mem_stat (stat, purpose, cat, m_meta_info, true, (void *) this); db::mem_stat (stat, purpose, cat, m_shape_repository, true, (void *) this); db::mem_stat (stat, purpose, cat, m_array_repository, true, (void *) this); @@ -1505,6 +1539,11 @@ Layout::register_cell_name (const char *name, cell_index_type ci) void Layout::rename_cell (cell_index_type id, const char *name) { + static const char *anonymous_name = ""; + if (! name) { + name = anonymous_name; + } + tl_assert (id < m_cell_names.size ()); if (strcmp (m_cell_names [id], name) != 0) { @@ -1521,7 +1560,10 @@ Layout::rename_cell (cell_index_type id, const char *name) delete [] m_cell_names [id]; m_cell_names [id] = cp; - m_cell_map.insert (std::make_pair (cp, id)); + // NOTE: anonymous cells (empty name string) are not registered in the cell name map + if (*cp != 0) { + m_cell_map.insert (std::make_pair (cp, id)); + } // to enforce a redraw and a rebuild cell_name_changed (); @@ -1690,27 +1732,119 @@ Layout::cleanup (const std::set &keep) return; } + if (tl::verbosity () >= 30) { + tl::info << "Cleaning up layout .."; + } + + // Do some polishing of the proxies - sometimes, specifically when resolving indirect library references, + // different proxies to the same library object exist. We can identify them and clean them up, so there + // is a single reference. We can also try to ensure that cell names reflect the library cell names. + // The latter is good for LVS for example. + + { + db::LayoutLocker locker (this); + + // join library proxies pointing to the same object + + for (auto c = m_lib_proxy_map.begin (); c != m_lib_proxy_map.end (); ) { + + auto c0 = c++; + size_t n = 1; + while (c != m_lib_proxy_map.end () && c->first == c0->first) { + ++n; + ++c; + } + + if (n > 1) { + auto cc = c0; + ++cc; + while (cc != c) { + if (keep.find (cc->second) == keep.end ()) { + if (tl::verbosity () >= 30) { + tl::info << "Joining lib proxy " << cell_name (cc->second) << " into " << cell_name (c0->second); + } + replace_instances_of (cc->second, c0->second); + } + ++cc; + } + } + + } + + // join cold proxies pointing to the same object + + for (auto c = m_cold_proxy_map.begin (); c != m_cold_proxy_map.end (); ) { + + auto c0 = c++; + size_t n = 1; + while (c != m_cold_proxy_map.end () && c->first == c0->first) { + ++n; + ++c; + } + + if (n > 1) { + auto cc = c0; + ++cc; + while (cc != c) { + if (keep.find (cc->second) == keep.end ()) { + if (tl::verbosity () >= 30) { + tl::info << "Joining cold proxy " << cell_name (cc->second) << " into " << cell_name (c0->second); + } + replace_instances_of (cc->second, c0->second); + } + ++cc; + } + } + + } + + } + + std::set cells_to_delete; + // deleting cells may create new top cells which need to be deleted as well, hence we iterate // until there are no more cells to delete while (true) { // delete all cells that are top cells and are proxies. Those cells are proxies no longer required. - std::set cells_to_delete; for (top_down_iterator c = begin_top_down (); c != end_top_cells (); ++c) { - if (cell (*c).is_proxy ()) { + if (cell (*c).is_proxy () && keep.find (*c) == keep.end ()) { cells_to_delete.insert (*c); } } - for (std::set::const_iterator k = keep.begin (); k != keep.end (); ++k) { - cells_to_delete.erase (*k); - } - if (cells_to_delete.empty ()) { break; } delete_cells (cells_to_delete); + cells_to_delete.clear (); + + } + + // Try to ensure that cell names reflect the library cell names. The latter is good for LVS for example. + + for (auto c = m_lib_proxy_map.begin (); c != m_lib_proxy_map.end (); ++c) { + + std::string bn = cell (c->second).get_basic_name (); + if (bn != cell_name (c->second) && ! cell_by_name (bn.c_str ()).first) { + if (tl::verbosity () >= 30) { + tl::info << "Renaming lib proxy " << cell_name (c->second) << " to " << bn; + } + rename_cell (c->second, bn.c_str ()); + } + + } + + for (auto c = m_cold_proxy_map.begin (); c != m_cold_proxy_map.end (); ++c) { + + std::string bn = cell (c->second).get_basic_name (); + if (bn != cell_name (c->second) && ! cell_by_name (bn.c_str ()).first) { + if (tl::verbosity () >= 30) { + tl::info << "Renaming cold proxy " << cell_name (c->second) << " to " << bn; + } + rename_cell (c->second, bn.c_str ()); + } } } @@ -3151,13 +3285,32 @@ Layout::variant_name (cell_index_type cell_index) const void Layout::register_lib_proxy (db::LibraryProxy *lib_proxy) { - m_lib_proxy_map.insert (std::make_pair (std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()), lib_proxy->Cell::cell_index ())); + auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()); + + auto l = m_lib_proxy_map.find (key); + while (l != m_lib_proxy_map.end () && l->first == key) { + if (l->second == lib_proxy->Cell::cell_index ()) { + return; + } + ++l; + } + + m_lib_proxy_map.insert (std::make_pair (key, lib_proxy->Cell::cell_index ())); } void Layout::unregister_lib_proxy (db::LibraryProxy *lib_proxy) { - m_lib_proxy_map.erase (std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ())); + auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()); + + auto l = m_lib_proxy_map.find (key); + while (l != m_lib_proxy_map.end () && l->first == key) { + if (l->second == lib_proxy->Cell::cell_index ()) { + m_lib_proxy_map.erase (l); + break; + } + ++l; + } } void @@ -3177,9 +3330,13 @@ Layout::get_lib_proxy_as (Library *lib, cell_index_type cell_index, cell_index_t cell_index_type Layout::get_lib_proxy (Library *lib, cell_index_type cell_index) { - lib_proxy_map::const_iterator lp = m_lib_proxy_map.find (std::make_pair (lib->get_id (), cell_index)); - if (lp != m_lib_proxy_map.end ()) { + auto key = std::make_pair (lib->get_id (), cell_index); + + lib_proxy_map::const_iterator lp = m_lib_proxy_map.find (key); + if (lp != m_lib_proxy_map.end () && lp->first == key) { + return lp->second; + } else { // create a new unique name @@ -3210,35 +3367,82 @@ Layout::get_lib_proxy (Library *lib, cell_index_type cell_index) } } +void +Layout::register_cold_proxy (db::ColdProxy *cold_proxy) +{ + auto l = m_cold_proxy_map.find (cold_proxy->context_info ()); + while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) { + if (l->second == cold_proxy->Cell::cell_index ()) { + return; + } + ++l; + } + + m_cold_proxy_map.insert (std::make_pair (cold_proxy->context_info (), cold_proxy->Cell::cell_index ())); +} + +void +Layout::unregister_cold_proxy (db::ColdProxy *cold_proxy) +{ + auto l = m_cold_proxy_map.find (cold_proxy->context_info ()); + while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) { + if (l->second == cold_proxy->Cell::cell_index ()) { + m_cold_proxy_map.erase (l); + break; + } + ++l; + } +} + +std::pair +Layout::find_cold_proxy (const db::LayoutOrCellContextInfo &info) +{ + cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info); + if (lp != m_cold_proxy_map.end () && lp->first == info) { + return std::make_pair (true, lp->second); + } else { + return std::make_pair (false, 0); + } +} + cell_index_type Layout::create_cold_proxy (const db::LayoutOrCellContextInfo &info) { - // create a new unique name - std::string b; - if (! info.cell_name.empty ()) { - b = info.cell_name; - } else if (! info.pcell_name.empty ()) { - b = info.pcell_name; + cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info); + if (lp != m_cold_proxy_map.end () && lp->first == info) { + + return lp->second; + + } else { + + // create a new unique name + std::string b; + if (! info.cell_name.empty ()) { + b = info.cell_name; + } else if (! info.pcell_name.empty ()) { + b = info.pcell_name; + } + if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) { + b = uniquify_cell_name (b.c_str ()); + } + + // create a new cell (a LibraryProxy) + cell_index_type new_index = allocate_new_cell (); + + ColdProxy *proxy = new ColdProxy (new_index, *this, info); + m_cells.push_back_ptr (proxy); + m_cell_ptrs [new_index] = proxy; + + // enter its index and cell_name + register_cell_name (b.c_str (), new_index); + + if (manager () && manager ()->transacting ()) { + manager ()->queue (this, new NewRemoveCellOp (new_index, m_cell_names [new_index], false /*new*/, 0)); + } + + return new_index; + } - if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) { - b = uniquify_cell_name (b.c_str ()); - } - - // create a new cell (a LibraryProxy) - cell_index_type new_index = allocate_new_cell (); - - ColdProxy *proxy = new ColdProxy (new_index, *this, info); - m_cells.push_back_ptr (proxy); - m_cell_ptrs [new_index] = proxy; - - // enter its index and cell_name - register_cell_name (b.c_str (), new_index); - - if (manager () && manager ()->transacting ()) { - manager ()->queue (this, new NewRemoveCellOp (new_index, m_cell_names [new_index], false /*new*/, 0)); - } - - return new_index; } void diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index e68b5746c..b9bc0f1c9 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -62,6 +62,7 @@ class PCellDeclaration; class PCellHeader; class Library; class LibraryProxy; +class ColdProxy; class CellMapping; class LayerMapping; class Region; @@ -427,6 +428,9 @@ struct DB_PUBLIC LayoutOrCellContextInfo std::map pcell_parameters; std::map > meta_info; + bool operator== (const LayoutOrCellContextInfo &other) const; + bool operator< (const LayoutOrCellContextInfo &other) const; + static LayoutOrCellContextInfo deserialize (std::vector::const_iterator from, std::vector::const_iterator to); void serialize (std::vector &strings); @@ -469,7 +473,8 @@ public: typedef db::pcell_id_type pcell_id_type; typedef std::map pcell_name_map; typedef pcell_name_map::const_iterator pcell_iterator; - typedef std::map, cell_index_type> lib_proxy_map; + typedef std::multimap, cell_index_type> lib_proxy_map; + typedef std::multimap cold_proxy_map; typedef LayerIterator layer_iterator; typedef size_t meta_info_name_id_type; typedef std::map meta_info_map; @@ -1003,6 +1008,12 @@ public: */ void get_lib_proxy_as (Library *lib, cell_index_type cell_index, cell_index_type target_cell_index, ImportLayerMapping *layer_mapping = 0, bool retain_layout = false); + /** + * @brief Find an existing cold proxy for a given context + * @return A pair of success flag and cell index of the proxy + */ + std::pair find_cold_proxy (const db::LayoutOrCellContextInfo &info); + /** * @brief Creates a cold proxy representing the given context information */ @@ -1839,6 +1850,20 @@ public: */ void unregister_lib_proxy (db::LibraryProxy *lib_proxy); + /** + * @brief Register a cold proxy + * + * This method is used by ColdProxy to register itself. + */ + void register_cold_proxy (db::ColdProxy *cold_proxy); + + /** + * @brief Unregister a cold proxy + * + * This method is used by ColdProxy to unregister itself. + */ + void unregister_cold_proxy (db::ColdProxy *cold_proxy); + /** * @brief Gets the editable status of this layout * @@ -2153,6 +2178,7 @@ private: std::vector m_pcells; pcell_name_map m_pcell_ids; lib_proxy_map m_lib_proxy_map; + cold_proxy_map m_cold_proxy_map; bool m_do_cleanup; bool m_editable; std::map m_meta_info_name_map; diff --git a/src/db/db/dbLibraryProxy.cc b/src/db/db/dbLibraryProxy.cc index cd93e048c..ce299c888 100644 --- a/src/db/db/dbLibraryProxy.cc +++ b/src/db/db/dbLibraryProxy.cc @@ -221,14 +221,25 @@ LibraryProxy::update (db::ImportLayerMapping *layer_mapping) real_lib = db::LibraryManager::instance ().lib (lp->lib_id ()); } - inst.object ().cell_index (layout ()->get_lib_proxy (real_lib, real_cil)); - ColdProxy *cp = dynamic_cast (&real_lib->layout ().cell (real_cil)); if (cp) { + // The final item is a cold proxy ("" cell) - treat it like // a library proxy as it may become one in the future, but replace it // by a cold proxy now. - layout ()->create_cold_proxy_as (cp->context_info (), inst.object ().cell_index ()); + + auto p = layout ()->find_cold_proxy (cp->context_info ()); + if (p.first) { + // reuse existing proxy + inst.object ().cell_index (p.second); + } else { + // create a new proxy, reusing the first library proxy's replica + inst.object ().cell_index (layout ()->get_lib_proxy (real_lib, real_cil)); + layout ()->create_cold_proxy_as (cp->context_info (), inst.object ().cell_index ()); + } + + } else { + inst.object ().cell_index (layout ()->get_lib_proxy (real_lib, real_cil)); } inst.transform_into (db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ())); diff --git a/src/db/db/dbLibraryProxy.h b/src/db/db/dbLibraryProxy.h index 233348daa..1e18d206b 100644 --- a/src/db/db/dbLibraryProxy.h +++ b/src/db/db/dbLibraryProxy.h @@ -129,12 +129,12 @@ public: /** * @brief Reimplemented from Cell: unregisters the proxy at the layout */ - void unregister (); + virtual void unregister (); /** * @brief Reimplemented from Cell: reregisters the proxy at the layout */ - void reregister (); + virtual void reregister (); private: lib_id_type m_lib_id; diff --git a/src/db/unit_tests/dbLibrariesTests.cc b/src/db/unit_tests/dbLibrariesTests.cc index d39e025ae..65ca6e4f9 100644 --- a/src/db/unit_tests/dbLibrariesTests.cc +++ b/src/db/unit_tests/dbLibrariesTests.cc @@ -773,8 +773,8 @@ TEST(7_monsterlib) } // as NOEX and NOEX2 are not present, a number of references are defunct (aka cold proxies) - EXPECT_EQ (num_defunct (layout), size_t (15)); - EXPECT_EQ (num_cells (layout), size_t (46)); + EXPECT_EQ (num_defunct (layout), size_t (6)); + EXPECT_EQ (num_cells (layout), size_t (25)); EXPECT_EQ (num_top_cells (layout), size_t (1)); // NOTE: normalization would spoil the layout, so don't do it @@ -794,7 +794,7 @@ TEST(7_monsterlib) // all references now need to be resolved EXPECT_EQ (num_defunct (layout), size_t (0)); - EXPECT_EQ (num_cells (layout), size_t (36)); + EXPECT_EQ (num_cells (layout), size_t (25)); EXPECT_EQ (num_top_cells (layout), size_t (1)); db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au2.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons)); @@ -807,7 +807,7 @@ TEST(7_monsterlib) // all references now need to be resolved EXPECT_EQ (num_defunct (layout), size_t (0)); - EXPECT_EQ (num_cells (layout), size_t (32)); + EXPECT_EQ (num_cells (layout), size_t (25)); EXPECT_EQ (num_top_cells (layout), size_t (1)); db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au3.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons)); @@ -816,8 +816,8 @@ TEST(7_monsterlib) db::LibraryManager::instance ().delete_lib (lib_noex2); // after removing the libraries, we have defunct cells again - EXPECT_EQ (num_defunct (layout), size_t (11)); - EXPECT_EQ (num_cells (layout), size_t (32)); + EXPECT_EQ (num_defunct (layout), size_t (6)); + EXPECT_EQ (num_cells (layout), size_t (25)); EXPECT_EQ (num_top_cells (layout), size_t (1)); // but the layout did not change @@ -827,8 +827,8 @@ TEST(7_monsterlib) db::LibraryManager::instance ().delete_lib (lib_ex2); // after removing all libraries, we have even more defunct cells (i.e. all, except top) - EXPECT_EQ (num_defunct (layout), size_t (19)); - EXPECT_EQ (num_cells (layout), size_t (32)); + EXPECT_EQ (num_defunct (layout), size_t (12)); + EXPECT_EQ (num_cells (layout), size_t (25)); EXPECT_EQ (num_top_cells (layout), size_t (1)); // but the layout did not change diff --git a/testdata/libman/design_au1.gds b/testdata/libman/design_au1.gds index 5419c775426281eabf3b77fe528734a0715c6398..9d9d354d8aa514e70cc43de7e8c21dea1cf7656e 100644 GIT binary patch delta 879 zcmcbScEwzYfsKKQDS|*#BJdKlm z<(d?*tKxyEQZY5&+{=@~NJy2@WI?_~Amc=_8n>B;v!4-%l%HTcE~!I8`*BH46DcR; zC?k`}3q@W{=I4E+h1F3EY;0_91`I4*3=BL%;4lIDcCw*tmNZV~E(Vhuq?9IesPCD4 zL7Gzvr#iRE%O%t%%gY4dG0JeVl58c8Fxen0kIUmvoWo)NcOoDC+o zX{fTW39-3NKBzA|d6lLtgmYgz3%d&^`)JkUkSWvFhG;G{kYsW(oV-q_8HY+YU0i0| z(siBOZjgsV-z5E+IAk0Qv?sIbN#W7^%@CK~B}VEXcRNqMY~;lXVi-(TGd2PMbESV6 literal 13144 zcmeI2zi%8>5XZ;s^ZMdABq2c%VH6<_@k2=D?$*R$|EGx2vkyg1KNc~{Gkl|k zr8?)ud*1`z$J#Vo6UvW%ejl83^y7bB|2wzF{5S49-ohUu^z-ibR~%&?{ZPiy&$_k_ zl=1LsnH(WGKS9pRH$+Z7bM*WJk@r3nF>3uv)A@_Cul|>u{(A#YBlklVIJ4`d<84JP zA$?KXSYh#nvj$ef3XHdT^q~nTnql2S3+=qE7Fy0Ml&pYuvf$`uT) zZTEbGYrYLmZYT8v74kX5Qr(^}KPPJ^HcEZ%OXS=~ZKEXM%Vza{GF>%2vQ7d~+pO!n z_4m=L@9Un0s#oQ>jyTKWHh9EoP<~XZpv5?^7!TSQ=Fo3-Mx(49iu-%oiF$sen&cSC$1PpZT}(-B4si1X8Scl_Mjf6(Sy}ML0$oYY_~z z&}K1$84jDLv$jSWhT}m1$GbCH$+bJG?KVvUS=(=A-$eFB#T?&6)&#ZxAt2hc?9KcS zWAIZ(>Bmno&gRj_y5_4GPlJ-3;m?ew4EJXieOVI7#%4{&Z9OOY4T~(R=dv1$E%^R1 z-Ppoq62oTYHkw70^t2Yx(~|aCjIG}=JUtfpuUr;VZ%$M1Y6nODn`Qe_#|_y}>k)ct z634s#lm5~ZcgTc{e52CTiwWLMVVY~=_*<#75{HH zU8z9Bq*vpMktb7*-psNkJb-#K)O20|5uB`)7eIu?st42?3<*Z7Yk#v}^51puR8NK0 zCd-gn5*2^r&pr6EJC7Q|)*bha$<0%o=D7UnMUkWD4Nmmc Z)3JxsIjrdXPR=c@SeR!kYQ)>=$p6Pr8m<5U diff --git a/testdata/libman/design_au2.gds b/testdata/libman/design_au2.gds index d7153f8f4b0c7286e2d72479ea3d7434bd74ab1d..cc5f9d1e3cb0f5adc5e2d8b56ef1374089b83442 100644 GIT binary patch delta 945 zcmX@-cFs(RfsKKQDS|! zV+(PO2vITI{DiTYQ5L%*4v3=7t5}#BvD>ow5UV~8smZq(r8g_GFCydu6DlSY7$%#42rtIv;|-$nxTKzl`7`2npR>W_I(~)88|2xT+zcig zNQttt39$kF?<3W$i8Fkh3>a9r7#O&OAPL2IvVuS+?(lY=%&DjbG8PzSFbfPPuaRlS zo^B?)$kyXF%ysg4X+@B8xtt9dcm$Xk7##v87bu8NR#Xz1T+gRIIYkanY&aTBULpYU uZ6OZ3%@njjE_DXGbaJgi9u9SH6lda)nWd!80y1IpNhL3!0bG+6m30A>kcL_S literal 9038 zcmeI2KW`LQ6vgk3*W<-8@gE2%iV;HD2#6x#HESC;2;(&Zfdoh{tca45f+8h}Qltny z9TXHuY4`w0X;Prne1Lp_C=~?-5>4)J-kjZ;omt}nVGrOZjnC-)oBQs$@4oX^w%DR* z%jO%=#D6ws$L)wU>~!|sT2XxM%0;uoW8>%7?yp?^`;V8ay=P}0Km2ji=Et`g7ss0$ zTbtHwnZ*Yqi!A;!GP7GVWBxZ&zS%N6^t+kx=v}k1f6au`kIlY#W+vpFc420!=ByRI z_kiy)H$`)TJk;}?oC)~->rj8}+MxZbd!cRoA2aIt_IL9E8Am-xKkAuR^MLfHos!y7 zX6FysdHjOef#(5dADDf6%1o$w$7KA3ZR`KZ;Q#hw!6Wy|7&!PeSjX40Uc&mUw_#!8 zMPLKgV1fP`M;)F3@eK23O4-UkSB9oihPvklQQO$%U83*(L`=XVMf?+ua1X*K(GPxo zJMd3G;sK+F%6a;iLnzjAofvAI4BGog!rJGKM+dsi&XD zf%Iq2Oxb68z_jvTccqyBKGxk^_k1azFD?~*{;BP+x^of7@udZ8PMWpm!i{T+497X) zPm(DPMj?R*Iw-^ z<+!^4Leec!s~=0;*WJfK^_@-5#X9LwKYJ!?q>No*1?^B5)#nOpJ=`8-QKzUHf(H{- zpD2p+zYgvep)FCfN!!n!vvu$Aws;N&m$dwu8CC5@!foV@-%y0rZzSu(%67X|t_FKc zSDC8x8m`iryqITlsmNqCzv~7ldQV89x*Zi?3!$YX zzLYiJawkw+2`jD>&*xXk?;?d&roP{yP4c<0x=kDTXjmy)6#TvpM_Ajqa3i;OJ&z~* zroc(HD7xIUsCU>*SaBt;S=|c7(N3~dy3*49n?_hY_wQPsvE@?6Jfr$Pe@{+`^k349 z3RdLABYqXrp3mhZ*cWW6Txh=5IlBIg1-+ z$w=f@Pj*CRc5CN=`wiNGHM3n$%!K>ynhkt56OO(#+rDllHMi;5xo zUI?w4tuFcoJP&OMrUoqHClERzOzxo+o-EPV*Afq$m=tlaYpsl%|?GmCuPjpLdLvP zmNCgpVa?N-G09?NEl`e!YveY*tcRY!<)o zcN@D5y_nC?Q^o8v}@oCQkckWeZWOK57|2CLYtpcI?_~9xN zVf6}RA-})r;{K{>q*_Q=U8BW(#^=70+Aplr^7bpSd1l!Xn`dm4*j(QDi<(BZ7FOTk zLVkzb4hlQWtlpzevbM0gPD^<|m@R7LZSL4X*V^3mPH%Q6CEZ2HMm68L+_b2<+e}z} z7xVdD%yip@^ZMV^74{!yCl9Qy>r&ne$bAJb$l2UN%Vs@(A2~Lvd5&x?tiF%=j(wDQ zv)9)7eK)K3=;yy_b^bnMj--H{NwQ S8EH?)YQY?>=Kd2C$MzGRN;Yl) literal 8300 zcmeI1yK5C&7{$L#PA10~jqi(?i--{)2%e+n)rc`CLAinoDS}v9T3A@BSXgLpr-g-J z;~yZ{SctWMfPa8kSy)`S&Ha9}XPwL|-c010QL|yPhW(m7-&*_IYfdb-C|b7hfoS-% z4cbB5=YO|m@2wrhmoHr~+cP+HZtn5)U+-UjneDwk_5A7eB^w|5#<)0MT3BAPR@*Gz z7FlHR_Q=d`>=^XFnQ~y+Z1*EG;UBln20xhz$DW&QeQhS>o_1kos@6^`J?{z6V{VG( z1i7f^I=Um^iN8brpO;tK_udU{Lm$nk=h;Wb12T?!kbcxNujT>iPdg=DN7Xt{vd)9& z&9=M?xbunG;bUe()jKBRhwYp39|`fFI9-a!-6{sQy$Y-2Y1t^T`fRjeVfaH}1J+=H z{u)OekpPJd^JYrboBys1jiwBBJ1eRk_`$nG&%1*r;E^K!Q6t=g@QM1t{Wk;u^g|CI z_4GsGs8+~$?E;XJKBta~XZkAEikqTht*7Y`?Diw5O#BMY;YUUH;vWv-7ldE&%sur& z_B;EWcJ{o+Q_q~#adA&Sknz-kS@*8i-_&fW zGx_vn`Aq)Rc30E6h~s#%W33Uh_O9?PF-m^pl<+Ib6I=56FFwR~MtdxrYl)*C_-dx> zc*|_&g_%(Gj%)EEZPqj4=V+5w)ay)4Co38WF>=4Z;ZgE6`B#TWsX=R6?550(rg6e` zuNEoojQ>|p=sT#XIcsu__qql4W;QrZ@;k2cc7DI#s`5MdimYQT`W-w7o&%I`5z<$_ z1k998UsCoZQzb^86&b-v;`3ny2MFr70y4RrsG?3E*b!0>JM_~y>X=sy(Vqh1G+|Mg zG=x8!46ZOJpL2Gq>~l71S1Ic@-5Z{*@PKd0w|50#(kmQTcy z#HA+wFmV;<9;~?3(3ZHOr0t&PBRN=ai+AAzl9o4_QT4nnzV;vYy2AS2cD@+*^}gkF zc$Wsgr89XU&*Ws8$$D|uw}fyVahEfdx@OC3O4UWr)zpP+TTNY^&{Cl;<%9olx>A*} z=35f?ywm0TI$3SS*W4P{q=*aauW2E#g;clM6@|FZEA9j5uNHT%XND3Z^%QimXHjpD znXu-2xI6ZpTh!2c_wJchHMG$g$~82X=WD90ppZ>{kAEj8==^?~O~IO+_*xh8ddFv# z^j=VxmCuYMCo?Xmyi{Ln+$YkNu;#mIlBIg1-+ z$w=f@Pj*CRc5CN=`wiNGHM3n$%!K>ynhkt56OO(#+rDllHMi;5xo zUI?w4tuFcoJP&OMrUoqHClERzOzxo+o-EPV*Afq$m=tlaYpsl%|?GmCuPjpLdLvP zmNCgpVa?N-G09?NEl`e!YveY*tcRY!<)o zcN@D5y_nC?Q^o8v}@oCQkckWeZWOK57|2CLYtpcI?_~9xN zVf6}RA-})r;{K{>q*_Q=U8BW(#^=70+Aplr^7bpSd1l!Xn`dm4*j(QDi<(BZ7FOTk zLVkzb4hlQWtlpzevbM0gPD^<|m@R7LZSL4X*V^3mPH%Q6CEZ2HMm68L+_b2<+e}z} z7xVdD%yip@^ZMV^74{!yCl9Qy>r&ne$bAJb$l2UN%Vs@(A2~Lvd5&x?tiF%=j(wDQ zv)9)7eK)K3=;yy_b^bnMj--H{NwQ S8EH?)YQY?>=Kd2C$MzGRN;Yl) literal 8300 zcmeI1J5L=~6oog#Ww<6dVEl+8V_8v%9b1udFE+0Rfw>?@L5d?$Aj_po7b#LYmWvcg zPZt+1BBkL6SW1%ux#kDt2Slz?q=@9k`-68du{wjLj0$uOA&ck#lY$9usWWWjS{QRMjIA} zcLN)+1`G7pIO>Q5NMx8dQ>xzlePw7gWvJWPQ|-Y2yi4@F@6iN2Qp7)MgnJM^Q9t1@jEGuCS7&(@u) z%wA-Wh>9WFU>ar=P}w^k>dYIbpiY^g^F$Wt-!= zceVbeW=oyP(=+8W`CZ#xP3Izx zj(Xr9GhNqOX7j(A303cS5HHeZJyU*;Hfcq@&eUA8qLC0I_xnd4C0~<&b$FB-w5GLg z%G_ugCtUY_khysfA@#6BKaHb~dBqU@ zDIiW07KKSe_;bSG9)t2ZH)qQ}=csm-vToD8;mrzfcwBVf^cZvgbf#`J%02iwy%Kz= z{7koeB90_3HSvdut5|%z=Tbvk;);^CA3Pt)!FpRffe%Ppe$R}m=WX$|FWu`3>wDXk zV%!h=mUH1<8u*sZcdloK5+n5# zbh~F!@0^*i=6kq1_MKbQ&|&xPjeRwA)EUY(w4CQ_wydC#O?{8QB`4_oQJPJ`nwIlBIg1-+ z$w=f@Pj*CRc5CN=`wiNGHM3n$%!K>ynhkt56OO(#+rDllHMi;5xo zUI?w4tuFcoJP&OMrUoqHClERzOzxo+o-EPV*Afq$m=tlaYpsl%|?GmCuPjpLdLvP zmNCgpVa?N-G09?NEl`e!YveY*tcRY!<)o zcN@D5y_nC?Q^o8v}@oCQkckWeZWOK57|2CLYtpcI?_~9xN zVf6}RA-})r;{K{>q*_Q=U8BW(#^=70+Aplr^7bpSd1l!Xn`dm4*j(QDi<(BZ7FOTk zLVkzb4hlQWtlpzevbM0gPD^<|m@R7LZSL4X*V^3mPH%Q6CEZ2HMm68L+_b2<+e}z} z7xVdD%yip@^ZMV^74{!yCl9Qy>r&ne$bAJb$l2UN%Vs@(A2~Lvd5&x?tiF%=j(wDQ zv)9)7eK)K3=;yy_b^bnMj--H{NwQ S8EH?)YQY?>=Kd2C$MzGRN;Yl) literal 8300 zcmeI1yH6BR7{xz^Wmpje-!Wo{5#u8!?y9^RBd`#SCTg+;F_xAV7M5r%EVQ@N!otMH zKfuJsf>`qh_y-s(3kwq)?{Ds$WnLhIQ3m8D!x`^u?tJIm@19vLv>;fp;jW;7UU9DHrI`@NZvd)kGGscL&I_q-Q8kGUzD6Xc?v z>(r3Pvp;4(Bm zt&s8B1t2AUP8k(Xc2uktS4G8IPt_yX>_kwR_#K?XkBaWaKODj@2*2W)d+LSkclJ5$ z?0Jo+o;j)G;+}pWOySc)>D?53kb2^(c`9cjID6?l7B%)%7HW&f9 z$l_p<(IoTg!x-vy1`CglI(ApMy$QFfTlGd|Fs@YO!@`bVd znY`V0SJk-)!*IS~wLY`@KL0JzPk!T+a95NkcBS)Qc%1Ky`k+78B1hfz-Avb+n%U$V zGok7ouE&eiSnkQF3dVsM?z5n=y0!ah!17t3^sXJM_~y>X=sy(Vqh1G+|K~HTXX}46ZOJ zpL1zE?{hY5S1IdO-5Xvi@P=DO_f?NE=TBqwVx`=JJEv#850%Gi=1;`F$fZX9Fme@_ zpRBl4-xfKwsO^!9>h*~}*MwRon_}ZV&>k7+z+u3Z~H~N+n{#`2gmX67p zG?Qa_CdYB>0DOMLfS5p_RZ8dc@d`p44ln;L6bfqd`$+sl#X{XEg zb+p=oueo)uNf8&8U(;M#3$bpKEAnxlRouI-+{*4;ml<-5lvB{vmIbW?X2O#1;oPy? zxmgWubnjkVRYRMdp`3=M(|nER738z2+v9)82|9lqXH&2wCvL5CX}#mKa(d6G%kpPN zl#>~kQeLdDb?y`CN?7t;(3ewJ{Pn!8bJTQ(v!WaoE36<7mt*A*73O?W`=(1q{PN@c zgIjr#x*S_Ecb(SxTmH8$KhCMN)ADpV+2ZyZEy3-Nsf@>rKHGF!GczW}X-4-y)wP=a E0<5*$8vp