From 09df614d8a93afcc2ab7332e534cf91f9fd245e5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 22 Oct 2021 23:52:35 +0200 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 455c40ced60c3c5dc00530bfb5e98660bc3d2bfe Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Oct 2021 21:31:05 +0200 Subject: [PATCH 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 12/19] 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 13/19] 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 14/19] 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:

    @@ -790,8 +827,8 @@ See enclosing for a description of that method the DRC framework. These variants have more options and are more intuitive to use. See 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 diff --git a/src/lay/lay/doc/images/drc_encd1.png b/src/lay/lay/doc/images/drc_encd1.png new file mode 100644 index 0000000000000000000000000000000000000000..4be32165a2f4cc8299ea5352de50f6746db13a8c GIT binary patch literal 9333 zcmdT~dpOkF-dE|OM6!1%ax0WJLK}^SkW0CA(M4_{m(q5bp)sLJsbMFVA_hY?QYs?F zFent6F-oKuBy-uqsF|WMn0Nige|YAZpEc|EUEj;+^ZBl|V)xox ztz5o#xrm6!N*im7{URa@K0|-Y7J?@hW!635LoCSJ2`M5X(E$A|NYRv72R#hgbtJ^x z`_!qeB90DwtxnOlgNI7c!~Nb~AtD;T=xS}#G28+^baZrKhFbbYx?2pjjEoF7h3(v7 z1%`^)SnPBR%N)V?mvmd7InA@TTjJ~-@8M6{dRP9!q3aKvHuNLJ2R=3E?5?8+y;FQK zreC@9d}V4wcqK1$;sd8zlJHJdWP2Ua@9L)KE55S7AVU^Q;nK7JSP>J`#M#GEQCY;6 zCm?UG%2X3I3}4hHx?j~J-Sp%JTWRYn7AI9;#yTpiFMG*JB`pw){e!y8VoAfgLe=`W z>k`!x*NVNG`@w$cxBXF4Nk`;KF)`vX2pK84Rdg*CU8UZ=3UZX*bNW&3)@gG;NJ?*# zkc^9Icz?vw;<#yYRZP=4L!{!54AVdXOFmut&=fSbBu^~QvTknP*mjH4VaF^stZCE* zOR7uZRTjPcS<7=uy_5Lb$_%}fWg`{^Lt?_i!(FezFnvY*J734f#%fH4Hzf>T33Zip zpCY6bs+CV0p_^-ws7FYDEb+5qXL)&fV}^_P?0fRm@WJj{iha{C3EQTW@b+Eayvo+i zWAP(2CNUr|?2}d}=jw`Ql2`d^$ubGui+y-6ric^Shr0va^QRQyZJZ={c@~Bl8n}bs z!#y0F6>=*KiF+SdN^j!!nVK~t$%YB6QIf}FUcu?1#`?{No9|h3I%g5dg!QG07 zuN6}jhdE*W3AFOD4&{A3{u6cMri7XoRoNIOc4_$*wo8S9qjP}k*B4qb>jyb^DJErj z$(ldCdHhn+7#pXEF_QX;x~?$d^?8z82(Ng$zVkN@E1q;{dcddlA>J8An;2TulrtNq z*mfG1=w}=Jb?AcR4tCiuCJLm?fR9h=3c70_;1v}}#_USPxBS#Ao-E_HC2`{B4qiBC z7oEqE6MKkNZC2AG=p^^7QY6d$zDe(-Rr!J5l>z*6lh32s%YM(!UC~~adcA=EJfh-g z?(#de*zz@D#?zG@zhZ9D`Y#_U4j$AlD?d$xk#M zTUhIwxbvX)Hm$FL=}R*rCO5jd4ycx4qTMKoBtf3#sd?yVGsbdaeDkF$SyalLX{!@-HK%FYeZM)A8MG82Z)$N8IBDq=a|gv&A3B)>q<_xj3@ z|2oGLvkMoPafk5O^%zEqWbo00rblnVldl2lo(!@nu8Y#^@oO; zSji+FdmVbENQsRHMg8aUfsdTj;%v+my?ioUD1K94wE3LQjj>HI**D@cf$mwYrFTh) zh+Vd%!&2hdf?Gt%M%`jhCyD7rNT)+b-KMY~3mmfd?CX)A>rZ|x?$;5D99eBif zV%%BrHMGQtR#ArjNj@rq7(0H`-7Md}{UKM9ZhwNT-NZTatV7qKp@dB8Pn%F699YY_ zj6|>L-5dFf#sQ2@U(tI$?m^r!xTTgugXXx5nGRc;V;oZ;_aq|DCF`<*qx<3~P4wOv ze48oj_%zpjT6F7Ln!OsWBIw;RVDH{#KA2Ee^0P@N0vHvuCCVezr4cTML89+h9@M~=UT`^pr2!v} z(lEU4@$BlH2@83Wi|CX4QP2YHi|M>w1os6E_xsUVZThXgxTnUyrWV|wx=XZxM_+EL zr#q3oW?fmL_S?8+#alh)18bXnx2loxwnx`XSOF%xSW1ZV+K+JS^`G*k=*DyJAp3GA zmdlff%Wv91uR)?01Iu;neg{6%LuwU2#_>X30?EGxt`eVaLYcYVzrKX+6eq^P@1_4~ za^GQ{bg%M@E>rHb%orWYN(41>Xp=zr%#DT26B+LD|PwdO!D`)i2{MN}Vr^1Ax?UBL^d86CFqSGes_dHHx)MwVkP_J0jq(XD;m$$Ge^_f(q19ZD$P?DMOGR{ zxYZ2C`e<=@1Y?=NTQ$qi53c8KdFIc)Tr2(IH7rXh`)QgEO4kULraTjF(FYmoeV^KG zSSvfA7i_PyGw@&~-@Hs*x$DLj(wX!$_skX9^8+s*96#&_F> zGttn0>Qyxss;t;Z^<&VBhe}WJ`Td-hTm)UK=Y9mg$vyfdSucZ{>fLkJFFSDkRZZ03 zC2uDqOTE_Q&bSnJ@**Uia#8OoRzFJiUgd?gZqQIDV}LJGj!C+sV$NS zdi+Jb_2>0B{UYsGR$ndER&b_Zzhic&Qyr7!oF!>@DtoLV`g4Dif=VJJ>e#V+bkfm2 zp?H`sv&oefJwm-G+hY8nlA?fv%zhfmY9bPS8d~t`l$5HFMa_PUazxW)j|&0WPo7q` zlb|E2UrO|Cl5S&Ce0R9v$`350P>nWdj}9#vn}0_aC+*k~&LLJNfkQU3VyUP*{_c?; zP?xFW{;Nq-O4Vnx6S$yU95cJ~VkxC30_BnnTSEz3*0v$LqQugFdOG&QS;aBwbZLk$z@RVn9Z)0 z{d=`m<+fxBtV1*AjPK$YHuxpBdfKLqMxUfpa4;;59xOIoKj(SJ-=kz-&sez?A!C$N zW?S)+%xN8Zza8ZXy<*mPPwyfP`ZhMZp7DU8ltGa1LY6e+SNM)eo+#CMO)!V5u2<99 zP45{6-wh)xN7h74*{0PZX%bS_D%+A2KniCwZp}I8+jv7k7&Bf1+(k@*`Lc|WMN~r2 zGxJ0>DCAYT*a-EXJZgyaZV9o`0}844;j#pC(W};W$F1gk3L@A5oR%al6lo}Bx(O?` z&ITU(y21-zh2Dj3k%f=`+M0u4*5Rhi(@o=+fZA)9PyGmA7lmZj!^oZEw^>7$U*?Pw^QsIUMDC72swkHHV5^o^z8r}SB0`yC#XxpX_l)N1f! z;5tCb2?DmJlc36Br%M^2vJ9aRY2@Pin)e}@I3&QCAPt$Amp+v4O$+9d!iS%zHiDM- z)E5vH%IqP6NyeMAVWS@_L21vH`{t*n;{Nt&+8K&8S;FPay+_pmgmIan!FbqK=pD0O z_k2yr7+pP!ByTA_LAgDA!v~s3z+CszLRlT^_>1GQEk+3JvJIetix)--CzbRHGYu}(C18Y zDk~`XGyxF8gT>|x&b4x`{(yxH?>}bi1SEF&7kk`ec;aaE=3+T7X3vQF?Teqep#A+y zHA$LFw|}t)^Ve;Dssk{1#+A(qf@0r)RPDtao*b2F-aa1T=gBK;+?Xe6#ab1$0kp+Q z4ld_~K}#s)E+C1v!~x5H{vO248KZM>g3w4Ex&qZ?G`jedpyb#_yjJ>m%TZtMi%3Eb1r~aXoJc#NJmejT{VWNEvUv_1h4)$tdbo&cAYGt~fpkR1Q+L@PaRD_*^0%Z?(eELvZ^ZeW)wF5> z^$!(hj*F6IhEZUFD1`QeSs*E62FJXFs5`#qpjMuPCQ3jO344SL-!I;9pApS1@h*{=<6#GJZxirWKY4V+N-?B zJ{4g+c7$-J<({*ar1!;cYt_WnVaBwHjv+o$jW_UyVA4+ zxsS~2c#e%5?@C(EI(46Lt(kkghwz z3~<{whte}ESh)2l&mMORK3*%md9!3DO7ZN~t}l^=t=gAA%oFdo6!-&@N2_`s$pD!c z^eMvrcLhq7(?VJOBvlY8T4|Bbl*K%AM0q z=iZawCXiSzh6Bph*USknfo1im=IGB+5$cX}^~WJC$zSSb@T#Nv9su=a4@D z=7rSHBczNc_zIL+fSa07dJ5!DjdUT=Ck~HdDy;5ik~&-ak$j7*vx0UNIpA!%K$*z{ z3i_VN^4E27Wc@0|X7wTgks5FdGBw)Bqvq7vM9uNTd#{X;+((T+f7L6FhI zRX@;H!}`+guo&ewKG0oEgWs3a>a8)(j(C5`{Z4vnZ4o5oAaeibFii+rvLjJ#Rqn&l z3; z0do*^I|~IpM?0t|h-|)v1_*3Ft;(|rUyqf&H^JfxSpMIT@=zNjB#t3vuPqHVYoK79 zWQVBAQ1{sYeX{hx^ciMOV>X&@o_VOl#-+w9zo)5R{!y3p z7yNFlc27XIBpWQ?@-a6oP4@6;OXpC2n6k^Pf?Fm1}{qQbH6f$my8OtIT| z^GGVDsC{^n8Spo%@6xntdia5chAm`3)mQyJ*q{b43d0}-MosEkPk>4XGN~sbRRgsJ zBKzm3S(nyb;W))%8|f2ppi#m@ml_fsPR&bu;pSAp$bX^bp#~4q@?{_J@Tf?oKCup{ z*Oz!cN~|Ah29Q6~0^j;+(bl^&u;>MC3kbJ-P_6tGV9)laecZbZmmz7N(ivBRWR6K7 zt+Pis3dNY7&GUW3G9Lqz7D4X;DL}*I6%BrR%#q}C-)kTN??L@FBq|~5LwUPJg8c`@ zx(GChGrsES*zNc|M-=1cEpUub+XwZI-)%=+ETQxe{~-E^XQMKhduR6`lq^BI zd8tIIJ97QsQMn%_I6zPED)&Vi&QX~;!8GnyWUsxLAw+=C3jA8VZU9;AOb|41K;-4@ z7k>pfQXoK!s3n3HJKuZZmD3dlW)r40(kCIo%*lxzd9^+@5k4m&tb`g62+Z#bNo8m= zr~+)Gq;|$t5I7A`?wHE8OdgWJ)n-7r_0}Zsd5%kw!CKk_S!o6=rweN3fTo=5mz?IiS}+b1WWQUbGLwzNJDYcbx08eB%F7N& zIZ^G)Vz10j2~<vhUZrMWLX1!xs#x0b8dPL zZbDN)z|pH*64^Ddx(%y}YKlz<8#&mem!6M{Y1fZ3FdLwj9V}?F#CXxbHclQjUo{za zt*7hcrB3oSL~1;k^1}+tbI=#{`e3=d*lnAY>?CehzjV9YqWqm%+8xKjPR}URWSI1g zPO|i1vnx%MPB{*dUf)HKNR8f!YOimd)91qb7l+-WDgz+Oc7G=1FIoa!PNX)2|4Gvl zZ0Z3QRXywwFUG0UwG?uwP*g`4J*snP6|(Ei@6yJ{T5Y=Fm_@#6EAd7&-KItvw6_p1L2pbGJan&9Z!qtw~CtG2pDqYH~oan?E(qkMNbV77FVs8h%PDNqSQK0447wIUO?2=g}mBh@2PU&ez2xeTGk=?R?epLSdeKObV3a$VUbGGYd zRxdcQ@OF~U+=(BwNxEKED)wnf9Jn7KIQS1{z<*?z;HJX+l^sd_!z-=9J%;G{dBGO) z$Hy_&TjISfESAiiGwO~1&@jP;hQM3#;=X~VbN3?z^9rif(!w)%iz>M7AXv@W!X?Y- z#i;dXCUJ-14JQsRII$Rg!uf6Uq0Uq8SqW7~z8NgI{qR-o=8ss>)cSJqLJeQF+G)1) zF=;6&{Y)v&iEIA7FBZR57QTxyb9tjpb9B1#9q7Zl`6Ltm3!hyUZq0FCINy>hApmJz zrj%4xQ`!w2J4#~Wn)3oZyOwOK�~%V4wW<$aTjj^Y9T0dFr*K$;4_E6$jtS60()n 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 literal 0 HcmV?d00001 diff --git a/src/lay/lay/doc/images/drc_encd2.png b/src/lay/lay/doc/images/drc_encd2.png new file mode 100644 index 0000000000000000000000000000000000000000..88e7ad63e41d317bd545161da5b457c9eee85af3 GIT binary patch literal 9332 zcmdT~2~<k;Wl{#AOaUP#0un?N1r!zAA}Gq_0GOyD5{+6kP#Fab0TLV# zQ0k0gFoG9UrVJHC1R_+(ASywV5)6Ul?GuJ#Z~JQB>sohR*UG}2b+XTY{^9$+fB!qt z&u8uInG0vCsHn_d=jE|kMP<^j&_{JLxZ+Xj^&0$9i}CsvqoShm1o}+MveZ}vT|BgA z`yo{5{{40;TYUZ2?&q!q7Y(6{n?rXWQZfI=-rCXD*$VuzwY5h$TRXt*t(>jlaA%9y z)vMNmTUFM1tlkov*GoB=(p`&NTe2W-O7%xQbrWoM-bGkXcQ#2CeTEsv{)L9p`t1Cx z0q2{!CFYx7pZmOO+asdkTlu=e<0y1TjO+P=Ozt$iA~yAgp)pGAe`u1nHqNDfherX5 zYJz3WJ=GMi5+3cA(^V@ss1@tm`}^|aG9HVKW57hmG6@mTp}(Y008t&7r3(-x{Vjs0M= z&bMz)YNv14Z%9Zulz?8K-J=yQ{Hoh{*1(buzg|67#|ej%FTAqH)=kf;ovE3W@T6ka zo$>`58)~!SUaaw#m9v*iPWN&Jv1+(ka$aRAoqOujYEini&CK(&7?O*_^uhdq_UPa% z!v-BqVya6SwNhMOMpf|e7Dx7#?RTj>%2$&Sie4>n=CD>FxKu#^0)DLj+!4hP?Our( z6Sa8Y-7-mjWkz&`a8-M!6qDuaWk0!h*>-Yf!1raM;p?xZptF9Vx;GITXGc>Sqy!xVCc!DWMupvEgs3d;9ha?(QbX z1r4MR6WYwf1a0LoN5n2vC~Zda}RchwGGzyYz=OYLAfH| zyLORb2+gl~-}CCddfM7o-}Y|YkY_seD+)~H@91Vuc#V5252s4CindNGn8&?xAk^}E z%H3l)7h#BY;?vtnpkB?fV{SpW&Jfv>tvi5$_z67{b@t~}bn*WC7Z0IrMi=xcCLggG=RJtG?c(kcCq z63P4>7Y&Zf<)7s5Z7O-44Z+wY%g;0wE9d2|rMCI{ap$>B@5 zEiSXDOq4}%^=(E;c44yOab967ayh`U+(KzhDxveJ+_kU{X|MRSTXr)EuNPfRol$>? zza|Anp&?VELslkgVU8(ciP;VYq*pGzJ*#1Db<%u=$ZSh~jPqn1JyEgv*E!3dx~cD@ zZXT5MexH6fX$4x|+wQ90fLu^xZ$sSaAij?5h~OWvX1miW=To0ENO_?S7~IPG;nN?J z!Ya5{m&py5AJ4oiG1R@zSwu&WCmpLG&{0X#X&K~wc|+v~G)|gmMHjmpWxvM5uoMLN z$eIpXVV1ZxkFEM6Z;j=4-G;g0L*Fe82tR3!HFIG@LTqjj0=1h0zVi&kdJX1&p4;_JTkgpmR+ zaDu0?BlzAd`y3iwHM+QB=Bej~`A@S~2=`2vtk#{4HoJRN$A4%33iS~~TQj{M>|z%6 zTp!v}>xzEq_#vC-v?V`fH#uWx4l6HmB?hO@gKQ+*z#G8BJThes;|~*b&2Da)v(KS6 z+zu`3(!kt$?u@nHgmczE8H<`%-!(VkIm5f+n9LKFgA*;jX7d;X)lH8{uU!ocZMg^x zWkm4dGxV59!2xs4F2*pD<+PA{S67FRxF}cTC$K{{SPV;T2*$ei^@nu^f4U>1TrMEKMhiX!}vzrB7+xf^7g)m>QCh}P~a-r1L z&K17ECAY?>!ppJMVgis}$riQj)u^DQ^Q65f9HJS8Xz#SbS4o@(w#v%Gqg=lWL~n@G zC=Ksku;ggHr*+HvAos{+zknpcl|JBRGfm7V8_!zCiQw~YiXFaD5X4gcCK{U5U^^^N zuT;HE4xdl+ZK4nT?9z!`8X!dL6-M%PnMnU2*tX+#nv5*O0@hrE#;GBt?=<%A%pYFF z+JuudNO6Z}Ag-}Do5Z*d$)|Vj(aRX@$p~Z8n!3FrPiv1PomRPI2Pg(`*BFS!-4+xXfT$&MCmj)S4b6tXiMEHmh zS!vC(0?oCftEc-0o_fxJ-F?qVhp!bH&#)3$KZ)RzTMr9sp@p*p1u2Y%eV!Z+?ic7g zNaCSw8sy4cORRNXt7}1BVIa$?jgJU*AN+uS*-geFmi@w@phTjIsGSBW9HYtW0P|d0 zm8y2eF`CCBBU1A)Tx+!372U#~=?pb-s5lv{y4Cy}ohS$KkQlk?B`x=6%2{+xm7eD6 z9IlSj%ZT0%L){g&`F8I)4Jg!Gom>o8L)#1Do(9@1yBu8D5emXyP`iVBq7Il$r-waZ zQf9ES9tKVaSsTqOrip8zu-y8>i8na3m@q|}X)jJc1S(-u2G;!^TA(!i!Bokc+Qr6c zkl0nGuTQp3ZZubyc2?i-J5vyFsQ`zjAnc%^iW0qTfeFuk!Z-yltKV^>3N3U`3Zs&D z0H;AA{$S9!C*&s#5kG%-6+PfSJuHcWD8pu>27yL5V|2pNMAfB`s6Exn)Wp01vCK2U zrcYVHn{3xsJL1W;(5sx0AgAXBpnv==kXkO zf2Ox1iS?9O;S$Zr$#xJb`C|gBWL8sI6sJDh{LZ5Cn~R!>CfEeGIe^t)WzKpMVIt_w zJORTlfhz;?J8OCNTYlUH%+Iq8UeQ^bg_zHZRc$ON-EiUgiUChAgIcURQzw)X=i+et zSX=j;#_^SF&8+`5nu$vDe{b&%%vwIr(IiTUjDhUGkQ8>@lE~;}X34yZpGkN(5t^?R z446Sz={27&(PGP9Pf-ST#pYv0jePI2_M06R7`~(gaTw95b{PDpq?Q+#up-slae;Lo zAu5Vc*ZxBsThc(31P?HJNrW3OJ`4$>D$m8$ggN76B7B?W+S+^Xe!(Fo^BI)2tVbZj z{4CBymQTV|;al-0z`gU6AG!e3pq|G+>R? z8Y9mipnSudZGrFszO)lZY$1<7W0S@7knJN@)<9L$X#d6&#&%_AyyqlBg_1Uc2e2^8 zL+SEO!tKu8MqU{_&TOYPc@3FDMpfwS1?hg%rN@fyyPuVg@Gwk#;jwMQgkUHU%KS39 zvwjmskdEP5lXm%AYh6yxEAyW`hF1~n-4_4=d{4tnuzfS+L74a*1Xo)825)L5(fq#e z;lQfFontkBCA2Czj7p%Iwa|2wE}D=e5Ik*X?EuURgRIAq%>hIKAgp_`T@duh-}IIy z;Hdh2&tBoMvUPinAn`(E59_QOb^dC=CA*Z)g%Ij-VW6SsB6xJs)Vxsy`I%bd)-*f2 z41$S~s$6fk0bJ1GH}DtH0Ai7cQ8}6C)f~onoS%4ywaOp|h_QoeJ=J5L;_8rq(o$BP z(oqvo3@S>~XDhMdA4NIZPS_>R&;>tFSY;N9-^U*f@Er@1IC;9%>RCMQ=Kg;iY}42w zTrTil94KAzU~vMSd-b@td~X|ev_TvG zbw*k6Fm1&^v$Q?G9|3}51u>MbkxZ9TqhAcYwtlJKMx z(^>eZT+$r-Z@JOwP$8|Op#@dsE>sQ#dqRf3h~gJ76`iv|ECo9E|B%e$qm(!qUYr(4 z!@Nl%8g2wBGC;w4QSv}7UV|tFTb5rizfmBoiTliPO&{~-)VsL`e2D&(w7$Q7T1aWS z_{jRl5C=ov{7Vf1*!?#Rk=7TVV^+U%;BV^(-S-@(W&N0d7p(*_99QY`#T->bQ1TMr&??^oX11tx{ z=ahq!rd!{YvmP z!bzo}CyzOgviXk^q^a)9QLYa6!sf;Nj}r{Hn^1vD?;sUUbA9`t2m+0)KAVyo65wz_vy8RD>En!;otBRq+f{U_z93Pdp?Mi52f4K2B zY-1Zyq_}^yn8RmL8qu=c)9lvcvY+|ysq|soch@mC`QI{6H}vJVbL0TV=&m@$Na+ir zUFUtE3%lEIayxG8`E*nu^YLA0f=hBpl`IJIf_`HbNGB_0z8yfJfXp!n6jbT`3ief# zURL;Q8GqVh|4guHs;cfp~l|5?VAekTZWVIpk< zgn`a?30@iau4fBpi@r_D!$YLpF=K9r*^2Q8O%P1VGz1i%j>9n%auJ$J1en0ltkbY1 zryN={8>F7?_@WI;Rdx@eYxq(d*I=t#2b0^Ey3HdQK6ra3fLYmp`=y7e=}w(DS%Y>@y&a`Uf$0faJIFNd0fS zXIm*xc!+In$@^KmN|Lv9UM-b2_DV{y^?9^m5xY66@~4AeNO@N3eR0>2%Z0s_uPcWs z(zt%m*H;cLd7NXsGRjr0bWB6jb1l@|GYq z?7r*w$$#tu7foaanTClY!VB0UX^NmSDr>ys z{+p6wgzTYo!G;ALE84uppbC$x6Ax%Tm0V5n-foaIwmmSB50ywYs`MTLl?h3eKhel` z_>Kcq6c1pRjOcJu7sA2BUnzdPnCW0XtxGH?_*+1UQ|83k&QG%kyH#!le%tzXd zGk}m2dweUPYHr-0FL@CE>-fqO8f#?(%;TJfgzA|26XOoOjuLl`#8}Lz=}Oog>)HNs zN&jEFeYd_(J z6)m(*=>{>0Uq&^oa;L#s9lRd2FtAIqb~;=C;Beb>T7g>4zR_)y$1Ykll+TNi0q2Gz zOG8+UHQUXxrZOEB@ncY(j$l2F=uYxl9zeX4=UUpGbX;z@QJ6nn{TxaN40R*|)_l2mfwchqoAPfZD|Y!dAy#t*2mjnKq# z4{`2FSjeSBu%n^!$5tpkKIZ=4Op2l3|B&|4B~MoV)d{e8=9q_Q;k~cnxQ8hBK{uDm z{enodDez4?%1M_k&EFhkq85-Y7dT{&>ug(Bh&yM4ck_K+4|9c$e9ACLV@Rp32yqQ6l0I)WKGkjeS=@7UW;F^#S z5BqiY*27lDkL=&(W}jcZeju!pa}ekF#Ziq6%THGy-G^Z+`>Vr)-95NH>Q3iz+*3P3 z&(?0$1rFi8MYRY7iou5HL2#I4MnxD#FioNiv>P?V$T8d z?sV-Ke#gJfkB@q0l)WPLX}X4cdZ-?{!&)4&S#eKCTa^VJDN&Ayy`s@r+s#$KKUor8 z9OeweMi>96deSGV7%eAjYo|wjGFSvg(f^x^dk)-7N=R4=)ehLJ2M0*La;Saq4{ZOl q**BQhFSedAWcqDde`}a9NyEASeZW&jFgP5fvTlu!#|`(Lr~U^UdO1Y^ literal 0 HcmV?d00001 diff --git a/src/lay/lay/doc/images/drc_encd2u.png b/src/lay/lay/doc/images/drc_encd2u.png new file mode 100644 index 0000000000000000000000000000000000000000..ec3615fca76c978e56e7e337dfed4f9f11d38031 GIT binary patch literal 9503 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/images/drc_enc2.png doc/images/drc_enc1u.png doc/images/drc_enc2u.png + doc/images/drc_encd1.png + doc/images/drc_encd2.png + doc/images/drc_encd1u.png + doc/images/drc_encd2u.png doc/images/drc_overlap1.png doc/images/drc_overlap2.png doc/images/drc_overlap1u.png From 319efc1f951688ba0b0ed0588afdf0a7d04de464 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 9 Nov 2021 00:32:07 +0100 Subject: [PATCH 15/19] Bugfix: negative output wasn't working properly for enclosing and overlap. --- src/db/db/dbRegionCheckUtils.cc | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/db/db/dbRegionCheckUtils.cc b/src/db/db/dbRegionCheckUtils.cc index 38cfe9f51..5b85311f2 100644 --- a/src/db/db/dbRegionCheckUtils.cc +++ b/src/db/db/dbRegionCheckUtils.cc @@ -231,12 +231,21 @@ Edge2EdgeCheckBase::add (const db::Edge *o1, size_t p1, const db::Edge *o2, size 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)); + + bool antiparallel = (mp_check->relation () == WidthRelation || mp_check->relation () == SpaceRelation); + + // pseudo1 and pseudo2 are the connecting edges of the edge pairs. Together with the + // original edges they form a quadrangle. + db::Edge pseudo1 (ep.first ().p1 (), antiparallel ? ep.second ().p2 () : ep.second ().p1 ()); + db::Edge pseudo2 (antiparallel ? ep.second ().p1 () : ep.second ().p2 (), ep.first ().p2 ()); + + m_pseudo_edges.insert (std::make_pair (pseudo1, p1)); + m_pseudo_edges.insert (std::make_pair (pseudo2, 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)); + m_pseudo_edges.insert (std::make_pair (pseudo1, p2)); + m_pseudo_edges.insert (std::make_pair (pseudo2, p2)); } + } } From ac1167278e806a1fb9844eea92e0ac34c6aedf1e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 9 Nov 2021 00:32:23 +0100 Subject: [PATCH 16/19] Updated tests with 'enclosed' rule --- testdata/drc/drcGenericTests_2.drc | 1 + testdata/drc/drcGenericTests_au2.gds | Bin 5994 -> 6506 bytes testdata/drc/drcGenericTests_au2d.gds | Bin 5598 -> 6110 bytes testdata/drc/drcSimpleTests_48.drc | 8 ++++++++ testdata/drc/drcSimpleTests_au48.gds | Bin 4586 -> 5162 bytes testdata/drc/drcSimpleTests_au48d.gds | Bin 4586 -> 5162 bytes testdata/drc/drcSuiteTests.drc | 8 ++++++++ testdata/drc/drcSuiteTests_au1.oas | Bin 14835 -> 15026 bytes testdata/drc/drcSuiteTests_au5.oas | Bin 14839 -> 15014 bytes testdata/ruby/dbEdgesTest.rb | 2 ++ testdata/ruby/dbRegionTest.rb | 2 ++ 11 files changed, 21 insertions(+) diff --git a/testdata/drc/drcGenericTests_2.drc b/testdata/drc/drcGenericTests_2.drc index 5a241e6cf..54a49540c 100644 --- a/testdata/drc/drcGenericTests_2.drc +++ b/testdata/drc/drcGenericTests_2.drc @@ -43,4 +43,5 @@ l1.drc(isolated(projection, not_opposite) < 1.0).output(144, 0) # enclosing, overlap l2.drc(enclosing(l1, projection) < 1.0).polygons.output(150, 0) l1.drc(overlap(l2, projection) < 2.0).polygons.output(151, 0) +l1.drc(enclosed(l2, projection) < 1.0).polygons.output(152, 0) diff --git a/testdata/drc/drcGenericTests_au2.gds b/testdata/drc/drcGenericTests_au2.gds index 0cd389b252822e2893851c4f63fa98f6a60862cd..2e9bb50f43b62491cbc14a420e94334f5e5dd984 100644 GIT binary patch delta 287 zcmZvVy=nqc5QWE8?!D`}fW*L#IW8EbD_FbC%qbLy8*Iq=#EQZ?*N^#m{F`AQn?Z z!XT0Xd}*+hLxk!b3G0da!`gmB&z>T&cZeK=v}h1J0hUF+<9>8zNK5>KD|Q2nTm!#s zaCFV@s65Lftg@-X0o5&=Ii$uRwSUBQHq{M6J;26$#YBH0^?2`nBhg*>jTfbeglo|P cE3cnhU@S7~3s)J(+5D98>8f=oBvGM2XYGbo@&Et; delta 232 zcmZvVF%Q9D5Qa}R->0e#)z%^g#wG?0sYPzE+Qnb6OCyHvVvzS|n8ac*h*sBY%p>dALoYLWtb}C?4~$W~%?!5SO%V2>Y8HQnX1swDq5#yL)kcaCK21 z%syU>`edF9EugIw39B?y6jlx`A8{o6DCIl$Rff_Y;MLBcRgR>=cPptBy20-z|8AUZQN@+tROxih L(W$hFOq2cqZjNT( delta 326 zcmZvUze>Yk6vb~ok}qlgv}sH>p-?ydph9qPu_5#gT*Rd>@C}YmlEFhYW{%&i$Qxx3lfZRJ15{C5Mjkf5eegL~Y|y|C*bCSjG%)lgq^C?o#@U4n zoW?V(!USDJ7M0E|p1nDiafL||ZFaFzFe6rn3*3x8U{ju_HD1#@*fBlTj`=S8gvprW h63Z-sJ=nj)B!7f0N_^)J*m1Yc&#)@awJ+kj@&}o9Rv!QW diff --git a/testdata/drc/drcSimpleTests_48.drc b/testdata/drc/drcSimpleTests_48.drc index 672fe9270..51f882378 100644 --- a/testdata/drc/drcSimpleTests_48.drc +++ b/testdata/drc/drcSimpleTests_48.drc @@ -33,9 +33,17 @@ l2.separation(l1, projection, 1.0, whole_edges).output(211, 0) l1.drc(enclosing(l2, projection) < 1.0).output(400, 0) l1.drc(enclosing(l2, whole_edges, projection) < 1.0).output(401, 0) l1.drc(enclosing(l2, projection) >= 1.0).output(402, 0) + +l2.drc(enclosed(l1, projection) < 1.0).output(403, 0) +l2.drc(enclosed(l1, whole_edges, projection) < 1.0).output(404, 0) +l2.drc(enclosed(l1, projection) >= 1.0).output(405, 0) + l1.enclosing(l2, projection, 1.0).output(410, 0) l1.enclosing(l2, projection, 1.0, whole_edges).output(411, 0) +l2.enclosed(l1, projection, 1.0).output(412, 0) +l2.enclosed(l1, projection, 1.0, whole_edges).output(413, 0) + l1.drc(overlap(l2, projection) < 1.0).output(500, 0) l1.drc(overlap(l2, whole_edges, projection) < 1.0).output(501, 0) l1.drc(overlap(l2, projection) >= 1.0).output(502, 0) diff --git a/testdata/drc/drcSimpleTests_au48.gds b/testdata/drc/drcSimpleTests_au48.gds index 4f81f169aff29b6fbee71375829e1fb6b63f39a6..e70a42f47b2b2235da6b559fc3284cf8afbac336 100644 GIT binary patch delta 164 zcmaE*yh=lffsKKQDS|dH>f|+SQfdqgqPh$W%)C&V`~Uy9pMiXC1{OgE1{Mwm1~y(Mpi&(!kcJ!x$qm-b VZ9jQ6UjySDu#s~&zv27G1OWfdB7y(_ delta 77 zcmZ3b@k&{VfsKKQDS|vy z=GWW@m>4Hd&f&3VoHF?hpFQK$$!pl8)EF2o3o)Hq= diff --git a/testdata/drc/drcSuiteTests.drc b/testdata/drc/drcSuiteTests.drc index 35615d564..34ea0613c 100644 --- a/testdata/drc/drcSuiteTests.drc +++ b/testdata/drc/drcSuiteTests.drc @@ -134,6 +134,10 @@ def run_testsuite(dm, ic, tiled = false, hier = false) a.enclosing(b, 0.5, whole_edges).second_edges.extended_out(0.01).output(lb + 1, dm) a.enclosing(b, 0.5, whole_edges).edges.extended_out(0.01).output(lb + 2, dm) + b.enclosed(a, 0.5, whole_edges).second_edges.extended_out(0.01).output(lb + 3, dm) + b.enclosed(a, 0.5, whole_edges).first_edges.extended_out(0.01).output(lb + 4, dm) + b.enclosed(a, 0.5, whole_edges).edges.extended_out(0.01).output(lb + 5, dm) + lb += 10 #150 message "--- hulls, holes #{lb}" @@ -323,6 +327,8 @@ def run_testsuite(dm, ic, tiled = false, hier = false) else a.enclosing(b, 0.5, euclidian, projection_limits(0, 0.4)).polygons.output(lb + 7, dm) end + + b.enclosed(a, 0.5, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 8, dm) lb += 10 #220 message "--- enclosing (edges) #{lb}" @@ -343,6 +349,8 @@ def run_testsuite(dm, ic, tiled = false, hier = false) ae.enclosing(be, 0.5, euclidian, projection_limits(0, 0.4)).polygons.output(lb + 7, dm) end + be.enclosed(ae, 0.5, euclidian, whole_edges).polygons.output(lb + 8, dm) + lb += 10 #230 message "--- isolated #{lb}" diff --git a/testdata/drc/drcSuiteTests_au1.oas b/testdata/drc/drcSuiteTests_au1.oas index 0dd5c6851ff1490e8cb2a9f2f7bc6beb35af837b..dfe13b4c449edfa62a173097271f8516dddd534a 100644 GIT binary patch delta 147 zcmexdys31Q|3 rDY#N(bB@?=7P*5w6PO!n7t2p%UMPRk7@`IwGLd;AGf+*dbe`*-Kng=P=I+#s=?&Y$q9adfxOGwE&vL(_nOv=Op9B zsFOaT8eo3PVIcpe|K|DPMl2|%9FQ^L;AS|(At0)_c=9|sWw`MNWV}c+dh&d6^U1lg r7Lzx~iph!Cq<|d5&6uKOt01bljERMLv(gF11B)0z?%aG^Rz?N@fDCYa delta 299 zcmZ2h`n`C=3bxH`>=JyN?L=D`g`P38FdyP+FzV$w$#^m9q>re@WJW3X$$7GJlgq?q z_;5(gmlT^UDIu%_l0FF6$-;b?=LBPe_d>Rlj5|GV`imNX%}zNCR4bvXhjhx&JDcWKp6(< Date: Tue, 9 Nov 2021 21:55:55 +0100 Subject: [PATCH 17/19] Restored performance of some checks --- src/db/db/dbRegionCheckUtils.cc | 28 +++++++++++++++ src/db/db/dbRegionCheckUtils.h | 3 ++ src/db/db/dbRegionLocalOperations.cc | 54 +++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/db/db/dbRegionCheckUtils.cc b/src/db/db/dbRegionCheckUtils.cc index 5b85311f2..db3434914 100644 --- a/src/db/db/dbRegionCheckUtils.cc +++ b/src/db/db/dbRegionCheckUtils.cc @@ -468,6 +468,34 @@ poly2poly_check::enter (const PolygonType &o, size_t p) } } +// TODO: move to generic header +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 &o, size_t p, const poly2poly_check::box_type &box) +{ + if (box.empty ()) { + return; + } + + for (typename PolygonType::polygon_edge_iterator e = o.begin_edge (); ! e.at_end (); ++e) { + if (interact (box, *e)) { + m_edge_heap.push_back (*e); + m_scanner.insert (& m_edge_heap.back (), p); + } + } +} + template void poly2poly_check::process () diff --git a/src/db/db/dbRegionCheckUtils.h b/src/db/db/dbRegionCheckUtils.h index d8311980d..08297468d 100644 --- a/src/db/db/dbRegionCheckUtils.h +++ b/src/db/db/dbRegionCheckUtils.h @@ -309,6 +309,8 @@ template class DB_PUBLIC poly2poly_check { public: + typedef typename PolygonType::box_type box_type; + poly2poly_check (Edge2EdgeCheckBase &output); poly2poly_check (); @@ -318,6 +320,7 @@ public: void connect (Edge2EdgeCheckBase &output); void enter (const PolygonType &o, size_t p); + void enter (const PolygonType &o, size_t p, const box_type &search_box); void process (); private: diff --git a/src/db/db/dbRegionLocalOperations.cc b/src/db/db/dbRegionLocalOperations.cc index 339f5e2dd..ac873dc06 100644 --- a/src/db/db/dbRegionLocalOperations.cc +++ b/src/db/db/dbRegionLocalOperations.cc @@ -218,12 +218,40 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape } } + bool take_all = edge_check.has_negative_edge_output () || interactions.num_intruders () == 0; + + db::Box common_box; + if (! take_all) { + + db::Vector e (edge_check.distance (), edge_check.distance ()); + + db::Box subject_box; + for (typename shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { + subject_box += db::box_convert () (interactions.subject_shape (i->first)); + } + + if (edge_check.requires_different_layers ()) { + db::Box intruder_box; + for (std::set::const_iterator id = ids.begin (); id != ids.end (); ++id) { + intruder_box += db::box_convert () (interactions.intruder_shape (*id).second); + } + common_box = subject_box.enlarged (e) & intruder_box.enlarged (e); + } else { + common_box = subject_box.enlarged (e); + } + + } + if (m_has_other) { size_t n = 0; for (typename shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { const TS &subject = interactions.subject_shape (i->first); - m_poly_check.enter (subject, n); + if (! take_all) { + m_poly_check.enter (subject, n, common_box); + } else { + m_poly_check.enter (subject, n); + } n += 2; } @@ -261,7 +289,11 @@ 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) { - m_poly_check.enter (*o, n); + if (! take_all) { + m_poly_check.enter (*o, n, common_box); + } else { + m_poly_check.enter (*o, n); + } n += 2; } @@ -269,7 +301,11 @@ 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) { - m_poly_check.enter (interactions.intruder_shape (*id).second, n); + if (! take_all) { + m_poly_check.enter (interactions.intruder_shape (*id).second, n, common_box); + } else { + m_poly_check.enter (interactions.intruder_shape (*id).second, n); + } n += 2; } @@ -285,7 +321,11 @@ 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); - m_poly_check.enter (ts, n); + if (! take_all) { + m_poly_check.enter (ts, n, common_box); + } else { + m_poly_check.enter (ts, n); + } n += 2; } @@ -294,7 +334,11 @@ 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 ()) { - m_poly_check.enter (ti, n); + if (! take_all) { + m_poly_check.enter (ti, n, common_box); + } else { + m_poly_check.enter (ti, n); + } n += 2; } } From d2478881b50507b3ca9b2dd65ea5a5213848cae0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 10 Nov 2021 22:30:32 +0100 Subject: [PATCH 18/19] Bugfix: threading issue - multiple threads accessed the same object --- src/db/db/dbRegionLocalOperations.cc | 26 +++++++++++++------------- src/db/db/dbRegionLocalOperations.h | 1 - 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/db/db/dbRegionLocalOperations.cc b/src/db/db/dbRegionLocalOperations.cc index ac873dc06..6c14bc5d1 100644 --- a/src/db/db/dbRegionLocalOperations.cc +++ b/src/db/db/dbRegionLocalOperations.cc @@ -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_poly_check () + : m_check (check), m_different_polygons (different_polygons), m_has_other (has_other), m_other_is_merged (other_is_merged), m_options (options) { // .. nothing yet .. } @@ -206,7 +206,7 @@ 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); - m_poly_check.connect (edge_check); + poly2poly_check poly_check (edge_check); std::list heap; std::unordered_set polygons; @@ -248,9 +248,9 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape for (typename shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { const TS &subject = interactions.subject_shape (i->first); if (! take_all) { - m_poly_check.enter (subject, n, common_box); + poly_check.enter (subject, n, common_box); } else { - m_poly_check.enter (subject, n); + poly_check.enter (subject, n); } n += 2; } @@ -290,9 +290,9 @@ 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) { if (! take_all) { - m_poly_check.enter (*o, n, common_box); + poly_check.enter (*o, n, common_box); } else { - m_poly_check.enter (*o, n); + poly_check.enter (*o, n); } n += 2; } @@ -302,9 +302,9 @@ 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) { if (! take_all) { - m_poly_check.enter (interactions.intruder_shape (*id).second, n, common_box); + poly_check.enter (interactions.intruder_shape (*id).second, n, common_box); } else { - m_poly_check.enter (interactions.intruder_shape (*id).second, n); + poly_check.enter (interactions.intruder_shape (*id).second, n); } n += 2; } @@ -322,9 +322,9 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape const TS &ts = interactions.subject_shape (i->first); insert_into_hash (polygons, ts); if (! take_all) { - m_poly_check.enter (ts, n, common_box); + poly_check.enter (ts, n, common_box); } else { - m_poly_check.enter (ts, n); + poly_check.enter (ts, n); } n += 2; } @@ -335,9 +335,9 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape const TI &ti = interactions.intruder_shape (*id).second; if (polygons.find (ti) == polygons.end ()) { if (! take_all) { - m_poly_check.enter (ti, n, common_box); + poly_check.enter (ti, n, common_box); } else { - m_poly_check.enter (ti, n); + poly_check.enter (ti, n); } n += 2; } @@ -346,7 +346,7 @@ check_local_operation::do_compute_local (db::Layout *layout, const shape } do { - m_poly_check.process (); + 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 b8f8b8b3f..40ca85057 100644 --- a/src/db/db/dbRegionLocalOperations.h +++ b/src/db/db/dbRegionLocalOperations.h @@ -219,7 +219,6 @@ 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; From 7ca01cdae929191a3bf5bbf79d3aa12851302c5a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 11 Nov 2021 23:05:28 +0100 Subject: [PATCH 19/19] Updated test data --- testdata/drc/drcSuiteTests_au2.oas | Bin 67234 -> 67811 bytes testdata/drc/drcSuiteTests_au3.oas | Bin 1339832 -> 1347251 bytes testdata/drc/drcSuiteTests_au4.oas | Bin 67020 -> 67597 bytes testdata/drc/drcSuiteTests_au6.oas | Bin 20761 -> 20936 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/testdata/drc/drcSuiteTests_au2.oas b/testdata/drc/drcSuiteTests_au2.oas index 6ccbca6146ccda5946f63469f2d1a68900e4796c..68929488acc35a2c0229b39aca9e15c3fa10d99a 100644 GIT binary patch delta 142 zcmZ3~%kp?4%Z3O^7LnS1#>ov5dnP+b2w2rlVBEmT^&;pA7lTL**BOp0K|esO8!SMv z2{H^KwG$Z;qHvWlH&`Z2UdX$Bv$f>x1jbvFIVu!3U#;|F;(x-(!hA}#!TX#lSo`Fh cN(Fus7Us>I9ejrhHge7s{9&_Mrlze606f++wEzGB delta 24 gcmaDnk!4XY%Z3Qa&1TYd37fA~dopcKsB7*50EB4@oB#j- diff --git a/testdata/drc/drcSuiteTests_au3.oas b/testdata/drc/drcSuiteTests_au3.oas index c42f0b09822b5be44a3b8e4660777d93182ad8c6..df46e93b86c1b6b3c46dc1f030611d92494f76be 100644 GIT binary patch delta 36845 zcmbrn2Y6J~);FGg>de`b>66|_2m}Z%fP@Z$q97eq1ZfUkidX=p3;|-K4h&==b?5;D z1Ol9hAVUOhRD{qZ^Z_YBTEL(Y0l&4+B$Ru-@Av+{|NA`m4YSYQYp=b^Z>?R;`=v|n zvaW7-%^70OdQt|An#mMtp8ZxhnC@)V&V1R6&ODdAtGckCGgopLMN0$@(Xr)k7@CRDsWm z^(C&0J>-wR^-J#SN(Kql9nyKF(@5_De_ANT|GCx)x0dssd)`pbRr-1ERKpNXpCs_B zVha+lwej@dncxXFnc`9fuGo6bI_rvj>le|HDhT-d^x~ar}NzOYi!j#W=8?Dbe zbhXD08^hMmq&UybziqjTO*M!|unYmu9jncA+8ip;pu#_7tLL8C<{2&*H}r+O^Gne!AAW(aj!Jp>gj1vbFsNdaP#FP z*P9sCzQaOc|1Z4V6ICm-x$Yb-5~j;V!Zf*9o-W^zOHM@py z)N+ucK9NfKqoy*&Z{24=vX>L!gPVLYZ@$~;XHaELrwBV^!;LmK#8$6$WACbHAmC8gnA)A|H za9@r0>^>L|vYl)P|0{g0T)Fu|DOdmFX&G8Z>D%Q_J@69Y#}x^lrpIf7dAzk_uCvp6 z0uSbjt#aS*@*V+wO|mfe*HdLuZsT{-MLrq(op*Hr_}}Ga�@>xeu<+5pw;`mK-El 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≥ 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�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 t@f diff --git a/testdata/drc/drcSuiteTests_au6.oas b/testdata/drc/drcSuiteTests_au6.oas index 1b47dc5fb4f7b8ade76a1516a1a4a1c97448f714..f5341e4e3b8931c57e2f10eede6be245ee5fae46 100644 GIT binary patch delta 319 zcmbQai1EZ?#toa#~O=P}m$IY;gS4gCSYYFdC=7V-3l@lfxvPm*doZQVO z#RnC$IK#1p@ulVF&1|*2jJGxmiY#Unc*4lSeCP_}In`sGj1z$xC-aNyGd*LR>@Tf0 z`JtS`WEDxF$pT{LlLMtykoeM*cZq509OgN}*x-tc#&lEWPWk;$#-Qe qCM(E_$%)vcfE>fkn4)B>AgZ{GiG_Ky(h0@`ix@%f-0UrT&KLmtzi6re delta 301 zcmX@Hm~rMJ#toa;~vB{m{!a5-7gK(WJ%!heSFgAEEWIM^Y)AOdkr~%mQl*3>Zn@@-vv0yjQ zT#}pN42OWIvtB%_sNCT9~DrU`*j=Oi{8`5Gh&1w1;&gCpSZiwwOr8 h3C1-{oen3BMJ<;Bt=z11g7Ls2Mwq)d|B^jt3;=TYYTW<;