diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 7df5be64b..c19dae98e 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -29,11 +29,13 @@ #include "dbSaveLayoutOptions.h" #include "dbRegion.h" #include "dbDeepShapeStore.h" +#include "dbCellGraphUtils.h" #include "gsiExpression.h" #include "tlCommandLineParser.h" #include "tlThreads.h" #include "tlThreadedWorkers.h" #include "tlTimer.h" +#include "tlOptional.h" namespace { @@ -271,17 +273,19 @@ HealingTileLayoutOutputReceiver::output (const db::Box &box) struct ResultDescriptor { ResultDescriptor () - : shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0) + : shape_count (0), flat_shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0) { // .. nothing yet .. } size_t shape_count; + size_t flat_shape_count; int layer_a; int layer_b; int layer_output; db::Layout *layout; db::cell_index_type top_cell; + tl::optional results; size_t count () const { @@ -296,6 +300,20 @@ struct ResultDescriptor } } + size_t flat_count () const + { + if (layout && layer_output >= 0) { + size_t res = 0; + db::CellCounter counter (layout, top_cell); + for (db::Layout::const_iterator c = layout->begin (); c != layout->end (); ++c) { + res += c->shapes (layer_output).size () * counter.weight (c->cell_index ()); + } + return res; + } else { + return flat_shape_count; + } + } + bool is_empty () const { if (layout && layer_output >= 0) { @@ -586,12 +604,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) const char *line_format = " %-10s %-12s %s"; - std::string headline; - if (deep) { - headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (hierarchical shape count)"))); - } else { - headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)"))); - } + std::string headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), + deep ? tl::to_string (tr ("Differences (hierarchical/flat count)")) : tl::to_string (tr ("Differences (shape count)"))); const char *sep = " ----------------------------------------------------------------"; @@ -619,7 +633,11 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) if (r->second.layer_output >= 0 && r->second.layout) { out = r->second.layout->get_properties (r->second.layer_output).to_string (); } - value = tl::to_string (r->second.count ()); + if (deep) { + value = tl::sprintf (tl::to_string (tr ("%-6lu / %-6lu")), r->second.count (), r->second.flat_count ()); + } else { + value = tl::to_string (r->second.count ()); + } } if (! value.empty ()) { tl::info << tl::sprintf (line_format, r->first.second.to_string (), out, value); @@ -767,8 +785,15 @@ bool run_tiled_xor (const XORData &xor_data) proc.execute ("Running XOR"); } + // no stored results currently + for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) { + tl_assert (! r->second.results.has_value ()); + } + // Determines the output status for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end () && result; ++r) { + // no stored results currently + tl_assert (! r->second.results.has_value ()); result = r->second.is_empty (); } @@ -846,29 +871,40 @@ class XORTask tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + m_layer_props.to_string ()); - db::RecursiveShapeIterator ri_a, ri_b; + db::Region xor_res; - if (m_la >= 0) { - ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); - } else { - ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), std::vector ()); - } - ri_a.set_for_merged_input (true); + if (m_la < 0) { + + tl_assert (m_lb >= 0); + + db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); + xor_res = db::Region (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + + } else if (m_lb < 0) { + + db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); + xor_res = db::Region (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); - if (m_lb >= 0) { - ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); } else { - ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), std::vector ()); - } - ri_b.set_for_merged_input (true); - db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); - db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); + db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); + + db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); + db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + + bool a_empty = in_a.empty (); + bool b_empty = in_b.empty (); + + if (a_empty && ! b_empty) { + xor_res = in_b; + } else if (! a_empty && b_empty) { + xor_res = in_a; + } else if (! a_empty && ! b_empty) { + tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ()); + xor_res = in_a ^ in_b; + } - db::Region xor_res; - { - tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ()); - xor_res = in_a ^ in_b; } int tol_index = 0; @@ -896,9 +932,12 @@ class XORTask if (mp_xor_data->output_layout) { result.layer_output = result.layout->insert_layer (lp); - xor_res.insert_into (mp_xor_data->output_layout, mp_xor_data->output_cell, result.layer_output); + if (! xor_res.empty ()) { + result.results = xor_res; + } } else { result.shape_count = xor_res.hier_count (); + result.flat_shape_count = xor_res.count (); } } @@ -964,6 +1003,22 @@ bool run_deep_xor (const XORData &xor_data) job.start (); job.wait (); + // Deliver the outputs + // NOTE: this is done single-threaded and in a delayed fashion as it is not efficient during + // computation and shifting hierarchy of the working layout + + if (xor_data.output_layout) { + + tl::SelfTimer timer (tl::verbosity () >= 11, "Result delivery"); + + for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) { + if (r->second.results.has_value ()) { + r->second.results.value ().insert_into (xor_data.output_layout, xor_data.output_cell, r->second.layer_output); + } + } + + } + // Determine the output status bool result = (xor_data.layers_missing == 0); diff --git a/src/buddies/unit_tests/bdStrmxorTests.cc b/src/buddies/unit_tests/bdStrmxorTests.cc index bb06a9428..caec430a4 100644 --- a/src/buddies/unit_tests/bdStrmxorTests.cc +++ b/src/buddies/unit_tests/bdStrmxorTests.cc @@ -146,11 +146,11 @@ TEST(1A_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 3/0 3\n" - " 6/0 6/0 314\n" - " 8/1 8/1 1\n" + " 3/0 3/0 3 / 30 \n" + " 6/0 6/0 314 / 314 \n" + " 8/1 8/1 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -188,11 +188,11 @@ TEST(1A_DeepNoEmptyCells) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 3/0 3\n" - " 6/0 6/0 314\n" - " 8/1 8/1 1\n" + " 3/0 3/0 3 / 30 \n" + " 6/0 6/0 314 / 314 \n" + " 8/1 8/1 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -248,11 +248,11 @@ TEST(1B_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 - 3\n" - " 6/0 - 314\n" - " 8/1 - 1\n" + " 3/0 - 3 / 30 \n" + " 6/0 - 314 / 314 \n" + " 8/1 - 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -830,10 +830,10 @@ TEST(7_OptimizeDeep) EXPECT_EQ (cap.captured_text (), "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 2/0 2/0 1\n" - " 3/0 3/0 8\n" + " 2/0 2/0 1 / 12 \n" + " 3/0 3/0 8 / 8 \n" "\n" ); } diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 44edac6b7..188abf0d8 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -15,6 +15,7 @@ SOURCES = \ dbCellGraphUtils.cc \ dbCellHullGenerator.cc \ dbCellInst.cc \ + dbCellInstanceSetHasher.cc \ dbCellMapping.cc \ dbClipboard.cc \ dbClipboardData.cc \ @@ -253,6 +254,7 @@ HEADERS = \ dbCell.h \ dbCellHullGenerator.h \ dbCellInst.h \ + dbCellInstanceSetHasher.h \ dbCellMapping.h \ dbClipboardData.h \ dbClipboard.h \ diff --git a/src/db/db/dbBoxTree.h b/src/db/db/dbBoxTree.h index 5ceb905e6..ad1a6be2e 100644 --- a/src/db/db/dbBoxTree.h +++ b/src/db/db/dbBoxTree.h @@ -981,9 +981,10 @@ class box_tree * Only after sorting the query iterators are available. * Sorting complexity is approx O(N*log(N)). */ - void sort (const BoxConv &conv) + template + void sort (const BC &conv) { - typename BoxConv::complexity complexity_tag; + typename BC::complexity complexity_tag; sort (conv, complexity_tag); } @@ -1192,7 +1193,8 @@ class box_tree box_tree_node *mp_root; /// Sort implementation for simple bboxes - no caching - void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/) { m_elements.clear (); m_elements.reserve (m_objects.size ()); @@ -1204,7 +1206,7 @@ class box_tree if (! m_objects.empty ()) { - box_tree_picker_type picker (conv); + box_tree_picker picker (conv); box_type bbox; for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) { @@ -1221,7 +1223,8 @@ class box_tree } /// Sort implementation for complex bboxes - with caching - void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/) { m_elements.clear (); m_elements.reserve (m_objects.size ()); @@ -1233,7 +1236,7 @@ class box_tree if (! m_objects.empty ()) { - box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); + box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) { m_elements.push_back (o.index ()); @@ -1997,9 +2000,10 @@ class unstable_box_tree * Only after sorting the query iterators are available. * Sorting complexity is approx O(N*log(N)). */ - void sort (const BoxConv &conv) + template + void sort (const BC &conv) { - typename BoxConv::complexity complexity_tag; + typename BC::complexity complexity_tag; sort (conv, complexity_tag); } @@ -2159,13 +2163,14 @@ class unstable_box_tree box_tree_node *mp_root; /// Sort implementation for simple bboxes - no caching - void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/) { if (m_objects.empty ()) { return; } - box_tree_picker_type picker (conv); + box_tree_picker picker (conv); if (mp_root) { delete mp_root; @@ -2184,13 +2189,14 @@ class unstable_box_tree } /// Sort implementation for complex bboxes - with caching - void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/) { if (m_objects.empty ()) { return; } - box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); + box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); if (mp_root) { delete mp_root; diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index cb56e7354..7f54211d1 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -274,11 +274,67 @@ Cell::is_shape_bbox_dirty () const return false; } +namespace +{ + +/** + * @brief An alternative box converter for CellInst which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalCellInstBoxConverter (const db::Layout *layout) + : mp_layout (layout) + { + // .. nothing yet .. + } + + box_type operator() (const db::CellInst &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update (); + } + +private: + const db::Layout *mp_layout; +}; + +/** + * @brief An alternative box converter for CellInst and layer which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalPerLayerCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalPerLayerCellInstBoxConverter (const db::Layout *layout, unsigned int layer) + : mp_layout (layout), m_layer (layer) + { + // .. nothing yet .. + } + + box_type operator() (const db::CellInst &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_no_update (m_layer); + } + +private: + const db::Layout *mp_layout; + unsigned int m_layer; +}; + +} + bool Cell::update_bbox (unsigned int layers) { - unsigned int l; - // determine the bounding box box_type org_bbox = m_bbox; m_bbox = box_type (); @@ -308,10 +364,10 @@ Cell::update_bbox (unsigned int layers) ++o; } - for (l = 0; l < layers; ++l) { + for (unsigned int l = 0; l < layers; ++l) { // the per-layer bounding boxes - db::box_convert bc (*mp_layout, l); + InternalPerLayerCellInstBoxConverter bc (mp_layout, l); box_type lbox = o1_inst->bbox_from_raw_bbox (raw_box, bc); if (! lbox.empty ()) { @@ -326,7 +382,7 @@ Cell::update_bbox (unsigned int layers) } - db::box_convert bc_we (*mp_layout); + InternalCellInstBoxConverter bc_we (mp_layout); m_bbox_with_empty += o1_inst->bbox_from_raw_bbox (raw_box, bc_we); } @@ -486,6 +542,17 @@ Cell::bbox (unsigned int l) const } } +const Cell::box_type & +Cell::bbox_no_update (unsigned int l) const +{ + box_map::const_iterator b = m_bboxes.find (l); + if (b != m_bboxes.end ()) { + return b->second; + } else { + return ms_empty_box; + } +} + Cell::const_iterator Cell::begin () const { @@ -725,7 +792,7 @@ Cell::count_hier_levels () const { unsigned int l = 0; - for (const_iterator c = begin (); !c.at_end (); ++c) { + for (const_iterator c = m_instances.begin (); !c.at_end (); ++c) { l = std::max (l, (unsigned int) mp_layout->cell (c->cell_index ()).m_hier_levels + 1); } diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index 7ba54fc71..44660962d 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -522,21 +522,6 @@ class DB_PUBLIC Cell */ bool is_shape_bbox_dirty () const; - /** - * @brief Updates the bbox - * - * This will update the bbox from the shapes and instances. - * This requires the bboxes of the child cells to be computed - * before. Practically this will be done by computing the - * bboxes bottom-up in the hierarchy. - * In addition, the number of hierarchy levels below is also - * updated. - * - * @param layers The max. number of layers in the child cells - * @return true, if the bounding box has changed. - */ - bool update_bbox (unsigned int layers); - /** * @brief Sorts the shapes lists * @@ -1110,6 +1095,23 @@ class DB_PUBLIC Cell */ void move_shapes (db::Cell &source_cell, const db::LayerMapping &layer_mapping); + /** + * @brief Gets the current bounding box (with empty) without calling Layout::update + * + * This method is intended for internal purposes only. + */ + const box_type &bbox_with_empty_no_update () const + { + return m_bbox_with_empty; + } + + /** + * @brief Gets the current per-layer bounding box without calling Layout::update + * + * This method is intended for internal purposes only. + */ + const box_type &bbox_no_update (unsigned int l) const; + protected: /** * @brief Standard constructor: create an empty cell object @@ -1135,6 +1137,7 @@ class DB_PUBLIC Cell virtual Cell *clone (db::Layout &layout) const; private: + friend class db::Layout; cell_index_type m_cell_index; mutable db::Layout *mp_layout; shapes_map m_shapes_map; @@ -1170,9 +1173,9 @@ class DB_PUBLIC Cell } /** - * @brief Return a reference to the instances object + * @brief Return a reference to the instances object */ - instances_type &instances () + const instances_type &instances () const { return m_instances; } @@ -1180,7 +1183,7 @@ class DB_PUBLIC Cell /** * @brief Return a reference to the instances object */ - const instances_type &instances () const + instances_type &instances () { return m_instances; } @@ -1219,6 +1222,21 @@ class DB_PUBLIC Cell * @param force Force sorting, even if not strictly needed */ void sort_inst_tree (bool force); + + /** + * @brief Updates the bbox + * + * This will update the bbox from the shapes and instances. + * This requires the bboxes of the child cells to be computed + * before. Practically this will be done by computing the + * bboxes bottom-up in the hierarchy. + * In addition, the number of hierarchy levels below is also + * updated. + * + * @param layers The max. number of layers in the child cells + * @return true, if the bounding box has changed. + */ + bool update_bbox (unsigned int layers); }; /** diff --git a/src/db/db/dbCellInstanceSetHasher.cc b/src/db/db/dbCellInstanceSetHasher.cc new file mode 100644 index 000000000..a68c7ad22 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.cc @@ -0,0 +1,154 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbCellInstanceSetHasher.h" +#include "dbHash.h" + +namespace db +{ + +CellInstanceSetHasher::MatrixHash::MatrixHash (double s) + : db::IMatrix3d (s, 0, 0, 0, s, 0, 0, 0, s) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::ICplxTrans &trans) + : db::IMatrix3d (trans) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::CellInstArray &array) + : db::IMatrix3d (array.complex_trans ()) +{ + db::Vector a, b; + unsigned long na = 0, nb = 0; + + if (array.is_regular_array (a, b, na, nb)) { + + na = std::max ((unsigned long) 1, na); + nb = std::max ((unsigned long) 1, nb); + + // compute the sum of all individual matrices + *this *= double (na * nb); + + db::DVector dab = db::DVector (a) * double ((nb * (na - 1) * na) / 2) + db::DVector (b) * double ((na * (nb - 1) * nb) / 2); + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } else if (array.is_iterated_array ()) { + + db::DVector dab; + double n = 0.0; + + tl_assert (! array.begin ().at_end ()); + db::DVector d0 = db::DVector ((*array.begin ()).disp ()); + for (auto i = array.begin (); ! i.at_end (); ++i) { + n += 1.0; + dab += db::DVector ((*i).disp ()) - d0; + } + + *this *= n; + + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } +} + +static inline size_t d2h (double d) +{ + return d < 0 ? size_t (d - 0.5) : size_t (d + 0.5); +} + +size_t +CellInstanceSetHasher::MatrixHash::hash_value () const +{ + // The "close-to-unity" elements are scaled with this value, so + // after rounding to int for the hash value we are able to + // resolve a certain level of details. This applies to the + // rotation/shear/scale submatrix elements (m11, m12, m21, m22). + const double res = 1024.0; + + size_t h = d2h (m ()[0][0] * res); + h = tl::hcombine (d2h (m ()[0][1] * res), h); + h = tl::hcombine (d2h (m ()[0][2]), h); + h = tl::hcombine (d2h (m ()[1][0] * res), h); + h = tl::hcombine (d2h (m ()[1][1] * res), h); + h = tl::hcombine (d2h (m ()[1][2]), h); + // m31 and m32 are always zero, so we don't count them here + h = tl::hcombine (d2h (m ()[2][2]), h); + return h; +} + +CellInstanceSetHasher::CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection) + : mp_layout (layout), m_top_cell (top_cell), mp_selection (selection) +{ + // .. nothing yet .. +} + +size_t +CellInstanceSetHasher::instance_set_hash (db::cell_index_type for_cell) +{ + return get_hash (for_cell).hash_value (); +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash (cell_index_type for_cell) +{ + auto c = m_cache.find (for_cell); + if (c != m_cache.end ()) { + return c->second; + } else { + MatrixHash hm = get_hash_uncached (for_cell); + m_cache [for_cell] = hm; + return hm; + } +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash_uncached (cell_index_type for_cell) +{ + if (for_cell == m_top_cell) { + + return MatrixHash (); + + } else { + + const db::Cell &fc = mp_layout->cell (for_cell); + + MatrixHash hm (0.0); + for (auto pi = fc.begin_parent_insts (); ! pi.at_end (); ++pi) { + auto pci = pi->parent_cell_index (); + if (! mp_selection || mp_selection->find (pci) != mp_selection->end ()) { + hm += get_hash (pci) * MatrixHash (pi->child_inst ().cell_inst ()); + } + } + + return hm; + + } +} + +} diff --git a/src/db/db/dbCellInstanceSetHasher.h b/src/db/db/dbCellInstanceSetHasher.h new file mode 100644 index 000000000..8cc1cacf6 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.h @@ -0,0 +1,90 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbCellInstanceSetHasher +#define HDR_dbCellInstanceSetHasher + +#include "dbCommon.h" +#include "dbMatrix.h" +#include "dbLayout.h" + +namespace db +{ + +/** + * @brief A hasher for a set of cell instances + * + * The hasher starts with a layout, a top cell and optionally a + * set of cells selected. Only selected cells will be considered + * in the cell tree (the "cone"). + * + * The hasher allows to compute a hash value for a given cell, + * representative for the flat set of instances of that cell in + * the top cell. + */ +class DB_PUBLIC CellInstanceSetHasher +{ +public: + class MatrixHash + : public IMatrix3d + { + public: + MatrixHash (double s = 1.0); + MatrixHash (const db::ICplxTrans &trans); + MatrixHash (const db::CellInstArray &array); + + size_t hash_value () const; + }; + + /** + * @brief Creates a new cell instance set hasher + * + * @param layout The layout the hasher refers to + * @param top_cell The top cell the hasher starts with + * @param selection A set of selected cells or a null pointer if all cells should be considered + * + * The hasher will not take ownership over the layout, nor + * the selected cell set. + */ + CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection = 0); + + /** + * @brief Computes the hash value representative for the flat instance set of the given cell in the top cell and the selection + */ + size_t instance_set_hash (db::cell_index_type for_cell); + +private: + const db::Layout *mp_layout; + db::cell_index_type m_top_cell; + const std::set *mp_selection; + std::map m_cache; + + MatrixHash get_hash (db::cell_index_type for_cell); + MatrixHash get_hash_uncached (db::cell_index_type for_cell); +}; + +} // namespace db + +#endif + diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index 7bc3dc712..26d5770e5 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -25,6 +25,8 @@ #include "dbCellGraphUtils.h" #include "dbCellMapping.h" #include "dbLayoutUtils.h" +#include "dbCellInstanceSetHasher.h" +#include "dbHash.h" #include "tlLog.h" #include "tlTimer.h" @@ -122,28 +124,30 @@ struct SortedCellIndexIterator // Some utility class: a compare function for a instance set of two cells in the context // of two layouts and two initial cells. -class InstanceSetCompareFunction +#if 1 + +class InstanceSetCompareFunction { public: - typedef std::multiset > trans_set_t; + typedef std::unordered_multiset trans_set_t; - InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b) - : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), - m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b), m_cell_a (std::numeric_limits::max ()), m_repr_set (false) { // .. } - bool compare (db::cell_index_type cell_a, const std::set &selection_cone_a, db::cell_index_type cell_b, const std::set &selection_cone_b) + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) { if (cell_a != m_cell_a) { m_cell_a = cell_a; m_callers_a.clear (); - m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, selection_cone_a, -1); + m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, *m_selection_cone_a, -1); m_callers_a.insert (cell_a); m_trans.clear (); @@ -162,7 +166,7 @@ class InstanceSetCompareFunction } std::set callers_b; - m_layout_b.cell (cell_b).collect_caller_cells (callers_b, selection_cone_b, -1); + m_layout_b.cell (cell_b).collect_caller_cells (callers_b, *m_selection_cone_b, -1); callers_b.insert (cell_b); trans_set_t trans (m_trans); @@ -175,11 +179,18 @@ class InstanceSetCompareFunction return trans.empty (); } + void make_endpoint (db::cell_index_type /*cell_a*/, db::cell_index_type /*cell_b*/) + { + // not supported by this version + } + private: const db::Layout &m_layout_a; db::cell_index_type m_initial_cell_a; + const std::set *m_selection_cone_a; const db::Layout &m_layout_b; db::cell_index_type m_initial_cell_b; + const std::set *m_selection_cone_b; db::cell_index_type m_cell_a; std::set m_callers_a; trans_set_t m_trans; @@ -256,6 +267,146 @@ class InstanceSetCompareFunction } }; +#else + +/* + * An alternative implementation of the InstanceSetCompareFunction + * + * This allows setting "endpoints" which are basically known cell identities + * and allow shortcutting the instance set comparison. They act as additional + * top cells. + * + * This implementation has a potential for higher performance, but + * in general it requires somewhat more memory and is not fully optimized yet. + */ + +namespace { + +struct InstanceSetCompareFunctionHash +{ + std::size_t operator () (const std::pair &p) const + { + return tl::hcombine (p.first, tl::hfunc (p.second)); + } +}; + +} + +class InstanceSetCompareFunction +{ +public: + typedef std::unordered_multiset, InstanceSetCompareFunctionHash> trans_set_t; + + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_selection_cone_b (selection_cone_b), + m_cell_a (std::numeric_limits::max ()), + m_ep_index (0) + { + m_endpoints_a.insert (std::make_pair (initial_cell_a, m_ep_index)); + m_endpoints_b.insert (std::make_pair (initial_cell_b, m_ep_index)); + ++m_ep_index; + } + + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) + { + if (cell_a != m_cell_a) { + m_cell_a = cell_a; + m_trans.clear (); + collect_or_compare_trans_set (true, m_trans, m_endpoints_a, m_layout_a, *m_selection_cone_a, m_cell_a, db::ICplxTrans ()); + } + + trans_set_t trans (m_trans); + + double mag = m_layout_b.dbu () / m_layout_a.dbu (); + if (! collect_or_compare_trans_set (false, trans, m_endpoints_b, m_layout_b, *m_selection_cone_b, cell_b, db::ICplxTrans (mag))) { + return false; + } else { + return trans.empty (); + } + } + + void make_endpoint (db::cell_index_type cell_a, db::cell_index_type cell_b) + { + m_cell_a = std::numeric_limits::max (); + m_trans.clear (); + + m_endpoints_a.insert (std::make_pair (cell_a, m_ep_index)); + m_endpoints_b.insert (std::make_pair (cell_b, m_ep_index)); + ++m_ep_index; + } + +private: + const db::Layout &m_layout_a; + const std::set *m_selection_cone_a; + const db::Layout &m_layout_b; + const std::set *m_selection_cone_b; + db::cell_index_type m_cell_a; + std::set m_callers_a; + trans_set_t m_trans; + std::map m_endpoints_a, m_endpoints_b; + unsigned int m_ep_index; + + bool collect_or_compare_trans_set (bool collect, + trans_set_t &ts, + const std::map &endpoints, + const db::Layout &layout, + const std::set &selection, + db::cell_index_type for_cell, + const db::ICplxTrans &child_trans) + { + auto ep = endpoints.find (for_cell); + if (ep != endpoints.end ()) { + + auto key = std::make_pair (ep->second, child_trans); + + if (collect) { + ts.insert (key); + return true; + } else { + auto i = ts.find (key); + if (i == ts.end () || *i != key) { + return false; + } else { + ts.erase (i); + return true; + } + } + + } + + const db::Cell &fc = layout.cell (for_cell); + + std::set selected_parents; + for (auto p = fc.begin_parent_cells (); p != fc.end_parent_cells (); ++p) { + if (selection.find (*p) != selection.end ()) { + selected_parents.insert (*p); + } + } + + for (auto p = fc.begin_parent_insts (); ! p.at_end (); ++p) { + + db::cell_index_type parent_cell = p->parent_cell_index (); + if (selected_parents.find (parent_cell) != selected_parents.end ()) { + + const db::CellInstArray &inst = *p->basic_child_inst (); + for (auto a = inst.begin (); ! a.at_end (); ++a) { + auto ta = inst.complex_trans (*a); + if (! collect_or_compare_trans_set (collect, ts, endpoints, layout, selection, parent_cell, ta * child_trans)) { + return false; + } + } + + } + + } + + return true; + } +}; + +#endif + // ------------------------------------------------------------------------------------- // CellMapping implementation @@ -438,18 +589,18 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty db::CellCounter cc_b (&layout_b, cell_index_b); std::multimap cm_b; - for (db::CellCounter::selection_iterator c = cc_b.begin (); c != cc_b.end (); ++c) { + for (auto c = cc_b.begin (); c != cc_b.end (); ++c) { cm_b.insert (std::make_pair (*c == cell_index_b ? 0 : cc_b.weight (*c), *c)); } std::multimap cm_a; - for (db::CellCounter::selection_iterator c = cc_a.begin (); c != cc_a.end (); ++c) { + for (auto c = cc_a.begin (); c != cc_a.end (); ++c) { cm_a.insert (std::make_pair (*c == cell_index_a ? 0 : cc_a.weight (*c), *c)); } std::map > candidates; // key = index(a), value = indices(b) - InstanceSetCompareFunction cmp (layout_a, cell_index_a, layout_b, cell_index_b); + InstanceSetCompareFunction cmp (layout_a, cell_index_a, &cc_a.selection (), layout_b, cell_index_b, &cc_b.selection ()); std::multimap::const_iterator a = cm_a.begin (), b = cm_b.begin (); while (a != cm_a.end () && b != cm_b.end ()) { @@ -480,10 +631,11 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty unsigned int g = 0; std::map > b_group; std::map b_group_of_cell; + std::map > new_candidates; while (a != cm_a.end () && a->first == w) { - candidates.insert (std::make_pair (a->second, std::vector ())); + new_candidates.insert (std::make_pair (a->second, std::vector ())); std::set groups_taken; @@ -494,16 +646,16 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty if (bg != b_group_of_cell.end ()) { if (groups_taken.find (bg->second) == groups_taken.end ()) { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { - candidates [a->second] = b_group [bg->second]; + if (cmp.compare (a->second, bb->second)) { + new_candidates [a->second] = b_group [bg->second]; groups_taken.insert (bg->second); } } } else { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { - candidates [a->second].push_back (bb->second); + if (cmp.compare (a->second, bb->second)) { + new_candidates [a->second].push_back (bb->second); b_group_of_cell.insert (std::make_pair (bb->second, g)); b_group.insert (std::make_pair (g, std::vector ())).first->second.push_back (bb->second); } @@ -527,6 +679,15 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty ++b; } + for (auto c = new_candidates.begin (); c != new_candidates.end (); ++c) { + if (c->second.size () == 1) { + // a single candidate: establish as new endpoint for the comparer + cmp.make_endpoint (c->first, c->second.front ()); + } + } + + candidates.insert (new_candidates.begin (), new_candidates.end ()); + } } diff --git a/src/db/db/dbHierarchyBuilder.cc b/src/db/db/dbHierarchyBuilder.cc index b54354858..ca49ebd98 100644 --- a/src/db/db/dbHierarchyBuilder.cc +++ b/src/db/db/dbHierarchyBuilder.cc @@ -390,10 +390,12 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn if (all) { CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst.object ().cell_index ()), std::set ()); + + // NOTE: this will set m_cm_new_entry db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst.object ().cell_index ())); // for new cells, create this instance - if (m_cell_stack.back ().first) { + if (m_cell_stack.back ().first || m_cm_new_entry) { db::CellInstArray new_inst (inst, &mp_target->array_repository ()); new_inst.object () = db::CellInst (new_cell); new_inst.transform (always_apply); @@ -432,10 +434,12 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db: } CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst_cell), clip_variant.second); + + // NOTE: this will set m_cm_new_entry db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst_cell)); // for a new cell, create this instance - if (m_cell_stack.back ().first) { + if (m_cell_stack.back ().first || m_cm_new_entry) { db::CellInstArray new_inst (db::CellInst (new_cell), always_apply * trans); new_inst.transform_into (m_trans); for (std::vector::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) { diff --git a/src/db/db/dbInstances.cc b/src/db/db/dbInstances.cc index 0e43a2293..d4245a51a 100644 --- a/src/db/db/dbInstances.cc +++ b/src/db/db/dbInstances.cc @@ -1524,6 +1524,84 @@ Instances::sort_child_insts (bool force) std::sort (m_insts_by_cell_index.begin (), m_insts_by_cell_index.end (), cell_inst_compare_f ()); } +namespace { + +/** + * @brief An alternative box converter for CellInst which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalCellInstBoxConverter (const db::Layout *layout) + : mp_layout (layout) + { + // .. nothing yet .. + } + + db::Cell::box_type operator() (const db::Cell::cell_inst_type &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update (); + } + +private: + const db::Layout *mp_layout; +}; + +/** + * @brief An alternative box converter for CellInstArray with properties which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +struct InternalCellInstArrayWithPropertiesBoxConverter +{ + typedef db::Cell::cell_inst_array_type cell_inst_array; + typedef db::Cell::box_type box_type; + typedef db::complex_bbox_tag complexity; + + InternalCellInstArrayWithPropertiesBoxConverter (const db::Layout *layout) + : m_bc (layout) + { } + + box_type operator() (const db::object_with_properties &array) const + { + return array.bbox (m_bc); + } + +private: + InternalCellInstBoxConverter m_bc; +}; + +/** + * @brief An alternative box converter for CellInstArray which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +struct InternalCellInstArrayBoxConverter +{ + typedef db::Cell::cell_inst_array_type cell_inst_array; + typedef db::Cell::box_type box_type; + typedef db::complex_bbox_tag complexity; + + InternalCellInstArrayBoxConverter (const db::Layout *layout) + : m_bc (layout) + { } + + box_type operator() (const cell_inst_array &array) const + { + return array.bbox (m_bc); + } + +private: + InternalCellInstBoxConverter m_bc; +}; + +} + void Instances::sort_inst_tree (const Layout *g, bool force) { @@ -1534,18 +1612,18 @@ Instances::sort_inst_tree (const Layout *g, bool force) if (m_generic.any) { if (is_editable ()) { - m_generic.stable_tree->sort (cell_inst_array_box_converter (*g)); + m_generic.stable_tree->sort (InternalCellInstArrayBoxConverter (g)); } else { - m_generic.unstable_tree->sort (cell_inst_array_box_converter (*g)); + m_generic.unstable_tree->sort (InternalCellInstArrayBoxConverter (g)); // since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case sort_child_insts (true); } } if (m_generic_wp.any) { if (is_editable ()) { - m_generic_wp.stable_tree->sort (cell_inst_wp_array_box_converter (*g)); + m_generic_wp.stable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g)); } else { - m_generic_wp.unstable_tree->sort (cell_inst_wp_array_box_converter (*g)); + m_generic_wp.unstable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g)); // since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case sort_child_insts (true); } diff --git a/src/db/db/dbLayerMapping.h b/src/db/db/dbLayerMapping.h index 9b4c34484..4a01eebcb 100644 --- a/src/db/db/dbLayerMapping.h +++ b/src/db/db/dbLayerMapping.h @@ -97,7 +97,7 @@ class DB_PUBLIC LayerMapping /** * @brief Add a layer mapping * - * @param layer_b The index of the layer in layout_a (the source of the mapping) + * @param layer_b The index of the layer in layout_b (the source of the mapping) * @param layer_a The index of the layer in layout_a (the target of the mapping) */ void map (unsigned int layer_b, unsigned int layer_a) diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index fbf1abf14..c14df882a 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1532,6 +1532,8 @@ Layout::rename_cell (cell_index_type id, const char *name) bool Layout::topological_sort () { + // NOTE: using cell.instances methods instead of the corresponding cell methods avoids a recursive update call + m_top_cells = 0; m_top_down_list.clear (); @@ -1559,7 +1561,7 @@ Layout::topological_sort () // child cells. for (const_iterator c = begin (); c != end (); ++c) { - if (c->parent_cells () == num_parents [c->cell_index ()]) { + if (c->instances ().parent_cells () == num_parents [c->cell_index ()]) { m_top_down_list.push_back (c->cell_index ()); num_parents [c->cell_index ()] = std::numeric_limits::max (); } @@ -1568,7 +1570,7 @@ Layout::topological_sort () // For all these a cells, increment the reported parent instance // count in all the child cells. for (cell_index_vector::const_iterator ii = m_top_down_list.begin () + n_top_down_cells; ii != m_top_down_list.end (); ++ii) { - for (cell_type::child_cell_iterator cc = cell (*ii).begin_child_cells (); ! cc.at_end (); ++cc) { + for (cell_type::child_cell_iterator cc = cell (*ii).instances ().begin_child_cells (); ! cc.at_end (); ++cc) { tl_assert (num_parents [*cc] != std::numeric_limits::max ()); num_parents [*cc] += 1; } @@ -1583,7 +1585,7 @@ Layout::topological_sort () } // Determine the number of top cells - for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).is_top (); ++e) { + for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).instances ().is_top (); ++e) { ++m_top_cells; } @@ -1815,6 +1817,8 @@ Layout::force_update_no_lock () const void Layout::update () const { + tl::MutexLocker locker (&lock ()); + // NOTE: the assumption is that either one thread is writing or // multiple threads are reading. Hence, we do not need to lock hier_dirty() or bboxes_dirty(). // We still do double checking as another thread might do the update as well. @@ -1822,8 +1826,6 @@ Layout::update () const return; } - tl::MutexLocker locker (&lock ()); - if (! under_construction ()) { force_update_no_lock (); } @@ -1886,13 +1888,14 @@ Layout::do_update () unsigned int layers = 0; pr->set (0); pr->set_desc (tl::to_string (tr ("Updating bounding boxes"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); if (cp.is_shape_bbox_dirty () || dirty_parents.find (*c) != dirty_parents.end ()) { if (cp.update_bbox (layers)) { // the bounding box has changed - need to insert parents into "dirty parents" list - for (cell_type::parent_cell_iterator p = cp.begin_parent_cells (); p != cp.end_parent_cells (); ++p) { + // NOTE: using "instances" instead of the cell directly avoids a recursive update call + for (cell_type::parent_cell_iterator p = cp.instances ().begin_parent_cells (); p != cp.instances ().end_parent_cells (); ++p) { dirty_parents.insert (*p); } } @@ -1907,7 +1910,7 @@ Layout::do_update () tl::SelfTimer timer (tl::verbosity () > layout_base_verbosity + 10, "Sorting shapes"); pr->set (0); pr->set_desc (tl::to_string (tr ("Sorting shapes"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); cp.sort_shapes (); @@ -1922,7 +1925,7 @@ Layout::do_update () size_t layers = 0; pr->set (0); pr->set_desc (tl::to_string (tr ("Sorting instances"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); bool force_sort_inst_tree = dirty_parents.find (*c) != dirty_parents.end (); diff --git a/src/db/unit_tests/dbCellInstanceSetHasherTests.cc b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc new file mode 100644 index 000000000..7bbaff206 --- /dev/null +++ b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc @@ -0,0 +1,145 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbCellInstanceSetHasher.h" +#include "tlUnitTest.h" + +TEST(1) +{ + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash ().to_string (), "(1,0,0) (0,1,0) (0,0,1)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (0).to_string (), "(0,0,0) (0,0,0) (0,0,0)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (2.0, 90.0, false, db::Vector (1, 2))).to_string (), "(0,-2,1) (2,0,2) (0,0,1)"); + + db::ICplxTrans t0 (2.0, 90.0, false, db::Vector (1, 2)); + + db::CellInstArray array (db::CellInst (0), t0, db::Vector (0, 100), db::Vector (100, 0), 2, 3); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (array).to_string (), "(0,-12,606) (12,0,312) (0,0,6)"); + + // emulate the regular array with an iterated array + std::vector dd; + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + dd.push_back (d); + } + } + db::CellInstArray iter_array (db::CellInst (0), t0, dd.begin (), dd.end ()); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (iter_array).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); + + // equivalence of sum of matrices and computed matrix for array + db::CellInstanceSetHasher::MatrixHash hm (0.0); + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + hm += db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (d) * t0); + } + } + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (hm).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); +} + +TEST(2) +{ + db::Layout ly; + + db::cell_index_type top = ly.add_cell ("TOP"); + db::cell_index_type c1 = ly.add_cell ("C1"); + db::cell_index_type c2 = ly.add_cell ("C2"); + db::cell_index_type c3 = ly.add_cell ("C3"); + db::cell_index_type c4a = ly.add_cell ("C4A"); + db::cell_index_type c5a = ly.add_cell ("C5A"); + db::cell_index_type c4b = ly.add_cell ("C4B"); + db::cell_index_type c5b = ly.add_cell ("C5B"); + + ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (1, true, db::Vector (0, 0)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (0, false, db::Vector (0, 10000)))); + + ly.cell (c1).insert (db::CellInstArray (db::CellInst (c2), db::Trans (1, true, db::Vector (100, 200)), db::Vector (0, 1000), db::Vector (1000, 0), 2l, 3l)); + + // C4 and C5 are single instances in C2, C5 with mag 2 + ly.cell (c2).insert (db::CellInstArray (db::CellInst (c4a), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20)))); + ly.cell (c2).insert (db::CellInstArray (db::CellInst (c5a), db::ICplxTrans (2.0, 0.0, false, db::Vector (10, 20)))); + + // C3 has same instances as C2, but flat + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 1100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 2100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 1100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 2100)))); + + // C4 and C5 are single instances in C3, C5 with a different complex angle (45 degree) + ly.cell (c3).insert (db::CellInstArray (db::CellInst (c4b), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20)))); + ly.cell (c3).insert (db::CellInstArray (db::CellInst (c5b), db::ICplxTrans (1.0, 45.0, false, db::Vector (10, 20)))); + + db::CellInstanceSetHasher hasher1 (&ly, top, 0); + + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (top)), "00004450"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c1)), "00023711"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c2)), "001260aa"); + EXPECT_EQ (hasher1.instance_set_hash (c3), hasher1.instance_set_hash (c2)); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c4a)), "001270ba"); + EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher1.instance_set_hash (c4b)); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5a)), "0010da3a"); // != hash of C4A because of mag 2 + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle + + std::set set1; + set1.insert (top); + set1.insert (c1); + set1.insert (c2); + set1.insert (c3); + set1.insert (c4a); + set1.insert (c5a); + set1.insert (c4b); + set1.insert (c5b); + db::CellInstanceSetHasher hasher2 (&ly, top, &set1); + + EXPECT_EQ (hasher1.instance_set_hash (top), hasher2.instance_set_hash (top)); + EXPECT_EQ (hasher1.instance_set_hash (c1), hasher2.instance_set_hash (c1)); + EXPECT_EQ (hasher1.instance_set_hash (c2), hasher2.instance_set_hash (c2)); + EXPECT_EQ (hasher1.instance_set_hash (c3), hasher2.instance_set_hash (c3)); + EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher2.instance_set_hash (c4a)); + EXPECT_EQ (hasher1.instance_set_hash (c4b), hasher2.instance_set_hash (c4b)); + EXPECT_EQ (hasher1.instance_set_hash (c5a), hasher2.instance_set_hash (c5a)); + EXPECT_EQ (hasher1.instance_set_hash (c5b), hasher2.instance_set_hash (c5b)); + + std::set set2 = set1; + // Remove C1 from selected set + set2.erase (c1); + db::CellInstanceSetHasher hasher3 (&ly, top, &set2); + + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (top)), "00004450"); + // NOTE: C1 hash is not valid as this cell is not selected + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c2)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c3)), "001260aa"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4a)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4b)), "001270ba"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5a)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle +} + diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ace029ba3..a51884427 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -7,6 +7,7 @@ TARGET = db_tests include($$PWD/../../lib_ut.pri) SOURCES = \ + dbCellInstanceSetHasherTests.cc \ dbCompoundOperationTests.cc \ dbEdgeNeighborhoodTests.cc \ dbFillToolTests.cc \