From e1cd6aaeb1a9cfa32f2488d607d8e7277435b3ee Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Dec 2021 01:12:36 +0100 Subject: [PATCH] Bugfix for #954 The bug was that while iterating a Region during the gate traversal, the "select_interacting" was triggering a sort() which changed the order. Solution is to pre-sort when iterators are issued also when the iterator is non-region selecting. This way, plain and region query iterators can be used together. In addition, the dirty flag scheme of Cell+Shapes was cleaned up a little for bboxes. --- src/db/db/dbCell.cc | 3 +- src/db/db/dbCell.h | 17 +-- src/db/db/dbFlatEdgePairs.cc | 1 - src/db/db/dbFlatEdges.cc | 1 - src/db/db/dbFlatRegion.cc | 1 - src/db/db/dbFlatTexts.cc | 1 - src/db/db/dbGenericShapeIterator.h | 27 ++-- src/db/db/dbLayer.h | 10 +- src/db/db/dbLayout.cc | 1 + src/db/db/dbShapes.cc | 12 +- src/db/db/dbShapes.h | 23 ++-- src/db/db/dbShapes2.h | 5 + src/db/unit_tests/dbNetlistExtractorTests.cc | 125 +++++++++++++++++++ src/db/unit_tests/dbShapeArrayTests.cc | 1 - src/db/unit_tests/dbShapesTests.cc | 16 --- testdata/algo/device_extract_issue954_au.gds | Bin 0 -> 95980 bytes 16 files changed, 180 insertions(+), 64 deletions(-) create mode 100644 testdata/algo/device_extract_issue954_au.gds diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index 906752112..1b5c4871b 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -306,7 +306,8 @@ Cell::update_bbox (unsigned int layers) // update the bboxes of the shapes lists for (shapes_map::iterator s = m_shapes_map.begin (); s != m_shapes_map.end (); ++s) { - s->second.update_bbox (); + s->second.reset_bbox_dirty (); + box_type sbox (s->second.bbox ()); if (! sbox.empty ()) { diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index db7089b2a..06b54995d 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -65,7 +65,7 @@ class LayerMapping; * a set of child cell instances and auxiliary information such as * the parent instance list. * A cell is identified through an index given to the cell upon instantiation. - * The cell index is valid in the context of a cell graph object which + * The cell index is valid in the context of a layout object which * must issue the cell index. */ @@ -483,7 +483,7 @@ public: bool is_shape_bbox_dirty () const; /** - * @brief Update the bbox + * @brief Updates the bbox * * This will update the bbox from the shapes and instances. * This requires the bboxes of the child cells to be computed @@ -496,8 +496,9 @@ public: * @return true, if the bounding box has changed. */ bool update_bbox (unsigned int layers); + /** - * @brief Sort the shapes lists + * @brief Sorts the shapes lists * * This will sort the shapes lists for query of regions * on a per-shape basis. Since sorting of the shapes is @@ -511,7 +512,7 @@ public: * * Before the bounding box can be retrieved, it must have * been computed using update_bbox. This is performed by - * requesting an update from the graph. + * requesting an update from the layout. * * @return The bounding box that was computed by update_bbox */ @@ -522,7 +523,7 @@ public: * * Before the bounding box can be retrieved, it must have * been computed using update_bbox. This is performed by - * requesting an update from the graph. + * requesting an update from the layout. * * @return The bounding box that was computed by update_bbox */ @@ -1026,10 +1027,10 @@ protected: /** * @brief Standard constructor: create an empty cell object * - * Takes the manager object from the graph object. + * Takes the manager object from the layout object. * * @param ci The index of the cell - * @param g A reference to the graph object that owns the cell + * @param g A reference to the layout object that owns the cell */ Cell (cell_index_type ci, db::Layout &g); @@ -1065,7 +1066,7 @@ private: // linked list, used by Layout Cell *mp_last, *mp_next; - // clear the shapes without telling the graph + // clear the shapes without telling the layout void clear_shapes_no_invalidate (); // helper function for computing the number of hierarchy levels diff --git a/src/db/db/dbFlatEdgePairs.cc b/src/db/db/dbFlatEdgePairs.cc index 561aa4829..4cadc784a 100644 --- a/src/db/db/dbFlatEdgePairs.cc +++ b/src/db/db/dbFlatEdgePairs.cc @@ -91,7 +91,6 @@ size_t FlatEdgePairs::hier_count () const Box FlatEdgePairs::compute_bbox () const { - mp_edge_pairs->update_bbox (); return mp_edge_pairs->bbox (); } diff --git a/src/db/db/dbFlatEdges.cc b/src/db/db/dbFlatEdges.cc index 1421f1353..531850b2c 100644 --- a/src/db/db/dbFlatEdges.cc +++ b/src/db/db/dbFlatEdges.cc @@ -181,7 +181,6 @@ bool FlatEdges::is_merged () const Box FlatEdges::compute_bbox () const { - mp_edges->update_bbox (); return mp_edges->bbox (); } diff --git a/src/db/db/dbFlatRegion.cc b/src/db/db/dbFlatRegion.cc index 516fd8a31..2c28be4ca 100644 --- a/src/db/db/dbFlatRegion.cc +++ b/src/db/db/dbFlatRegion.cc @@ -190,7 +190,6 @@ bool FlatRegion::is_merged () const Box FlatRegion::compute_bbox () const { - mp_polygons->update_bbox (); return mp_polygons->bbox (); } diff --git a/src/db/db/dbFlatTexts.cc b/src/db/db/dbFlatTexts.cc index 59d0ec2c9..ef3ee4a54 100644 --- a/src/db/db/dbFlatTexts.cc +++ b/src/db/db/dbFlatTexts.cc @@ -91,7 +91,6 @@ size_t FlatTexts::hier_count () const Box FlatTexts::compute_bbox () const { - mp_texts->update_bbox (); return mp_texts->bbox (); } diff --git a/src/db/db/dbGenericShapeIterator.h b/src/db/db/dbGenericShapeIterator.h index 273485332..49ca3ce79 100644 --- a/src/db/db/dbGenericShapeIterator.h +++ b/src/db/db/dbGenericShapeIterator.h @@ -158,8 +158,14 @@ class DB_PUBLIC generic_shapes_iterator_delegate { public: generic_shapes_iterator_delegate (const db::Shapes *shapes) - : mp_shapes (shapes), m_iter (mp_shapes->begin (shape_flags ())) + : mp_shapes (shapes) { + // NOTE: to allow multiple iterators acting on the same Shapes container at once, we always sort before we deliver the iterator - + // also in the non-region case. Without this, sorting may happen while another iterator is progressing. + if (mp_shapes->is_bbox_dirty ()) { + const_cast (mp_shapes)->update (); + } + m_iter = mp_shapes->begin (shape_flags ()); m_is_addressable = shape_flags () == shape_flags_pure () || mp_shapes->begin (shape_flags () - shape_flags_pure ()).at_end (); set (); } @@ -171,17 +177,17 @@ public: virtual void do_reset (const db::Box &box, bool overlapping) { + // NOTE: to allow multiple iterators acting on the same Shapes container at once, we always sort before we deliver the iterator - + // also in the non-region case. Without this, sorting may happen while another iterator is progressing. + if (mp_shapes->is_bbox_dirty ()) { + const_cast (mp_shapes)->update (); + } if (box == db::Box::world ()) { m_iter = mp_shapes->begin (shape_flags ()); + } else if (overlapping) { + m_iter = mp_shapes->begin_overlapping (box, shape_flags ()); } else { - if (mp_shapes->is_bbox_dirty ()) { - const_cast (mp_shapes)->update (); - } - if (overlapping) { - m_iter = mp_shapes->begin_overlapping (box, shape_flags ()); - } else { - m_iter = mp_shapes->begin_touching (box, shape_flags ()); - } + m_iter = mp_shapes->begin_touching (box, shape_flags ()); } set (); @@ -214,9 +220,6 @@ public: virtual db::Box bbox () const { - if (mp_shapes->is_bbox_dirty ()) { - const_cast (mp_shapes)->update (); - } return mp_shapes->bbox (); } diff --git a/src/db/db/dbLayer.h b/src/db/db/dbLayer.h index 5c94064ee..b89c02e1b 100644 --- a/src/db/db/dbLayer.h +++ b/src/db/db/dbLayer.h @@ -459,13 +459,21 @@ struct layer } /** - * @brief Return true if the bounding box is "dirty" + * @brief Return true if the bounding box needs update */ bool is_bbox_dirty () const { return m_bbox_dirty; } + /** + * @brief Return true if the tree needs update + */ + bool is_tree_dirty () const + { + return m_tree_dirty; + } + /** * @brief Reserve a certain number of elements */ diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index c3fa44e5a..b60cc3968 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1748,6 +1748,7 @@ Layout::do_update () cp.sort_shapes (); } } + } // sort the instance trees now, since we have computed the bboxes diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index 9a1fdcfbc..855d89719 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -1016,15 +1016,12 @@ Shapes::clear () } } -void Shapes::update_bbox () +void Shapes::reset_bbox_dirty () { - for (tl::vector::const_iterator l = m_layers.begin (); l != m_layers.end (); ++l) { - (*l)->update_bbox (); - } set_dirty (false); } -void Shapes::update () +void Shapes::update () { for (tl::vector::const_iterator l = m_layers.begin (); l != m_layers.end (); ++l) { (*l)->sort (); @@ -1039,7 +1036,7 @@ bool Shapes::is_bbox_dirty () const return true; } for (tl::vector::const_iterator l = m_layers.begin (); l != m_layers.end (); ++l) { - if ((*l)->is_bbox_dirty ()) { + if ((*l)->is_tree_dirty ()) { return true; } } @@ -1050,6 +1047,9 @@ Shapes::box_type Shapes::bbox () const { box_type box; for (tl::vector::const_iterator l = m_layers.begin (); l != m_layers.end (); ++l) { + if ((*l)->is_bbox_dirty ()) { + (*l)->update_bbox (); + } box += (*l)->bbox (); } return box; diff --git a/src/db/db/dbShapes.h b/src/db/db/dbShapes.h index 96cd0f41a..27894780c 100644 --- a/src/db/db/dbShapes.h +++ b/src/db/db/dbShapes.h @@ -485,6 +485,7 @@ public: virtual bool is_bbox_dirty () const = 0; virtual size_t size () const = 0; virtual bool empty () const = 0; + virtual bool is_tree_dirty () const = 0; virtual void sort () = 0; virtual LayerBase *clone () const = 0; virtual bool is_same_type (const LayerBase *other) const = 0; @@ -1177,28 +1178,20 @@ public: shape_type transform (const shape_type &ref, const Trans &t); /** - * @brief updates the bbox and sorts if necessary - * - * This is equivalent to calling sort () and update_bbox () in this order. + * @brief Updates the quad trees (sort ()) and resets the dirty flag */ void update (); /** - * @brief updates the bbox - * - * Updating the bbox is required after insert operations - * and is performed only as far as necessary. - */ - void update_bbox (); - - /** - * @brief check if the bounding box needs update - * - * Returns true if the bounding box of the shapes has changed and - * requires an update. + * @brief Returns a value indicating whether the shape container is modified and needs update */ bool is_bbox_dirty () const; + /** + * @brief Resets the "dirty bbox" condition (see is_bbox_dirty) + */ + void reset_bbox_dirty (); + /** * @brief Retrieve the bbox * diff --git a/src/db/db/dbShapes2.h b/src/db/db/dbShapes2.h index 9f66bf33b..5802553d7 100644 --- a/src/db/db/dbShapes2.h +++ b/src/db/db/dbShapes2.h @@ -126,6 +126,11 @@ public: return m_layer.is_bbox_dirty (); } + virtual bool is_tree_dirty () const + { + return m_layer.is_tree_dirty (); + } + size_t size () const { return m_layer.size (); diff --git a/src/db/unit_tests/dbNetlistExtractorTests.cc b/src/db/unit_tests/dbNetlistExtractorTests.cc index 7dea4307b..3eef9f160 100644 --- a/src/db/unit_tests/dbNetlistExtractorTests.cc +++ b/src/db/unit_tests/dbNetlistExtractorTests.cc @@ -3176,3 +3176,128 @@ TEST(14_JoinNets) db::compare_layouts (_this, ly, au); } + +TEST(100_issue954) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int active = define_layer (ly, lmap, 1); + unsigned int poly = define_layer (ly, lmap, 2); + + { + db::LoadLayoutOptions options; + options.get_options ().layer_map = lmap; + options.get_options ().create_other_layers = false; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "device_extract_issue954.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 ractive (db::RecursiveShapeIterator (ly, tc, active), dss); + db::Region rpoly (db::RecursiveShapeIterator (ly, tc, poly), dss); + + // derived regions + + db::Region rpgate = ractive & rpoly; + db::Region rpsd = ractive - rpgate; + + // Global + + db::Region bulk (dss); + + // return the computed layers into the original layout and write it for debugging purposes + + unsigned int lgate = ly.insert_layer (db::LayerProperties (10, 0)); // 10/0 -> Gate + unsigned int lsd = ly.insert_layer (db::LayerProperties (11, 0)); // 11/0 -> Source/Drain + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (12, 0)); // 12/0 -> P Diffusion + + rpgate.insert_into (&ly, tc.cell_index (), lgate); + rpsd.insert_into (&ly, tc.cell_index (), lsd); + rpsd.insert_into (&ly, tc.cell_index (), lpdiff); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["SD"] = &rpsd; + dl["G"] = &rpgate; + dl["W"] = &bulk; + dl["P"] = &rpoly; // not needed for extraction but to return terminal shapes + pmos_ex.extract (dss, 0, dl, nl, cl); + + // perform the net extraction + + db::NetlistExtractor net_ex; + + db::Connectivity conn; + + // Global nets + conn.connect_global (bulk, "BULK"); + + // Intra-layer + conn.connect (rpsd); + conn.connect (rpoly); + + // extract the nets + + std::list > jn; + + jn.push_back (std::set ()); + jn.back ().insert ("BULK"); + jn.back ().insert ("VSS"); + + jn.push_back (std::set ()); + jn.back ().insert ("NWELL"); + jn.back ().insert ("VDD"); + + net_ex.set_joined_nets ("INV2", jn); + + std::list gp; + gp.push_back (tl::GlobPattern ("NEXT")); + gp.push_back (tl::GlobPattern ("FB")); + net_ex.set_joined_net_names (gp); + + net_ex.extract_nets (dss, 0, conn, nl, cl); + + EXPECT_EQ (all_net_names_unique (nl), true); + + // debug layers produced for nets + // 200/0 -> Poly + // 201/0 -> N source/drain + // 202/0 -> Bulk + std::map dump_map; + dump_map [layer_of (bulk) ] = ly.insert_layer (db::LayerProperties (202, 0)); + dump_map [layer_of (rpsd) ] = ly.insert_layer (db::LayerProperties (201, 0)); + dump_map [layer_of (rpoly) ] = ly.insert_layer (db::LayerProperties (200, 0)); + + // write nets to layout + db::CellMapping cm = dss.cell_mapping_to_original (0, &ly, tc.cell_index ()); + dump_nets_to_layout (nl, cl, ly, dump_map, cm, true); + + // compare the collected test data + + std::string au = tl::testdata (); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_issue954_au.gds"); + + db::compare_layouts (_this, ly, au); +} + diff --git a/src/db/unit_tests/dbShapeArrayTests.cc b/src/db/unit_tests/dbShapeArrayTests.cc index 9acb0a017..648b00854 100644 --- a/src/db/unit_tests/dbShapeArrayTests.cc +++ b/src/db/unit_tests/dbShapeArrayTests.cc @@ -119,7 +119,6 @@ TEST(2) shapes.insert (p2); shapes.insert (db::SimplePolygonRef (p2, *rep)); shapes.sort (); - shapes.update_bbox (); EXPECT_EQ (shapes.bbox () == db::Box (100,-5200,2300,2000), true); diff --git a/src/db/unit_tests/dbShapesTests.cc b/src/db/unit_tests/dbShapesTests.cc index fadd52ce4..59387528c 100644 --- a/src/db/unit_tests/dbShapesTests.cc +++ b/src/db/unit_tests/dbShapesTests.cc @@ -34,26 +34,21 @@ TEST(1) db::Shapes s (&m, 0, db::default_editable_mode ()); db::Box b_empty; - s.update_bbox (); EXPECT_EQ (s.bbox (), b_empty); db::Box b (0, 100, 1000, 1200); s.insert (b); - s.update_bbox (); EXPECT_EQ (s.bbox (), b); db::Edge e (-100, -200, 0, 0); s.insert (e); - s.update_bbox (); EXPECT_EQ (s.bbox (), db::Box (-100, -200, 1000, 1200)); db::Shapes s2 (s); - s2.update_bbox (); EXPECT_EQ (s2.bbox (), db::Box (-100, -200, 1000, 1200)); if (db::default_editable_mode ()) { s2.erase (db::Box::tag (), db::stable_layer_tag (), s2.begin (db::Box::tag (), db::stable_layer_tag ())); - s2.update_bbox (); EXPECT_EQ (s2.bbox (), db::Box (-100, -200, 0, 0)); } } @@ -64,25 +59,20 @@ TEST(1a) db::Shapes s (&m, 0, true); db::Box b_empty; - s.update_bbox (); EXPECT_EQ (s.bbox (), b_empty); db::Box b (0, 100, 1000, 1200); s.insert (b); - s.update_bbox (); EXPECT_EQ (s.bbox (), b); db::Edge e (-100, -200, 0, 0); s.insert (e); - s.update_bbox (); EXPECT_EQ (s.bbox (), db::Box (-100, -200, 1000, 1200)); db::Shapes s2 (s); - s2.update_bbox (); EXPECT_EQ (s2.bbox (), db::Box (-100, -200, 1000, 1200)); s2.erase (db::Box::tag (), db::stable_layer_tag (), s2.begin (db::Box::tag (), db::stable_layer_tag ())); - s2.update_bbox (); EXPECT_EQ (s2.bbox (), db::Box (-100, -200, 0, 0)); } @@ -92,21 +82,17 @@ TEST(1b) db::Shapes s (&m, 0, false); db::Box b_empty; - s.update_bbox (); EXPECT_EQ (s.bbox (), b_empty); db::Box b (0, 100, 1000, 1200); s.insert (b); - s.update_bbox (); EXPECT_EQ (s.bbox (), b); db::Edge e (-100, -200, 0, 0); s.insert (e); - s.update_bbox (); EXPECT_EQ (s.bbox (), db::Box (-100, -200, 1000, 1200)); db::Shapes s2 (s); - s2.update_bbox (); EXPECT_EQ (s2.bbox (), db::Box (-100, -200, 1000, 1200)); } @@ -3206,12 +3192,10 @@ TEST(23) db::Shapes s (&m, 0, db::default_editable_mode ()); db::Box b_empty; - s.update_bbox (); EXPECT_EQ (s.bbox (), b_empty); db::EdgePair ep (db::Edge (-100, -200, 0, 0), db::Edge (0, -100, 100, 100)); s.insert (ep); - s.update_bbox (); EXPECT_EQ (s.bbox (), db::Box (-100, -200, 100, 100)); db::ShapeIterator si = s.begin (db::ShapeIterator::EdgePairs); diff --git a/testdata/algo/device_extract_issue954_au.gds b/testdata/algo/device_extract_issue954_au.gds new file mode 100644 index 0000000000000000000000000000000000000000..5a7bdc610618f9d5d1e95e13967f491254726ee7 GIT binary patch literal 95980 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