From 747807224b3dce9360118d1bf72a461615a84b5c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 6 Oct 2021 23:17:45 +0200 Subject: [PATCH 01/52] 2.5d view enhancements * Fine-tuned 2.5d view's key events * better error message in case of z-stack/layers mismatch * Don't confine bounding box to actual layout - that can be confusing --- src/plugins/tools/view_25d/lay_plugin/layD25View.cc | 2 +- .../tools/view_25d/lay_plugin/layD25ViewWidget.cc | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25View.cc b/src/plugins/tools/view_25d/lay_plugin/layD25View.cc index 0055a43cf..603136699 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25View.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25View.cc @@ -203,7 +203,7 @@ D25View::activated () bool any = mp_ui->d25_view->attach_view (view ()); if (! any) { mp_ui->d25_view->attach_view (0); - throw tl::Exception (tl::to_string (tr ("No z data configured for the layers in the view.\nUse \"Tools/Manage Technologies\" to set up a z stack."))); + throw tl::Exception (tl::to_string (tr ("No z data configured for the layers in this view.\nUse \"Tools/Manage Technologies\" to set up a z stack or check if it applies to the layers here."))); } mp_ui->d25_view->reset (); diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc index f83b57214..3eb97dfee 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc @@ -311,13 +311,13 @@ D25ViewWidget::keyPressEvent (QKeyEvent *event) // Move "into" or "out" - double d = (event->key () == Qt::Key_Up ? 0.1 : -0.1); + double d = (event->key () == Qt::Key_Up ? 0.05 : -0.05); QMatrix4x4 t; t.rotate (cam_azimuth (), 0.0, 1.0, 0.0); QVector3D cd = t.inverted ().map (QVector3D (0, 0, cam_dist ())); - set_displacement (displacement () + d * cd); + set_displacement (displacement () + d * cd / m_scale_factor); } @@ -327,7 +327,7 @@ D25ViewWidget::keyPressEvent (QKeyEvent *event) // Ctrl + left/right changes azimuths - double d = (event->key () == Qt::Key_Right ? 2 : -2); + double d = (event->key () == Qt::Key_Right ? 1 : -1); double a = cam_azimuth () + d; if (a < -180.0) { @@ -348,7 +348,7 @@ D25ViewWidget::keyPressEvent (QKeyEvent *event) t.rotate (cam_azimuth (), 0.0, 1.0, 0.0); QVector3D cd = t.inverted ().map (QVector3D (cam_dist (), 0, 0)); - set_displacement (displacement () + d * cd); + set_displacement (displacement () + d * cd / m_scale_factor); } @@ -634,11 +634,6 @@ D25ViewWidget::prepare_view () } - m_bbox &= cell_bbox; - if (m_bbox.empty ()) { - return false; - } - bool any = false; tl::AbsoluteProgress progress (tl::to_string (tr ("Rendering ..."))); From ec527367f24698a794985e6b923b60fbf1ae2b6e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 6 Oct 2021 23:26:12 +0200 Subject: [PATCH 02/52] 'tap' shows layer details like view name --- src/edt/edt/edtMainService.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index f778d6fe2..133b6f18a 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -2115,7 +2115,7 @@ MainService::cm_tap () QPoint mp = view ()->view_object_widget ()->mapToGlobal (view ()->view_object_widget ()->mouse_position ()); for (std::vector::const_iterator l = tapped_layers.begin (); l != tapped_layers.end (); ++l) { - QAction *a = menu->addAction (lay::LayerTreeModel::icon_for_layer (*l, view (), icon_size, icon_size, 0, true), tl::to_qstring ((*l)->source (true).to_string ())); + QAction *a = menu->addAction (lay::LayerTreeModel::icon_for_layer (*l, view (), icon_size, icon_size, 0, true), tl::to_qstring ((*l)->display_string (view (), true, true /*with source*/))); a->setData (int (l - tapped_layers.begin ())); } From 710d217b173624ee01bd66fcec7d3d98219bcd21 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 Oct 2021 22:17:03 +0200 Subject: [PATCH 03/52] strmxor: better command line doc, show file read timing like main app. --- src/buddies/src/bd/strmxor.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 6741f773b..6072e1e73 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -180,7 +180,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) << tl::arg ("input_b", &infile_b, "The second input file (any format, may be gzip compressed)") << tl::arg ("?output", &output, "The output file to which the XOR differences are written", "This argument is optional. If not given, the exit status alone will indicate whether the layouts " - "are identical or not." + "are identical or not. The output is a layout file. The format of the file is derived " + "from the file name's suffix (.oas[.gz] for (gzipped) OASIS, .gds[.gz] for (gzipped) GDS2 etc.)." ) << tl::arg ("-ta|--top-a=name", &top_a, "Specifies the top cell for the first layout", "Use this option to take a specific cell as the top cell from the first layout. All " @@ -248,6 +249,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) db::Layout layout_b; { + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading file (A): ")) + infile_a); + db::LoadLayoutOptions load_options; generic_reader_options_a.configure (load_options); @@ -257,6 +260,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) } { + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading file (B): ")) + infile_b); + db::LoadLayoutOptions load_options; generic_reader_options_b.configure (load_options); From c48f930fb3a2fc59741582919f8ed99b885c51c5 Mon Sep 17 00:00:00 2001 From: klayoutmatthias Date: Thu, 14 Oct 2021 23:48:24 +0200 Subject: [PATCH 04/52] Fixed #918 by using ignore size policy for the editor options tab widget --- src/laybasic/laybasic/layEditorOptionsPages.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 0d53bae60..eaac5cd8c 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -56,6 +56,7 @@ EditorOptionsPages::EditorOptionsPages (QWidget *parent, const std::vectorsetMargin (0); mp_pages = new QTabWidget (this); + mp_pages->setSizePolicy (QSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored)); ly1->addWidget (mp_pages); m_pages = pages; From 4d64e95680c22f5d693eea8825ed03447b7e41b5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 18 Oct 2021 23:08:38 +0200 Subject: [PATCH 05/52] Shape objects from custom queries 'select' are translated into shape markers when exporting to RDB --- src/lay/lay/laySearchReplaceDialog.cc | 5 +-- src/rdb/rdb/rdbUtils.cc | 47 +++++++++++++++++++++++++++ src/rdb/rdb/rdbUtils.h | 12 +++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/lay/lay/laySearchReplaceDialog.cc b/src/lay/lay/laySearchReplaceDialog.cc index 4f8196e64..3c70640ba 100644 --- a/src/lay/lay/laySearchReplaceDialog.cc +++ b/src/lay/lay/laySearchReplaceDialog.cc @@ -38,6 +38,7 @@ #include "tlLog.h" #include "tlProgress.h" #include "tlTimer.h" +#include "rdbUtils.h" #include #include @@ -537,10 +538,10 @@ SearchReplaceResults::export_rdb (rdb::Database &rdb, double dbu) rdb::Item *item = rdb.create_item (cell->id (), cat->id ()); if (! v->is_list ()) { - item->add_value (std::string (v->to_string ())); + rdb::add_item_value (item, *v, dbu); } else { for (std::vector::const_iterator i = v->get_list ().begin (); i != v->get_list ().end (); ++i) { - item->add_value (std::string (i->to_string ())); + rdb::add_item_value (item, *i, dbu); } } diff --git a/src/rdb/rdb/rdbUtils.cc b/src/rdb/rdb/rdbUtils.cc index 336ed963e..c6940a56a 100644 --- a/src/rdb/rdb/rdbUtils.cc +++ b/src/rdb/rdb/rdbUtils.cc @@ -317,5 +317,52 @@ void create_items_from_edge_pairs (rdb::Database *db, rdb::id_type cell_id, rdb: } } +void add_item_value (rdb::Item *item, const tl::Variant &v, double dbu) +{ + if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (dbu > 0 && v.is_user ()) { + db::DPoint p = db::CplxTrans (dbu) * v.to_user (); + item->add_value (db::DEdge (p, p)); + } else if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (dbu > 0 && v.is_user ()) { + db::DPolygon p; + db::DSimplePolygon sp = db::CplxTrans (dbu) * v.to_user (); + p.assign_hull (sp.begin_hull (), sp.end_hull ()); + item->add_value (p); + } else if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (dbu > 0 && v.is_user ()) { + item->add_value (db::CplxTrans (dbu) * v.to_user ()); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else if (v.is_user ()) { + db::DPoint p = v.to_user (); + item->add_value (db::DEdge (p, p)); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else if (v.is_user ()) { + db::DPolygon p; + db::DSimplePolygon sp = v.to_user (); + p.assign_hull (sp.begin_hull (), sp.end_hull ()); + item->add_value (p); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else if (v.is_user ()) { + item->add_value (v.to_user ()); + } else { + item->add_value (std::string (v.to_string ())); + } +} + } diff --git a/src/rdb/rdb/rdbUtils.h b/src/rdb/rdb/rdbUtils.h index d89cd6c72..f6b579008 100644 --- a/src/rdb/rdb/rdbUtils.h +++ b/src/rdb/rdb/rdbUtils.h @@ -136,6 +136,18 @@ RDB_PUBLIC_TEMPLATE void create_items_from_sequence (rdb::Database *db, rdb::id_ } } +/** + * @brief Creates a value from a tl::Variant + * + * This produces values which reflect some values the variant can assume - specifically + * shapes are converted into corresponding RDB values. The database unit is used to + * translate integer-type values. Using a database unit of 0 will disable the conversion of + * such types. + * + * Unknown types are converted to strings. + */ +RDB_PUBLIC void add_item_value (rdb::Item *item, const tl::Variant &v, double dbu = 0.0); + } #endif From 04deb785baa35b2b7790a68fc3a3749edbb3e7b4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 18 Oct 2021 23:10:39 +0200 Subject: [PATCH 06/52] Asking whether to apply a .lyp file on multiple layouts on MRU-listed .lyp files too. --- src/lay/lay/layMainWindow.cc | 67 +++++++++++++++++++----------------- src/lay/lay/layMainWindow.h | 1 + 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index c97f0def7..b886acfc8 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -2025,44 +2025,47 @@ MainWindow::cm_load_layer_props () if (current_view ()) { std::string fn; if (mp_lprops_fdia->get_open (fn, tl::to_string (QObject::tr ("Load Layer Properties File")))) { - - int target_cv_index = -2; - - if (current_view ()->cellviews () > 1 && is_single_cv_layer_properties_file (fn)) { - - QStringList items; - items << QString (QObject::tr ("Take it as it is")); - items << QString (QObject::tr ("Apply to all layouts")); - for (unsigned int i = 0; i < current_view ()->cellviews (); ++i) { - items << QString (tl::to_qstring (tl::to_string (QObject::tr ("Apply to ")) + current_view ()->cellview (i)->name () + " (@" + tl::to_string (i + 1) + ")")); - } - - bool ok; - QString item = QInputDialog::getItem(this, QObject::tr ("Apply Layer Properties File"), - QObject::tr ("There are multiple layouts in that panel but the layer properties file contains properties for a single one.\nWhat should be done?"), - items, 1, false, &ok); - if (!ok || item.isEmpty()) { - return; - } - - target_cv_index = items.indexOf (item) - 2; - - } - - if (target_cv_index > -2) { - load_layer_properties (fn, target_cv_index, false /*current view only*/, false /*don't add default*/); - } else { - load_layer_properties (fn, false /*current view only*/, false /*don't add default*/); - } - + load_layer_props_from_file (fn); add_to_other_mru (fn, cfg_mru_layer_properties); - } } else { throw tl::Exception (tl::to_string (QObject::tr ("No view open to load the layer properties for"))); } } +void +MainWindow::load_layer_props_from_file (const std::string &fn) +{ + int target_cv_index = -2; + + if (current_view ()->cellviews () > 1 && is_single_cv_layer_properties_file (fn)) { + + QStringList items; + items << QString (QObject::tr ("Take it as it is")); + items << QString (QObject::tr ("Apply to all layouts")); + for (unsigned int i = 0; i < current_view ()->cellviews (); ++i) { + items << QString (tl::to_qstring (tl::to_string (QObject::tr ("Apply to ")) + current_view ()->cellview (i)->name () + " (@" + tl::to_string (i + 1) + ")")); + } + + bool ok; + QString item = QInputDialog::getItem(this, QObject::tr ("Apply Layer Properties File"), + QObject::tr ("There are multiple layouts in that panel but the layer properties file contains properties for a single one.\nWhat should be done?"), + items, 1, false, &ok); + if (!ok || item.isEmpty()) { + return; + } + + target_cv_index = items.indexOf (item) - 2; + + } + + if (target_cv_index > -2) { + load_layer_properties (fn, target_cv_index, false /*current view only*/, false /*don't add default*/); + } else { + load_layer_properties (fn, false /*current view only*/, false /*don't add default*/); + } +} + void MainWindow::save_session (const std::string &fn) { @@ -3238,7 +3241,7 @@ MainWindow::open_recent_layer_properties (size_t n) if (n < m_mru_layer_properties.size ()) { std::string fn = m_mru_layer_properties [n]; - load_layer_properties (fn, false /*current view only*/, false /*don't add default*/); + load_layer_props_from_file (fn); add_to_other_mru (fn, cfg_mru_layer_properties); // make it the latest } diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index f9fe51611..cb2f2246d 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -805,6 +805,7 @@ private: int dirty_files (std::string &dirty_files); + void load_layer_props_from_file (const std::string &fn); void interactive_close_view (int index, bool all_cellviews); void call_on_current_view (void (lay::LayoutView::*func) (), const std::string &op_desc); void current_view_changed (); From 97d393c78a8e63c4074d9089b0b2b64fb5089206 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 18 Oct 2021 23:24:28 +0200 Subject: [PATCH 07/52] Layout#is_editable --- src/laybasic/laybasic/gsiDeclLayLayoutView.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutView.cc b/src/laybasic/laybasic/gsiDeclLayLayoutView.cc index a01db5183..938ca9d36 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutView.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutView.cc @@ -690,6 +690,11 @@ Class decl_LayoutView (QT_EXTERNAL_BASE (QWidget) "lay", "Layou "\n" "In 0.25, this method has been moved from MainWindow to LayoutView.\n" ) + + gsi::method ("is_editable?", &lay::LayoutView::is_editable, + "@brief Returns true if the view is in editable mode\n" + "\n" + "This read-only attribute has been added in version 0.27.5.\n" + ) + gsi::method ("reload_layout", &lay::LayoutView::reload_layout, gsi::arg ("cv"), "@brief Reloads the given cellview\n" "\n" From 89a50abeddf99f26c1a7b4cc74f8081d1e9bec2d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Oct 2021 00:11:27 +0200 Subject: [PATCH 08/52] Some more documentation about layer views. --- src/lay/lay/doc/manual/basic.xml | 2 +- .../lay/doc/manual/layer_source_expert.xml | 4 + src/lay/lay/doc/manual/layer_views.xml | 84 +++++++++++++++++++ src/lay/lay/layHelpResources.qrc | 1 + 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/lay/lay/doc/manual/layer_views.xml diff --git a/src/lay/lay/doc/manual/basic.xml b/src/lay/lay/doc/manual/basic.xml index 92969e177..ec136b253 100644 --- a/src/lay/lay/doc/manual/basic.xml +++ b/src/lay/lay/doc/manual/basic.xml @@ -24,9 +24,9 @@ + - diff --git a/src/lay/lay/doc/manual/layer_source_expert.xml b/src/lay/lay/doc/manual/layer_source_expert.xml index 30619a4c1..97a50d9dc 100644 --- a/src/lay/lay/doc/manual/layer_source_expert.xml +++ b/src/lay/lay/doc/manual/layer_source_expert.xml @@ -100,5 +100,9 @@ a selector as well, only those shapes will be shown that meet both criteria.

+

+ A general description for the source notation is found here: . +

+ diff --git a/src/lay/lay/doc/manual/layer_views.xml b/src/lay/lay/doc/manual/layer_views.xml new file mode 100644 index 000000000..c9c294b0c --- /dev/null +++ b/src/lay/lay/doc/manual/layer_views.xml @@ -0,0 +1,84 @@ + + + + + + The Layer List (Layer Views) + + + +

+ An important concept in KLayout are the layer views. KLayout displays the layers + of a layout by default in a list on the right side of the main window. + This list however, does not directly reflect the layers in the layout database. + Instead this list is a collection of "views". A view is a description of what is + to be displayed and how. +

+ +

+ Essentially, the entries in the layer list are pointers to layers in the + database, together with a description how to paint the shapes on these layers (the + "layer properties"). +

+ +

+ The pointer is the "source" of a layer view. This is typically a GDS layer and datatype, + but can be a layer name (for DXF for example). There are also abstract sources (such as + cell boundaries) and the sources can include selectors or modifiers. Selectors are + used to only display shapes with certain user properties or from certain hierarchy + levels. Modifiers transform the shapes before they are drawn for example. + The source is defined by a "source specification" - this is a string describing the + database layer and selectors and modifiers. A simple source string is "1/0" which is + for GDS layer 1, datatype 0 without and selectors or modifiers. + To change the source, use "Change Source" from the layer list's context menu. +

+ +

+ See for some source specification string applications + and more details. +

+ +

+ Beside the source, a layer entry has a display name. This is an arbitrary text + providing a description for the user. By default - when no such name is present - + the source of the layer will be displayed. + To change the display name, use "Rename" from the layer list's context menu. +

+ +

+ Plus of course, the layer views have many options to specify the drawing style, + animations and decorations. +

+ +

+ The concept of separating views from the database layers opens some interesting options: +

+ +
    +
  • Layer views can refer to individual layouts from multi-layout views (through the "@1", "@2", ... notation in the source). + Hence, multiple layouts can be mixed in a single layer list.
  • +
  • Layers can be present in the list which do not need to be present in the database. Such a layer is shown as empty. + This is important as in GDS an empty layer is equivalent to non-existing. Still you may want to have it shown in the + layer list - the views offer this option.
  • +
  • Vice versa, database layer may not be listed in the layer list if no corresponding layer view is present. This + way, auxiliary or debug layers can be omitted from the layer list. A "wildcard specification" is available to make + sure, all layers are shown if you need to see all.
  • +
  • Multiple tabs can be present to provide multiple views on the same layouts. This is just an alternative set of + layer views.
  • +
  • Layer grouping, sorting etc. are just operations on the views, no database change is involved.
  • +
+ +

+ The concept on the other hand is slightly counter-intuitive at first. + Here are some hints: +

+ +
    +
  • Renaming a layer does not change the source - if you rename a layer to something like "1/0", you are likely to fool yourself thinking this is layer 1, datatype 0.
  • +
  • Changing a layer view's source does not change the database too - it will just change the pointer. To change a layer's information in the database, use Edit/Layer/Edit Layer Specification.
  • +
  • Deleting a layer from the layer list does not delete the layer from the database. Use Edit/Layer/Delete Layer instead.
  • +
  • Additing a new layer does not immediately create the layer in the database. Only once you draw something on that layer, it is generated in the database.
  • +
+ +
+ diff --git a/src/lay/lay/layHelpResources.qrc b/src/lay/lay/layHelpResources.qrc index 0e0053df0..06524e1f5 100644 --- a/src/lay/lay/layHelpResources.qrc +++ b/src/lay/lay/layHelpResources.qrc @@ -158,6 +158,7 @@ doc/manual/landmarks.xml doc/manual/layer_animation.xml doc/manual/layer_boolean.xml + doc/manual/layer_views.xml doc/manual/layer_color.xml doc/manual/layer_content.xml doc/manual/layer_fill.xml From abe40ae99f83e72d79eb1ee3a84117b7c1a544ef Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Oct 2021 20:48:13 +0200 Subject: [PATCH 09/52] Bugfix plus more tests --- src/db/db/dbNetlistDeviceExtractorClasses.cc | 5 +- .../dbNetlistDeviceExtractorTests.cc | 292 ++++++++++++++++++ testdata/algo/bjt3_1.gds | Bin 0 -> 234 bytes testdata/algo/bjt3_2.gds | Bin 0 -> 298 bytes testdata/algo/bjt3_3.gds | Bin 0 -> 298 bytes testdata/algo/bjt3_4.gds | Bin 0 -> 362 bytes testdata/algo/bjt4_1.gds | Bin 0 -> 362 bytes 7 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 testdata/algo/bjt3_1.gds create mode 100644 testdata/algo/bjt3_2.gds create mode 100644 testdata/algo/bjt3_3.gds create mode 100644 testdata/algo/bjt3_4.gds create mode 100644 testdata/algo/bjt4_1.gds diff --git a/src/db/db/dbNetlistDeviceExtractorClasses.cc b/src/db/db/dbNetlistDeviceExtractorClasses.cc index 957c9e8bb..c4cbd3844 100644 --- a/src/db/db/dbNetlistDeviceExtractorClasses.cc +++ b/src/db/db/dbNetlistDeviceExtractorClasses.cc @@ -694,13 +694,16 @@ void NetlistDeviceExtractorBJT3Transistor::extract_devices (const std::vector collector terminal is collector outside base - rcollector = rcollector2base - rbase; + rcollector = rcollectors.selected_interacting (rbase) - rbase; } else { // lateral transistor: base is reduced by collector area rcollector = rcollector2base; rbase -= rcollector2base; } + // TODO: rbase - rcollector2base above could basically split a base region into different + // subregions potentially forming one transistor each. + // this is what is the true base contact rbase -= remitter2base; diff --git a/src/db/unit_tests/dbNetlistDeviceExtractorTests.cc b/src/db/unit_tests/dbNetlistDeviceExtractorTests.cc index c6077ce61..6978a692c 100644 --- a/src/db/unit_tests/dbNetlistDeviceExtractorTests.cc +++ b/src/db/unit_tests/dbNetlistDeviceExtractorTests.cc @@ -853,3 +853,295 @@ TEST(42_DMOS4DeviceExtractorTestCircular) EXPECT_EQ (o3.to_string (), "(-100,-500;-100,900;1000,900;1000,-500/200,-200;700,-200;700,600;200,600)"); EXPECT_EQ (o4.to_string (), "(-100,-500;-100,900;1000,900;1000,-500/200,-200;700,-200;700,600;200,600)"); } + +TEST(50_BJT3DeviceExtractorTest) +{ + db::Layout ly; + + { + db::LoadLayoutOptions options; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "bjt3_1.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 l0 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(0, 0))), dss); + db::Region l1 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(1, 0))), dss); + db::Region l2 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(2, 0))), dss); + db::Region o1 (dss); + db::Region o2 (dss); + db::Region o3 (dss); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorBJT3Transistor ex ("BJT3"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["E"] = &l0; + dl["B"] = &l1; + dl["C"] = &l2; + dl["tE"] = &o1; + dl["tB"] = &o2; + dl["tC"] = &o3; + ex.extract (dss, 0, dl, nl, cl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device BJT3 $1 (C=(null),B=(null),E=(null)) (AE=0.81,PE=3.6,AB=5,PB=9,AC=5,PC=9,NE=1);\n" + "end;\n" + ); + EXPECT_EQ (o1.to_string (), "(700,400;700,1300;1600,1300;1600,400)"); + EXPECT_EQ (o2.to_string (), "(0,0;0,2000;2500,2000;2500,0/700,400;1600,400;1600,1300;700,1300)"); + EXPECT_EQ (o3.to_string (), "(0,0;0,2000;2500,2000;2500,0/700,400;1600,400;1600,1300;700,1300)"); +} + +TEST(51_BJT3DeviceExtractorTest) +{ + db::Layout ly; + + { + db::LoadLayoutOptions options; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "bjt3_2.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 l0 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(0, 0))), dss); + db::Region l1 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(1, 0))), dss); + db::Region l2 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(2, 0))), dss); + db::Region o1 (dss); + db::Region o2 (dss); + db::Region o3 (dss); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorBJT3Transistor ex ("BJT3"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["E"] = &l0; + dl["B"] = &l1; + dl["C"] = &l2; + dl["tE"] = &o1; + dl["tB"] = &o2; + dl["tC"] = &o3; + ex.extract (dss, 0, dl, nl, cl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device BJT3 $1 (C=(null),B=(null),E=(null)) (AE=0.81,PE=3.6,AB=5,PB=9,AC=5,PC=9,NE=1);\n" + "end;\n" + ); + EXPECT_EQ (o1.to_string (), "(700,400;700,1300;1600,1300;1600,400)"); + EXPECT_EQ (o2.to_string (), "(0,0;0,2000;2500,2000;2500,0/700,400;1600,400;1600,1300;700,1300)"); + EXPECT_EQ (o3.to_string (), "(-1000,-500;-1000,2500;3000,2500;3000,-500/0,0;2500,0;2500,2000;0,2000)"); +} + +TEST(52_BJT3DeviceExtractorTestLateral) +{ + db::Layout ly; + + { + db::LoadLayoutOptions options; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "bjt3_3.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 l0 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(0, 0))), dss); + db::Region l1 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(1, 0))), dss); + db::Region l2 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(2, 0))), dss); + db::Region o1 (dss); + db::Region o2 (dss); + db::Region o3 (dss); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorBJT3Transistor ex ("BJT3"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["E"] = &l0; + dl["B"] = &l1; + dl["C"] = &l2; + dl["tE"] = &o1; + dl["tB"] = &o2; + dl["tC"] = &o3; + ex.extract (dss, 0, dl, nl, cl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device BJT3 $1 (C=(null),B=(null),E=(null)) (AE=0.81,PE=3.6,AB=5,PB=9,AC=0.8,PC=4.8,NE=1);\n" + "end;\n" + ); + EXPECT_EQ (o1.to_string (), "(700,400;700,1300;1600,1300;1600,400)"); + EXPECT_EQ (o2.to_string (), "(0,0;0,2000;2100,2000;2100,0/700,400;1600,400;1600,1300;700,1300)"); + EXPECT_EQ (o3.to_string (), "(2100,0;2100,2000;2500,2000;2500,0)"); +} + +TEST(53_BJT3DeviceExtractorTestMultEmitter) +{ + db::Layout ly; + + { + db::LoadLayoutOptions options; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "bjt3_4.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 l0 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(0, 0))), dss); + db::Region l1 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(1, 0))), dss); + db::Region l2 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(2, 0))), dss); + db::Region o1 (dss); + db::Region o2 (dss); + db::Region o3 (dss); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorBJT3Transistor ex ("BJT3"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["E"] = &l0; + dl["B"] = &l1; + dl["C"] = &l2; + dl["tE"] = &o1; + dl["tB"] = &o2; + dl["tC"] = &o3; + ex.extract (dss, 0, dl, nl, cl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device BJT3 $1 (C=(null),B=(null),E=(null)) (AE=0.5,PE=3,AB=10,PB=14,AC=10,PC=14,NE=1);\n" + " device BJT3 $2 (C=(null),B=(null),E=(null)) (AE=0.5,PE=3,AB=10,PB=14,AC=10,PC=14,NE=1);\n" + "end;\n" + ); + EXPECT_EQ (o1.to_string (), "(1000,500;1000,1500;1500,1500;1500,500);(3500,500;3500,1500;4000,1500;4000,500)"); + EXPECT_EQ (o2.to_string (), "(0,0;0,2000;5000,2000;5000,0/1000,500;1500,500;1500,1500;1000,1500/3500,500;4000,500;4000,1500;3500,1500);(0,0;0,2000;5000,2000;5000,0/1000,500;1500,500;1500,1500;1000,1500/3500,500;4000,500;4000,1500;3500,1500)"); + EXPECT_EQ (o3.to_string (), "(-500,-500;-500,2500;5500,2500;5500,-500/0,0;5000,0;5000,2000;0,2000);(-500,-500;-500,2500;5500,2500;5500,-500/0,0;5000,0;5000,2000;0,2000)"); +} + +TEST(54_BJT4DeviceExtractorTest) +{ + db::Layout ly; + + { + db::LoadLayoutOptions options; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "bjt4_1.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 l0 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(0, 0))), dss); + db::Region l1 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(1, 0))), dss); + db::Region l2 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(2, 0))), dss); + db::Region l3 (db::RecursiveShapeIterator (ly, tc, ly.get_layer (db::LayerProperties(3, 0))), dss); + db::Region o1 (dss); + db::Region o2 (dss); + db::Region o3 (dss); + db::Region o4 (dss); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorBJT4Transistor ex ("BJT4"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["E"] = &l0; + dl["B"] = &l1; + dl["C"] = &l2; + dl["S"] = &l3; + dl["tE"] = &o1; + dl["tB"] = &o2; + dl["tC"] = &o3; + dl["tS"] = &o4; + ex.extract (dss, 0, dl, nl, cl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP ();\n" + " device BJT4 $1 (C=(null),B=(null),E=(null),S=(null)) (AE=0.81,PE=3.6,AB=5,PB=9,AC=5,PC=9,NE=1);\n" + "end;\n" + ); + EXPECT_EQ (o1.to_string (), "(700,400;700,1300;1600,1300;1600,400)"); + EXPECT_EQ (o2.to_string (), "(0,0;0,2000;2500,2000;2500,0/700,400;1600,400;1600,1300;700,1300)"); + EXPECT_EQ (o3.to_string (), "(-1000,-500;-1000,2500;3000,2500;3000,-500/0,0;2500,0;2500,2000;0,2000)"); + EXPECT_EQ (o4.to_string (), "(0,0;0,2000;2500,2000;2500,0)"); +} + diff --git a/testdata/algo/bjt3_1.gds b/testdata/algo/bjt3_1.gds new file mode 100644 index 0000000000000000000000000000000000000000..11a0abdc5060455b33d4a9f0b37573e3257bb7d9 GIT binary patch literal 234 zcmZQzV_;&6V31*CVt>lO#UKoX{0ywfYz7V{HXlzX1_lvkRy)T|bMIrzKUli#&|fe+ zDuh9X6^}kPwh;dS1_l-m1_m}>CI&_ZHa;c>1_m7gW*~_K*e@_Ja2{b`fYC@&EP@FA jKvNLKq&_V4HE~_VEybsGgue^l;Rh_ literal 0 HcmV?d00001 diff --git a/testdata/algo/bjt3_2.gds b/testdata/algo/bjt3_2.gds new file mode 100644 index 0000000000000000000000000000000000000000..08c87c9ffc74cd8c2033b6314f05f01002ab1b14 GIT binary patch literal 298 zcmZQzV_;&6V31*CVt>lO#UKoX{0w5qYz7V{HXlzX1_lvkRy)T|bMIrzKUli#&|fe+ zDuh9X6^}kPwh;dS1_l-m1_m}>CI&_ZHa;c>1_m7gW*~_K*e@_Ja2{b`fYC@&EP@FA zObGq||NoKr|NkEkh=%Cp-T|Xw;vkvY4#FfcGqU|;~ztRg@u2Lufh2hw2u K>_9iLFaQ9|TrK|q literal 0 HcmV?d00001 diff --git a/testdata/algo/bjt3_3.gds b/testdata/algo/bjt3_3.gds new file mode 100644 index 0000000000000000000000000000000000000000..11d454da87196f1203365429663ae8a71378b4f8 GIT binary patch literal 298 zcmZQzV_;&6V31*CVt>lO#UKoX$_(1bYz7V{HXlzX1_lvkRy)T|bMIrzKUli#&|fe+ zDuh9X6^}kPwh;dS1_l-m1_m}>CI&_ZHa;c>1_m7gW*~_K*e@_Ja2{b`fYC@&EP@FA zOvw5$HTzDVFJ<1y#q$W#KAPOexNH5_A~8aU|^iUzyP9IMSxNc2pT31q`~^x Jfo@=7000KdCLsU- literal 0 HcmV?d00001 diff --git a/testdata/algo/bjt3_4.gds b/testdata/algo/bjt3_4.gds new file mode 100644 index 0000000000000000000000000000000000000000..d326300d8c75c64a2a44477aac7e50066e4bcb2e GIT binary patch literal 362 zcmZQzV_;&6V31*CVt>lO#UKoXrVQf9Yz7V{HXlzX1_lvkRy)T|bMIrzKUli#&|fe+ zDuh9X6^}kPwh;dS1_l-m1_m}>CI&_ZHa;c>1_m7gW*~_K*e@_J2zM|rz-S~X7D0r5 zCWQX~|Nrs8FarbU5e5d)8W;@|2hj}3`hl)M*w6fefr0T00|SU=y@QHj;y@awe%>_@ Y{U92opML>@2I+^&gY~lm{lLNi02_)r;{X5v literal 0 HcmV?d00001 diff --git a/testdata/algo/bjt4_1.gds b/testdata/algo/bjt4_1.gds new file mode 100644 index 0000000000000000000000000000000000000000..bffdecae4660d3aae2b7e5b063ba4ed57709f5ba GIT binary patch literal 362 zcmaKny$%6E6h_a^?#}vKA+}H`l|m^RK{P@_v4waeonE8UcmRb`)Lufb-iR?vCNq^O z=1jhullu)4A=b1!5j`l;AfZd>z9|T~m`xz@!{K<_pWlwp!Svia>^CW`@Q){Dx?Dj} z02vEBWK{s2ny+u?M0Y4&!O$56b?Xc3EA?tB^{(Xnx#hE=$#dfRx`o{@F5vATXPD^G g$kODRuJ2o4sjU6$khLM-9iI$M{_XblBmIYf4{eJ?O8@`> literal 0 HcmV?d00001 From 17a6459f833d2ebc9a803f7e01bbe6900eb2b20a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Oct 2021 21:05:09 +0200 Subject: [PATCH 10/52] Fixed #920 (fill border parameter sign issue) --- src/lay/lay/layFillDialog.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lay/lay/layFillDialog.cc b/src/lay/lay/layFillDialog.cc index c3cb63568..b9b6458e1 100644 --- a/src/lay/lay/layFillDialog.cc +++ b/src/lay/lay/layFillDialog.cc @@ -220,7 +220,7 @@ FillDialog::generate_fill (const FillParameters &fp) // preprocess fill regions if (distance_x != 0 || distance_y != 0) { - fill_region.size (distance_x, distance_y); + fill_region.size (-distance_x, -distance_y); } else { fill_region.merge (); } From 4ecfd93c693c95ed947ce72f63cafae752eddde8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Oct 2021 22:50:05 +0200 Subject: [PATCH 11/52] Using Python3 for CentOS7 builds now --- scripts/rpm-data/klayout.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rpm-data/klayout.spec b/scripts/rpm-data/klayout.spec index e326ed4ff..023bf38cf 100644 --- a/scripts/rpm-data/klayout.spec +++ b/scripts/rpm-data/klayout.spec @@ -52,7 +52,7 @@ Requires: qt5-qttools-devel >= 5.11.1 # CentOS7 requirements %if "%{target_system}" == "centos7" Requires: ruby >= 2.0.0 -Requires: python >= 2.7.5 +Requires: python >= 3.6.0 Requires: qt-x11 >= 4.8.5 %define buildopt -j2 %define pylib %{python_sitearch} From 8a892cb6e25a4d5b083f2e7867502f877cbdf44e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 20 Oct 2021 21:35:00 +0200 Subject: [PATCH 12/52] Fixed python3 dependency for CentOS7 RPM --- scripts/rpm-data/klayout.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rpm-data/klayout.spec b/scripts/rpm-data/klayout.spec index 023bf38cf..072327cd1 100644 --- a/scripts/rpm-data/klayout.spec +++ b/scripts/rpm-data/klayout.spec @@ -52,7 +52,7 @@ Requires: qt5-qttools-devel >= 5.11.1 # CentOS7 requirements %if "%{target_system}" == "centos7" Requires: ruby >= 2.0.0 -Requires: python >= 3.6.0 +Requires: python3 >= 3.6.0 Requires: qt-x11 >= 4.8.5 %define buildopt -j2 %define pylib %{python_sitearch} From f968751a44a5836c59e9f8741787acabaab55765 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 20 Oct 2021 22:21:56 +0200 Subject: [PATCH 13/52] RPM build script patch for Python3 @ CentOS7 --- scripts/rpm-data/klayout.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rpm-data/klayout.spec b/scripts/rpm-data/klayout.spec index 072327cd1..11bdb5614 100644 --- a/scripts/rpm-data/klayout.spec +++ b/scripts/rpm-data/klayout.spec @@ -55,7 +55,7 @@ Requires: ruby >= 2.0.0 Requires: python3 >= 3.6.0 Requires: qt-x11 >= 4.8.5 %define buildopt -j2 -%define pylib %{python_sitearch} +%define pylib %{python3_sitearch} %endif %if "%{target_system}" == "centos6" From 09df614d8a93afcc2ab7332e534cf91f9fd245e5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 22 Oct 2021 23:52:35 +0200 Subject: [PATCH 14/52] Bugfix: array iterator must not iterate on empty search box. --- src/db/db/dbArray.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index a61f1a8cd..5834743c4 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -964,9 +964,7 @@ struct iterated_array virtual std::pair *, bool> begin_touching (const box_type &b) const { - if (b.empty ()) { - return std::make_pair (new iterated_array_iterator (m_v.begin (), m_v.end ()), false); - } else if (! b.touches (m_box)) { + if (b.empty () || ! b.touches (m_box)) { return std::make_pair (new iterated_array_iterator (m_v.end (), m_v.end ()), false); } else { box_convert_type bc; From c6bd8563311b0c3cebc5daa618d5df4fdf17acd7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 Oct 2021 00:05:50 +0200 Subject: [PATCH 15/52] WIP --- src/db/db/dbHierProcessor.cc | 386 +++++++++++++++++++++++++++++++---- 1 file changed, 348 insertions(+), 38 deletions(-) diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index 33d9fbc7b..01d47e1ec 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -1015,9 +1015,141 @@ private: } }; +#if 1 +template +struct interaction_registration_inst2inst + : db::box_scanner_receiver2 +{ +public: + typedef typename local_processor_cell_contexts::context_key_type interactions_value_type; + typedef std::unordered_map, interactions_value_type> interactions_type; + + interaction_registration_inst2inst (const db::Layout *subject_layout, unsigned int subject_layer, const db::Layout *intruder_layout, unsigned int intruder_layer, bool foreign, db::Coord dist, interactions_type *result) + : mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout), m_subject_layer (subject_layer), m_intruder_layer (intruder_layer), m_dist (dist), mp_result (result), m_foreign (foreign) + { + // nothing yet .. + } + + void add (const db::CellInstArray *inst1, unsigned int id1, const db::CellInstArray *inst2, unsigned int id2) + { + // NOTE: self-interactions are possible for arrays: different elements of the + // array may interact which is a cell-external interaction. + if (mp_subject_layout != mp_intruder_layout || id1 != id2 || inst1->size () > 1) { + + bool ignore = false; + if (mp_subject_layout == mp_intruder_layout && m_subject_layer == m_intruder_layer && ! m_foreign) { + if (m_interactions.find (std::make_pair (id2, id1)) != m_interactions.end ()) { + // for self interactions ignore the reverse interactions + ignore = true; + } else { + m_interactions.insert (std::make_pair (id1, id2)); + } + } + + if (! ignore) { + collect_instance_interactions (inst1, inst2); + } + + } + } + +private: + const db::Layout *mp_subject_layout, *mp_intruder_layout; + unsigned int m_subject_layer, m_intruder_layer; + db::Coord m_dist; + interactions_type *mp_result; + std::unordered_set > m_interactions; + bool m_foreign; + + void + collect_instance_interactions (const db::CellInstArray *inst1, const db::CellInstArray *inst2) + { +printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", + mp_subject_layout->cell_name (inst1->object ().cell_index ()), int (inst1->size ()), (inst1->bbox(db::box_convert (*mp_subject_layout)).to_string()).c_str(), + mp_intruder_layout->cell_name (inst2->object ().cell_index ()), int (inst2->size ()), (inst2->bbox(db::box_convert (*mp_intruder_layout)).to_string()).c_str()); // @@@ + // TODO: this algorithm is not in particular effective for identical arrays + + const db::Cell &cell1 = mp_subject_layout->cell (inst1->object ().cell_index ()); + const db::Cell &cell2 = mp_intruder_layout->cell (inst2->object ().cell_index ()); + db::box_convert inst2_bc (*mp_intruder_layout, m_intruder_layer); + + std::unordered_map interactions_cache; + + for (db::CellInstArray::iterator n = inst1->begin (); ! n.at_end (); ++n) { + + db::ICplxTrans tn1 = inst1->complex_trans (*n); + db::ICplxTrans tni1 = tn1.inverted (); + db::Box ibox1 = tn1 * cell1.bbox (m_subject_layer).enlarged (db::Vector (m_dist, m_dist)); + + std::set *insts = 0; + + if (! ibox1.empty ()) { + + // TODO: in some cases, it may be possible to optimize this for arrays + + for (db::CellInstArray::iterator k = inst2->begin_touching (safe_box_enlarged (ibox1, -1, -1), inst2_bc); ! k.at_end (); ++k) { + + if (inst1 == inst2 && *n == *k) { + // skip self-interactions - this is handled inside the cell + continue; + } + + db::ICplxTrans tn2 = inst2->complex_trans (*k); + + // NOTE: we need to enlarge both subject *and* intruder boxes - either object comes close to intruder or the other way around + db::Box ibox2 = tn2 * cell2.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); + + db::ICplxTrans tn21 = tni1 * tn2; + + db::Box cbox = ibox1 & ibox2; + if (! cbox.empty ()) { + + bool interacts = false; + + std::unordered_map::const_iterator ic = interactions_cache.find (tn21); + if (ic != interactions_cache.end ()) { + + interacts = ic->second; + + } else { + + db::ICplxTrans tni2 = tn2.inverted (); + + // not very strong, but already useful: the cells interact if there is a layer1 in cell1 + // in the common box and a layer2 in the cell2 in the common box + // NOTE: don't use overlap mode for the RecursiveShapeIterator as this would not capture dot-like + // objects like texts. Instead safe-shrink the search box and use touching mode ("false" for the last + // argument) + interacts = (! db::RecursiveShapeIterator (*mp_subject_layout, cell1, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end () && + ! db::RecursiveShapeIterator (*mp_intruder_layout, cell2, m_intruder_layer, safe_box_enlarged (tni2 * cbox, -1, -1), false).at_end ()); + interactions_cache.insert (std::make_pair (tn21, interacts)); + + } + + if (interacts) { + if (! insts) { + insts = & (*mp_result) [std::make_pair (cell1.cell_index (), tn1)].first; + } + insts->insert (db::CellInstArray (db::CellInst (cell2.cell_index ()), tn21)); + } + + } + + } + + } + + } + } +}; +#else + static bool instances_interact (const db::Layout *layout1, const db::CellInstArray *inst1, unsigned int layer1, const db::Layout *layout2, const db::CellInstArray *inst2, unsigned int layer2, db::Coord dist) { +printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", + layout1->cell_name (inst1->object ().cell_index ()), int (inst1->size ()), (inst1->bbox(db::box_convert (*layout1)).to_string()).c_str(), + layout2->cell_name (inst2->object ().cell_index ()), int (inst2->size ()), (inst2->bbox(db::box_convert (*layout2)).to_string()).c_str()); // @@@ // TODO: this algorithm is not in particular effective for identical arrays const db::Cell &cell1 = layout1->cell (inst1->object ().cell_index ()); @@ -1124,7 +1256,69 @@ private: std::unordered_set > m_interactions; bool m_foreign; }; +#endif +#if 1 +template +struct interaction_registration_inst2shape + : db::box_scanner_receiver2 +{ +public: + typedef typename local_processor_cell_contexts::context_key_type interactions_value_type; + typedef std::unordered_map, interactions_value_type> interactions_type; + + interaction_registration_inst2shape (db::Layout *subject_layout, unsigned int subject_layer, db::Coord dist, interactions_type *result) + : mp_subject_layout (subject_layout), m_subject_layer (subject_layer), m_dist (dist), mp_result (result) + { + // nothing yet .. + } + + void add (const db::CellInstArray *inst, unsigned int, const TI *ref, unsigned int layer) + { + collect_instance_shape_interactions (inst, layer, *ref, m_dist); + } + +private: + db::Layout *mp_subject_layout; + unsigned int m_subject_layer; + db::Coord m_dist; + interactions_type *mp_result; + + void + collect_instance_shape_interactions (const db::CellInstArray *inst, unsigned int layer, const TI &ref, db::Coord dist) + { + const db::Cell &cell = mp_subject_layout->cell (inst->object ().cell_index ()); + db::box_convert inst_bc (*mp_subject_layout, m_subject_layer); + db::Box rbox = db::box_convert () (ref); + + for (db::CellInstArray::iterator n = inst->begin_touching (safe_box_enlarged (rbox, dist - 1, dist - 1), inst_bc); ! n.at_end (); ++n) { + + db::ICplxTrans tn = inst->complex_trans (*n); + db::Box cbox = (tn * cell.bbox (m_subject_layer)).enlarged (db::Vector (dist, dist)) & rbox.enlarged (db::Vector (dist, dist)); + + if (! cbox.empty ()) { + + db::ICplxTrans tni = tn.inverted (); + db::shape_reference_translator_with_trans rt (mp_subject_layout, tni); + + std::set *shapes = 0; + + // not very strong, but already useful: the cells interact if there is a layer in cell + // in the common box + // NOTE: don't use overlapping mode here, because this will not select point-like objects as texts or + // dot edges. Instead safe-shrink the search box and use touching mode. + for (db::RecursiveShapeIterator s (*mp_subject_layout, cell, m_subject_layer, safe_box_enlarged (tni * cbox, -1, -1), false); !s.at_end (); ++s) { + if (! shapes) { + shapes = & (*mp_result) [std::make_pair (cell.cell_index (), tn)].second [layer]; + } + shapes->insert (rt (ref)); + } + } + + } + } +}; +#else template static bool instance_shape_interacts (const db::Layout *layout, const db::CellInstArray *inst, unsigned int layer, const T &ref, db::Coord dist) @@ -1181,7 +1375,7 @@ private: db::Coord m_dist; std::unordered_map, std::map > > > *mp_result; }; - +#endif } // --------------------------------------------------------------------------------------------- @@ -1504,9 +1698,119 @@ void local_processor::compute_contexts (local_processor_contextsbegin ().at_end ()) { - typedef std::pair, std::map > > interaction_value_type; +#if 1 + // Key: single instance given by cell index and transformation + // Value the contexts for the child cell for this instance + typedef typename local_processor_cell_contexts::context_key_type interactions_value_type; + typedef std::unordered_map, interactions_value_type> interactions_type; + interactions_type interactions; - std::unordered_map interactions; + // insert dummy interactions to handle at least the child cell vs. itself + // - this is important so we will always handle the instances unless they are + // entirely empty in the subject layer + for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { + if (! inst_bcs (i->cell_inst ()).empty ()) { + db::cell_index_type ci = i->cell_inst ().object ().cell_index (); + for (db::CellInstArray::iterator n = i->begin (); ! n.at_end (); ++n) { + db::ICplxTrans tn = i->complex_trans (*n); + interactions.insert (std::make_pair (std::make_pair (ci, tn), interactions_value_type ())); + } + } + } + + // TODO: can we shortcut this if interactions is empty? + for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { + + db::box_convert inst_bci (*mp_intruder_layout, contexts.actual_intruder_layer (*il)); + + db::box_scanner2 scanner; + interaction_registration_inst2inst rec (mp_subject_layout, contexts.subject_layer (), mp_intruder_layout, contexts.actual_intruder_layer (*il), contexts.is_foreign (*il), dist, &interactions); + + unsigned int id = 0; + + if (subject_cell == intruder_cell) { + + // Use the same id's for same instances - this way we can easily detect same instances + // and don't make them self-interacting + + for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { + unsigned int iid = ++id; + if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { + scanner.insert1 (&i->cell_inst (), iid); + } + if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) { + scanner.insert2 (&i->cell_inst (), iid); + } + } + + } else { + + for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { + if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { + scanner.insert1 (&i->cell_inst (), ++id); + } + } + + if (intruder_cell) { + for (db::Cell::const_iterator i = intruder_cell->begin (); !i.at_end (); ++i) { + if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) { + scanner.insert2 (&i->cell_inst (), ++id); + } + } + } + + } + + std::list instance_heap; + + for (std::set::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) { + if (! inst_bci (*i).empty ()) { + scanner.insert2 (i.operator-> (), ++id); + } + } + + scanner.process (rec, dist, inst_bcs, inst_bci); + + } + +// TODO: can we shortcut this if interactions is empty? + { + db::box_scanner2 scanner; + interaction_registration_inst2shape rec (mp_subject_layout, contexts.subject_layer (), dist, &interactions); + + for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { + if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { + scanner.insert1 (&i->cell_inst (), 0); + } + } + + for (typename std::map >::const_iterator il = intruders.second.begin (); il != intruders.second.end (); ++il) { + for (typename std::set::const_iterator i = il->second.begin (); i != il->second.end (); ++i) { + scanner.insert2 (i.operator-> (), il->first); + } + } + + for (std::map::const_iterator im = intruder_shapes.begin (); im != intruder_shapes.end (); ++im) { + for (db::Shapes::shape_iterator i = im->second->begin (shape_flags ()); !i.at_end (); ++i) { + scanner.insert2 (i->basic_ptr (typename TI::tag ()), im->first); + } + } + + scanner.process (rec, dist, inst_bcs, db::box_convert ()); + } + + // produce the tasks for computing the next-level interactions + for (typename interactions_type::iterator i = interactions.begin (); i != interactions.end (); ++i) { + + db::Cell *subject_child_cell = &mp_subject_layout->cell (i->first.first); + db::Cell *intruder_child_cell = (subject_cell == intruder_cell ? subject_child_cell : 0); + + issue_compute_contexts (contexts, cell_context, subject_cell, subject_child_cell, i->first.second, intruder_child_cell, i->second, dist); + + } + +#else + typedef std::pair, std::map > > interaction_value_type; // insert dummy interactions to handle at least the child cell vs. itself // - this is important so we will always handle the instances unless they are @@ -1560,6 +1864,8 @@ void local_processor::compute_contexts (local_processor_contexts instance_heap; + for (std::set::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) { if (! inst_bci (*i).empty ()) { scanner.insert2 (i.operator-> (), ++id); @@ -1601,57 +1907,61 @@ void local_processor::compute_contexts (local_processor_contexts > effective_instance_cache_type; effective_instance_cache_type effective_instance_cache; +size_t n = 0, nmax = interactions.size(); // @@@ for (typename std::unordered_map::const_iterator i = interactions.begin (); i != interactions.end (); ++i) { +printf("@@@ %d/%d -> #%d,#%d\n", int(n++), int(nmax), int (i->second.first.size()), int(i->second.second.size())); fflush(stdout); - db::Cell &subject_child_cell = mp_subject_layout->cell (i->first->object ().cell_index ()); + db::Cell *subject_child_cell = &mp_subject_layout->cell (i->first->object ().cell_index ()); + db::Box sc_bbox = subject_child_cell->bbox (contexts.subject_layer ()).enlarged (db::Vector (dist, dist)); + if (sc_bbox.empty ()) { + continue; + } + + db::Cell *intruder_child_cell = (subject_cell == intruder_cell ? subject_child_cell : 0); for (db::CellInstArray::iterator n = i->first->begin (); ! n.at_end (); ++n) { db::ICplxTrans tn = i->first->complex_trans (*n); db::ICplxTrans tni = tn.inverted (); - db::Box nbox = tn * subject_child_cell.bbox (contexts.subject_layer ()).enlarged (db::Vector (dist, dist)); + db::Box nbox = tn * sc_bbox; - if (! nbox.empty ()) { + typename local_processor_cell_contexts::context_key_type intruders_below; - typename local_processor_cell_contexts::context_key_type intruders_below; + db::shape_reference_translator_with_trans rt (mp_subject_layout, tni); - db::shape_reference_translator_with_trans rt (mp_subject_layout, tni); - - for (typename std::map >::const_iterator pl = i->second.second.begin (); pl != i->second.second.end (); ++pl) { - std::set &out = intruders_below.second [pl->first]; - for (typename std::unordered_set::const_iterator p = pl->second.begin (); p != pl->second.end (); ++p) { - if (nbox.overlaps (db::box_convert () (*p))) { - out.insert (rt (*p)); - } + for (typename std::map >::const_iterator pl = i->second.second.begin (); pl != i->second.second.end (); ++pl) { + std::set &out = intruders_below.second [pl->first]; + for (typename std::unordered_set::const_iterator p = pl->second.begin (); p != pl->second.end (); ++p) { + if (nbox.overlaps (db::box_convert () (*p))) { + out.insert (rt (*p)); } } + } - // TODO: in some cases, it may be possible to optimize this for arrays + // TODO: in some cases, it may be possible to optimize this for arrays - for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { + for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { - unsigned int ail = contexts.actual_intruder_layer (*il); - db::box_convert inst_bcii (*mp_intruder_layout, ail); + unsigned int ail = contexts.actual_intruder_layer (*il); + db::box_convert inst_bcii (*mp_intruder_layout, ail); - for (std::unordered_set::const_iterator j = i->second.first.begin (); j != i->second.first.end (); ++j) { + for (std::unordered_set::const_iterator j = i->second.first.begin (); j != i->second.first.end (); ++j) { - for (db::CellInstArray::iterator k = (*j)->begin_touching (safe_box_enlarged (nbox, -1, -1), inst_bcii); ! k.at_end (); ++k) { + for (db::CellInstArray::iterator k = (*j)->begin_touching (safe_box_enlarged (nbox, -1, -1), inst_bcii); ! k.at_end (); ++k) { - db::ICplxTrans tk = (*j)->complex_trans (*k); - // NOTE: no self-interactions - if (i->first != *j || tn != tk) { - - // optimize the intruder instance so it will be as low as possible - effective_instance_cache_key_type key (ail, std::make_pair ((*j)->object ().cell_index (), tni * tk)); - effective_instance_cache_type::iterator cached = effective_instance_cache.find (key); - if (cached == effective_instance_cache.end ()) { - std::pair ei = effective_instance (contexts.subject_layer (), i->first->object ().cell_index (), ail, (*j)->object ().cell_index (), tni * tk, dist); - cached = effective_instance_cache.insert (std::make_pair (key, ei)).first; - } - if (cached->second.first) { - intruders_below.first.insert (cached->second.second); - } + db::ICplxTrans tk = (*j)->complex_trans (*k); + // NOTE: no self-interactions + if (i->first != *j || tn != tk) { + // optimize the intruder instance so it will be as low as possible + effective_instance_cache_key_type key (ail, std::make_pair ((*j)->object ().cell_index (), tni * tk)); + effective_instance_cache_type::iterator cached = effective_instance_cache.find (key); + if (cached == effective_instance_cache.end ()) { + std::pair ei = effective_instance (contexts.subject_layer (), i->first->object ().cell_index (), ail, (*j)->object ().cell_index (), tni * tk, dist); + cached = effective_instance_cache.insert (std::make_pair (key, ei)).first; + } + if (cached->second.first) { + intruders_below.first.insert (cached->second.second); } } @@ -1660,14 +1970,14 @@ void local_processor::compute_contexts (local_processor_contexts Date: Sun, 24 Oct 2021 20:28:15 +0200 Subject: [PATCH 16/52] WIP --- src/db/db/dbHierProcessor.cc | 475 ++++++----------------------------- 1 file changed, 78 insertions(+), 397 deletions(-) diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index 01d47e1ec..e8dc9dc1b 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -1015,7 +1015,6 @@ private: } }; -#if 1 template struct interaction_registration_inst2inst : db::box_scanner_receiver2 @@ -1064,16 +1063,18 @@ private: void collect_instance_interactions (const db::CellInstArray *inst1, const db::CellInstArray *inst2) { +#if 0 printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", mp_subject_layout->cell_name (inst1->object ().cell_index ()), int (inst1->size ()), (inst1->bbox(db::box_convert (*mp_subject_layout)).to_string()).c_str(), mp_intruder_layout->cell_name (inst2->object ().cell_index ()), int (inst2->size ()), (inst2->bbox(db::box_convert (*mp_intruder_layout)).to_string()).c_str()); // @@@ +#endif // TODO: this algorithm is not in particular effective for identical arrays const db::Cell &cell1 = mp_subject_layout->cell (inst1->object ().cell_index ()); const db::Cell &cell2 = mp_intruder_layout->cell (inst2->object ().cell_index ()); db::box_convert inst2_bc (*mp_intruder_layout, m_intruder_layer); - std::unordered_map interactions_cache; + std::unordered_map > > interactions_cache; for (db::CellInstArray::iterator n = inst1->begin (); ! n.at_end (); ++n) { @@ -1099,38 +1100,26 @@ printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", // NOTE: we need to enlarge both subject *and* intruder boxes - either object comes close to intruder or the other way around db::Box ibox2 = tn2 * cell2.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); - db::ICplxTrans tn21 = tni1 * tn2; - db::Box cbox = ibox1 & ibox2; - if (! cbox.empty ()) { + if (! cbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, cell1, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end ()) { - bool interacts = false; + db::ICplxTrans tn21 = tni1 * tn2; - std::unordered_map::const_iterator ic = interactions_cache.find (tn21); + std::list > *interactions = 0; + + std::unordered_map > >::iterator ic = interactions_cache.find (tn21); if (ic != interactions_cache.end ()) { - - interacts = ic->second; - + interactions = &ic->second; } else { - - db::ICplxTrans tni2 = tn2.inverted (); - - // not very strong, but already useful: the cells interact if there is a layer1 in cell1 - // in the common box and a layer2 in the cell2 in the common box - // NOTE: don't use overlap mode for the RecursiveShapeIterator as this would not capture dot-like - // objects like texts. Instead safe-shrink the search box and use touching mode ("false" for the last - // argument) - interacts = (! db::RecursiveShapeIterator (*mp_subject_layout, cell1, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end () && - ! db::RecursiveShapeIterator (*mp_intruder_layout, cell2, m_intruder_layer, safe_box_enlarged (tni2 * cbox, -1, -1), false).at_end ()); - interactions_cache.insert (std::make_pair (tn21, interacts)); - + interactions = &interactions_cache [tn21]; + collect_intruder_tree_interactions (cell1, cell2, tni1, tn21, cbox, *interactions); } - if (interacts) { + for (std::list >::const_iterator i = interactions->begin (); i != interactions->end (); ++i) { if (! insts) { insts = & (*mp_result) [std::make_pair (cell1.cell_index (), tn1)].first; } - insts->insert (db::CellInstArray (db::CellInst (cell2.cell_index ()), tn21)); + insts->insert (db::CellInstArray (db::CellInst (i->first), i->second)); } } @@ -1141,124 +1130,98 @@ printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", } } -}; -#else -static bool -instances_interact (const db::Layout *layout1, const db::CellInstArray *inst1, unsigned int layer1, const db::Layout *layout2, const db::CellInstArray *inst2, unsigned int layer2, db::Coord dist) -{ -printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", - layout1->cell_name (inst1->object ().cell_index ()), int (inst1->size ()), (inst1->bbox(db::box_convert (*layout1)).to_string()).c_str(), - layout2->cell_name (inst2->object ().cell_index ()), int (inst2->size ()), (inst2->bbox(db::box_convert (*layout2)).to_string()).c_str()); // @@@ - // TODO: this algorithm is not in particular effective for identical arrays +// @@@ + bool has_intruder_tree_interactions (const db::Cell &subject_cell, const db::Cell &intruder_cell, const db::ICplxTrans &tni1, const db::ICplxTrans &tn21, const db::Box &cbox) + { + db::ICplxTrans tni2 = tn21.inverted () * tni1; + db::Box tbox2 = safe_box_enlarged (tni2 * cbox, -1, -1); - const db::Cell &cell1 = layout1->cell (inst1->object ().cell_index ()); - const db::Cell &cell2 = layout2->cell (inst2->object ().cell_index ()); - db::box_convert inst2_bc (*layout2, layer2); + if (! intruder_cell.shapes (m_intruder_layer).begin_touching (tbox2, ShapeIterator::All).at_end ()) { - std::unordered_set relative_trans_seen; + // we're overlapping with shapes from the intruder - do not recursive further + return true; - for (db::CellInstArray::iterator n = inst1->begin (); ! n.at_end (); ++n) { + } - db::ICplxTrans tn1 = inst1->complex_trans (*n); - db::ICplxTrans tni1 = tn1.inverted (); - db::Box ibox1 = tn1 * cell1.bbox (layer1).enlarged (db::Vector (dist, dist)); + for (db::Cell::touching_iterator i = intruder_cell.begin_touching (tbox2); ! i.at_end (); ++i) { - if (! ibox1.empty ()) { + const db::Cell &ic = mp_intruder_layout->cell (i->cell_index ()); - // TODO: in some cases, it may be possible to optimize this for arrays + for (db::CellInstArray::iterator ia = i->begin_touching (tbox2, mp_intruder_layout); ! ia.at_end (); ++ia) { - for (db::CellInstArray::iterator k = inst2->begin_touching (safe_box_enlarged (ibox1, -1, -1), inst2_bc); ! k.at_end (); ++k) { + db::ICplxTrans it = i->complex_trans (*ia); - if (inst1 == inst2 && *n == *k) { - // skip self-interactions - this is handled inside the cell - continue; - } + db::Box ibox2 = tni2.inverted () * it * ic.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); + db::Box ccbox = cbox & ibox2; - db::ICplxTrans tn2 = inst2->complex_trans (*k); - - // NOTE: we need to enlarge both subject *and* intruder boxes - either object comes close to intruder or the other way around - db::Box ibox2 = tn2 * cell2.bbox (layer2).enlarged (db::Vector (dist, dist)); - - db::ICplxTrans tn21 = tni1 * tn2; - if (! relative_trans_seen.insert (tn21).second) { - // this relative transformation was already seen - continue; - } - - db::Box cbox = ibox1 & ibox2; - if (! cbox.empty ()) { - - db::ICplxTrans tni2 = tn2.inverted (); - - // not very strong, but already useful: the cells interact if there is a layer1 in cell1 - // in the common box and a layer2 in the cell2 in the common box - // NOTE: don't use overlap mode for the RecursiveShapeIterator as this would not capture dot-like - // objects like texts. Instead safe-shrink the search box and use touching mode ("false" for the last - // argument) - if (! db::RecursiveShapeIterator (*layout1, cell1, layer1, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end () && - ! db::RecursiveShapeIterator (*layout2, cell2, layer2, safe_box_enlarged (tni2 * cbox, -1, -1), false).at_end ()) { + if (! ccbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * ccbox, -1, -1), false).at_end ()) { + if (has_intruder_tree_interactions (subject_cell, ic, tni1, tn21 * it, ccbox)) { return true; } - } } } + return false; } +// @@@ - return false; -} - -template -struct interaction_registration_inst2inst - : db::box_scanner_receiver2 -{ -public: - typedef std::pair, std::map > > interaction_value_type; - - interaction_registration_inst2inst (const db::Layout *subject_layout, unsigned int subject_layer, const db::Layout *intruder_layout, unsigned int intruder_layer, bool foreign, db::Coord dist, std::unordered_map *result) - : mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout), m_subject_layer (subject_layer), m_intruder_layer (intruder_layer), m_dist (dist), mp_result (result), m_foreign (foreign) + void collect_intruder_tree_interactions (const db::Cell &subject_cell, const db::Cell &intruder_cell, const db::ICplxTrans &tni1, const db::ICplxTrans &tn21, const db::Box &cbox, std::list > &interactions) { - // nothing yet .. - } +#if 0 + if (has_intruder_tree_interactions (subject_cell, intruder_cell, tni1, tn21, cbox)) { + interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); + } +#elif 1 + db::ICplxTrans tni2 = tn21.inverted () * tni1; + db::Box tbox2 = safe_box_enlarged (tni2 * cbox, -1, -1); - void add (const db::CellInstArray *inst1, unsigned int id1, const db::CellInstArray *inst2, unsigned int id2) - { - // NOTE: self-interactions are possible for arrays: different elements of the - // array may interact which is a cell-external interaction. - if (mp_subject_layout != mp_intruder_layout || id1 != id2 || inst1->size () > 1) { + if (! intruder_cell.shapes (m_intruder_layer).begin_touching (tbox2, ShapeIterator::All).at_end ()) { - bool ignore = false; - if (mp_subject_layout == mp_intruder_layout && m_subject_layer == m_intruder_layer && ! m_foreign) { - if (m_interactions.find (std::make_pair (id2, id1)) != m_interactions.end ()) { - // for self interactions ignore the reverse interactions - ignore = true; - } else { - m_interactions.insert (std::make_pair (id1, id2)); + // we're overlapping with shapes from the intruder - do not recursive further + interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); + return; + + } + + for (db::Cell::touching_iterator i = intruder_cell.begin_touching (tbox2); ! i.at_end (); ++i) { + + const db::Cell &ic = mp_intruder_layout->cell (i->cell_index ()); + + for (db::CellInstArray::iterator ia = i->begin_touching (tbox2, mp_intruder_layout); ! ia.at_end (); ++ia) { + + db::ICplxTrans it = i->complex_trans (*ia); + + db::Box ibox2 = tni2.inverted () * it * ic.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); + db::Box ccbox = cbox & ibox2; + + if (! ccbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * ccbox, -1, -1), false).at_end ()) { + collect_intruder_tree_interactions (subject_cell, ic, tni1, tn21 * it, ccbox, interactions); } - } - if (! ignore && instances_interact (mp_subject_layout, inst1, m_subject_layer, mp_intruder_layout, inst2, m_intruder_layer, m_dist)) { - (*mp_result) [inst1].first.insert (inst2); } } - } +#else + // not very strong, but already useful: the cells interact if there is a layer1 in cell1 + // in the common box and a layer2 in the cell2 in the common box + // NOTE: don't use overlap mode for the RecursiveShapeIterator as this would not capture dot-like + // objects like texts. Instead safe-shrink the search box and use touching mode ("false" for the last + // argument) + db::ICplxTrans tni2 = tn21.inverted () * tni1; + bool interacts = (! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end () && + ! db::RecursiveShapeIterator (*mp_intruder_layout, intruder_cell, m_intruder_layer, safe_box_enlarged (tni2 * cbox, -1, -1), false).at_end ()); -private: - const db::Layout *mp_subject_layout, *mp_intruder_layout; - unsigned int m_subject_layer, m_intruder_layer; - db::Coord m_dist; - std::unordered_map, std::map > > > *mp_result; - std::unordered_set > m_interactions; - bool m_foreign; -}; + if (interacts) { + interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); + } #endif + } +}; -#if 1 template struct interaction_registration_inst2shape : db::box_scanner_receiver2 @@ -1318,64 +1281,7 @@ private: } } }; -#else -template -static bool -instance_shape_interacts (const db::Layout *layout, const db::CellInstArray *inst, unsigned int layer, const T &ref, db::Coord dist) -{ - const db::Cell &cell = layout->cell (inst->object ().cell_index ()); - db::box_convert inst_bc (*layout, layer); - db::Box rbox = db::box_convert () (ref); - for (db::CellInstArray::iterator n = inst->begin_touching (safe_box_enlarged (rbox, dist - 1, dist - 1), inst_bc); ! n.at_end (); ++n) { - - db::ICplxTrans tn = inst->complex_trans (*n); - db::Box cbox = (tn * cell.bbox (layer)).enlarged (db::Vector (dist, dist)) & rbox.enlarged (db::Vector (dist, dist)); - - if (! cbox.empty ()) { - - db::ICplxTrans tni = tn.inverted (); - - // not very strong, but already useful: the cells interact if there is a layer in cell - // in the common box - // NOTE: don't use overlapping mode here, because this will not select point-like objects as texts or - // dot edges. Instead safe-shrink the search box and use touching mode. - if (! db::RecursiveShapeIterator (*layout, cell, layer, safe_box_enlarged (tni * cbox, -1, -1), false).at_end ()) { - return true; - } - - } - - } - - return false; -} - -template -struct interaction_registration_inst2shape - : db::box_scanner_receiver2 -{ -public: - interaction_registration_inst2shape (const db::Layout *subject_layout, unsigned int subject_layer, db::Coord dist, std::unordered_map, std::map > > > *result) - : mp_subject_layout (subject_layout), m_subject_layer (subject_layer), m_dist (dist), mp_result (result) - { - // nothing yet .. - } - - void add (const db::CellInstArray *inst, unsigned int, const T *ref, unsigned int layer) - { - if (instance_shape_interacts (mp_subject_layout, inst, m_subject_layer, *ref, m_dist)) { - (*mp_result) [inst].second [layer].insert (*ref); - } - } - -private: - const db::Layout *mp_subject_layout; - unsigned int m_subject_layer; - db::Coord m_dist; - std::unordered_map, std::map > > > *mp_result; -}; -#endif } // --------------------------------------------------------------------------------------------- @@ -1630,6 +1536,9 @@ void local_processor::compute_contexts (local_processor_contexts::context_key_type &intruders, db::Coord dist) const { +#if 0 // @@@ +printf("@@@ --- compute_contexts (%s @ %s)\n", mp_subject_layout->cell_name (subject_cell->cell_index ()), subject_cell_inst.to_string().c_str()); fflush(stdout); +#endif CRONOLOGY_COLLECTION_BRACKET(event_compute_contexts) if (tl::verbosity () >= m_base_verbosity + 20) { @@ -1698,7 +1607,6 @@ void local_processor::compute_contexts (local_processor_contextsbegin ().at_end ()) { -#if 1 // Key: single instance given by cell index and transformation // Value the contexts for the child cell for this instance typedef typename local_processor_cell_contexts::context_key_type interactions_value_type; @@ -1773,7 +1681,7 @@ void local_processor::compute_contexts (local_processor_contexts scanner; interaction_registration_inst2shape rec (mp_subject_layout, contexts.subject_layer (), dist, &interactions); @@ -1809,237 +1717,10 @@ void local_processor::compute_contexts (local_processor_contexts, std::map > > interaction_value_type; - - // insert dummy interactions to handle at least the child cell vs. itself - // - this is important so we will always handle the instances unless they are - // entirely empty in the subject layer - for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { - if (! inst_bcs (i->cell_inst ()).empty ()) { - interactions.insert (std::make_pair (&i->cell_inst (), interaction_value_type ())); - } - } - -// TODO: can we shortcut this if interactions is empty? - for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { - - db::box_convert inst_bci (*mp_intruder_layout, contexts.actual_intruder_layer (*il)); - - db::box_scanner2 scanner; - interaction_registration_inst2inst rec (mp_subject_layout, contexts.subject_layer (), mp_intruder_layout, contexts.actual_intruder_layer (*il), contexts.is_foreign (*il), dist, &interactions); - - unsigned int id = 0; - - if (subject_cell == intruder_cell) { - - // Use the same id's for same instances - this way we can easily detect same instances - // and don't make them self-interacting - - for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { - unsigned int iid = ++id; - if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { - scanner.insert1 (&i->cell_inst (), iid); - } - if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) { - scanner.insert2 (&i->cell_inst (), iid); - } - } - - } else { - - for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { - if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { - scanner.insert1 (&i->cell_inst (), ++id); - } - } - - if (intruder_cell) { - for (db::Cell::const_iterator i = intruder_cell->begin (); !i.at_end (); ++i) { - if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) { - scanner.insert2 (&i->cell_inst (), ++id); - } - } - } - - } - - std::list instance_heap; - - for (std::set::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) { - if (! inst_bci (*i).empty ()) { - scanner.insert2 (i.operator-> (), ++id); - } - } - - scanner.process (rec, dist, inst_bcs, inst_bci); - - } - -// TODO: can we shortcut this if interactions is empty? - { - db::box_scanner2 scanner; - interaction_registration_inst2shape rec (mp_subject_layout, contexts.subject_layer (), dist, &interactions); - - for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) { - if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) { - scanner.insert1 (&i->cell_inst (), 0); - } - } - - for (typename std::map >::const_iterator il = intruders.second.begin (); il != intruders.second.end (); ++il) { - for (typename std::set::const_iterator i = il->second.begin (); i != il->second.end (); ++i) { - scanner.insert2 (i.operator-> (), il->first); - } - } - - for (std::map::const_iterator im = intruder_shapes.begin (); im != intruder_shapes.end (); ++im) { - for (db::Shapes::shape_iterator i = im->second->begin (shape_flags ()); !i.at_end (); ++i) { - scanner.insert2 (i->basic_ptr (typename TI::tag ()), im->first); - } - } - - scanner.process (rec, dist, inst_bcs, db::box_convert ()); - } - - // this cache should reduce the effort of checking array vs. array - typedef std::pair > effective_instance_cache_key_type; - typedef std::map > effective_instance_cache_type; - effective_instance_cache_type effective_instance_cache; - -size_t n = 0, nmax = interactions.size(); // @@@ - for (typename std::unordered_map::const_iterator i = interactions.begin (); i != interactions.end (); ++i) { -printf("@@@ %d/%d -> #%d,#%d\n", int(n++), int(nmax), int (i->second.first.size()), int(i->second.second.size())); fflush(stdout); - - db::Cell *subject_child_cell = &mp_subject_layout->cell (i->first->object ().cell_index ()); - db::Box sc_bbox = subject_child_cell->bbox (contexts.subject_layer ()).enlarged (db::Vector (dist, dist)); - if (sc_bbox.empty ()) { - continue; - } - - db::Cell *intruder_child_cell = (subject_cell == intruder_cell ? subject_child_cell : 0); - - for (db::CellInstArray::iterator n = i->first->begin (); ! n.at_end (); ++n) { - - db::ICplxTrans tn = i->first->complex_trans (*n); - db::ICplxTrans tni = tn.inverted (); - db::Box nbox = tn * sc_bbox; - - typename local_processor_cell_contexts::context_key_type intruders_below; - - db::shape_reference_translator_with_trans rt (mp_subject_layout, tni); - - for (typename std::map >::const_iterator pl = i->second.second.begin (); pl != i->second.second.end (); ++pl) { - std::set &out = intruders_below.second [pl->first]; - for (typename std::unordered_set::const_iterator p = pl->second.begin (); p != pl->second.end (); ++p) { - if (nbox.overlaps (db::box_convert () (*p))) { - out.insert (rt (*p)); - } - } - } - - // TODO: in some cases, it may be possible to optimize this for arrays - - for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { - - unsigned int ail = contexts.actual_intruder_layer (*il); - db::box_convert inst_bcii (*mp_intruder_layout, ail); - - for (std::unordered_set::const_iterator j = i->second.first.begin (); j != i->second.first.end (); ++j) { - - for (db::CellInstArray::iterator k = (*j)->begin_touching (safe_box_enlarged (nbox, -1, -1), inst_bcii); ! k.at_end (); ++k) { - - db::ICplxTrans tk = (*j)->complex_trans (*k); - // NOTE: no self-interactions - if (i->first != *j || tn != tk) { - - // optimize the intruder instance so it will be as low as possible - effective_instance_cache_key_type key (ail, std::make_pair ((*j)->object ().cell_index (), tni * tk)); - effective_instance_cache_type::iterator cached = effective_instance_cache.find (key); - if (cached == effective_instance_cache.end ()) { - std::pair ei = effective_instance (contexts.subject_layer (), i->first->object ().cell_index (), ail, (*j)->object ().cell_index (), tni * tk, dist); - cached = effective_instance_cache.insert (std::make_pair (key, ei)).first; - } - if (cached->second.first) { - intruders_below.first.insert (cached->second.second); - } - - } - - } - - } - - } - - issue_compute_contexts (contexts, cell_context, subject_cell, subject_child_cell, tn, intruder_child_cell, intruders_below, dist); - - } - - } -#endif - } } -/** - * @brief Returns a cell instance array suitable for adding as intruder - * - * The given intruder cell with the transformation ti2s - which transforms the intruder instance into - * the coordinate system of the subject cell - is analysed and either this instance or a sub-instance - * is chosen. - * Sub-instances are chosen if the intruder cell does not have shapes which interact with the subject - * cell and there is exactly one sub-instance interacting with the subject cell. - */ -template -std::pair -local_processor::effective_instance (unsigned int subject_layer, db::cell_index_type subject_cell_index, unsigned int intruder_layer, db::cell_index_type intruder_cell_index, const db::ICplxTrans &ti2s, db::Coord dist) const -{ - db::Box bbox = safe_box_enlarged (mp_subject_layout->cell (subject_cell_index).bbox (subject_layer), dist - 1, dist - 1); - if (bbox.empty ()) { - // should not happen, but skip if it does - return std::make_pair (false, db::CellInstArray ()); - } - - db::Box ibbox = bbox.transformed (ti2s.inverted ()); - - const db::Cell &intruder_cell = mp_intruder_layout->cell (intruder_cell_index); - const db::Shapes &intruder_shapes = intruder_cell.shapes (intruder_layer); - if (! intruder_shapes.empty () && ! intruder_shapes.begin_touching (ibbox, db::ShapeIterator::All).at_end ()) { - return std::make_pair (true, db::CellInstArray (db::CellInst (intruder_cell_index), ti2s)); - } - - db::box_convert inst_bcii (*mp_intruder_layout, intruder_layer); - - size_t ni = 0; - db::cell_index_type eff_cell_index = 0; - db::ICplxTrans eff_trans; - - for (db::Cell::touching_iterator i = intruder_cell.begin_touching (ibbox); ! i.at_end() && ni < 2; ++i) { - const db::CellInstArray &ci = i->cell_inst (); - db::Box cbox = mp_intruder_layout->cell (ci.object ().cell_index ()).bbox (intruder_layer); - for (db::CellInstArray::iterator k = ci.begin_touching (ibbox, inst_bcii); ! k.at_end () && ni < 2; ++k) { - db::ICplxTrans tk = ci.complex_trans (*k); - if (ibbox.overlaps (cbox.transformed (tk))) { - eff_trans = tk; - eff_cell_index = ci.object ().cell_index (); - ++ni; - } - } - } - - if (ni == 0) { - // should not happen, but skip if it does - return std::make_pair (false, db::CellInstArray ()); - } else if (ni == 1) { - // one instance - dive down - return effective_instance (subject_layer, subject_cell_index, intruder_layer, eff_cell_index, ti2s * eff_trans, dist); - } else { - return std::make_pair (true, db::CellInstArray (db::CellInst (intruder_cell_index), ti2s)); - } -} - template void local_processor::compute_results (local_processor_contexts &contexts, const local_operation *op, const std::vector &output_layers) const From 596080669da7ad1dc172db50c13a0b7009317a76 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 26 Oct 2021 23:10:29 +0200 Subject: [PATCH 17/52] WIP --- src/db/db/dbArray.h | 18 ++++------- src/db/db/dbHierProcessor.cc | 62 ++---------------------------------- src/db/db/dbTrans.h | 9 ++++++ src/db/db/gsiDeclDbTrans.cc | 10 +++++- 4 files changed, 28 insertions(+), 71 deletions(-) diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index 5834743c4..ba2efe78a 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -1697,7 +1697,7 @@ struct array * an appropriate basic_array object using a complex transformation. */ array (const Obj &obj, const complex_trans_type &ct, const vector_type &a, const vector_type &b, unsigned long amax, unsigned long bmax) - : m_obj (obj), m_trans (ct), mp_base (new regular_complex_array (ct.rcos (), ct.mag (), a, b, amax, bmax)) + : m_obj (obj), m_trans (ct), mp_base (ct.is_complex () ? new regular_complex_array (ct.rcos (), ct.mag (), a, b, amax, bmax) : new regular_array (a, b, amax, bmax)) { // .. nothing yet .. } @@ -1725,7 +1725,7 @@ struct array * it's own storage. */ array (const Obj &obj, const complex_trans_type &ct, ArrayRepository &rep, const vector_type &a, const vector_type &b, unsigned long amax, unsigned long bmax) - : m_obj (obj), m_trans (ct), mp_base (rep.insert (regular_complex_array (ct.rcos (), ct.mag (), a, b, amax, bmax))) + : m_obj (obj), m_trans (ct), mp_base (ct.is_complex () ? rep.insert (regular_complex_array (ct.rcos (), ct.mag (), a, b, amax, bmax)) : rep.insert (regular_array (a, b, amax, bmax))) { // .. nothing yet .. } @@ -1764,7 +1764,7 @@ struct array */ template array (const Obj &obj, const complex_trans_type &ct, Iter from, Iter to) - : m_obj (obj), m_trans (ct), mp_base (new iterated_complex_array (ct.rcos (), ct.mag (), from, to)) + : m_obj (obj), m_trans (ct), mp_base (ct.is_complex () ? new iterated_complex_array (ct.rcos (), ct.mag (), from, to) : new iterated_array (from, to)) { // .. nothing yet .. } @@ -1777,7 +1777,7 @@ struct array */ explicit array (const Obj &obj, const complex_trans_type &ct) - : m_obj (obj), m_trans (ct), mp_base (new single_complex_inst (ct.rcos (), ct.mag ())) + : m_obj (obj), m_trans (ct), mp_base (ct.is_complex () ? new single_complex_inst (ct.rcos (), ct.mag ()) : 0) { // .. nothing yet .. } @@ -1807,7 +1807,7 @@ struct array */ explicit array (const Obj &obj, const complex_trans_type &ct, ArrayRepository &rep) - : m_obj (obj), m_trans (ct), mp_base (rep.insert (single_complex_inst (ct.rcos (), ct.mag ()))) + : m_obj (obj), m_trans (ct), mp_base (ct.is_complex () ? rep.insert (single_complex_inst (ct.rcos (), ct.mag ())) : 0) { // .. nothing yet .. } @@ -1888,17 +1888,13 @@ struct array array_iterator begin_touching (const box_type &b, const BoxConv &bc) const { if (b.empty ()) { - if (mp_base) { - return array_iterator (m_trans, mp_base->begin_touching (box_type ())); - } else { - return array_iterator (m_trans, true); - } + return array_iterator (m_trans, true); } else if (b == box_type::world ()) { return begin (); } else if (mp_base) { box_type ob (bc (m_obj)); if (ob.empty ()) { - return array_iterator (m_trans, mp_base->begin_touching (box_type ())); + return array_iterator (m_trans, true); } else { if (mp_base->is_complex ()) { complex_trans_type ct = mp_base->complex_trans (simple_trans_type (m_trans)); diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index e8dc9dc1b..3b589f420 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -1076,6 +1076,9 @@ printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", std::unordered_map > > interactions_cache; +#if 0 +printf("@@@ -> #%d\n", int(inst1->size())); +#endif for (db::CellInstArray::iterator n = inst1->begin (); ! n.at_end (); ++n) { db::ICplxTrans tn1 = inst1->complex_trans (*n); @@ -1131,51 +1134,8 @@ printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", } } -// @@@ - bool has_intruder_tree_interactions (const db::Cell &subject_cell, const db::Cell &intruder_cell, const db::ICplxTrans &tni1, const db::ICplxTrans &tn21, const db::Box &cbox) - { - db::ICplxTrans tni2 = tn21.inverted () * tni1; - db::Box tbox2 = safe_box_enlarged (tni2 * cbox, -1, -1); - - if (! intruder_cell.shapes (m_intruder_layer).begin_touching (tbox2, ShapeIterator::All).at_end ()) { - - // we're overlapping with shapes from the intruder - do not recursive further - return true; - - } - - for (db::Cell::touching_iterator i = intruder_cell.begin_touching (tbox2); ! i.at_end (); ++i) { - - const db::Cell &ic = mp_intruder_layout->cell (i->cell_index ()); - - for (db::CellInstArray::iterator ia = i->begin_touching (tbox2, mp_intruder_layout); ! ia.at_end (); ++ia) { - - db::ICplxTrans it = i->complex_trans (*ia); - - db::Box ibox2 = tni2.inverted () * it * ic.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); - db::Box ccbox = cbox & ibox2; - - if (! ccbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * ccbox, -1, -1), false).at_end ()) { - if (has_intruder_tree_interactions (subject_cell, ic, tni1, tn21 * it, ccbox)) { - return true; - } - } - - } - - } - - return false; - } -// @@@ - void collect_intruder_tree_interactions (const db::Cell &subject_cell, const db::Cell &intruder_cell, const db::ICplxTrans &tni1, const db::ICplxTrans &tn21, const db::Box &cbox, std::list > &interactions) { -#if 0 - if (has_intruder_tree_interactions (subject_cell, intruder_cell, tni1, tn21, cbox)) { - interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); - } -#elif 1 db::ICplxTrans tni2 = tn21.inverted () * tni1; db::Box tbox2 = safe_box_enlarged (tni2 * cbox, -1, -1); @@ -1205,20 +1165,6 @@ printf("@@@ check instance interactions %s (#%d, %s) <-> %s (#%d, %s)\n", } } -#else - // not very strong, but already useful: the cells interact if there is a layer1 in cell1 - // in the common box and a layer2 in the cell2 in the common box - // NOTE: don't use overlap mode for the RecursiveShapeIterator as this would not capture dot-like - // objects like texts. Instead safe-shrink the search box and use touching mode ("false" for the last - // argument) - db::ICplxTrans tni2 = tn21.inverted () * tni1; - bool interacts = (! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end () && - ! db::RecursiveShapeIterator (*mp_intruder_layout, intruder_cell, m_intruder_layer, safe_box_enlarged (tni2 * cbox, -1, -1), false).at_end ()); - - if (interacts) { - interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); - } -#endif } }; @@ -1669,8 +1615,6 @@ printf("@@@ --- compute_contexts (%s @ %s)\n", mp_subject_layout->cell_name (sub } - std::list instance_heap; - for (std::set::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) { if (! inst_bci (*i).empty ()) { scanner.insert2 (i.operator-> (), ++id); diff --git a/src/db/db/dbTrans.h b/src/db/db/dbTrans.h index 93a6b08a7..c08915e74 100644 --- a/src/db/db/dbTrans.h +++ b/src/db/db/dbTrans.h @@ -2011,6 +2011,15 @@ public: m_mag = m_mag < 0.0 ? -m : m; } + /** + * @brief Returns a value indicating whether the transformation is a complex one + * The transformation can safely be converted to a simple transformation if this value is false. + */ + bool is_complex () const + { + return is_mag () || ! is_ortho (); + } + /** * @brief Test, if the transformation is mirroring */ diff --git a/src/db/db/gsiDeclDbTrans.cc b/src/db/db/gsiDeclDbTrans.cc index 0ebbb4d0b..a14eaa66b 100644 --- a/src/db/db/gsiDeclDbTrans.cc +++ b/src/db/db/gsiDeclDbTrans.cc @@ -916,7 +916,7 @@ struct cplx_trans_defs "@brief Gets the magnification\n" ) + method ("is_mag?", &C::is_mag, - "@brief Test, if the transformation is a magnifying one\n" + "@brief Tests, if the transformation is a magnifying one\n" "\n" "This is the recommended test for checking if the transformation represents\n" "a magnification.\n" @@ -925,6 +925,14 @@ struct cplx_trans_defs "@brief Sets the magnification\n" "@param m The new magnification" ) + + method ("is_complex?", &C::is_complex, + "@brief Return true if the transformation is a complex one\n" + "\n" + "If this value is false, the transformation can safely be converted to a simple transformation.\n" + "The return value is equivalent to 'is_mag || !is_ortho'.\n" + "\n" + "This method has been introduced in version 0.27.5." + ) + method ("R0", &trans_r0, "@brief A constant giving \"unrotated\" (unit) transformation\n" "The previous integer constant has been turned into a transformation in version 0.25." From 20f3733c58e7edee5234dcd343dfec87e7d01d5c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 27 Oct 2021 00:38:08 +0200 Subject: [PATCH 18/52] WIP: lean recursive shape touch check --- src/db/db/dbCell.cc | 35 ++++++++++++++++++++++++ src/db/db/dbCell.h | 5 ++++ src/db/db/dbHierProcessor.cc | 6 ++--- src/db/unit_tests/dbCellTests.cc | 46 ++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index 346f3d8ab..906752112 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -487,6 +487,41 @@ Cell::hierarchy_levels () const return m_hier_levels; } +static bool +has_shapes_touching_impl (const db::Cell &cell, unsigned int layer, const db::Box &box) +{ + if (! cell.shapes (layer).begin_touching (box, db::ShapeIterator::All).at_end ()) { + return true; + } + + for (db::Cell::touching_iterator i = cell.begin_touching (box); ! i.at_end (); ++i) { + + for (db::CellInstArray::iterator ia = i->cell_inst ().begin_touching (box, db::box_convert (*cell.layout (), layer)); ! ia.at_end (); ++ia) { + + db::Box cbox; + if (i->is_complex ()) { + cbox = i->complex_trans (*ia).inverted () * box; + } else { + cbox = (*ia).inverted () * box; + } + + if (has_shapes_touching_impl (cell.layout ()->cell (i->cell_index ()), layer, cbox)) { + return true; + } + + } + + } + + return false; +} + +bool +Cell::has_shapes_touching (unsigned int layer, const db::Box &box) const +{ + return has_shapes_touching_impl (*this, layer, box); +} + void Cell::collect_caller_cells (std::set &callers) const { diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index b20803953..db7089b2a 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -721,6 +721,11 @@ public: return shapes (layer).begin_touching (box, flags, prop_sel, inv_prop_sel); } + /** + * @brief A quick, recursive test whether the cell has shapes touching the given box on the given layer + */ + bool has_shapes_touching (unsigned int layer, const db::Box &box) const; + /** * @brief Collect all calling cells (either calling this cell directly or indirectly) * diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index 3b589f420..1bb829fd8 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -1104,7 +1104,7 @@ printf("@@@ -> #%d\n", int(inst1->size())); db::Box ibox2 = tn2 * cell2.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); db::Box cbox = ibox1 & ibox2; - if (! cbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, cell1, m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1), false).at_end ()) { + if (! cbox.empty () && cell1.has_shapes_touching (m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1))) { db::ICplxTrans tn21 = tni1 * tn2; @@ -1151,14 +1151,14 @@ printf("@@@ -> #%d\n", int(inst1->size())); const db::Cell &ic = mp_intruder_layout->cell (i->cell_index ()); - for (db::CellInstArray::iterator ia = i->begin_touching (tbox2, mp_intruder_layout); ! ia.at_end (); ++ia) { + for (db::CellInstArray::iterator ia = i->cell_inst ().begin_touching (tbox2, db::box_convert (*mp_intruder_layout, m_intruder_layer)); ! ia.at_end (); ++ia) { db::ICplxTrans it = i->complex_trans (*ia); db::Box ibox2 = tni2.inverted () * it * ic.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); db::Box ccbox = cbox & ibox2; - if (! ccbox.empty () && ! db::RecursiveShapeIterator (*mp_subject_layout, subject_cell, m_subject_layer, safe_box_enlarged (tni1 * ccbox, -1, -1), false).at_end ()) { + if (! ccbox.empty ()) { collect_intruder_tree_interactions (subject_cell, ic, tni1, tn21 * it, ccbox, interactions); } diff --git a/src/db/unit_tests/dbCellTests.cc b/src/db/unit_tests/dbCellTests.cc index 448583d81..6578dea84 100644 --- a/src/db/unit_tests/dbCellTests.cc +++ b/src/db/unit_tests/dbCellTests.cc @@ -1014,3 +1014,49 @@ TEST(6) } +TEST(10_HasShapesTouching) +{ + db::Layout ly; + unsigned int l1 = ly.insert_layer (db::LayerProperties (1, 0)); + + db::Cell &a = ly.cell (ly.add_cell ("A")); + + EXPECT_EQ (a.has_shapes_touching (l1, db::Box ()), false); + + a.shapes (l1).insert (db::Box (-100, -100, 0, 0)); + + EXPECT_EQ (a.has_shapes_touching (l1, db::Box ()), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, 0, 100, 100)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, 1, 100, 100)), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, -1, 100, 100)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (-1, -1, -1, -1)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (1, 1, 1, 1)), false); +} + +TEST(11_HasShapesTouchingWithHier) +{ + db::Layout ly; + unsigned int l1 = ly.insert_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.insert_layer (db::LayerProperties (2, 0)); + + db::Cell &a = ly.cell (ly.add_cell ("A")); + db::Cell &b = ly.cell (ly.add_cell ("B")); + + a.insert (db::CellInstArray (b.cell_index (), db::Trans (db::Vector (100, 100)), db::Vector (0, 200), db::Vector (200, 0), 2, 2)); + + EXPECT_EQ (a.has_shapes_touching (l1, db::Box ()), false); + EXPECT_EQ (a.has_shapes_touching (l2, db::Box ()), false); + + b.shapes (l1).insert (db::Box (0, 0, 10, 10)); + + EXPECT_EQ (a.has_shapes_touching (l1, db::Box ()), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, 0, 100, 100)), true); + EXPECT_EQ (a.has_shapes_touching (l2, db::Box (0, 0, 100, 100)), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, 0, 99, 100)), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (0, 0, 100, 99)), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (100, 100, 110, 110)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (150, 150, 160, 160)), false); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (300, 300, 310, 310)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (300, 100, 310, 110)), true); + EXPECT_EQ (a.has_shapes_touching (l1, db::Box (300, 400, 310, 410)), false); +} From ebe38912a6fdeb0cfd367945a0ffadd8bf6075fd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 27 Oct 2021 23:36:58 +0200 Subject: [PATCH 19/52] Tests and documentation for dbTrans#is_complex? --- src/db/db/gsiDeclDbTrans.cc | 7 ++++--- testdata/ruby/dbTransTest.rb | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/db/db/gsiDeclDbTrans.cc b/src/db/db/gsiDeclDbTrans.cc index a14eaa66b..397378c17 100644 --- a/src/db/db/gsiDeclDbTrans.cc +++ b/src/db/db/gsiDeclDbTrans.cc @@ -926,10 +926,11 @@ struct cplx_trans_defs "@param m The new magnification" ) + method ("is_complex?", &C::is_complex, - "@brief Return true if the transformation is a complex one\n" + "@brief Returns true if the transformation is a complex one\n" "\n" - "If this value is false, the transformation can safely be converted to a simple transformation.\n" - "The return value is equivalent to 'is_mag || !is_ortho'.\n" + "If this predicate is false, the transformation can safely be converted to a simple transformation.\n" + "Otherwise, this conversion will be lossy.\n" + "The predicate value is equivalent to 'is_mag || !is_ortho'.\n" "\n" "This method has been introduced in version 0.27.5." ) + diff --git a/testdata/ruby/dbTransTest.rb b/testdata/ruby/dbTransTest.rb index d69244910..e98cc4d87 100644 --- a/testdata/ruby/dbTransTest.rb +++ b/testdata/ruby/dbTransTest.rb @@ -155,6 +155,7 @@ class DBTrans_TestClass < TestBase assert_equal( c.is_unity?, false ) assert_equal( c.is_ortho?, true ) assert_equal( c.is_mag?, false ) + assert_equal( c.is_complex?, false ) assert_equal( c.is_mirror?, true ) assert_equal( c.rot, RBA::DCplxTrans::M135.rot ) assert_equal( c.s_trans.to_s, "m135 0,0" ) @@ -179,6 +180,7 @@ class DBTrans_TestClass < TestBase assert_equal( c.is_unity?, false ) assert_equal( c.is_ortho?, true ) assert_equal( c.is_mag?, true ) + assert_equal( c.is_complex?, true ) assert_equal( c.is_mirror?, false ) assert_equal( c.rot, RBA::DCplxTrans::R0.rot ) assert_equal( c.s_trans.to_s, "r0 0,0" ) @@ -190,6 +192,7 @@ class DBTrans_TestClass < TestBase assert_equal( c.to_s, "m22.5 *0.75 2.5,-12.5" ) assert_equal( c.is_unity?, false ) assert_equal( c.is_ortho?, false ) + assert_equal( c.is_complex?, true ) assert_equal( c.is_mag?, true ) assert_equal( c.rot, RBA::DCplxTrans::M0.rot ) assert_equal( c.s_trans.to_s, "m0 2.5,-12.5" ) From ea1bf40a1ee1c1c934e47a0020417503ab3d7e7e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Oct 2021 18:45:44 +0200 Subject: [PATCH 20/52] Using Jenkins for PyPI deployment. Travis stopped their generous open source support --- Jenkinsfile-pypi | 43 +++++++++++++++++++++++ ci-scripts/docker/docker_build_jenkins.sh | 31 ++++++++++++++++ setup.py | 8 ++++- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 Jenkinsfile-pypi create mode 100755 ci-scripts/docker/docker_build_jenkins.sh diff --git a/Jenkinsfile-pypi b/Jenkinsfile-pypi new file mode 100644 index 000000000..72d16f7da --- /dev/null +++ b/Jenkinsfile-pypi @@ -0,0 +1,43 @@ + +@Library("platform") _ + +properties([disableConcurrentBuilds()]) + +// from shared library - uses tags to set the platform name +// (this is a pretty stupid way to set a parameter, but I don't +// know of a better way to parameterize a multibranch pipeline) +platform = pypi_platform() +py_version = pypi_py_version() + +currentBuild.description = "PyPI deployment " + platform + +docker_image = "jenkins-manylinux2014_x86_64-pypi" +target = platform + ".whl" + +node("master") { + + stage("Checkout sources") { + checkout scm + } + + stage("Building target ${target}") { + + sh("rm -rf wheelhouse ; mkdir -p wheelhouse") + + withDockerContainer(image: docker_image, args: "-v " + pwd() + ":/io") { + sh("PY_VERSION=" + py_version + " /io/ci-scripts/docker/docker_build_jenkins.sh") + } + + } + + stage("Publish and test") { + + // publish for release tags + if (BRANCH_NAME.startsWith('v')) { + sh("twine upload --skip-existing wheelhouse/klayout-*manylinux2014*.whl wheelhouse/*.zip") + } + + } + +} + diff --git a/ci-scripts/docker/docker_build_jenkins.sh b/ci-scripts/docker/docker_build_jenkins.sh new file mode 100755 index 000000000..838a02dc0 --- /dev/null +++ b/ci-scripts/docker/docker_build_jenkins.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + + +if [[ -z $PY_VERSION ]]; then + echo '$PY_VERSION is not set' + exit 1 +fi + +echo PY_VERSION=$PY_VERSION + +# Use single cores only so we do not overload the Jenkins host +export KLAYOUT_SETUP_MULTICORE=1 + +# Compile wheel and build source distribution +cd /io +"/opt/python/$PY_VERSION/bin/python" setup.py bdist_wheel -d /io/wheelhouse/ || exit 1 +"/opt/python/$PY_VERSION/bin/python" setup.py sdist --formats=zip -d /io/wheelhouse || exit 1 + +# Bundle external shared libraries into the wheels via auditwheel +for whl in /io/wheelhouse/*linux_*.whl; do + auditwheel repair "$whl" -w /io/wheelhouse/ || exit 1 +done + +# Install packages and test +TEST_HOME=/io/testdata +"/opt/python/$PY_VERSION/bin/pip" install klayout --no-index -f /io/wheelhouse || exit 1 +"/opt/python/$PY_VERSION/bin/python" $TEST_HOME/pymod/import_db.py || exit 1 +"/opt/python/$PY_VERSION/bin/python" $TEST_HOME/pymod/import_rdb.py || exit 1 +"/opt/python/$PY_VERSION/bin/python" $TEST_HOME/pymod/import_tl.py || exit 1 +"/opt/python/$PY_VERSION/bin/python" $TEST_HOME/pymod/pya_tests.py || exit 1 + diff --git a/setup.py b/setup.py index d6e47cd81..3b3b5a3d7 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,13 @@ from distutils.errors import CompileError import distutils.command.build_ext import setuptools.command.build_ext import multiprocessing -N_cores = multiprocessing.cpu_count() + +# for Jenkins we do not want to be greedy +multicore = os.getenv("KLAYOUT_SETUP_MULTICORE") +if multicore: + N_cores = int(multicore) +else: + N_cores = multiprocessing.cpu_count() # monkey-patch for parallel compilation From 455c40ced60c3c5dc00530bfb5e98660bc3d2bfe Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Oct 2021 21:31:05 +0200 Subject: [PATCH 21/52] More consistent verbosity levels for cell mapping --- src/buddies/src/bd/strmxor.cc | 12 +++++---- src/db/db/dbCellMapping.cc | 42 ++++++++++++++++---------------- src/unit_tests/unit_test_main.cc | 3 ++- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 6072e1e73..ba165cd4b 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -598,6 +598,8 @@ bool run_deep_xor (const XORData &xor_data) } else { + tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + ll->first.to_string ()); + db::RecursiveShapeIterator ri_a, ri_b; if (ll->second.first >= 0) { @@ -612,15 +614,14 @@ bool run_deep_xor (const XORData &xor_data) db::Region in_b (ri_b, dss, db::ICplxTrans (xor_data.layout_b->dbu () / dbu)); db::Region xor_res; - xor_res = in_a ^ in_b; + { + tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + ll->first.to_string ()); + xor_res = in_a ^ in_b; + } int tol_index = 0; for (std::vector::const_iterator t = xor_data.tolerances.begin (); t != xor_data.tolerances.end (); ++t) { - if (tl::verbosity () >= 20) { - tl::log << "Running XOR on layer " << ll->first.to_string () << " with tolerance " << *t; - } - db::LayerProperties lp = ll->first; if (lp.layer >= 0) { lp.layer += tol_index * xor_data.tolerance_bump; @@ -633,6 +634,7 @@ bool run_deep_xor (const XORData &xor_data) result.top_cell = xor_data.output_cell; if (*t > db::epsilon) { + tl::SelfTimer timer (tl::verbosity () >= 21, "Tolerance " + tl::to_string (*t) + " on layer " + ll->first.to_string ()); xor_res.size (-db::coord_traits::rounded (0.5 * *t / dbu)); xor_res.size (db::coord_traits::rounded (0.5 * *t / dbu)); } diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index 4e138fb20..e945ca153 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -409,9 +409,9 @@ CellMapping::do_create_missing_mapping (db::Layout &layout_a, const db::Layout & void CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_type cell_index_a, const db::Layout &layout_b, db::cell_index_type cell_index_b) { - tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (tr ("Cell mapping"))); + tl::SelfTimer timer (tl::verbosity () >= 31, tl::to_string (tr ("Cell mapping"))); - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { tl::info << "Cell mapping - first step: mapping instance count and instance identity"; } @@ -449,7 +449,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty ++a; } else { - if (tl::verbosity () >= 30) { + if (tl::verbosity () >= 50) { size_t na = 0, nb = 0; for (std::multimap::const_iterator aa = a; aa != cm_a.end () && aa->first == w; ++aa) { ++na; @@ -497,7 +497,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } - if (tl::verbosity () >= 40) { + if (tl::verbosity () >= 60) { tl::info << "Checked cell " << layout_a.cell_name (a->second) << ": " << candidates [a->second].size () << " candidates remaining."; } @@ -519,7 +519,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty ++a; } - if (tl::verbosity () >= 40) { + if (tl::verbosity () >= 60) { tl::info << "Mapping candidates:"; dump_mapping (candidates, layout_a, layout_b); } @@ -536,7 +536,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty reduction = false; ++iteration; - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { tl::info << "Cell mapping - iteration " << iteration << ": cross-instance cone reduction"; } @@ -553,7 +553,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty refined_cand.clear (); refined_cand.insert (refined_cand.end (), cand->second.begin (), cand->second.end ()); - if (tl::verbosity () >= 50) { + if (tl::verbosity () >= 70) { tl::info << "--- Cell: " << layout_a.cell_name (cand->first); tl::info << "Before reduction: " << tl::noendl; for (size_t i = 0; i < refined_cand.size (); ++i) { @@ -582,7 +582,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } } - if (tl::verbosity () >= 50 && cout != refined_cand.end ()) { + if (tl::verbosity () >= 70 && cout != refined_cand.end ()) { tl::info << "Reduction because of caller mapping: " << layout_a.cell_name (*c) << " <-> " << layout_b.cell_name (others[0]); tl::info << " -> " << tl::noendl; for (size_t i = 0; i < size_t (cout - refined_cand.begin ()); ++i) { @@ -619,7 +619,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } } - if (tl::verbosity () >= 50 && cout != refined_cand.end ()) { + if (tl::verbosity () >= 70 && cout != refined_cand.end ()) { tl::info << "Reduction because of callee mapping: " << layout_a.cell_name (*c) << " <-> " << layout_b.cell_name (others[0]); tl::info << " -> " << tl::noendl; for (size_t i = 0; i < size_t (cout - refined_cand.begin ()); ++i) { @@ -646,7 +646,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty int ed = tl::edit_distance (layout_a.cell_name (ca), layout_b.cell_name (cb)); if (ed < uc->second.second) { uc->second = std::make_pair (ca, ed); - if (tl::verbosity () >= 40) { + if (tl::verbosity () >= 60) { tl::info << "Choosing " << layout_b.cell_name (cb) << " (layout_b) as new unique mapping for " << layout_a.cell_name (ca) << " (layout_a)"; } } @@ -654,7 +654,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } else { int ed = tl::edit_distance (layout_a.cell_name (ca), layout_b.cell_name (cb)); unique_candidates.insert (std::make_pair (cb, std::make_pair (ca, ed))); - if (tl::verbosity () >= 40) { + if (tl::verbosity () >= 60) { tl::info << "Choosing " << layout_b.cell_name (cb) << " (layout_b) as unique mapping for " << layout_a.cell_name (ca) << " (layout_a)"; } } @@ -679,12 +679,12 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } - if (tl::verbosity () >= 40) { + if (tl::verbosity () >= 60) { tl::info << "Further refined candidates:"; dump_mapping (candidates, layout_a, layout_b); } - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { tl::info << "Cell mapping - iteration " << iteration << ": removal of uniquely mapped cells on B side"; } @@ -709,15 +709,15 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } } - - if (tl::verbosity () >= 40) { + + if (tl::verbosity () >= 60) { tl::info << "After reduction of mapped cells on b side:"; dump_mapping (candidates, layout_a, layout_b); } } - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { int total = 0; int not_mapped = 0; @@ -747,7 +747,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty // Resolve mapping according to string match - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { tl::info << "Cell mapping - string mapping as last resort"; } @@ -784,7 +784,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { int total = 0; int not_mapped = 0; @@ -795,7 +795,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty for (std::map >::iterator cand = candidates.begin (); cand != candidates.end (); ++cand) { ++total; if (cand->second.size () == 0) { - if (tl::verbosity () >= 30) { + if (tl::verbosity () >= 50) { tl::info << "Unmapped cell: " << layout_a.cell_name (cand->first); } ++not_mapped; @@ -823,12 +823,12 @@ CellMapping::extract_unique (std::map second.size () == 1) { - if (tl::verbosity () >= 20) { + if (tl::verbosity () >= 40) { tl::info << " (U) " << layout_a.cell_name (cand->first) << " -> " << layout_b.cell_name (cand->second.front ()) << " (" << cand->first << " -> " << cand->second.front () << ")"; } unique_mapping.insert (std::make_pair (cand->second.front (), cand->first)); - } else if (tl::verbosity () >= 30) { + } else if (tl::verbosity () >= 50) { tl::info << " " << layout_a.cell_name (cand->first) << " ->" << tl::noendl; int n = 5; diff --git a/src/unit_tests/unit_test_main.cc b/src/unit_tests/unit_test_main.cc index b506ff893..51b675222 100644 --- a/src/unit_tests/unit_test_main.cc +++ b/src/unit_tests/unit_test_main.cc @@ -246,7 +246,8 @@ run_tests (const std::vector &selected_tests, bool editable, boo timer.stop(); ut::noctrl << "Time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)"; - ut::ctrl << ""; + ut::noctrl << "Memory: " << timer.memory_size () / 1024 << "k"; + ut::ctrl << ""; } catch (tl::CancelException &) { From 902375cc4d0ef312c2d3b8ef6d03a2af4a066558 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 31 Oct 2021 00:59:42 +0200 Subject: [PATCH 22/52] WIP: trying to reduce the number of DSS-internal cell mappings by caching cell maps --- src/buddies/src/bd/strmxor.cc | 1 + src/db/db/dbDeepShapeStore.cc | 51 +++++++++++++++++++++++++++++++---- src/db/db/dbDeepShapeStore.h | 31 +++++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index ba165cd4b..876373056 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -554,6 +554,7 @@ bool run_deep_xor (const XORData &xor_data) { db::DeepShapeStore dss; dss.set_threads (xor_data.threads); + dss.set_keep_layouts (true); // avoids excessive cell mapping double dbu = std::min (xor_data.layout_a->dbu (), xor_data.layout_b->dbu ()); diff --git a/src/db/db/dbDeepShapeStore.cc b/src/db/db/dbDeepShapeStore.cc index 74e8f6e8b..e62c2cfd9 100644 --- a/src/db/db/dbDeepShapeStore.cc +++ b/src/db/db/dbDeepShapeStore.cc @@ -154,17 +154,25 @@ DeepLayer::add_from (const DeepLayer &dl) db::cell_index_type source_cell = dl.initial_cell ().cell_index (); const db::Layout *source_layout = &dl.layout (); - db::CellMapping cm; - cm.create_from_geometry_full (*into_layout, into_cell, *source_layout, source_cell); + // create or reuse a layout mapping - // Actually copy the shapes + const db::CellMapping *cell_mapping = 0; + db::CellMapping cm; + if (store () == dl.store ()) { + cell_mapping = &const_cast (mp_store.get ())->internal_cell_mapping (layout_index (), dl.layout_index ()); + } else { + cm.create_from_geometry_full (*into_layout, into_cell, *source_layout, source_cell); + cell_mapping = &cm; + } + + // actually copy the shapes std::map lm; lm.insert (std::make_pair (dl.layer (), layer ())); std::vector source_cells; source_cells.push_back (source_cell); - db::copy_shapes (*into_layout, *source_layout, db::ICplxTrans (), source_cells, cm.table (), lm); + db::copy_shapes (*into_layout, *source_layout, db::ICplxTrans (), source_cells, cell_mapping->table (), lm); } } @@ -418,11 +426,13 @@ static unsigned int init_layer (db::Layout &layout, const db::RecursiveShapeIter } DeepShapeStore::DeepShapeStore () + : m_keep_layouts (false) { ++s_instance_count; } DeepShapeStore::DeepShapeStore (const std::string &topcell_name, double dbu) + : m_keep_layouts (false) { ++s_instance_count; @@ -711,6 +721,16 @@ db::Layout &DeepShapeStore::layout (unsigned int n) return m_layouts [n]->layout; } +unsigned int DeepShapeStore::layout_index (const db::Layout *layout) const +{ + for (std::vector::const_iterator i = m_layouts.begin (); i != m_layouts.end (); ++i) { + if (&(*i)->layout == layout) { + return (unsigned int) (i - m_layouts.begin ()); + } + } + tl_assert (false); +} + size_t DeepShapeStore::instance_count () { return s_instance_count; @@ -743,7 +763,7 @@ void DeepShapeStore::remove_ref (unsigned int layout, unsigned int layer) } - if ((m_layouts[layout]->refs -= 1) <= 0) { + if ((m_layouts[layout]->refs -= 1) <= 0 && ! m_keep_layouts) { delete m_layouts[layout]; m_layouts[layout] = 0; clear_breakout_cells (layout); @@ -919,6 +939,7 @@ void DeepShapeStore::invalidate_hier () { m_delivery_mapping_cache.clear (); + m_internal_mapping_cache.clear (); } void @@ -934,6 +955,26 @@ DeepShapeStore::issue_variants (unsigned int layout_index, const std::map, db::CellMapping>::iterator cm = m_internal_mapping_cache.find (std::make_pair (from_layout_index, into_layout_index)); + if (cm == m_internal_mapping_cache.end ()) { + + cm = m_internal_mapping_cache.insert (std::make_pair (std::make_pair (from_layout_index, into_layout_index), db::CellMapping ())).first; + + db::Layout &into_layout = layout (into_layout_index); + db::cell_index_type into_cell = initial_cell (into_layout_index).cell_index (); + const db::Layout &source_layout = layout (from_layout_index); + db::cell_index_type source_cell = initial_cell (from_layout_index).cell_index (); + + cm->second.create_from_geometry_full (into_layout, into_cell, source_layout, source_cell); + + } + + return cm->second; +} + const db::CellMapping & DeepShapeStore::cell_mapping_to_original (unsigned int layout_index, db::Layout *into_layout, db::cell_index_type into_cell, const std::set *excluded_cells, const std::set *included_cells) { diff --git a/src/db/db/dbDeepShapeStore.h b/src/db/db/dbDeepShapeStore.h index 9532ffd2b..990ecfb2e 100644 --- a/src/db/db/dbDeepShapeStore.h +++ b/src/db/db/dbDeepShapeStore.h @@ -339,6 +339,25 @@ public: */ bool is_singular () const; + /** + * @brief Sets a value indicating whether to keep layouts + * + * If this value is set to true, layouts are not released when their reference count + * goes down to zero. + */ + void set_keep_layouts (bool f) + { + m_keep_layouts = f; + } + + /** + * @brief Gets a value indicating whether to keep layouts + */ + bool keep_layouts () const + { + return m_keep_layouts; + } + /** * @brief Creates a new layer from a flat region (or the region is made flat) * @@ -479,6 +498,11 @@ public: */ const db::CellMapping &cell_mapping_to_original (unsigned int layout_index, db::Layout *into_layout, db::cell_index_type into_cell, const std::set *excluded_cells = 0, const std::set *included_cells = 0); + /** + * @brief Gets the cell mapping from one internal layout to another + */ + const db::CellMapping &internal_cell_mapping (unsigned int from_layout_index, unsigned int into_layout_index); + /** * @brief Create cell variants from the given variant collector * @@ -544,6 +568,11 @@ public: */ db::Cell &initial_cell (unsigned int n); + /** + * @brief Gets the layout index for a given internal layout + */ + unsigned int layout_index (const db::Layout *layout) const; + /** * @brief Gets the singular layout (const version) * @@ -752,6 +781,7 @@ private: layout_map_type m_layout_map; DeepShapeStoreState m_state; std::list m_state_stack; + bool m_keep_layouts; tl::Mutex m_lock; struct DeliveryMappingCacheKey @@ -781,6 +811,7 @@ private: }; std::map m_delivery_mapping_cache; + std::map, db::CellMapping> m_internal_mapping_cache; }; template From 64a6b3acdcbedbc61e882152c88989cebdb8af58 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 1 Nov 2021 09:53:51 +0100 Subject: [PATCH 23/52] WIP --- src/db/db/dbRecursiveShapeIterator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index 075752113..6c89c76f3 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -646,7 +646,7 @@ RecursiveShapeIterator::skip_inst_iter_for_complex_region () const // skip insts outside the complex region if (! m_inst.at_end ()) { - if (! is_outside_complex_region (m_inst->bbox ())) { + if (! is_outside_complex_region (m_inst->bbox (m_box_convert))) { break; } else { ++m_inst; @@ -775,7 +775,7 @@ RecursiveShapeIterator::down (RecursiveShapeReceiver *receiver) const // compute the region inside the new cell if (new_region != m_region) { new_region = m_trans.inverted () * m_region; - new_region &= cell ()->bbox (); + new_region &= m_box_convert (db::CellInst (cell ()->cell_index ())); } m_local_region_stack.push_back (new_region); From d244d5aac66cee70c1e3e166d29a5217490770fd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 1 Nov 2021 13:48:47 +0100 Subject: [PATCH 24/52] Debugging plus enhancments Basic change: DeepShapeStore+CellMapping uses (new) hier_generation_id of Layout to figure out if the hierarchy has changed and the cache needs update. --- src/db/db/dbCellMapping.cc | 5 ++++ src/db/db/dbCellMapping.h | 5 ++++ src/db/db/dbDeepShapeStore.cc | 38 ++++++++++++--------------- src/db/db/dbDeepShapeStore.h | 41 +++++++++++++++++++++++++++--- src/db/db/dbLayout.h | 4 +-- src/db/db/dbLayoutStateModel.cc | 5 ++-- src/db/db/dbLayoutStateModel.h | 13 ++++++++++ src/db/unit_tests/dbLayoutTests.cc | 12 +++++++++ 8 files changed, 94 insertions(+), 29 deletions(-) diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index e945ca153..6184e740a 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -269,6 +269,11 @@ void CellMapping::clear () m_b2a_mapping.clear (); } +void CellMapping::swap (CellMapping &other) +{ + m_b2a_mapping.swap (other.m_b2a_mapping); +} + std::vector CellMapping::source_cells () const { std::vector s; diff --git a/src/db/db/dbCellMapping.h b/src/db/db/dbCellMapping.h index a17ef92aa..31f72be94 100644 --- a/src/db/db/dbCellMapping.h +++ b/src/db/db/dbCellMapping.h @@ -64,6 +64,11 @@ public: */ void clear (); + /** + * @brief Swaps the cell mapping with another one + */ + void swap (CellMapping &other); + /** * @brief Create a single cell mapping * diff --git a/src/db/db/dbDeepShapeStore.cc b/src/db/db/dbDeepShapeStore.cc index e62c2cfd9..89b7e84e1 100644 --- a/src/db/db/dbDeepShapeStore.cc +++ b/src/db/db/dbDeepShapeStore.cc @@ -788,7 +788,6 @@ DeepShapeStore::layout_for_iter (const db::RecursiveShapeIterator &si, const db: } db::Layout &layout = m_layouts[layout_index]->layout; - layout.hier_changed_event.add (this, &DeepShapeStore::invalidate_hier); if (si.layout ()) { layout.dbu (si.layout ()->dbu () / trans.mag ()); } @@ -812,7 +811,6 @@ void DeepShapeStore::make_layout (unsigned int layout_index, const db::Recursive m_layouts[layout_index] = new LayoutHolder (trans); db::Layout &layout = m_layouts[layout_index]->layout; - layout.hier_changed_event.add (this, &DeepShapeStore::invalidate_hier); if (si.layout ()) { layout.dbu (si.layout ()->dbu () / trans.mag ()); } @@ -935,18 +933,9 @@ DeepLayer DeepShapeStore::create_text_layer (const db::RecursiveShapeIterator &s return create_custom_layer (si, &refs, trans); } -void -DeepShapeStore::invalidate_hier () -{ - m_delivery_mapping_cache.clear (); - m_internal_mapping_cache.clear (); -} - void DeepShapeStore::issue_variants (unsigned int layout_index, const std::map > &var_map) { - invalidate_hier (); - db::HierarchyBuilder &builder = m_layouts [layout_index]->builder; for (std::map >::const_iterator i = var_map.begin (); i != var_map.end (); ++i) { for (std::map::const_iterator j = i->second.begin (); j != i->second.end (); ++j) { @@ -956,19 +945,21 @@ DeepShapeStore::issue_variants (unsigned int layout_index, const std::map, db::CellMapping>::iterator cm = m_internal_mapping_cache.find (std::make_pair (from_layout_index, into_layout_index)); - if (cm == m_internal_mapping_cache.end ()) { + db::Layout &into_layout = layout (into_layout_index); + db::cell_index_type into_cell = initial_cell (into_layout_index).cell_index (); + const db::Layout &source_layout = layout (from_layout_index); + db::cell_index_type source_cell = initial_cell (from_layout_index).cell_index (); - cm = m_internal_mapping_cache.insert (std::make_pair (std::make_pair (from_layout_index, into_layout_index), db::CellMapping ())).first; + std::map, CellMappingWithGenerationIds>::iterator cm = m_internal_mapping_cache.find (std::make_pair (from_layout_index, into_layout_index)); + if (cm == m_internal_mapping_cache.end () || ! cm->second.is_valid (into_layout, source_layout)) { - db::Layout &into_layout = layout (into_layout_index); - db::cell_index_type into_cell = initial_cell (into_layout_index).cell_index (); - const db::Layout &source_layout = layout (from_layout_index); - db::cell_index_type source_cell = initial_cell (from_layout_index).cell_index (); + cm = m_internal_mapping_cache.insert (std::make_pair (std::make_pair (from_layout_index, into_layout_index), CellMappingWithGenerationIds ())).first; + cm->second.clear (); cm->second.create_from_geometry_full (into_layout, into_cell, source_layout, source_cell); + cm->second.set_generation_ids (into_layout, source_layout); } @@ -994,10 +985,11 @@ DeepShapeStore::cell_mapping_to_original (unsigned int layout_index, db::Layout DeliveryMappingCacheKey key (layout_index, tl::id_of (into_layout), into_cell); - std::map::iterator cm = m_delivery_mapping_cache.find (key); - if (cm == m_delivery_mapping_cache.end ()) { + std::map::iterator cm = m_delivery_mapping_cache.find (key); + if (cm == m_delivery_mapping_cache.end () || ! cm->second.is_valid (*into_layout, *source_layout)) { - cm = m_delivery_mapping_cache.insert (std::make_pair (key, db::CellMapping ())).first; + cm = m_delivery_mapping_cache.insert (std::make_pair (key, CellMappingWithGenerationIds ())).first; + cm->second.clear (); // collects the cell mappings we skip because they are variants (variant building or box variants) std::map cm_skipped_variants; @@ -1086,6 +1078,8 @@ DeepShapeStore::cell_mapping_to_original (unsigned int layout_index, db::Layout into_layout->delete_cells (cells_to_delete); } + cm->second.set_generation_ids (*into_layout, *source_layout); + } return cm->second; diff --git a/src/db/db/dbDeepShapeStore.h b/src/db/db/dbDeepShapeStore.h index 990ecfb2e..877cd2bc9 100644 --- a/src/db/db/dbDeepShapeStore.h +++ b/src/db/db/dbDeepShapeStore.h @@ -32,6 +32,7 @@ #include "dbLayout.h" #include "dbRecursiveShapeIterator.h" #include "dbHierarchyBuilder.h" +#include "dbCellMapping.h" #include "gsiObject.h" #include @@ -291,6 +292,41 @@ struct DB_PUBLIC RecursiveShapeIteratorCompareForTargetHierarchy } }; +/** + * @brief An object holding a cell mapping with the hierarchy generation Ids of the involved layouts + */ +class DB_PUBLIC CellMappingWithGenerationIds + : public db::CellMapping +{ +public: + CellMappingWithGenerationIds () + : db::CellMapping (), m_into_generation_id (0), m_from_generation_id (0) + { + // .. nothing yet .. + } + + void swap (CellMappingWithGenerationIds &other) + { + db::CellMapping::swap (other); + std::swap (m_into_generation_id, other.m_into_generation_id); + std::swap (m_from_generation_id, other.m_from_generation_id); + } + + bool is_valid (const db::Layout &into_layout, const db::Layout &from_layout) const + { + return into_layout.hier_generation_id () == m_into_generation_id && from_layout.hier_generation_id () == m_from_generation_id; + } + + void set_generation_ids (const db::Layout &into_layout, const db::Layout &from_layout) + { + m_into_generation_id = into_layout.hier_generation_id (); + m_from_generation_id = from_layout.hier_generation_id (); + } + +private: + size_t m_into_generation_id, m_from_generation_id; +}; + /** * @brief The "deep shape store" is a working model for the hierarchical ("deep") processor * @@ -759,7 +795,6 @@ private: struct LayoutHolder; - void invalidate_hier (); void add_ref (unsigned int layout, unsigned int layer); void remove_ref (unsigned int layout, unsigned int layer); @@ -810,8 +845,8 @@ private: db::cell_index_type into_cell; }; - std::map m_delivery_mapping_cache; - std::map, db::CellMapping> m_internal_mapping_cache; + std::map m_delivery_mapping_cache; + std::map, CellMappingWithGenerationIds> m_internal_mapping_cache; }; template diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index e5ee39006..478620390 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -528,12 +528,12 @@ public: * * The editable mode will be taken from db::default_editable_mode. */ - Layout (db::Manager *manager = 0); + explicit Layout (db::Manager *manager = 0); /** * @brief Standard constructor which allows one to specify editable mode */ - Layout (bool editable, db::Manager *manager = 0); + explicit Layout (bool editable, db::Manager *manager = 0); /** * @brief The copy ctor diff --git a/src/db/db/dbLayoutStateModel.cc b/src/db/db/dbLayoutStateModel.cc index 4bcb0755f..63cc42177 100644 --- a/src/db/db/dbLayoutStateModel.cc +++ b/src/db/db/dbLayoutStateModel.cc @@ -29,13 +29,13 @@ namespace db { LayoutStateModel::LayoutStateModel (bool busy) - : m_hier_dirty (false), m_all_bboxes_dirty (false), m_busy (busy) + : m_hier_dirty (false), m_hier_generation_id (0), m_all_bboxes_dirty (false), m_busy (busy) { // .. nothing yet .. } LayoutStateModel::LayoutStateModel (const LayoutStateModel &d) - : m_hier_dirty (d.m_hier_dirty), m_bboxes_dirty (d.m_bboxes_dirty), m_all_bboxes_dirty (d.m_all_bboxes_dirty), m_busy (d.m_busy) + : m_hier_dirty (d.m_hier_dirty), m_hier_generation_id (d.m_hier_generation_id), m_bboxes_dirty (d.m_bboxes_dirty), m_all_bboxes_dirty (d.m_all_bboxes_dirty), m_busy (d.m_busy) { // .. nothing yet .. } @@ -44,6 +44,7 @@ LayoutStateModel & LayoutStateModel::operator= (const LayoutStateModel &d) { m_hier_dirty = d.m_hier_dirty; + m_hier_generation_id = d.m_hier_generation_id; m_bboxes_dirty = d.m_bboxes_dirty; m_all_bboxes_dirty = d.m_all_bboxes_dirty; m_busy = d.m_busy; diff --git a/src/db/db/dbLayoutStateModel.h b/src/db/db/dbLayoutStateModel.h index f99e04c32..20760a70d 100644 --- a/src/db/db/dbLayoutStateModel.h +++ b/src/db/db/dbLayoutStateModel.h @@ -85,6 +85,7 @@ public: */ void invalidate_hier () { + ++m_hier_generation_id; if (! m_hier_dirty || m_busy) { do_invalidate_hier (); // must be called before the hierarchy is invalidated (stopping of redraw thread requires this) m_hier_dirty = true; @@ -127,6 +128,17 @@ public: return m_hier_dirty; } + /** + * @brief Gets the hierarchy generation ID + * + * The hierarchy generation ID is a number which is incremented on every hierarchy + * change. + */ + size_t hier_generation_id () const + { + return m_hier_generation_id; + } + /** * @brief The "dirty bounding box" attribute * @@ -195,6 +207,7 @@ public: private: bool m_hier_dirty; + size_t m_hier_generation_id; std::vector m_bboxes_dirty; bool m_all_bboxes_dirty; bool m_busy; diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index c4630e5ae..303187ecb 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -261,12 +261,15 @@ TEST(2) db::cell_index_type ci; db::Cell *top; + EXPECT_EQ (g.hier_generation_id (), size_t (0)); + ci = g.add_cell ("TOP"); EXPECT_EQ (el.flags, (unsigned int) 0); EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, true); + EXPECT_EQ (g.hier_generation_id (), size_t (1)); el.reset (); top = &g.cell (ci); @@ -276,6 +279,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, false); // needs g.update() before being issues again + EXPECT_EQ (g.hier_generation_id (), size_t (2)); el.reset (); top->insert (db::CellInstArray (ci, db::Trans ())); @@ -284,10 +288,12 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, true); EXPECT_EQ (el.bboxes_all_dirty, true); EXPECT_EQ (el.hier_dirty, false); // needs g.update() before being issues again + EXPECT_EQ (g.hier_generation_id (), size_t (3)); g.clear (); g.update (); el.reset (); + EXPECT_EQ (g.hier_generation_id (), size_t (4)); ci = g.add_cell ("TOP"); @@ -295,6 +301,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, true); + EXPECT_EQ (g.hier_generation_id (), size_t (5)); el.reset (); g.update (); @@ -305,6 +312,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, true); // OK - see above + EXPECT_EQ (g.hier_generation_id (), size_t (6)); el.reset (); g.update (); @@ -314,6 +322,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, true); EXPECT_EQ (el.bboxes_all_dirty, true); EXPECT_EQ (el.hier_dirty, true); // OK - see above + EXPECT_EQ (g.hier_generation_id (), size_t (7)); // busy mode will make events issued always g.clear (); @@ -326,6 +335,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, true); + EXPECT_EQ (g.hier_generation_id (), size_t (9)); el.reset (); top = &g.cell (ci); @@ -335,6 +345,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, false); EXPECT_EQ (el.bboxes_all_dirty, false); EXPECT_EQ (el.hier_dirty, true); // OK - see above + EXPECT_EQ (g.hier_generation_id (), size_t (10)); el.reset (); top->insert (db::CellInstArray (ci, db::Trans ())); @@ -343,6 +354,7 @@ TEST(2) EXPECT_EQ (el.bboxes_dirty, true); EXPECT_EQ (el.bboxes_all_dirty, true); EXPECT_EQ (el.hier_dirty, true); // OK - see above + EXPECT_EQ (g.hier_generation_id (), size_t (11)); } From a0367c15303c7632a05dc2b01a552204d4d229c1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 1 Nov 2021 17:27:52 +0100 Subject: [PATCH 25/52] More specific clip variants after bounding boxes have been reduced in RecursiveShapeIterator --- src/db/db/dbHierarchyBuilder.cc | 8 +++++--- src/db/db/dbRecursiveShapeIterator.cc | 4 ++-- src/db/db/dbRecursiveShapeIterator.h | 10 ++++++++++ testdata/algo/hierarchy_builder_au2a.gds | Bin 11526 -> 10512 bytes testdata/algo/hierarchy_builder_au2b.gds | Bin 11614 -> 10522 bytes testdata/algo/hierarchy_builder_au2c.gds | Bin 11902 -> 10810 bytes testdata/algo/hierarchy_builder_au2d.gds | Bin 11536 -> 10522 bytes testdata/algo/hierarchy_builder_au2f.gds | Bin 11978 -> 10886 bytes testdata/algo/hierarchy_builder_au4a.gds | Bin 82618 -> 65416 bytes 9 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/db/db/dbHierarchyBuilder.cc b/src/db/db/dbHierarchyBuilder.cc index 838e93243..fe48fd086 100644 --- a/src/db/db/dbHierarchyBuilder.cc +++ b/src/db/db/dbHierarchyBuilder.cc @@ -381,14 +381,16 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db: } else { - db::Box cell_bbox = iter->layout ()->cell (inst.object ().cell_index ()).bbox (); + db::cell_index_type inst_cell = inst.object ().cell_index (); + + db::Box cell_bbox = iter->cell_bbox (inst_cell); std::pair > clip_variant = compute_clip_variant (cell_bbox, trans, region, complex_region); if (! clip_variant.first) { return false; } - CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst.object ().cell_index ()), clip_variant.second); - db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst.object ().cell_index ())); + CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst_cell), clip_variant.second); + 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) { diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index 6c89c76f3..df95d9201 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -775,7 +775,7 @@ RecursiveShapeIterator::down (RecursiveShapeReceiver *receiver) const // compute the region inside the new cell if (new_region != m_region) { new_region = m_trans.inverted () * m_region; - new_region &= m_box_convert (db::CellInst (cell ()->cell_index ())); + new_region &= cell_bbox (cell_index ()); } m_local_region_stack.push_back (new_region); @@ -969,7 +969,7 @@ RecursiveShapeIterator::new_inst_member (RecursiveShapeReceiver *receiver) const // skip instance array members not part of the complex region while (! m_inst_array.at_end ()) { - db::Box ia_box = m_inst->complex_trans (*m_inst_array) * m_box_convert (m_inst->cell_inst ().object ()); + db::Box ia_box = m_inst->complex_trans (*m_inst_array) * cell_bbox (m_inst->cell_index ()); if (! is_outside_complex_region (ia_box)) { break; } else { diff --git a/src/db/db/dbRecursiveShapeIterator.h b/src/db/db/dbRecursiveShapeIterator.h index 5537619c6..ee70e8538 100644 --- a/src/db/db/dbRecursiveShapeIterator.h +++ b/src/db/db/dbRecursiveShapeIterator.h @@ -665,6 +665,16 @@ public: return reinterpret_cast (c - (c & size_t (3))); } + /** + * @brief Gets the current cell's bounding box + * + * The returned box is limited to the selected layer if applicable. + */ + box_type cell_bbox (db::cell_index_type cell_index) const + { + return m_box_convert (db::CellInst (cell_index)); + } + /** * @brief Increments the iterator (operator version) */ diff --git a/testdata/algo/hierarchy_builder_au2a.gds b/testdata/algo/hierarchy_builder_au2a.gds index 9d556aed68022a0a7ef97c024d1f7caf7f78fbac..0f6afd622a5fd0fa6ce463856be38a4444d9a2c8 100644 GIT binary patch literal 10512 zcmeHMO=w(I6h8BlH#5n!HZc?`MXJPw5ZcV-pJGY0Vna$&(lJOV=+dQ&Qbbe)T~t>t zTvZoEur9h21fi~6h-BqbP$V0dLZGXJHqY<=oVoYi`|f*hruh@Pm^s5e_uPBF^LNj^ zLlKqBZ_(LOx%LkYQHh4>G+nTNQ@tErU3!&hv@&pM@$=c`U%%g;zx>mMyI*f~=6_~QRWV# zyu)i(m#)3NzPL6ve`q_+QMIlxMHc+cOfH~aYV`>LxSJu_)KAMl>j-94*z9PO21^G$p} z<9ncg8_MH*n<|d9og8`Sy&>e`%NZeWD38-dpK&;K^g8>Pf$v4&+*CBa2f8P0rYMhx zZ6@S|%@i%o2swN-*hhr)8b3`<=FVw*SX!*xoqe0 z<6F@ACDgrJ(Em@=)-RayxS)OJVe7#69MmVQiQ+K$JY<%IJfib;A#W&;({i?ahI+R9v7tO3 z-Ra3Yrqi-^Ld@X=u6&?qbsZEZFjF1}mAReb>-?@RR z+d)kOt%+9!=);u9qfSo|h58;sQWU8pA3az41hy0XQt3-!u2g$DvkuN`(BfHXRZwAV z^xG$x@_3fjp+DY0DpQJ9zkxj7LYVS6sC@z@yuOcK^CA^jQQ^N)j?`{jI4?ZlMQr9g z_Q6wsN`DtQDLsFnQBC*9Y(alm^DKY(ew!mFgRDF&Fq_xb9aG}H^3gcr<_7L2<2b)= zqFQ~$l*a{4#CuYHOW04{Z8%Su@;K*9PWQu|JNhg;D)Ew-7Tw)?ZSxtaBOkV&y^1r7 z*U~T7jM{p+F1<^r^uT`({`d{S@Q$JAzW4?yY2#*w4UdlxyckC*Opo!IRIm8aXw74b z{?EL#wQmvbSy%is0&DnIn<{8sYo29Yf4%9RDu_3m*DjdBxV6|m#v5p62HSkz$&q(* z6M5$KWmv_T#cSng?RiD~caiFq$y6M^wXJ!q!%^aaWzzAq3gWTmS$KThZPx>qp9;+8 zwR8McSFd6c)1dB!(jzQ|%X`2dhU>(QrFC!)ual!IUNsXpdgX}`w}VUdL&hxL{g6=L z-;A}Tl{Z$$TDY<2Anwi;H*KY#w1DhUQR?o(y5J0_a-ZMM;=a6)#hpyvKj1lg-&eMb zY*7T_Hftf66J8<;=%~2?9W^(gqvqN=`ukp)=pBK++p;ZWzZvv_<_7R-ZUCR=2JmUF zgAWxUksD)p-If;a2dTLmIcv7nnt`~J-AT}=nj65ZxdF_Yn~PbV6-51DHunz)^F$`* z>C8CR+yG|H4Pe&XT+H%3=?}B%N0XS5t1@;1e%6r{h;7XcVAk9KX3fpRjB4n`w!V)g zUiWscpp99;s}E@Hcl;fVmtCej9xbT)S+tIsE;DH}$bYuo>rnv!|X>Pz~nww`c zR4>nFMKPNmE{PfOlZ9DVivVWL4Pe&X0A|h2!;GBn8*@vYS6*FJH{@X5*V!1rthoWq znj65Zxp|lopS@znTY>CI6k>Er2`{JnxUOcYy&SX&o`W^PbC4!@c{t&pUU8zElCON7 z)oa{~Xxwro*%817!^#eL&Cq?C-YLZAhzRl7BOyL#QHak`9O8533Gq4eLwv6GAwE~z P5FdT4XFpVyYL)&2JiC#e literal 11526 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 diff --git a/testdata/algo/hierarchy_builder_au2c.gds b/testdata/algo/hierarchy_builder_au2c.gds index 85f5d60828ff1e721f426cbfab24f4ec7f4d4abb..d822f9a7aba70dfe46788411d482f1f6c85edf20 100644 GIT binary patch delta 1812 zcmaixO=uHQ6oucL{7hPtXeN_PCCN-DnQ4;#P-rD>T!<~AR!gaEln|}xR&mp%NR6)Q zLa-bZp|+is3PKIcCMa}SM4`Cq&YeQti7P=7$4Nyu_qls;&iU@S_x-%|s@7m(CP{Uc z<&yk~9blX#Segz0`(_a-JUunZm>LKjo47Z2cKg|v>WTKSv)ZV!Y>3RQG|p=Ta%Fpg zl5mvM+9qGi6G#>fhih{aGxOF6xRk;!pTp@ufgBSL&|~ff)~K9Y3FbH{UJIp25s$(o z6=!6+&6d1iQcHL5FU@dSws0k4Q`PV!a`C!=GBtBy#HzBO%tT+xym`KJ{C`fj)g`#o z$L=J3r+8GjuRp(nB5ob-z$bl$o~rq$dAslCm0$fN!})^!Cf zO~txyl8IgYccO$lp3g`txNrPz;=ct=^1Tm9?oMwRS3PFgRDAXgbDH$NH&dkc!+b$% zTX|Y*7GC3Ap@+;aTQ=TVj?mM#ZG5-Ip1nRB14Rd)i*<5Vy<}sxB<3uay*_2Jr`_-M U=@)zU222zOAB*;KqeF}Q7e=)?KmY&$ literal 11902 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 T^2$4tC300iib39q8V&I;d^qVc diff --git a/testdata/algo/hierarchy_builder_au4a.gds b/testdata/algo/hierarchy_builder_au4a.gds index 504df9920758aaa48972b806e6876bb7b4ac31b5..8c9f4d9cea29e8d1d5fe3b7d14b94797126f8318 100644 GIT binary patch literal 65416 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 P`N+7yJy5sq?-&0EytkuK literal 82618 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 Date: Sun, 7 Nov 2021 21:46:18 +0100 Subject: [PATCH 26/52] WIP (code reduction and performance enhancement of basic DRC checks) --- src/db/db/db.pro | 2 + src/db/db/dbAsIfFlatRegion.cc | 2 +- src/db/db/dbCellVariants.cc | 2 +- src/db/db/dbDeepRegion.cc | 2 +- src/db/db/dbLayoutUtils.cc | 2 +- src/db/db/dbPolygonTools.cc | 88 +++ src/db/db/dbPolygonTools.h | 31 + src/db/db/dbRegionCheckUtils.cc | 601 +++++++++++++++ src/db/db/dbRegionCheckUtils.h | 424 +++++++++++ src/db/db/dbRegionLocalOperations.cc | 19 +- src/db/db/dbRegionLocalOperations.h | 2 + src/db/db/dbRegionUtils.cc | 708 +----------------- src/db/db/dbRegionUtils.h | 415 ---------- ...ilsTests.cc => dbRegionCheckUtilsTests.cc} | 22 +- src/db/unit_tests/unit_tests.pro | 2 +- 15 files changed, 1174 insertions(+), 1148 deletions(-) create mode 100644 src/db/db/dbRegionCheckUtils.cc create mode 100644 src/db/db/dbRegionCheckUtils.h rename src/db/unit_tests/{dbRegionUtilsTests.cc => dbRegionCheckUtilsTests.cc} (94%) diff --git a/src/db/db/db.pro b/src/db/db/db.pro index b6c9cb53a..df5648b48 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -72,6 +72,7 @@ SOURCES = \ dbRecursiveInstanceIterator.cc \ dbRecursiveShapeIterator.cc \ dbRegion.cc \ + dbRegionCheckUtils.cc \ dbRegionLocalOperations.cc \ dbSaveLayoutOptions.cc \ dbShape.cc \ @@ -290,6 +291,7 @@ HEADERS = \ dbRecursiveInstanceIterator.h \ dbRecursiveShapeIterator.h \ dbRegion.h \ + dbRegionCheckUtils.h \ dbRegionLocalOperations.h \ dbSaveLayoutOptions.h \ dbShape.h \ diff --git a/src/db/db/dbAsIfFlatRegion.cc b/src/db/db/dbAsIfFlatRegion.cc index 0362a5088..9efc43576 100644 --- a/src/db/db/dbAsIfFlatRegion.cc +++ b/src/db/db/dbAsIfFlatRegion.cc @@ -1150,7 +1150,7 @@ AsIfFlatRegion::run_single_polygon_check (db::edge_relation_type rel, db::Coord size_t n = 0; for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) { - poly_check.enter (*p, n); + poly_check.single (*p, n); n += 2; } diff --git a/src/db/db/dbCellVariants.cc b/src/db/db/dbCellVariants.cc index 081d77a92..3b8fd6b03 100644 --- a/src/db/db/dbCellVariants.cc +++ b/src/db/db/dbCellVariants.cc @@ -22,7 +22,7 @@ #include "dbCellVariants.h" -#include "dbRegionUtils.h" +#include "dbPolygonTools.h" #include "tlUtils.h" namespace db diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index b536941c8..bfe1938c2 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -1725,7 +1725,7 @@ DeepRegion::run_single_polygon_check (db::edge_relation_type rel, db::Coord d, c s->polygon (poly); do { - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (edge_check.prepare_next_pass ()); } diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 0343e4828..98718791e 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -23,7 +23,7 @@ #include "dbLayoutUtils.h" #include "dbCellVariants.h" -#include "dbRegionUtils.h" +#include "dbPolygonTools.h" #include "tlProgress.h" namespace db diff --git a/src/db/db/dbPolygonTools.cc b/src/db/db/dbPolygonTools.cc index dc252fc9b..4c7e22c55 100644 --- a/src/db/db/dbPolygonTools.cc +++ b/src/db/db/dbPolygonTools.cc @@ -2827,5 +2827,93 @@ decompose_trapezoids (const db::SimplePolygon &sp, TrapezoidDecompositionMode mo } } +// ------------------------------------------------------------------------------------- +// Polygon snapping + +db::Polygon +snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord gy, std::vector &heap) +{ + db::Polygon pnew; + + for (size_t i = 0; i < poly.holes () + 1; ++i) { + + heap.clear (); + + db::Polygon::polygon_contour_iterator b, e; + + if (i == 0) { + b = poly.begin_hull (); + e = poly.end_hull (); + } else { + b = poly.begin_hole ((unsigned int) (i - 1)); + e = poly.end_hole ((unsigned int) (i - 1)); + } + + for (db::Polygon::polygon_contour_iterator pt = b; pt != e; ++pt) { + heap.push_back (db::Point (snap_to_grid ((*pt).x (), gx), snap_to_grid ((*pt).y (), gy))); + } + + if (i == 0) { + pnew.assign_hull (heap.begin (), heap.end ()); + } else { + pnew.insert_hole (heap.begin (), heap.end ()); + } + + } + + return pnew; +} + +db::Polygon +scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy, std::vector &heap) +{ + db::Polygon pnew; + + int64_t dgx = int64_t (gx) * int64_t (dx); + int64_t dgy = int64_t (gy) * int64_t (dy); + + for (size_t i = 0; i < poly.holes () + 1; ++i) { + + heap.clear (); + + db::Polygon::polygon_contour_iterator b, e; + + if (i == 0) { + b = poly.begin_hull (); + e = poly.end_hull (); + } else { + b = poly.begin_hole ((unsigned int) (i - 1)); + e = poly.end_hole ((unsigned int) (i - 1)); + } + + for (db::Polygon::polygon_contour_iterator pt = b; pt != e; ++pt) { + int64_t x = snap_to_grid (int64_t ((*pt).x ()) * mx + int64_t (ox), dgx) / int64_t (dx); + int64_t y = snap_to_grid (int64_t ((*pt).y ()) * my + int64_t (oy), dgy) / int64_t (dy); + heap.push_back (db::Point (db::Coord (x), db::Coord (y))); + } + + if (i == 0) { + pnew.assign_hull (heap.begin (), heap.end ()); + } else { + pnew.insert_hole (heap.begin (), heap.end ()); + } + + } + + return pnew; +} + +db::Vector +scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy) +{ + int64_t dgx = int64_t (gx) * int64_t (dx); + int64_t dgy = int64_t (gy) * int64_t (dy); + + int64_t x = snap_to_grid (int64_t (v.x ()) * mx + int64_t (ox), dgx) / int64_t (dx); + int64_t y = snap_to_grid (int64_t (v.y ()) * my + int64_t (oy), dgy) / int64_t (dy); + + return db::Vector (db::Coord (x), db::Coord (y)); +} + } diff --git a/src/db/db/dbPolygonTools.h b/src/db/db/dbPolygonTools.h index c90691ba4..0a4bc11d6 100644 --- a/src/db/db/dbPolygonTools.h +++ b/src/db/db/dbPolygonTools.h @@ -760,6 +760,37 @@ void DB_PUBLIC decompose_trapezoids (const db::Polygon &p, TrapezoidDecompositio */ void DB_PUBLIC decompose_trapezoids (const db::SimplePolygon &p, TrapezoidDecompositionMode mode, SimplePolygonSink &sink); +template +static inline C snap_to_grid (C c, C g) +{ + // This form of snapping always snaps g/2 to right/top. + if (c < 0) { + c = -g * ((-c + (g - 1) / 2) / g); + } else { + c = g * ((c + g / 2) / g); + } + return c; +} + +/** + * @brief Snaps a polygon to the given grid + * Heap is a vector of points reused for the point list + */ +DB_PUBLIC db::Polygon snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord gy, std::vector &heap); + +/** + * @brief Scales and snaps a polygon to the given grid + * Heap is a vector of points reused for the point list + * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. + */ +DB_PUBLIC db::Polygon scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy, std::vector &heap); + +/** + * @brief Scales and snaps a vector to the given grid + * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. + */ +DB_PUBLIC db::Vector scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy); + } #endif diff --git a/src/db/db/dbRegionCheckUtils.cc b/src/db/db/dbRegionCheckUtils.cc new file mode 100644 index 000000000..38cfe9f51 --- /dev/null +++ b/src/db/db/dbRegionCheckUtils.cc @@ -0,0 +1,601 @@ + +/* + + 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 "dbRegionCheckUtils.h" +#include "dbPolygonTools.h" +#include "dbEdgeBoolean.h" +#include "tlSelect.h" + +namespace db +{ + +// ------------------------------------------------------------------------------------- +// Edge2EdgeCheckBase implementation + +Edge2EdgeCheckBase::Edge2EdgeCheckBase (const EdgeRelationFilter &check, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) + : mp_check (&check), m_requires_different_layers (requires_different_layers), m_different_polygons (different_polygons), + m_first_pseudo (std::numeric_limits::max ()), + m_with_shielding (with_shielding), + m_symmetric_edges (symmetric_edges), + m_has_edge_pair_output (true), + m_has_negative_edge_output (false), + m_pass (0) +{ + m_distance = check.distance (); +} + +bool +Edge2EdgeCheckBase::prepare_next_pass () +{ + ++m_pass; + + if (m_pass == 1) { + + m_first_pseudo = m_ep.size (); + + if (m_with_shielding && ! m_ep.empty ()) { + + m_ep_discarded.resize (m_ep.size (), false); + + // second pass: + return true; + + } else if (m_has_negative_edge_output) { + + // second pass: + return true; + + } + + } + + if (! m_ep.empty () && m_has_edge_pair_output) { + + std::vector::const_iterator d = m_ep_discarded.begin (); + std::vector::const_iterator i = m_ep_intra_polygon.begin (); + std::vector::const_iterator ep = m_ep.begin (); + while (ep != m_ep.end () && size_t (ep - m_ep.begin ()) < m_first_pseudo) { + bool use_result = true; + if (d != m_ep_discarded.end ()) { + use_result = ! *d; + ++d; + } + if (use_result) { + put (*ep, *i); + } + ++ep; + ++i; + } + + } + + return false; +} + +static inline bool shields (const db::EdgePair &ep, const db::Edge &q) +{ + db::Edge pe1 (ep.first ().p1 (), ep.second ().p2 ()); + db::Edge pe2 (ep.second ().p1 (), ep.first ().p2 ()); + + std::pair ip1 = pe1.intersect_point (q); + std::pair ip2 = pe2.intersect_point (q); + + if (ip1.first && ip2.first) { + return ip1.second != ip2.second || (pe1.side_of (q.p1 ()) != 0 && pe2.side_of (q.p2 ()) != 0); + } else { + return false; + } +} + +void +Edge2EdgeCheckBase::finish (const Edge *o, size_t p) +{ + if (m_has_negative_edge_output && m_pass == 1 && m_pseudo_edges.find (std::make_pair (*o, p)) == m_pseudo_edges.end ()) { + + std::pair k (*o, p); + std::multimap, size_t>::const_iterator i0 = m_e2ep.find (k); + + bool fully_removed = false; + bool any = false; + for (std::multimap, size_t>::const_iterator i = i0; ! fully_removed && i != m_e2ep.end () && i->first == k; ++i) { + size_t n = i->second / 2; + if (n >= m_ep_discarded.size () || !m_ep_discarded [n]) { + any = true; + fully_removed = (((i->second & 1) == 0 ? m_ep [n].first () : m_ep [n].second ()) == *o); + } + } + + if (! any) { + + put_negative (*o, (int) p); + + } else if (! fully_removed) { + + std::set partial_edges; + + db::EdgeBooleanCluster > ec (&partial_edges, db::EdgeNot); + ec.add (o, 0); + + for (std::multimap, size_t>::const_iterator i = i0; i != m_e2ep.end () && i->first == k; ++i) { + size_t n = i->second / 2; + if (n >= m_ep_discarded.size () || !m_ep_discarded [n]) { + ec.add (((i->second & 1) == 0 ? &m_ep [n].first () : &m_ep [n].second ()), 1); + } + } + + ec.finish (); + + for (std::set::const_iterator e = partial_edges.begin (); e != partial_edges.end (); ++e) { + put_negative (*e, (int) p); + } + + } + + } +} + +bool +Edge2EdgeCheckBase::feed_pseudo_edges (db::box_scanner &scanner) +{ + if (m_pass == 1) { + for (std::set >::const_iterator e = m_pseudo_edges.begin (); e != m_pseudo_edges.end (); ++e) { + scanner.insert (&e->first, e->second); + } + return ! m_pseudo_edges.empty (); + } else { + return false; + } +} + +inline bool edges_considered (bool requires_different_polygons, bool requires_different_layers, size_t p1, size_t p2) +{ + if (p1 == p2) { + if (requires_different_polygons) { + return false; + } else if ((p1 & size_t (1)) != 0) { + // edges from the same polygon are only considered on first layer. + // Reasoning: this case happens when "intruder" polygons are put on layer 1 + // while "subject" polygons are put on layer 0. We don't want "intruders" + // to generate intra-polygon markers. + return false; + } + } + + if (((p1 ^ p2) & size_t (1)) == 0) { + if (requires_different_layers) { + return false; + } else if ((p1 & size_t (1)) != 0) { + // edges on the same layer are only considered on first layer. + // Reasoning: this case happens when "intruder" polygons are put on layer 1 + // while "subject" polygons are put on layer 0. We don't want "intruders" + // to generate inter-polygon markers between them. + return false; + } + } + + return true; +} + +void +Edge2EdgeCheckBase::add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) +{ + if (m_pass == 0) { + + // Overlap or inside checks require input from different layers + if (edges_considered (m_different_polygons, m_requires_different_layers, p1, p2)) { + + // ensure that the first check argument is of layer 1 and the second of + // layer 2 (unless both are of the same layer) + int l1 = int (p1 & size_t (1)); + int l2 = int (p2 & size_t (1)); + + if (l1 > l2) { + std::swap (o1, o2); + std::swap (p1, p2); + } + + db::EdgePair ep; + if (mp_check->check (*o1, *o2, &ep)) { + + ep.set_symmetric (m_symmetric_edges); + + // found a violation: store inside the local buffer for now. In the second + // pass we will eliminate those which are shielded completely (with shielding) + // and/or compute the negative edges. + size_t n = m_ep.size (); + + m_ep.push_back (ep); + m_ep_intra_polygon.push_back (p1 == p2); + + m_e2ep.insert (std::make_pair (std::make_pair (*o1, p1), n * 2)); + m_e2ep.insert (std::make_pair (std::make_pair (*o2, p2), n * 2 + 1)); + + if (m_has_negative_edge_output) { + m_pseudo_edges.insert (std::make_pair (db::Edge (ep.first ().p1 (), ep.second ().p2 ()), p1)); + m_pseudo_edges.insert (std::make_pair (db::Edge (ep.second ().p1 (), ep.first ().p2 ()), p1)); + if (p1 != p2) { + m_pseudo_edges.insert (std::make_pair (db::Edge (ep.first ().p1 (), ep.second ().p2 ()), p2)); + m_pseudo_edges.insert (std::make_pair (db::Edge (ep.second ().p1 (), ep.first ().p2 ()), p2)); + } + } + + } + + } + + } else { + + // set the discarded flags for shielded output + if (m_with_shielding) { + + // a simple (complete) shielding implementation which is based on the + // assumption that shielding is relevant as soon as a foreign edge cuts through + // both of the edge pair's connecting edges. + + // TODO: this implementation does not take into account the nature of the + // EdgePair - because of "whole_edge" it may not reflect the part actually + // violating the distance. + + std::vector n1, n2; + + for (unsigned int p = 0; p < 2; ++p) { + + std::pair k (*o1, p1); + for (std::multimap, size_t>::const_iterator i = m_e2ep.find (k); i != m_e2ep.end () && i->first == k; ++i) { + size_t n = i->second / 2; + if (n < m_first_pseudo && ! m_ep_discarded [n]) { + n1.push_back (n); + } + } + + std::sort (n1.begin (), n1.end ()); + + std::swap (o1, o2); + std::swap (p1, p2); + n1.swap (n2); + + } + + for (unsigned int p = 0; p < 2; ++p) { + + std::vector nn; + std::set_difference (n1.begin (), n1.end (), n2.begin (), n2.end (), std::back_inserter (nn)); + + for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { + db::EdgePair ep = m_ep [*i].normalized (); + if (shields (ep, *o2)) { + m_ep_discarded [*i] = true; + } + } + + std::swap (o1, o2); + std::swap (p1, p2); + n1.swap (n2); + + } + + } + + // for negative output edges are cancelled by short interactions perpendicular to them + // For this we have generated "pseudo edges" running along the sides of the original violation. We now check a real + // edge vs. a pseudo edge with the same conditions as the normal interaction and add them to the results. In the + // negative case this means we cancel a real edge. + + if (m_has_negative_edge_output && + (m_pseudo_edges.find (std::make_pair (*o1, p1)) != m_pseudo_edges.end ()) != (m_pseudo_edges.find (std::make_pair (*o2, p2)) != m_pseudo_edges.end ())) { + + // Overlap or inside checks require input from different layers + if (edges_considered (m_different_polygons, m_requires_different_layers, p1, p2)) { + + // ensure that the first check argument is of layer 1 and the second of + // layer 2 (unless both are of the same layer) + int l1 = int (p1 & size_t (1)); + int l2 = int (p2 & size_t (1)); + + if (l1 > l2) { + std::swap (o1, o2); + std::swap (p1, p2); + } + + db::EdgePair ep; + if (mp_check->check (*o1, *o2, &ep)) { + + size_t n = m_ep.size (); + + m_ep.push_back (ep); + m_ep_intra_polygon.push_back (p1 == p2); // not really required, but there for consistency + + m_e2ep.insert (std::make_pair (std::make_pair (*o1, p1), n * 2)); + m_e2ep.insert (std::make_pair (std::make_pair (*o2, p2), n * 2 + 1)); + + } + + } + + } + + } + +} + +/** + * @brief Gets a value indicating whether the check requires different layers + */ +bool +Edge2EdgeCheckBase::requires_different_layers () const +{ + return m_requires_different_layers; +} + +/** + * @brief Sets a value indicating whether the check requires different layers + */ +void +Edge2EdgeCheckBase::set_requires_different_layers (bool f) +{ + m_requires_different_layers = f; +} + +/** + * @brief Gets a value indicating whether the check requires different layers + */ +bool +Edge2EdgeCheckBase::different_polygons () const +{ + return m_different_polygons; +} + +/** + * @brief Sets a value indicating whether the check requires different layers + */ +void +Edge2EdgeCheckBase::set_different_polygons (bool f) +{ + m_different_polygons = f; +} + +/** + * @brief Gets the distance value + */ +EdgeRelationFilter::distance_type +Edge2EdgeCheckBase::distance () const +{ + return m_distance; +} + +// ------------------------------------------------------------------------------------- +// Poly2PolyCheckBase implementation + +template +poly2poly_check::poly2poly_check (Edge2EdgeCheckBase &output) + : mp_output (& output) +{ + // .. nothing yet .. +} + +template +poly2poly_check::poly2poly_check () + : mp_output (0) +{ + // .. nothing yet .. +} + +static size_t vertices (const db::Polygon &p) +{ + return p.vertices (); +} + +static size_t vertices (const db::PolygonRef &p) +{ + return p.obj ().vertices (); +} + +template +void +poly2poly_check::single (const PolygonType &o, size_t p) +{ + tl_assert (! mp_output->requires_different_layers () && ! mp_output->different_polygons ()); + + // finally we check the polygons vs. itself for checks involving intra-polygon interactions + + m_scanner.clear (); + m_scanner.reserve (vertices (o)); + + m_edge_heap.clear (); + + for (typename PolygonType::polygon_edge_iterator e = o.begin_edge (); ! e.at_end (); ++e) { + m_edge_heap.push_back (*e); + m_scanner.insert (& m_edge_heap.back (), p); + } + + mp_output->feed_pseudo_edges (m_scanner); + + m_scanner.process (*mp_output, mp_output->distance (), db::box_convert ()); +} + +template +void +poly2poly_check::connect (Edge2EdgeCheckBase &output) +{ + mp_output = &output; + clear (); +} + +template +void +poly2poly_check::clear () +{ + m_scanner.clear (); + m_edge_heap.clear (); +} + +template +void +poly2poly_check::enter (const PolygonType &o, size_t p) +{ + for (typename PolygonType::polygon_edge_iterator e = o.begin_edge (); ! e.at_end (); ++e) { + m_edge_heap.push_back (*e); + m_scanner.insert (& m_edge_heap.back (), p); + } +} + +template +void +poly2poly_check::process () +{ + mp_output->feed_pseudo_edges (m_scanner); + m_scanner.process (*mp_output, mp_output->distance (), db::box_convert ()); +} + +// explicit instantiations +template class poly2poly_check; +template class poly2poly_check; + +// ------------------------------------------------------------------------------------- +// RegionToEdgeInteractionFilterBase implementation + +template +region_to_edge_interaction_filter_base::region_to_edge_interaction_filter_base (bool inverse, bool get_all) + : m_inverse (inverse), m_get_all (get_all) +{ + // .. nothing yet .. +} + +template +void +region_to_edge_interaction_filter_base::preset (const OutputType *s) +{ + m_seen.insert (s); +} + +template +void +region_to_edge_interaction_filter_base::add (const PolygonType *p, size_t, const EdgeType *e, size_t) +{ + const OutputType *o = 0; + tl::select (o, p, e); + + if (m_get_all || (m_seen.find (o) == m_seen.end ()) != m_inverse) { + + // A polygon and an edge interact if the edge is either inside completely + // of at least one edge of the polygon intersects with the edge + bool interacts = false; + if (p->box ().contains (e->p1 ()) && db::inside_poly (p->begin_edge (), e->p1 ()) >= 0) { + interacts = true; + } else { + for (typename PolygonType::polygon_edge_iterator pe = p->begin_edge (); ! pe.at_end () && ! interacts; ++pe) { + if ((*pe).intersect (*e)) { + interacts = true; + } + } + } + + if (interacts) { + if (m_inverse) { + m_seen.erase (o); + } else { + if (! m_get_all) { + m_seen.insert (o); + } + put (*o); + } + } + + } +} + +template +void +region_to_edge_interaction_filter_base::fill_output () +{ + for (typename std::set::const_iterator s = m_seen.begin (); s != m_seen.end (); ++s) { + put (**s); + } +} + +// explicit instantiations +template class region_to_edge_interaction_filter_base; +template class region_to_edge_interaction_filter_base; +template class region_to_edge_interaction_filter_base; +template class region_to_edge_interaction_filter_base; + +// ------------------------------------------------------------------------------------- +// RegionToTextInteractionFilterBase implementation + +template +region_to_text_interaction_filter_base::region_to_text_interaction_filter_base (bool inverse, bool get_all) + : m_inverse (inverse), m_get_all (get_all) +{ + // .. nothing yet .. +} + +template +void +region_to_text_interaction_filter_base::preset (const OutputType *s) +{ + m_seen.insert (s); +} + +template +void +region_to_text_interaction_filter_base::add (const PolygonType *p, size_t, const TextType *t, size_t) +{ + const OutputType *o = 0; + tl::select (o, p, t); + + if (m_get_all || (m_seen.find (o) == m_seen.end ()) != m_inverse) { + + // A polygon and an text interact if the text is either inside completely + // of at least one text of the polygon intersects with the text + db::Point pt = db::box_convert () (*t).p1 (); + if (p->box ().contains (pt) && db::inside_poly (p->begin_edge (), pt) >= 0) { + if (m_inverse) { + m_seen.erase (o); + } else { + if (! m_get_all) { + m_seen.insert (o); + } + put (*o); + } + } + + } +} + +template +void +region_to_text_interaction_filter_base::fill_output () +{ + for (typename std::set::const_iterator s = m_seen.begin (); s != m_seen.end (); ++s) { + put (**s); + } +} + +// explicit instantiations +template class region_to_text_interaction_filter_base; +template class region_to_text_interaction_filter_base; +template class region_to_text_interaction_filter_base; +template class region_to_text_interaction_filter_base; +template class region_to_text_interaction_filter_base; + +} diff --git a/src/db/db/dbRegionCheckUtils.h b/src/db/db/dbRegionCheckUtils.h new file mode 100644 index 000000000..d8311980d --- /dev/null +++ b/src/db/db/dbRegionCheckUtils.h @@ -0,0 +1,424 @@ + +/* + + 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_dbRegionCheckUtils +#define HDR_dbRegionCheckUtils + +#include "dbCommon.h" +#include "dbCellVariants.h" +#include "dbBoxScanner.h" +#include "dbEdgePairRelations.h" + +namespace db { + +/** + * @brief A helper class for the DRC functionality which acts as an edge pair receiver + */ +class DB_PUBLIC Edge2EdgeCheckBase + : public db::box_scanner_receiver +{ +public: + Edge2EdgeCheckBase (const EdgeRelationFilter &check, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges); + + /** + * @brief Call this to initiate a new pass until the return value is false + */ + bool prepare_next_pass (); + + /** + * @brief Before the scanner is run, this method must be called to feed additional edges into the scanner + * (required for negative edge output - cancellation of perpendicular edges) + */ + bool feed_pseudo_edges (db::box_scanner &scanner); + + /** + * @brief Reimplementation of the box_scanner_receiver interface + */ + void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2); + + /** + * @brief Reimplementation of the box_scanner_receiver interface + */ + void finish (const Edge *o, size_t); + + /** + * @brief Gets a value indicating whether the check requires different layers + */ + bool requires_different_layers () const; + + /** + * @brief Sets a value indicating whether the check requires different layers + */ + void set_requires_different_layers (bool f); + + /** + * @brief Gets a value indicating whether the check requires different layers + */ + bool different_polygons () const; + + /** + * @brief Sets a value indicating whether the check requires different layers + */ + void set_different_polygons (bool f); + + /** + * @brief Sets a flag indicating that this class wants negative edge output + */ + void set_has_negative_edge_output (bool f) + { + m_has_negative_edge_output = f; + } + + /** + * @brief Gets a flag indicating that this class wants negative edge output + */ + bool has_negative_edge_output () const + { + return m_has_negative_edge_output; + } + + /** + * @brief Sets a flag indicating that this class wants normal edge pair output + */ + void set_has_edge_pair_output (bool f) + { + m_has_edge_pair_output = f; + } + + /** + * @brief Gets a flag indicating that this class wants normal edge pair output + */ + bool has_edge_pair_output () const + { + return m_has_edge_pair_output; + } + + /** + * @brief Gets the distance value + */ + EdgeRelationFilter::distance_type distance () const; + +protected: + /** + * @brief Normal edge pair output (violations) + */ + virtual void put (const db::EdgePair & /*edge*/, bool /*intra-polygon*/) const { } + + /** + * @brief Negative edge output + */ + virtual void put_negative (const db::Edge & /*edge*/, int /*layer*/) const { } + +private: + const EdgeRelationFilter *mp_check; + bool m_requires_different_layers; + bool m_different_polygons; + EdgeRelationFilter::distance_type m_distance; + std::vector m_ep; + std::multimap, size_t> m_e2ep; + std::set > m_pseudo_edges; + size_t m_first_pseudo; + std::vector m_ep_discarded, m_ep_intra_polygon; + bool m_with_shielding; + bool m_symmetric_edges; + bool m_has_edge_pair_output; + bool m_has_negative_edge_output; + unsigned int m_pass; +}; + +/** + * @brief A helper class for the DRC functionality + * + * This class implements the edge-to-edge part of the polygon DRC. + */ +template +class DB_PUBLIC_TEMPLATE edge2edge_check + : public Edge2EdgeCheckBase +{ +public: + edge2edge_check (const EdgeRelationFilter &check, Output &output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) + : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, symmetric_edges), mp_output_inter (&output), mp_output_intra (0) + { + // .. nothing yet .. + } + + edge2edge_check (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) + : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, symmetric_edges), mp_output_inter (&output_inter), mp_output_intra (&output_intra) + { + // .. nothing yet .. + } + +protected: + void put (const db::EdgePair &edge, bool inter_polygon) const + { + if (! inter_polygon || ! mp_output_intra) { + mp_output_inter->insert (edge); + } else { + mp_output_intra->insert (edge); + } + } + +private: + Output *mp_output_inter; + Output *mp_output_intra; +}; + +/** + * @brief A helper class for the DRC functionality + * + * This class implements the edge-to-edge part of the polygon DRC. + * This version allows delivery of the negative edges. + */ +template +class DB_PUBLIC_TEMPLATE edge2edge_check_with_negative_output + : public edge2edge_check +{ +public: + edge2edge_check_with_negative_output (const EdgeRelationFilter &check, Output &output, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) + : edge2edge_check (check, output, different_polygons, requires_different_layers, with_shielding, symmetric_edges), + mp_l1_negative_output (&l1_negative_output), + mp_l2_negative_output (&l2_negative_output) + { + edge2edge_check::set_has_negative_edge_output (true); + } + + edge2edge_check_with_negative_output (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) + : edge2edge_check (check, output_inter, output_intra, different_polygons, requires_different_layers, with_shielding, symmetric_edges), + mp_l1_negative_output (&l1_negative_output), + mp_l2_negative_output (&l2_negative_output) + { + edge2edge_check::set_has_negative_edge_output (true); + } + +protected: + void put_negative (const db::Edge &edge, int layer) const + { + if (layer == 0) { + mp_l1_negative_output->insert (edge); + } + if (layer == 1) { + mp_l2_negative_output->insert (edge); + } + } + +private: + NegativeEdgeOutput *mp_l1_negative_output, *mp_l2_negative_output; +}; + +/** + * @brief A helper class for the DRC functionality + * + * This class implements the edge-to-edge part of the polygon DRC. + * This version has only negative edge output. + */ +template +class DB_PUBLIC_TEMPLATE edge2edge_check_negative + : public Edge2EdgeCheckBase +{ +public: + edge2edge_check_negative (const EdgeRelationFilter &check, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding) + : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, false), + mp_l1_negative_output (&l1_negative_output), + mp_l2_negative_output (&l2_negative_output) + { + set_has_negative_edge_output (true); + set_has_edge_pair_output (false); + } + +protected: + void put_negative (const db::Edge &edge, int layer) const + { + if (layer == 0) { + mp_l1_negative_output->insert (edge); + } + if (layer == 1) { + mp_l2_negative_output->insert (edge); + } + } + +private: + NegativeEdgeOutput *mp_l1_negative_output, *mp_l2_negative_output; +}; + +/** + * @brief A helper class for the DRC functionality + * + * This class implements the edge-to-edge part of the polygon DRC. + * This version has positive or negative output. Negative output is mapped to edge pairs + * as well. + */ +template +class DB_PUBLIC_TEMPLATE edge2edge_check_negative_or_positive + : public edge2edge_check +{ +public: + edge2edge_check_negative_or_positive (const EdgeRelationFilter &check, Output &output, bool negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric) + : edge2edge_check (check, output, different_polygons, requires_different_layers, with_shielding, symmetric) + { + edge2edge_check::set_has_negative_edge_output (negative_output); + edge2edge_check::set_has_edge_pair_output (! negative_output); + } + + edge2edge_check_negative_or_positive (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, bool negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric) + : edge2edge_check (check, output_inter, output_intra, different_polygons, requires_different_layers, with_shielding, symmetric) + { + edge2edge_check::set_has_negative_edge_output (negative_output); + edge2edge_check::set_has_edge_pair_output (! negative_output); + } + +protected: + void put_negative (const db::Edge &edge, int layer) const + { + if (layer == 0) { + edge2edge_check::put (db::EdgePair (edge, edge.swapped_points ()), false); + } +#if 0 + // NOTE: second-input negative edge output isn't worth a lot as the second input often is not merged, hence + // the outer edges to not represent the actual contour. + if (layer == 1) { + edge2edge_check::put (db::EdgePair (edge.swapped_points (), edge), false); + } +#endif + } +}; + +/** + * @brief A helper class for the DRC functionality which acts as an edge pair receiver + */ +template +class DB_PUBLIC poly2poly_check +{ +public: + poly2poly_check (Edge2EdgeCheckBase &output); + poly2poly_check (); + + void clear (); + + void single (const PolygonType&o, size_t p); + + void connect (Edge2EdgeCheckBase &output); + void enter (const PolygonType &o, size_t p); + void process (); + +private: + db::Edge2EdgeCheckBase *mp_output; + db::box_scanner m_scanner; + std::list m_edge_heap; +}; + +/** + * @brief A helper class for the region to edge interaction functionality + */ +template +class DB_PUBLIC region_to_edge_interaction_filter_base + : public db::box_scanner_receiver2 +{ +public: + region_to_edge_interaction_filter_base (bool inverse, bool get_all); + + void preset (const OutputType *s); + void add (const PolygonType *p, size_t, const EdgeType *e, size_t); + void fill_output (); + +protected: + virtual void put (const OutputType &s) const = 0; + +private: + std::set m_seen; + bool m_inverse, m_get_all; +}; + +/** + * @brief A helper class for the region to edge interaction functionality + */ +template +class DB_PUBLIC_TEMPLATE region_to_edge_interaction_filter + : public region_to_edge_interaction_filter_base +{ +public: + region_to_edge_interaction_filter (OutputContainer &output, bool inverse, bool get_all = false) + : region_to_edge_interaction_filter_base (inverse, get_all), mp_output (&output) + { + // .. nothing yet .. + } + +protected: + virtual void put (const OutputType &res) const + { + mp_output->insert (res); + } + +private: + OutputContainer *mp_output; +}; + +/** + * @brief A helper class for the region to text interaction functionality + */ +template +class DB_PUBLIC region_to_text_interaction_filter_base + : public db::box_scanner_receiver2 +{ +public: + region_to_text_interaction_filter_base (bool inverse, bool get_all); + + void preset (const OutputType *s); + void add (const PolygonType *p, size_t, const TextType *e, size_t); + void fill_output (); + +protected: + virtual void put (const OutputType &s) const = 0; + +private: + std::set m_seen; + bool m_inverse, m_get_all; +}; + +/** + * @brief A helper class for the region to text interaction functionality + */ +template +class DB_PUBLIC_TEMPLATE region_to_text_interaction_filter + : public region_to_text_interaction_filter_base +{ +public: + region_to_text_interaction_filter (OutputContainer &output, bool inverse, bool get_all = false) + : region_to_text_interaction_filter_base (inverse, get_all), mp_output (&output) + { + // .. nothing yet .. + } + +protected: + virtual void put (const OutputType &poly) const + { + mp_output->insert (poly); + } + +private: + OutputContainer *mp_output; +}; + +} // namespace db + +#endif + diff --git a/src/db/db/dbRegionLocalOperations.cc b/src/db/db/dbRegionLocalOperations.cc index 0a09c411e..339f5e2dd 100644 --- a/src/db/db/dbRegionLocalOperations.cc +++ b/src/db/db/dbRegionLocalOperations.cc @@ -21,8 +21,8 @@ */ -#include "dbRegionUtils.h" #include "dbRegionLocalOperations.h" +#include "dbRegionUtils.h" #include "dbLocalOperationUtils.h" #include "dbHierProcessor.h" @@ -123,7 +123,7 @@ static bool shields_interaction (const db::EdgePair &ep, const P &poly) template check_local_operation::check_local_operation (const EdgeRelationFilter &check, bool different_polygons, bool has_other, bool other_is_merged, const db::RegionCheckOptions &options) - : m_check (check), m_different_polygons (different_polygons), m_has_other (has_other), m_other_is_merged (other_is_merged), m_options (options) + : m_check (check), m_different_polygons (different_polygons), m_has_other (has_other), m_other_is_merged (other_is_merged), m_options (options), m_poly_check () { // .. nothing yet .. } @@ -206,10 +206,9 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape bool symmetric_edge_pairs = ! m_has_other && m_options.opposite_filter == db::NoOppositeFilter && m_options.rect_filter == RectFilter::NoRectFilter; edge2edge_check_negative_or_positive > edge_check (m_check, result, intra_polygon_result, m_options.negative, m_different_polygons, m_has_other, m_options.shielded, symmetric_edge_pairs); - poly2poly_check poly_check (edge_check); + m_poly_check.connect (edge_check); std::list heap; - db::box_scanner scanner; std::unordered_set polygons; std::set ids; @@ -224,7 +223,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape size_t n = 0; for (typename shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { const TS &subject = interactions.subject_shape (i->first); - scanner.insert (push_polygon_to_heap (layout, subject, heap), n); + m_poly_check.enter (subject, n); n += 2; } @@ -262,7 +261,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape n = 1; for (typename std::unordered_set::const_iterator o = polygons.begin (); o != polygons.end (); ++o) { - scanner.insert (push_polygon_to_heap (layout, *o, heap), n); + m_poly_check.enter (*o, n); n += 2; } @@ -270,7 +269,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape n = 1; for (std::set::const_iterator id = ids.begin (); id != ids.end (); ++id) { - scanner.insert (push_polygon_to_heap (layout, interactions.intruder_shape (*id).second, heap), n); + m_poly_check.enter (interactions.intruder_shape (*id).second, n); n += 2; } @@ -286,7 +285,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape // we can't directly insert because TS may be != TI const TS &ts = interactions.subject_shape (i->first); insert_into_hash (polygons, ts); - scanner.insert (push_polygon_to_heap (layout, ts, heap), n); + m_poly_check.enter (ts, n); n += 2; } @@ -295,7 +294,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape for (std::set::const_iterator id = ids.begin (); id != ids.end (); ++id) { const TI &ti = interactions.intruder_shape (*id).second; if (polygons.find (ti) == polygons.end ()) { - scanner.insert (push_polygon_to_heap (layout, ti, heap), n); + m_poly_check.enter (ti, n); n += 2; } } @@ -303,7 +302,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape } do { - scanner.process (poly_check, m_check.distance (), db::box_convert ()); + m_poly_check.process (); } while (edge_check.prepare_next_pass ()); // detect and remove parts of the result which have or do not have results "opposite" diff --git a/src/db/db/dbRegionLocalOperations.h b/src/db/db/dbRegionLocalOperations.h index f4bec55f0..b8f8b8b3f 100644 --- a/src/db/db/dbRegionLocalOperations.h +++ b/src/db/db/dbRegionLocalOperations.h @@ -28,6 +28,7 @@ #include "dbEdgePairRelations.h" #include "dbLocalOperation.h" #include "dbEdgeProcessor.h" +#include "dbRegionCheckUtils.h" #include #include @@ -218,6 +219,7 @@ private: bool m_has_other; bool m_other_is_merged; db::RegionCheckOptions m_options; + mutable poly2poly_check m_poly_check; }; typedef check_local_operation CheckLocalOperation; diff --git a/src/db/db/dbRegionUtils.cc b/src/db/db/dbRegionUtils.cc index e4e33e563..4c8acd690 100644 --- a/src/db/db/dbRegionUtils.cc +++ b/src/db/db/dbRegionUtils.cc @@ -22,6 +22,7 @@ #include "dbRegionUtils.h" +#include "dbRegionCheckUtils.h" #include "dbPolygonTools.h" #include "dbEdgeBoolean.h" #include "tlSelect.h" @@ -29,496 +30,6 @@ namespace db { -// ------------------------------------------------------------------------------------- -// Edge2EdgeCheckBase implementation - -Edge2EdgeCheckBase::Edge2EdgeCheckBase (const EdgeRelationFilter &check, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) - : mp_check (&check), m_requires_different_layers (requires_different_layers), m_different_polygons (different_polygons), - m_first_pseudo (std::numeric_limits::max ()), - m_with_shielding (with_shielding), - m_symmetric_edges (symmetric_edges), - m_has_edge_pair_output (true), - m_has_negative_edge_output (false), - m_pass (0) -{ - m_distance = check.distance (); -} - -bool -Edge2EdgeCheckBase::prepare_next_pass () -{ - ++m_pass; - - if (m_pass == 1) { - - m_first_pseudo = m_ep.size (); - - if (m_with_shielding && ! m_ep.empty ()) { - - m_ep_discarded.resize (m_ep.size (), false); - - // second pass: - return true; - - } else if (m_has_negative_edge_output) { - - // second pass: - return true; - - } - - } - - if (! m_ep.empty () && m_has_edge_pair_output) { - - std::vector::const_iterator d = m_ep_discarded.begin (); - std::vector::const_iterator i = m_ep_intra_polygon.begin (); - std::vector::const_iterator ep = m_ep.begin (); - while (ep != m_ep.end () && size_t (ep - m_ep.begin ()) < m_first_pseudo) { - bool use_result = true; - if (d != m_ep_discarded.end ()) { - use_result = ! *d; - ++d; - } - if (use_result) { - put (*ep, *i); - } - ++ep; - ++i; - } - - } - - return false; -} - -static inline bool shields (const db::EdgePair &ep, const db::Edge &q) -{ - db::Edge pe1 (ep.first ().p1 (), ep.second ().p2 ()); - db::Edge pe2 (ep.second ().p1 (), ep.first ().p2 ()); - - std::pair ip1 = pe1.intersect_point (q); - std::pair ip2 = pe2.intersect_point (q); - - if (ip1.first && ip2.first) { - return ip1.second != ip2.second || (pe1.side_of (q.p1 ()) != 0 && pe2.side_of (q.p2 ()) != 0); - } else { - return false; - } -} - -void -Edge2EdgeCheckBase::finish (const Edge *o, size_t p) -{ - if (m_has_negative_edge_output && m_pass == 1 && m_pseudo_edges.find (std::make_pair (*o, p)) == m_pseudo_edges.end ()) { - - std::pair k (*o, p); - std::multimap, size_t>::const_iterator i0 = m_e2ep.find (k); - - bool fully_removed = false; - bool any = false; - for (std::multimap, size_t>::const_iterator i = i0; ! fully_removed && i != m_e2ep.end () && i->first == k; ++i) { - size_t n = i->second / 2; - if (n >= m_ep_discarded.size () || !m_ep_discarded [n]) { - any = true; - fully_removed = (((i->second & 1) == 0 ? m_ep [n].first () : m_ep [n].second ()) == *o); - } - } - - if (! any) { - - put_negative (*o, (int) p); - - } else if (! fully_removed) { - - std::set partial_edges; - - db::EdgeBooleanCluster > ec (&partial_edges, db::EdgeNot); - ec.add (o, 0); - - for (std::multimap, size_t>::const_iterator i = i0; i != m_e2ep.end () && i->first == k; ++i) { - size_t n = i->second / 2; - if (n >= m_ep_discarded.size () || !m_ep_discarded [n]) { - ec.add (((i->second & 1) == 0 ? &m_ep [n].first () : &m_ep [n].second ()), 1); - } - } - - ec.finish (); - - for (std::set::const_iterator e = partial_edges.begin (); e != partial_edges.end (); ++e) { - put_negative (*e, (int) p); - } - - } - - } -} - -bool -Edge2EdgeCheckBase::feed_pseudo_edges (db::box_scanner &scanner) -{ - if (m_pass == 1) { - for (std::set >::const_iterator e = m_pseudo_edges.begin (); e != m_pseudo_edges.end (); ++e) { - scanner.insert (&e->first, e->second); - } - return ! m_pseudo_edges.empty (); - } else { - return false; - } -} - -inline bool edges_considered (bool requires_different_polygons, bool requires_different_layers, size_t p1, size_t p2) -{ - if (p1 == p2) { - if (requires_different_polygons) { - return false; - } else if ((p1 & size_t (1)) != 0) { - // edges from the same polygon are only considered on first layer. - // Reasoning: this case happens when "intruder" polygons are put on layer 1 - // while "subject" polygons are put on layer 0. We don't want "intruders" - // to generate intra-polygon markers. - return false; - } - } - - if (((p1 ^ p2) & size_t (1)) == 0) { - if (requires_different_layers) { - return false; - } else if ((p1 & size_t (1)) != 0) { - // edges on the same layer are only considered on first layer. - // Reasoning: this case happens when "intruder" polygons are put on layer 1 - // while "subject" polygons are put on layer 0. We don't want "intruders" - // to generate inter-polygon markers between them. - return false; - } - } - - return true; -} - -void -Edge2EdgeCheckBase::add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2) -{ - if (m_pass == 0) { - - // Overlap or inside checks require input from different layers - if (edges_considered (m_different_polygons, m_requires_different_layers, p1, p2)) { - - // ensure that the first check argument is of layer 1 and the second of - // layer 2 (unless both are of the same layer) - int l1 = int (p1 & size_t (1)); - int l2 = int (p2 & size_t (1)); - - if (l1 > l2) { - std::swap (o1, o2); - std::swap (p1, p2); - } - - db::EdgePair ep; - if (mp_check->check (*o1, *o2, &ep)) { - - ep.set_symmetric (m_symmetric_edges); - - // found a violation: store inside the local buffer for now. In the second - // pass we will eliminate those which are shielded completely (with shielding) - // and/or compute the negative edges. - size_t n = m_ep.size (); - - m_ep.push_back (ep); - m_ep_intra_polygon.push_back (p1 == p2); - - m_e2ep.insert (std::make_pair (std::make_pair (*o1, p1), n * 2)); - m_e2ep.insert (std::make_pair (std::make_pair (*o2, p2), n * 2 + 1)); - - if (m_has_negative_edge_output) { - m_pseudo_edges.insert (std::make_pair (db::Edge (ep.first ().p1 (), ep.second ().p2 ()), p1)); - m_pseudo_edges.insert (std::make_pair (db::Edge (ep.second ().p1 (), ep.first ().p2 ()), p1)); - if (p1 != p2) { - m_pseudo_edges.insert (std::make_pair (db::Edge (ep.first ().p1 (), ep.second ().p2 ()), p2)); - m_pseudo_edges.insert (std::make_pair (db::Edge (ep.second ().p1 (), ep.first ().p2 ()), p2)); - } - } - - } - - } - - } else { - - // set the discarded flags for shielded output - if (m_with_shielding) { - - // a simple (complete) shielding implementation which is based on the - // assumption that shielding is relevant as soon as a foreign edge cuts through - // both of the edge pair's connecting edges. - - // TODO: this implementation does not take into account the nature of the - // EdgePair - because of "whole_edge" it may not reflect the part actually - // violating the distance. - - std::vector n1, n2; - - for (unsigned int p = 0; p < 2; ++p) { - - std::pair k (*o1, p1); - for (std::multimap, size_t>::const_iterator i = m_e2ep.find (k); i != m_e2ep.end () && i->first == k; ++i) { - size_t n = i->second / 2; - if (n < m_first_pseudo && ! m_ep_discarded [n]) { - n1.push_back (n); - } - } - - std::sort (n1.begin (), n1.end ()); - - std::swap (o1, o2); - std::swap (p1, p2); - n1.swap (n2); - - } - - for (unsigned int p = 0; p < 2; ++p) { - - std::vector nn; - std::set_difference (n1.begin (), n1.end (), n2.begin (), n2.end (), std::back_inserter (nn)); - - for (std::vector::const_iterator i = nn.begin (); i != nn.end (); ++i) { - db::EdgePair ep = m_ep [*i].normalized (); - if (shields (ep, *o2)) { - m_ep_discarded [*i] = true; - } - } - - std::swap (o1, o2); - std::swap (p1, p2); - n1.swap (n2); - - } - - } - - // for negative output edges are cancelled by short interactions perpendicular to them - // For this we have generated "pseudo edges" running along the sides of the original violation. We now check a real - // edge vs. a pseudo edge with the same conditions as the normal interaction and add them to the results. In the - // negative case this means we cancel a real edge. - - if (m_has_negative_edge_output && - (m_pseudo_edges.find (std::make_pair (*o1, p1)) != m_pseudo_edges.end ()) != (m_pseudo_edges.find (std::make_pair (*o2, p2)) != m_pseudo_edges.end ())) { - - // Overlap or inside checks require input from different layers - if (edges_considered (m_different_polygons, m_requires_different_layers, p1, p2)) { - - // ensure that the first check argument is of layer 1 and the second of - // layer 2 (unless both are of the same layer) - int l1 = int (p1 & size_t (1)); - int l2 = int (p2 & size_t (1)); - - if (l1 > l2) { - std::swap (o1, o2); - std::swap (p1, p2); - } - - db::EdgePair ep; - if (mp_check->check (*o1, *o2, &ep)) { - - size_t n = m_ep.size (); - - m_ep.push_back (ep); - m_ep_intra_polygon.push_back (p1 == p2); // not really required, but there for consistency - - m_e2ep.insert (std::make_pair (std::make_pair (*o1, p1), n * 2)); - m_e2ep.insert (std::make_pair (std::make_pair (*o2, p2), n * 2 + 1)); - - } - - } - - } - - } - -} - -/** - * @brief Gets a value indicating whether the check requires different layers - */ -bool -Edge2EdgeCheckBase::requires_different_layers () const -{ - return m_requires_different_layers; -} - -/** - * @brief Sets a value indicating whether the check requires different layers - */ -void -Edge2EdgeCheckBase::set_requires_different_layers (bool f) -{ - m_requires_different_layers = f; -} - -/** - * @brief Gets a value indicating whether the check requires different layers - */ -bool -Edge2EdgeCheckBase::different_polygons () const -{ - return m_different_polygons; -} - -/** - * @brief Sets a value indicating whether the check requires different layers - */ -void -Edge2EdgeCheckBase::set_different_polygons (bool f) -{ - m_different_polygons = f; -} - -/** - * @brief Gets the distance value - */ -EdgeRelationFilter::distance_type -Edge2EdgeCheckBase::distance () const -{ - return m_distance; -} - -// ------------------------------------------------------------------------------------- -// Poly2PolyCheckBase implementation - -template -poly2poly_check::poly2poly_check (Edge2EdgeCheckBase &output) - : mp_output (& output) -{ - // .. nothing yet .. -} - -template -void -poly2poly_check::finish (const PolygonType *o, size_t p) -{ - enter (*o, p); -} - -static size_t vertices (const db::Polygon &p) -{ - return p.vertices (); -} - -static size_t vertices (const db::PolygonRef &p) -{ - return p.obj ().vertices (); -} - -template -void -poly2poly_check::enter (const PolygonType &o, size_t p) -{ - if (! mp_output->requires_different_layers () && ! mp_output->different_polygons ()) { - - // finally we check the polygons vs. itself for checks involving intra-polygon interactions - - m_scanner.clear (); - m_scanner.reserve (vertices (o)); - - m_edges.clear (); - m_edges.reserve (vertices (o)); - - for (typename PolygonType::polygon_edge_iterator e = o.begin_edge (); ! e.at_end (); ++e) { - m_edges.push_back (*e); - m_scanner.insert (& m_edges.back (), p); - } - - mp_output->feed_pseudo_edges (m_scanner); - - tl_assert (m_edges.size () == vertices (o)); - - m_scanner.process (*mp_output, mp_output->distance (), db::box_convert ()); - - } -} - -template -void -poly2poly_check::add (const PolygonType *o1, size_t p1, const PolygonType *o2, size_t p2) -{ - enter (*o1, p1, *o2, p2); -} - -static bool interact (const db::Box &box, const db::Edge &e) -{ - if (! e.bbox ().touches (box)) { - return false; - } else if (e.is_ortho ()) { - return true; - } else { - return e.clipped (box).first; - } -} - -template -void -poly2poly_check::enter (const PolygonType &o1, size_t p1, const PolygonType &o2, size_t p2) -{ - if (p1 != p2 && (! mp_output->requires_different_layers () || ((p1 ^ p2) & 1) != 0)) { - - bool take_all = mp_output->has_negative_edge_output (); - - db::Box common_box; - if (! take_all) { - db::Vector e (mp_output->distance (), mp_output->distance ()); - common_box = o1.box ().enlarged (e) & o2.box ().enlarged (e); - if (common_box.empty ()) { - return; - } - } - - m_scanner.clear (); - m_scanner.reserve (vertices (o1) + vertices (o2)); - - m_edges.clear (); - m_edges.reserve (vertices (o1) + vertices (o2)); - - bool any_o1 = false, any_o2 = false; - - for (typename PolygonType::polygon_edge_iterator e = o1.begin_edge (); ! e.at_end (); ++e) { - if (take_all || interact (common_box, *e)) { - m_edges.push_back (*e); - m_scanner.insert (& m_edges.back (), p1); - any_o1 = true; - } - } - - for (typename PolygonType::polygon_edge_iterator e = o2.begin_edge (); ! e.at_end (); ++e) { - if (take_all || interact (common_box, *e)) { - m_edges.push_back (*e); - m_scanner.insert (& m_edges.back (), p2); - any_o2 = true; - } - } - - if (! take_all && (! any_o1 || ! any_o2)) { - return; - } - - mp_output->feed_pseudo_edges (m_scanner); - - // temporarily disable intra-polygon check in that step .. we do that later in finish() - // if required (#650). - bool no_intra = mp_output->different_polygons (); - mp_output->set_different_polygons (true); - - m_scanner.process (*mp_output, mp_output->distance (), db::box_convert ()); - - mp_output->set_different_polygons (no_intra); - - } -} - -// explicit instantiations -template class poly2poly_check; -template class poly2poly_check; - // ------------------------------------------------------------------------------------- // RegionPerimeterFilter implementation @@ -859,7 +370,7 @@ SinglePolygonCheck::process (const db::Polygon &polygon, std::vector poly_check (edge_check); do { - poly_check.enter (polygon, 0); + poly_check.single (polygon, 0); } while (edge_check.prepare_next_pass ()); res.insert (res.end (), result.begin (), result.end ()); @@ -967,219 +478,4 @@ HullExtractionProcessor::process (const db::Polygon &poly, std::vector -region_to_edge_interaction_filter_base::region_to_edge_interaction_filter_base (bool inverse, bool get_all) - : m_inverse (inverse), m_get_all (get_all) -{ - // .. nothing yet .. -} - -template -void -region_to_edge_interaction_filter_base::preset (const OutputType *s) -{ - m_seen.insert (s); -} - -template -void -region_to_edge_interaction_filter_base::add (const PolygonType *p, size_t, const EdgeType *e, size_t) -{ - const OutputType *o = 0; - tl::select (o, p, e); - - if (m_get_all || (m_seen.find (o) == m_seen.end ()) != m_inverse) { - - // A polygon and an edge interact if the edge is either inside completely - // of at least one edge of the polygon intersects with the edge - bool interacts = false; - if (p->box ().contains (e->p1 ()) && db::inside_poly (p->begin_edge (), e->p1 ()) >= 0) { - interacts = true; - } else { - for (typename PolygonType::polygon_edge_iterator pe = p->begin_edge (); ! pe.at_end () && ! interacts; ++pe) { - if ((*pe).intersect (*e)) { - interacts = true; - } - } - } - - if (interacts) { - if (m_inverse) { - m_seen.erase (o); - } else { - if (! m_get_all) { - m_seen.insert (o); - } - put (*o); - } - } - - } -} - -template -void -region_to_edge_interaction_filter_base::fill_output () -{ - for (typename std::set::const_iterator s = m_seen.begin (); s != m_seen.end (); ++s) { - put (**s); - } -} - -// explicit instantiations -template class region_to_edge_interaction_filter_base; -template class region_to_edge_interaction_filter_base; -template class region_to_edge_interaction_filter_base; -template class region_to_edge_interaction_filter_base; - -// ------------------------------------------------------------------------------------- -// RegionToTextInteractionFilterBase implementation - -template -region_to_text_interaction_filter_base::region_to_text_interaction_filter_base (bool inverse, bool get_all) - : m_inverse (inverse), m_get_all (get_all) -{ - // .. nothing yet .. -} - -template -void -region_to_text_interaction_filter_base::preset (const OutputType *s) -{ - m_seen.insert (s); -} - -template -void -region_to_text_interaction_filter_base::add (const PolygonType *p, size_t, const TextType *t, size_t) -{ - const OutputType *o = 0; - tl::select (o, p, t); - - if (m_get_all || (m_seen.find (o) == m_seen.end ()) != m_inverse) { - - // A polygon and an text interact if the text is either inside completely - // of at least one text of the polygon intersects with the text - db::Point pt = db::box_convert () (*t).p1 (); - if (p->box ().contains (pt) && db::inside_poly (p->begin_edge (), pt) >= 0) { - if (m_inverse) { - m_seen.erase (o); - } else { - if (! m_get_all) { - m_seen.insert (o); - } - put (*o); - } - } - - } -} - -template -void -region_to_text_interaction_filter_base::fill_output () -{ - for (typename std::set::const_iterator s = m_seen.begin (); s != m_seen.end (); ++s) { - put (**s); - } -} - -// explicit instantiations -template class region_to_text_interaction_filter_base; -template class region_to_text_interaction_filter_base; -template class region_to_text_interaction_filter_base; -template class region_to_text_interaction_filter_base; -template class region_to_text_interaction_filter_base; - -// ------------------------------------------------------------------------------------- -// Polygon snapping - -db::Polygon -snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord gy, std::vector &heap) -{ - db::Polygon pnew; - - for (size_t i = 0; i < poly.holes () + 1; ++i) { - - heap.clear (); - - db::Polygon::polygon_contour_iterator b, e; - - if (i == 0) { - b = poly.begin_hull (); - e = poly.end_hull (); - } else { - b = poly.begin_hole ((unsigned int) (i - 1)); - e = poly.end_hole ((unsigned int) (i - 1)); - } - - for (db::Polygon::polygon_contour_iterator pt = b; pt != e; ++pt) { - heap.push_back (db::Point (snap_to_grid ((*pt).x (), gx), snap_to_grid ((*pt).y (), gy))); - } - - if (i == 0) { - pnew.assign_hull (heap.begin (), heap.end ()); - } else { - pnew.insert_hole (heap.begin (), heap.end ()); - } - - } - - return pnew; -} - -db::Polygon -scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy, std::vector &heap) -{ - db::Polygon pnew; - - int64_t dgx = int64_t (gx) * int64_t (dx); - int64_t dgy = int64_t (gy) * int64_t (dy); - - for (size_t i = 0; i < poly.holes () + 1; ++i) { - - heap.clear (); - - db::Polygon::polygon_contour_iterator b, e; - - if (i == 0) { - b = poly.begin_hull (); - e = poly.end_hull (); - } else { - b = poly.begin_hole ((unsigned int) (i - 1)); - e = poly.end_hole ((unsigned int) (i - 1)); - } - - for (db::Polygon::polygon_contour_iterator pt = b; pt != e; ++pt) { - int64_t x = snap_to_grid (int64_t ((*pt).x ()) * mx + int64_t (ox), dgx) / int64_t (dx); - int64_t y = snap_to_grid (int64_t ((*pt).y ()) * my + int64_t (oy), dgy) / int64_t (dy); - heap.push_back (db::Point (db::Coord (x), db::Coord (y))); - } - - if (i == 0) { - pnew.assign_hull (heap.begin (), heap.end ()); - } else { - pnew.insert_hole (heap.begin (), heap.end ()); - } - - } - - return pnew; -} - -db::Vector -scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy) -{ - int64_t dgx = int64_t (gx) * int64_t (dx); - int64_t dgy = int64_t (gy) * int64_t (dy); - - int64_t x = snap_to_grid (int64_t (v.x ()) * mx + int64_t (ox), dgx) / int64_t (dx); - int64_t y = snap_to_grid (int64_t (v.y ()) * my + int64_t (oy), dgy) / int64_t (dy); - - return db::Vector (db::Coord (x), db::Coord (y)); -} - } diff --git a/src/db/db/dbRegionUtils.h b/src/db/db/dbRegionUtils.h index 62fca5cd3..61eb467bf 100644 --- a/src/db/db/dbRegionUtils.h +++ b/src/db/db/dbRegionUtils.h @@ -583,298 +583,6 @@ public: virtual bool result_must_not_be_merged () const { return false; } }; -/** - * @brief A helper class for the DRC functionality which acts as an edge pair receiver - */ -class DB_PUBLIC Edge2EdgeCheckBase - : public db::box_scanner_receiver -{ -public: - Edge2EdgeCheckBase (const EdgeRelationFilter &check, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges); - - /** - * @brief Call this to initiate a new pass until the return value is false - */ - bool prepare_next_pass (); - - /** - * @brief Before the scanner is run, this method must be called to feed additional edges into the scanner - * (required for negative edge output - cancellation of perpendicular edges) - */ - bool feed_pseudo_edges (db::box_scanner &scanner); - - /** - * @brief Reimplementation of the box_scanner_receiver interface - */ - void add (const db::Edge *o1, size_t p1, const db::Edge *o2, size_t p2); - - /** - * @brief Reimplementation of the box_scanner_receiver interface - */ - void finish (const Edge *o, size_t); - - /** - * @brief Gets a value indicating whether the check requires different layers - */ - bool requires_different_layers () const; - - /** - * @brief Sets a value indicating whether the check requires different layers - */ - void set_requires_different_layers (bool f); - - /** - * @brief Gets a value indicating whether the check requires different layers - */ - bool different_polygons () const; - - /** - * @brief Sets a value indicating whether the check requires different layers - */ - void set_different_polygons (bool f); - - /** - * @brief Sets a flag indicating that this class wants negative edge output - */ - void set_has_negative_edge_output (bool f) - { - m_has_negative_edge_output = f; - } - - /** - * @brief Gets a flag indicating that this class wants negative edge output - */ - bool has_negative_edge_output () const - { - return m_has_negative_edge_output; - } - - /** - * @brief Sets a flag indicating that this class wants normal edge pair output - */ - void set_has_edge_pair_output (bool f) - { - m_has_edge_pair_output = f; - } - - /** - * @brief Gets a flag indicating that this class wants normal edge pair output - */ - bool has_edge_pair_output () const - { - return m_has_edge_pair_output; - } - - /** - * @brief Gets the distance value - */ - EdgeRelationFilter::distance_type distance () const; - -protected: - /** - * @brief Normal edge pair output (violations) - */ - virtual void put (const db::EdgePair & /*edge*/, bool /*intra-polygon*/) const { } - - /** - * @brief Negative edge output - */ - virtual void put_negative (const db::Edge & /*edge*/, int /*layer*/) const { } - -private: - const EdgeRelationFilter *mp_check; - bool m_requires_different_layers; - bool m_different_polygons; - EdgeRelationFilter::distance_type m_distance; - std::vector m_ep; - std::multimap, size_t> m_e2ep; - std::set > m_pseudo_edges; - size_t m_first_pseudo; - std::vector m_ep_discarded, m_ep_intra_polygon; - bool m_with_shielding; - bool m_symmetric_edges; - bool m_has_edge_pair_output; - bool m_has_negative_edge_output; - unsigned int m_pass; -}; - -/** - * @brief A helper class for the DRC functionality - * - * This class implements the edge-to-edge part of the polygon DRC. - */ -template -class DB_PUBLIC_TEMPLATE edge2edge_check - : public Edge2EdgeCheckBase -{ -public: - edge2edge_check (const EdgeRelationFilter &check, Output &output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) - : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, symmetric_edges), mp_output_inter (&output), mp_output_intra (0) - { - // .. nothing yet .. - } - - edge2edge_check (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) - : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, symmetric_edges), mp_output_inter (&output_inter), mp_output_intra (&output_intra) - { - // .. nothing yet .. - } - -protected: - void put (const db::EdgePair &edge, bool inter_polygon) const - { - if (! inter_polygon || ! mp_output_intra) { - mp_output_inter->insert (edge); - } else { - mp_output_intra->insert (edge); - } - } - -private: - Output *mp_output_inter; - Output *mp_output_intra; -}; - -/** - * @brief A helper class for the DRC functionality - * - * This class implements the edge-to-edge part of the polygon DRC. - * This version allows delivery of the negative edges. - */ -template -class DB_PUBLIC_TEMPLATE edge2edge_check_with_negative_output - : public edge2edge_check -{ -public: - edge2edge_check_with_negative_output (const EdgeRelationFilter &check, Output &output, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) - : edge2edge_check (check, output, different_polygons, requires_different_layers, with_shielding, symmetric_edges), - mp_l1_negative_output (&l1_negative_output), - mp_l2_negative_output (&l2_negative_output) - { - edge2edge_check::set_has_negative_edge_output (true); - } - - edge2edge_check_with_negative_output (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric_edges) - : edge2edge_check (check, output_inter, output_intra, different_polygons, requires_different_layers, with_shielding, symmetric_edges), - mp_l1_negative_output (&l1_negative_output), - mp_l2_negative_output (&l2_negative_output) - { - edge2edge_check::set_has_negative_edge_output (true); - } - -protected: - void put_negative (const db::Edge &edge, int layer) const - { - if (layer == 0) { - mp_l1_negative_output->insert (edge); - } - if (layer == 1) { - mp_l2_negative_output->insert (edge); - } - } - -private: - NegativeEdgeOutput *mp_l1_negative_output, *mp_l2_negative_output; -}; - -/** - * @brief A helper class for the DRC functionality - * - * This class implements the edge-to-edge part of the polygon DRC. - * This version has only negative edge output. - */ -template -class DB_PUBLIC_TEMPLATE edge2edge_check_negative - : public Edge2EdgeCheckBase -{ -public: - edge2edge_check_negative (const EdgeRelationFilter &check, NegativeEdgeOutput &l1_negative_output, NegativeEdgeOutput &l2_negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding) - : Edge2EdgeCheckBase (check, different_polygons, requires_different_layers, with_shielding, false), - mp_l1_negative_output (&l1_negative_output), - mp_l2_negative_output (&l2_negative_output) - { - set_has_negative_edge_output (true); - set_has_edge_pair_output (false); - } - -protected: - void put_negative (const db::Edge &edge, int layer) const - { - if (layer == 0) { - mp_l1_negative_output->insert (edge); - } - if (layer == 1) { - mp_l2_negative_output->insert (edge); - } - } - -private: - NegativeEdgeOutput *mp_l1_negative_output, *mp_l2_negative_output; -}; - -/** - * @brief A helper class for the DRC functionality - * - * This class implements the edge-to-edge part of the polygon DRC. - * This version has positive or negative output. Negative output is mapped to edge pairs - * as well. - */ -template -class DB_PUBLIC_TEMPLATE edge2edge_check_negative_or_positive - : public edge2edge_check -{ -public: - edge2edge_check_negative_or_positive (const EdgeRelationFilter &check, Output &output, bool negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric) - : edge2edge_check (check, output, different_polygons, requires_different_layers, with_shielding, symmetric) - { - edge2edge_check::set_has_negative_edge_output (negative_output); - edge2edge_check::set_has_edge_pair_output (! negative_output); - } - - edge2edge_check_negative_or_positive (const EdgeRelationFilter &check, Output &output_inter, Output &output_intra, bool negative_output, bool different_polygons, bool requires_different_layers, bool with_shielding, bool symmetric) - : edge2edge_check (check, output_inter, output_intra, different_polygons, requires_different_layers, with_shielding, symmetric) - { - edge2edge_check::set_has_negative_edge_output (negative_output); - edge2edge_check::set_has_edge_pair_output (! negative_output); - } - -protected: - void put_negative (const db::Edge &edge, int layer) const - { - if (layer == 0) { - edge2edge_check::put (db::EdgePair (edge, edge.swapped_points ()), false); - } -#if 0 - // NOTE: second-input negative edge output isn't worth a lot as the second input often is not merged, hence - // the outer edges to not represent the actual contour. - if (layer == 1) { - edge2edge_check::put (db::EdgePair (edge.swapped_points (), edge), false); - } -#endif - } -}; - -/** - * @brief A helper class for the DRC functionality which acts as an edge pair receiver - */ -template -class DB_PUBLIC poly2poly_check - : public db::box_scanner_receiver -{ -public: - poly2poly_check (Edge2EdgeCheckBase &output); - - void finish (const PolygonType *o, size_t p); - void enter (const PolygonType&o, size_t p); - void add (const PolygonType *o1, size_t p1, const PolygonType *o2, size_t p2); - void enter (const PolygonType &o1, size_t p1, const PolygonType &o2, size_t p2); - -private: - db::Edge2EdgeCheckBase *mp_output; - db::box_scanner m_scanner; - std::vector m_edges; -}; - /** * @brief A class wrapping the single-polygon checks into a polygon-to-edge pair processor */ @@ -892,129 +600,6 @@ private: db::RegionCheckOptions m_options; }; -/** - * @brief A helper class for the region to edge interaction functionality - */ -template -class DB_PUBLIC region_to_edge_interaction_filter_base - : public db::box_scanner_receiver2 -{ -public: - region_to_edge_interaction_filter_base (bool inverse, bool get_all); - - void preset (const OutputType *s); - void add (const PolygonType *p, size_t, const EdgeType *e, size_t); - void fill_output (); - -protected: - virtual void put (const OutputType &s) const = 0; - -private: - std::set m_seen; - bool m_inverse, m_get_all; -}; - -/** - * @brief A helper class for the region to edge interaction functionality - */ -template -class DB_PUBLIC_TEMPLATE region_to_edge_interaction_filter - : public region_to_edge_interaction_filter_base -{ -public: - region_to_edge_interaction_filter (OutputContainer &output, bool inverse, bool get_all = false) - : region_to_edge_interaction_filter_base (inverse, get_all), mp_output (&output) - { - // .. nothing yet .. - } - -protected: - virtual void put (const OutputType &res) const - { - mp_output->insert (res); - } - -private: - OutputContainer *mp_output; -}; - -/** - * @brief A helper class for the region to text interaction functionality - */ -template -class DB_PUBLIC region_to_text_interaction_filter_base - : public db::box_scanner_receiver2 -{ -public: - region_to_text_interaction_filter_base (bool inverse, bool get_all); - - void preset (const OutputType *s); - void add (const PolygonType *p, size_t, const TextType *e, size_t); - void fill_output (); - -protected: - virtual void put (const OutputType &s) const = 0; - -private: - std::set m_seen; - bool m_inverse, m_get_all; -}; - -/** - * @brief A helper class for the region to text interaction functionality - */ -template -class DB_PUBLIC_TEMPLATE region_to_text_interaction_filter - : public region_to_text_interaction_filter_base -{ -public: - region_to_text_interaction_filter (OutputContainer &output, bool inverse, bool get_all = false) - : region_to_text_interaction_filter_base (inverse, get_all), mp_output (&output) - { - // .. nothing yet .. - } - -protected: - virtual void put (const OutputType &poly) const - { - mp_output->insert (poly); - } - -private: - OutputContainer *mp_output; -}; - -template -static inline C snap_to_grid (C c, C g) -{ - // This form of snapping always snaps g/2 to right/top. - if (c < 0) { - c = -g * ((-c + (g - 1) / 2) / g); - } else { - c = g * ((c + g / 2) / g); - } - return c; -} - -/** - * @brief Snaps a polygon to the given grid - * Heap is a vector of points reused for the point list - */ -DB_PUBLIC db::Polygon snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord gy, std::vector &heap); - -/** - * @brief Scales and snaps a polygon to the given grid - * Heap is a vector of points reused for the point list - * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. - */ -DB_PUBLIC db::Polygon scaled_and_snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy, std::vector &heap); - -/** - * @brief Scales and snaps a vector to the given grid - * The coordinate transformation is q = ((p * m + o) snap (g * d)) / d. - */ -DB_PUBLIC db::Vector scaled_and_snapped_vector (const db::Vector &v, db::Coord gx, db::Coord mx, db::Coord dx, db::Coord ox, db::Coord gy, db::Coord my, db::Coord dy, db::Coord oy); - } // namespace db #endif diff --git a/src/db/unit_tests/dbRegionUtilsTests.cc b/src/db/unit_tests/dbRegionCheckUtilsTests.cc similarity index 94% rename from src/db/unit_tests/dbRegionUtilsTests.cc rename to src/db/unit_tests/dbRegionCheckUtilsTests.cc index c27a41e3d..cfad79fdd 100644 --- a/src/db/unit_tests/dbRegionUtilsTests.cc +++ b/src/db/unit_tests/dbRegionCheckUtilsTests.cc @@ -25,7 +25,7 @@ #include "tlUnitTest.h" #include "tlStringEx.h" -#include "dbRegionUtils.h" +#include "dbRegionCheckUtils.h" TEST(1_SimpleLShape) { @@ -52,7 +52,7 @@ TEST(1_SimpleLShape) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(0,0;0,1000)|(1000,1000;1000,0),(2000,1000;1000,1000)|(1000,2000;2000,2000)"); @@ -85,7 +85,7 @@ TEST(1s_SimpleLShape) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(0,0;0,1000)/(1000,1000;1000,0),(1000,2000;2000,2000)/(2000,1000;1000,1000)"); @@ -118,7 +118,7 @@ TEST(2_SimpleLWithBigPart) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(0,0;0,1000)|(1000,1000;1000,0)"); @@ -153,7 +153,7 @@ TEST(3_SimpleTWithBigPart) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(0,0;0,1000)|(1000,1000;1000,0),(0,2500;0,3500)|(1000,3500;1000,2500)"); @@ -188,7 +188,7 @@ TEST(4_SimpleNotch) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(1000,1000;2000,1000)|(2000,2000;1000,2000)"); @@ -225,7 +225,7 @@ TEST(5_LShapeNotch) do { // single polygon check - poly_check.enter (poly, 0); + poly_check.single (poly, 0); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(1500,500;2000,500)|(2000,1500;1500,1500),(1500,1500;1500,2500)|(500,2500;500,1500)"); @@ -264,14 +264,12 @@ TEST(6_SeparationLvsBox) db::Polygon poly2; poly2.assign_hull (pts2, pts2 + sizeof (pts2) / sizeof (pts2[0])); - db::box_scanner scanner; - scanner.insert (&poly1, 0); // layer 0 - scanner.insert (&poly2, 1); // layer 1 - db::poly2poly_check poly_check (e2e); + poly_check.enter (poly1, 0); // layer 0 + poly_check.enter (poly2, 1); // layer 1 do { - scanner.process (poly_check, er.distance (), db::box_convert ()); + poly_check.process (); } while (e2e.prepare_next_pass ()); EXPECT_EQ (tl::to_string (ep), "(1000,1000;1000,0)/(2000,0;2000,1000),(3000,2000;2000,2000)/(2000,1000;3000,1000)"); diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index 1fc2fbe1d..8601dfb65 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -10,7 +10,7 @@ SOURCES = \ dbCompoundOperationTests.cc \ dbFillToolTests.cc \ dbRecursiveInstanceIteratorTests.cc \ - dbRegionUtilsTests.cc \ + dbRegionCheckUtilsTests.cc \ dbUtilsTests.cc \ dbWriterTools.cc \ dbLoadLayoutOptionsTests.cc \ From 2fd9401013dea16c17f7c704f4b5b47eee20761f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Nov 2021 23:30:15 +0100 Subject: [PATCH 27/52] Fixed some doc problems --- src/drc/drc/built-in-macros/_drc_layer.rb | 11 ++++++----- src/lay/lay/doc/about/drc_ref_layer.xml | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 31bc4b4d2..182886121 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -3547,7 +3547,7 @@ CODE # method will only report space violations to other polygons. \separation is a two-layer # space check where space is checked against polygons of another layer. # - # Like for the \width method, merged semantics applies. + # As for the other DRC methods, merged semantics applies. # # Distance values can be given as floating-point values (in micron) or integer values (in # database units). To explicitly specify the unit, use the unit denominators. @@ -3632,6 +3632,7 @@ CODE # will also trigger an error while for \space it will not. # # As for the other DRC methods, merged semantics applies. + # # Distance values can be given as floating-point values (in micron) or integer values (in # database units). To explicitly specify the unit, use the unit denominators. # @@ -3739,8 +3740,8 @@ CODE # the orientation of the edges matters: only edges which run back to back with their # inside side pointing towards each other are checked for distance. # - # As for the other DRC methods, merged semantics applies. The options available - # are the same than for \width. + # As for the other DRC methods, merged semantics applies. + # # Distance values can be given as floating-point values (in micron) or integer values (in # database units). To explicitly specify the unit, use the unit denominators. # @@ -3779,8 +3780,8 @@ CODE # the orientation of the edges matters and only edges looking into the same direction # are checked. # - # As for the other DRC methods, merged semantics applies. The options available - # are the same than for \width. + # As for the other DRC methods, merged semantics applies. + # # Distance values can be given as floating-point values (in micron) or integer values (in # database units). To explicitly specify the unit, use the unit denominators. # diff --git a/src/lay/lay/doc/about/drc_ref_layer.xml b/src/lay/lay/doc/about/drc_ref_layer.xml index 651945346..b86cc8e19 100644 --- a/src/lay/lay/doc/about/drc_ref_layer.xml +++ b/src/lay/lay/doc/about/drc_ref_layer.xml @@ -806,8 +806,8 @@ The enclosing method can be applied to both edge or polygon layers. On edge laye the orientation of the edges matters and only edges looking into the same direction are checked.

-As for the other DRC methods, merged semantics applies. The options available -are the same than for width. +As for the other DRC methods, merged semantics applies. +

Distance values can be given as floating-point values (in micron) or integer values (in database units). To explicitly specify the unit, use the unit denominators.

@@ -2046,8 +2046,8 @@ The overlap method can be applied to both edge or polygon layers. On edge layers the orientation of the edges matters: only edges which run back to back with their inside side pointing towards each other are checked for distance.

-As for the other DRC methods, merged semantics applies. The options available -are the same than for width. +As for the other DRC methods, merged semantics applies. +

Distance values can be given as floating-point values (in micron) or integer values (in database units). To explicitly specify the unit, use the unit denominators.

@@ -2569,6 +2569,7 @@ layers touch are also reported. More specifically, the case of zero spacing will also trigger an error while for space it will not.

As for the other DRC methods, merged semantics applies. +

Distance values can be given as floating-point values (in micron) or integer values (in database units). To explicitly specify the unit, use the unit denominators.

@@ -2789,7 +2790,7 @@ The notch method is similar, but will only report self-spac method will only report space violations to other polygons. separation is a two-layer space check where space is checked against polygons of another layer.

-Like for the width method, merged semantics applies. +As for the other DRC methods, merged semantics applies.

Distance values can be given as floating-point values (in micron) or integer values (in database units). To explicitly specify the unit, use the unit denominators. From 4a44329e381c53c711d544c9affaddc402635c82 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Nov 2021 23:18:01 +0100 Subject: [PATCH 28/52] Enabling 'enclosed' as a (sometimes) more efficient way of implementing an enclosing check. --- scripts/create_drc_samples.rb | 16 ++++ src/db/db/gsiDeclDbCompoundOperation.cc | 10 +++ src/db/db/gsiDeclDbEdges.cc | 4 +- src/db/db/gsiDeclDbRegion.cc | 3 +- .../drc/built-in-macros/_drc_complex_ops.rb | 2 +- .../built-in-macros/_drc_cop_integration.rb | 79 ++++++++++++++++++ src/drc/drc/built-in-macros/_drc_engine.rb | 1 + src/drc/drc/built-in-macros/_drc_layer.rb | 45 ++++++++-- src/lay/lay/doc/about/drc_ref_global.xml | 79 ++++++++++++++++++ src/lay/lay/doc/about/drc_ref_layer.xml | 43 +++++++++- src/lay/lay/doc/images/drc_encd1.png | Bin 0 -> 9333 bytes src/lay/lay/doc/images/drc_encd1u.png | Bin 0 -> 9446 bytes src/lay/lay/doc/images/drc_encd2.png | Bin 0 -> 9332 bytes src/lay/lay/doc/images/drc_encd2u.png | Bin 0 -> 9503 bytes src/lay/lay/layDRCLVSHelpResources.qrc | 4 + 15 files changed, 275 insertions(+), 11 deletions(-) create mode 100644 src/lay/lay/doc/images/drc_encd1.png create mode 100644 src/lay/lay/doc/images/drc_encd1u.png create mode 100644 src/lay/lay/doc/images/drc_encd2.png create mode 100644 src/lay/lay/doc/images/drc_encd2u.png diff --git a/scripts/create_drc_samples.rb b/scripts/create_drc_samples.rb index f7963be5d..2f0da729b 100644 --- a/scripts/create_drc_samples.rb +++ b/scripts/create_drc_samples.rb @@ -452,6 +452,22 @@ run_demo gen, "input1.drc(enclosing(input2) < 2.0.um)", "drc_enc1u.png" run_demo gen, "input1.drc(enclosing(input2,\n"+ " projection) < 2.0.um)", "drc_enc2u.png" +class Gen + def produce(s1, s2) + s1.insert(RBA::Box::new(3000, 0, 6000, 6000)) + s2.insert(RBA::Box::new(0, 1000, 6000, 7000)) + end +end + +gen = Gen::new + +run_demo gen, "input1.enclosed(input2, 2.0.um)", "drc_encd1.png" +run_demo gen, "input1.enclosed(input2, 2.0.um, projection)", "drc_encd2.png" + +run_demo gen, "input1.drc(enclosed(input2) < 2.0.um)", "drc_encd1u.png" +run_demo gen, "input1.drc(enclosed(input2,\n"+ + " projection) < 2.0.um)", "drc_encd2u.png" + class Gen def produce(s1, s2) diff --git a/src/db/db/gsiDeclDbCompoundOperation.cc b/src/db/db/gsiDeclDbCompoundOperation.cc index 9f2759c1a..9fbdf518f 100644 --- a/src/db/db/gsiDeclDbCompoundOperation.cc +++ b/src/db/db/gsiDeclDbCompoundOperation.cc @@ -460,6 +460,11 @@ static db::CompoundRegionOperationNode *new_enclosing_check (db::CompoundRegionO return new_check_node (other, db::OverlapRelation, true, d, whole_edges, metrics, ignore_angle, min_projection, max_projection, shielded, opposite_filter, rect_filter, negative); } +static db::CompoundRegionOperationNode *new_enclosed_check (db::CompoundRegionOperationNode *other, db::Coord d, bool whole_edges, db::metrics_type metrics, const tl::Variant &ignore_angle, const tl::Variant &min_projection, const tl::Variant &max_projection, bool shielded, db::OppositeFilter opposite_filter, db::RectFilter rect_filter, bool negative) +{ + return new_check_node (other, db::InsideRelation, true, d, whole_edges, metrics, ignore_angle, min_projection, max_projection, shielded, opposite_filter, rect_filter, negative); +} + static db::CompoundRegionOperationNode *new_perimeter_filter (db::CompoundRegionOperationNode *input, bool inverse, db::coord_traits::perimeter_type pmin, db::coord_traits::perimeter_type pmax) { check_non_null (input, "input"); @@ -673,6 +678,11 @@ Class decl_CompoundRegionOperationNode ("db", " gsi::constructor ("new_enclosing_check", &new_enclosing_check, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max."), gsi::arg ("shielded", true), gsi::arg ("opposite_filter", db::NoOppositeFilter, "NoOppositeFilter"), gsi::arg ("rect_filter", db::NoRectFilter, "NoRectFilter"), gsi::arg ("negative", false), "@brief Creates a node providing an inside (enclosure) check.\n" ) + + gsi::constructor ("new_enclosed_check", &new_enclosed_check, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max."), gsi::arg ("shielded", true), gsi::arg ("opposite_filter", db::NoOppositeFilter, "NoOppositeFilter"), gsi::arg ("rect_filter", db::NoRectFilter, "NoRectFilter"), gsi::arg ("negative", false), + "@brief Creates a node providing an enclosed (secondary enclosing primary) check.\n" + "\n" + "This method has been added in version 0.27.5.\n" + ) + gsi::constructor ("new_perimeter_filter", &new_perimeter_filter, gsi::arg ("input"), gsi::arg ("inverse", false), gsi::arg ("pmin", 0), gsi::arg ("pmax", std::numeric_limits::perimeter_type>::max (), "max"), "@brief Creates a node filtering the input by perimeter.\n" "This node renders the input if the perimeter is between pmin and pmax (exclusively). If 'inverse' is set to true, the " diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 84a0c9f6d..e302557cf 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -1160,7 +1160,7 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "The projected length must be larger or equal to \"min_projection\" and less than \"max_projection\". " "If you don't want to specify one threshold, pass nil to the respective value.\n" ) + - method_ext ("inside_check", &inside2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), + method_ext ("inside_check|enclosed_check", &inside2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), "@brief Performs an inside check with options\n" "@param d The minimum distance for which the edges are checked\n" "@param other The other edge collection against which to check\n" @@ -1186,6 +1186,8 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "It is sufficient if the projection of one edge on the other matches the specified condition. " "The projected length must be larger or equal to \"min_projection\" and less than \"max_projection\". " "If you don't want to specify one threshold, pass nil to the respective value.\n" + "\n" + "The 'enclosed_check' alias was introduced in version 0.27.5.\n" ) + method_ext ("enclosing_check", &enclosing2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), "@brief Performs an enclosing check with options\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index f7e560d39..621235460 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -2591,7 +2591,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "The 'shielded', 'negative', 'not_opposite' and 'rect_sides' options have been introduced in version 0.27." ) + - method_ext ("inside_check", &inside2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::metrics_type::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), gsi::arg ("shielded", true), gsi::arg ("opposite_filter", db::NoOppositeFilter, "NoOppositeFilter"), gsi::arg ("rect_filter", db::NoRectFilter, "NoRectFilter"), gsi::arg ("negative", false), + method_ext ("inside_check|enclosed_check", &inside2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::metrics_type::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), gsi::arg ("shielded", true), gsi::arg ("opposite_filter", db::NoOppositeFilter, "NoOppositeFilter"), gsi::arg ("rect_filter", db::NoRectFilter, "NoRectFilter"), gsi::arg ("negative", false), "@brief Performs an inside check with options\n" "@param d The minimum distance for which the polygons are checked\n" "@param other The other region against which to check\n" @@ -2639,6 +2639,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "The 'shielded', 'negative', 'not_opposite' and 'rect_sides' options have been introduced in version 0.27. " "The interpretation of the 'negative' flag has been restriced to first-layout only output in 0.27.1.\n" + "The 'enclosed_check' alias was introduced in version 0.27.5.\n" ) + method_ext ("overlap_check", &overlap2, gsi::arg ("other"), gsi::arg ("d"), gsi::arg ("whole_edges", false), gsi::arg ("metrics", db::metrics_type::Euclidian, "Euclidian"), gsi::arg ("ignore_angle", tl::Variant (), "default"), gsi::arg ("min_projection", tl::Variant (), "0"), gsi::arg ("max_projection", tl::Variant (), "max"), gsi::arg ("shielded", true), gsi::arg ("opposite_filter", db::NoOppositeFilter, "NoOppositeFilter"), gsi::arg ("rect_filter", db::NoRectFilter, "NoRectFilter"), gsi::arg ("negative", false), "@brief Performs an overlap check with options\n" diff --git a/src/drc/drc/built-in-macros/_drc_complex_ops.rb b/src/drc/drc/built-in-macros/_drc_complex_ops.rb index e0d2ea017..48b534a37 100644 --- a/src/drc/drc/built-in-macros/_drc_complex_ops.rb +++ b/src/drc/drc/built-in-macros/_drc_complex_ops.rb @@ -1888,7 +1888,7 @@ class DRCOpNodeCheck < DRCOpNodeWithCompare factory = { :width => :new_width_check, :space => :new_space_check, :notch => :new_notch_check, :separation => :new_separation_check, :isolated => :new_isolated_check, :overlap => :new_overlap_check, - :enclosing => :new_enclosing_check }[self.check] + :enclosing => :new_enclosing_check, :enclosed => :new_enclosed_check }[self.check] oargs = [] if self.other diff --git a/src/drc/drc/built-in-macros/_drc_cop_integration.rb b/src/drc/drc/built-in-macros/_drc_cop_integration.rb index 58b5518a4..e299350f7 100644 --- a/src/drc/drc/built-in-macros/_drc_cop_integration.rb +++ b/src/drc/drc/built-in-macros/_drc_cop_integration.rb @@ -1042,6 +1042,84 @@ CODE # actual edges from the first input (see \separation for an example). # + # %DRC% + # @name enclosed + # @brief Performs an enclosing check (other enclosing layer) + # @synopsis enclosed(other [, options ]) (in conditions) + # @synopsis enclosed(layer, other [, options ]) + # + # This check verifies if the polygons of the input layer are enclosed by shapes + # of the other input layer by a certain distance. + # It has manifold options. See \Layer#width for the basic options such + # as metrics, projection and angle constraints etc. This check also features + # opposite and rectangle filtering. See \Layer#separation for details about opposite and + # rectangle error filtering. + # + # This function is essentially the reverse of \enclosing. In case of + # "enclosed", the other layer must be bigger than the primary layer. + # + # @h3 Classic mode @/h3 + # + # This function can be used in classic mode with a layer argument. In this case it + # is equivalent to "layer.enclosed" (see \Layer#enclosed). + # + # @code + # # classic "enclosed" check for < 0.2 um + # in = layer(1, 0) + # other = layer(2, 0) + # errors = enclosed(in, other, 0.2.um) + # @/code + # + # @h3 Universal DRC @/h3 + # + # The version without a first layer is intended for use within \DRC# expressions + # together with the "universal DRC" method \Layer#drc. In this case, this function needs to be + # put into a condition to specify the check constraints. The other options + # of \Layer#enclosed (e.g. metrics, projection constraints, angle limits etc.) + # apply to this version too: + # + # @code + # # universal DRC "enclosed" check for < 0.2 um + # in = layer(1, 0) + # other = layer(2, 0) + # errors = in.drc(enclosed(other) < 0.2.um) + # @/code + # + # The conditions may involve an upper and lower limit. The following examples + # illustrate the use of this function with conditions: + # + # @code + # out = in.drc(enclosed(other) < 0.2.um) + # out = in.drc(enclosed(other) <= 0.2.um) + # out = in.drc(enclosed(other) > 0.2.um) + # out = in.drc(enclosed(other) >= 0.2.um) + # out = in.drc(enclosed(other) == 0.2.um) + # out = in.drc(enclosed(other) != 0.2.um) + # out = in.drc(0.1.um <= enclosed(other) < 0.2.um) + # @/code + # + # The result of the enclosed check are edges or edge pairs forming the markers. + # These markers indicate the presence of the specified condition. + # + # With a lower and upper limit, the results are edges marking the positions on the + # primary shape where the condition is met. + # With a lower limit alone, the results are edge pairs which are formed by two identical, but opposite edges attached to + # the primary shape. Without an upper limit only, the first edge of the marker is attached to the + # primary shape while the second edge is attached to the shape of the "other" layer. + # + # @table + # @tr + # @td @img(/images/drc_encd1u.png) @/td + # @td @img(/images/drc_encd2u.png) @/td + # @/tr + # @/table + # + # When "larger than" constraints are used, this function will produce the edges from the + # first layer only. The result will still be edge pairs for consistency, but each edge pair holds one edge from + # the original polygon plus a reverse copy of that edge in the second member. Use "first_edges" to extract the + # actual edges from the first input (see \separation for an example). + # + # %DRC% # @name separation # @brief Performs a separation check @@ -1367,6 +1445,7 @@ CODE %w( enclosing + enclosed isolated notch overlap diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index f5fc0e9cf..3600355ca 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -1751,6 +1751,7 @@ CODE %w( enc enclosing + enclosed overlap sep separation diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 182886121..2cb31656a 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -3756,7 +3756,7 @@ CODE # %DRC% # @name enclosing - # @brief An enclosing check + # @brief An enclosing check (layer enclosing other_layer) # @synopsis layer.enclosing(other_layer, value [, options]) # @synopsis layer.enc(other_layer, value [, options]) # @@ -3764,8 +3764,8 @@ CODE # the \DRC framework. These variants have more options and are more intuitive to use. # See \global#enclosing for more details. # - # This method checks whether layer encloses (is bigger than) other_layer by the - # given dimension. Locations, where this is not the case will be reported in form + # This method checks whether layer encloses (is bigger than) other_layer by not less than the + # given distance value. Locations, where the distance is less will be reported in form # of edge pair error markers. # Locations, where both edges coincide will be reported as errors as well. Formally # such locations form an enclosure with a distance of 0. Locations, where other_layer @@ -3794,13 +3794,48 @@ CODE # @/tr # @/table - %w(width space overlap enclosing separation isolated notch).each do |f| + # %DRC% + # @name enclosed + # @brief An enclosing check (other_layer enclosing layer) + # @synopsis layer.enclosed(other_layer, value [, options]) + # + # @b Note: @/b "enclosed" is available as operators for the "universal DRC" function \drc within + # the \DRC framework. These variants have more options and are more intuitive to use. + # See \global#enclosed for more details. + # + # This method checks whether layer is enclosed by (is inside of) other_layer by not less than the + # given distance value. Locations, where the distance is less will be reported in form + # of edge pair error markers. + # Locations, where both edges coincide will be reported as errors as well. Formally + # such locations form an enclosure with a distance of 0. Locations, where other_layer + # is inside layer will not be reported as errors. Such regions can be detected + # by \inside or a boolean "not" operation. + # + # The options are the same as for \separation. + # + # This method is available for edge and polygon layers. + # + # As for the other DRC methods, merged semantics applies. + # + # Distance values can be given as floating-point values (in micron) or integer values (in + # database units). To explicitly specify the unit, use the unit denominators. + # + # The following images show the effect of two enclosed checks (red: input1, blue: input2): + # + # @table + # @tr + # @td @img(/images/drc_encd1.png) @/td + # @td @img(/images/drc_encd2.png) @/td + # @/tr + # @/table + + %w(width space overlap enclosing enclosed separation isolated notch).each do |f| eval <<"CODE" def #{f}(*args) @engine._context("#{f}") do - if :#{f} == :width || :#{f} == :space || :#{f} == :overlap || :#{f} == :enclosing || :#{f} == :separation + if :#{f} == :width || :#{f} == :space || :#{f} == :overlap || :#{f} == :enclosed || :#{f} == :enclosing || :#{f} == :separation requires_edges_or_region else requires_region diff --git a/src/lay/lay/doc/about/drc_ref_global.xml b/src/lay/lay/doc/about/drc_ref_global.xml index 96c622861..34d3d7f07 100644 --- a/src/lay/lay/doc/about/drc_ref_global.xml +++ b/src/lay/lay/doc/about/drc_ref_global.xml @@ -536,6 +536,85 @@ See Source#edges for a description

"enc" is the short form for enclosing.

+

"enclosed" - Performs an enclosing check (other enclosing layer)

+ +

Usage:

+
    +
  • enclosed(other [, options ]) (in conditions)
  • +
  • enclosed(layer, other [, options ])
  • +
+

+This check verifies if the polygons of the input layer are enclosed by shapes +of the other input layer by a certain distance. +It has manifold options. See Layer#width for the basic options such +as metrics, projection and angle constraints etc. This check also features +opposite and rectangle filtering. See Layer#separation for details about opposite and +rectangle error filtering. +

+This function is essentially the reverse of enclosing. In case of +"enclosed", the other layer must be bigger than the primary layer. +

+

Classic mode

+

+This function can be used in classic mode with a layer argument. In this case it +is equivalent to "layer.enclosed" (see Layer#enclosed). +

+

+# classic "enclosed" check for < 0.2 um
+in = layer(1, 0)
+other = layer(2, 0)
+errors = enclosed(in, other, 0.2.um)
+
+

+

Universal DRC

+

+The version without a first layer is intended for use within DRC expressions +together with the "universal DRC" method Layer#drc. In this case, this function needs to be +put into a condition to specify the check constraints. The other options +of Layer#enclosed (e.g. metrics, projection constraints, angle limits etc.) +apply to this version too: +

+

+# universal DRC "enclosed" check for < 0.2 um
+in = layer(1, 0)
+other = layer(2, 0)
+errors = in.drc(enclosed(other) < 0.2.um)
+
+

+The conditions may involve an upper and lower limit. The following examples +illustrate the use of this function with conditions: +

+

+out = in.drc(enclosed(other) < 0.2.um)
+out = in.drc(enclosed(other) <= 0.2.um)
+out = in.drc(enclosed(other) > 0.2.um)
+out = in.drc(enclosed(other) >= 0.2.um)
+out = in.drc(enclosed(other) == 0.2.um)
+out = in.drc(enclosed(other) != 0.2.um)
+out = in.drc(0.1.um <= enclosed(other) < 0.2.um)
+
+

+The result of the enclosed check are edges or edge pairs forming the markers. +These markers indicate the presence of the specified condition. +

+With a lower and upper limit, the results are edges marking the positions on the +primary shape where the condition is met. +With a lower limit alone, the results are edge pairs which are formed by two identical, but opposite edges attached to +the primary shape. Without an upper limit only, the first edge of the marker is attached to the +primary shape while the second edge is attached to the shape of the "other" layer. +

+ + + + + +
+

+When "larger than" constraints are used, this function will produce the edges from the +first layer only. The result will still be edge pairs for consistency, but each edge pair holds one edge from +the original polygon plus a reverse copy of that edge in the second member. Use "first_edges" to extract the +actual edges from the first input (see separation for an example). +

"enclosing" - Performs an enclosing check

Usage:

diff --git a/src/lay/lay/doc/about/drc_ref_layer.xml b/src/lay/lay/doc/about/drc_ref_layer.xml index b86cc8e19..56dfbe4c8 100644 --- a/src/lay/lay/doc/about/drc_ref_layer.xml +++ b/src/lay/lay/doc/about/drc_ref_layer.xml @@ -778,7 +778,44 @@ individual ones unless raw mode is chosen.

See enclosing for a description of that method

-

"enclosing" - An enclosing check

+

"enclosed" - An enclosing check (other_layer enclosing layer)

+ +

Usage:

+
    +
  • layer.enclosed(other_layer, value [, options])
  • +
+

+Note: "enclosed" is available as operators for the "universal DRC" function drc within +the DRC framework. These variants have more options and are more intuitive to use. +See enclosed for more details. +

+This method checks whether layer is enclosed by (is inside of) other_layer by not less than the +given distance value. Locations, where the distance is less will be reported in form +of edge pair error markers. +Locations, where both edges coincide will be reported as errors as well. Formally +such locations form an enclosure with a distance of 0. Locations, where other_layer +is inside layer will not be reported as errors. Such regions can be detected +by inside or a boolean "not" operation. +

+The options are the same as for separation. +

+This method is available for edge and polygon layers. +

+As for the other DRC methods, merged semantics applies. +

+Distance values can be given as floating-point values (in micron) or integer values (in +database units). To explicitly specify the unit, use the unit denominators. +

+The following images show the effect of two enclosed checks (red: input1, blue: input2): +

+ + + + + +
+

+

"enclosing" - An enclosing check (layer enclosing other_layer)

Usage: