diff --git a/setup.py b/setup.py index 46e44fd58..744b46547 100644 --- a/setup.py +++ b/setup.py @@ -805,11 +805,13 @@ for pi in dbpi_dirs: mod_name = "_" + os.path.split(os.path.split(pi)[-2])[-1] + "_dbpi" pi_sources = glob.glob(os.path.join(pi, "*.cc")) + pi_sources += glob.glob(os.path.join(pi, "contrib", "*.cc")) pi_ext = Library( config.root + ".db_plugins." + mod_name, define_macros=config.macros() + [("MAKE_DB_PLUGIN_LIBRARY", 1)], include_dirs=[ + pi, os.path.join("src", "plugins", "common"), _db_path, _tl_path, diff --git a/src/buddies/src/bd/strmcmp.cc b/src/buddies/src/bd/strmcmp.cc index 637baaab0..aefcefe6f 100644 --- a/src/buddies/src/bd/strmcmp.cc +++ b/src/buddies/src/bd/strmcmp.cc @@ -41,6 +41,7 @@ BD_PUBLIC int strmcmp (int argc, char *argv[]) std::string infile_a, infile_b; std::string top_a, top_b; bool silent = false; + bool ignore_duplicates = false; bool no_text_orientation = true; bool no_text_details = true; bool no_properties = false; @@ -106,6 +107,10 @@ BD_PUBLIC int strmcmp (int argc, char *argv[]) << tl::arg ("--expand-arrays", &flatten_array_insts, "Expands array instances before compare", "With this option, arrays are equivalent single instances are treated identical." ) + << tl::arg ("-1|--ignore-duplicates", &ignore_duplicates, "Ignore duplicate instances and shapes", + "With this option, duplicate instances or shapes are ignored and duplication " + "does not count as a difference." + ) << tl::arg ("-l|--layer-details", &dont_summarize_missing_layers, "Prints details about differences for missing layers", "With this option, missing layers are treated as \"empty\" and details about differences to " "other, non-empty layers are printed. Essentially the content of the non-empty counterpart " @@ -155,6 +160,9 @@ BD_PUBLIC int strmcmp (int argc, char *argv[]) if (silent) { flags |= db::layout_diff::f_silent; } + if (ignore_duplicates) { + flags |= db::layout_diff::f_ignore_duplicates; + } if (no_text_orientation) { flags |= db::layout_diff::f_no_text_orientation; } diff --git a/src/db/db/dbBoxScanner.h b/src/db/db/dbBoxScanner.h index 5b562bee2..d329316f4 100644 --- a/src/db/db/dbBoxScanner.h +++ b/src/db/db/dbBoxScanner.h @@ -538,7 +538,7 @@ public: * @brief Default ctor */ box_scanner2 (bool report_progress = false, const std::string &progress_desc = std::string ()) - : m_fill_factor (2), m_scanner_thr (100), + : m_fill_factor (2), m_scanner_thr (100), m_scanner_thr1 (10), m_report_progress (report_progress), m_progress_desc (progress_desc) { // .. nothing yet .. @@ -564,6 +564,26 @@ public: return m_scanner_thr; } + /** + * @brief Sets the scanner threshold per class + * + * This value determines for how many elements in one class the implementation switches to the scanner + * implementation instead of the plain element-by-element interaction test. + * The default value is 10. + */ + void set_scanner_threshold1 (size_t n) + { + m_scanner_thr1 = n; + } + + /** + * @brief Gets the scanner threshold per class + */ + size_t scanner_threshold1 () const + { + return m_scanner_thr1; + } + /** * @brief Sets the fill factor * @@ -667,7 +687,7 @@ private: container_type1 m_pp1; container_type2 m_pp2; double m_fill_factor; - size_t m_scanner_thr; + size_t m_scanner_thr, m_scanner_thr1; bool m_report_progress; std::string m_progress_desc; @@ -732,7 +752,7 @@ private: rec.finish2 (i->first, i->second); } - } else if (m_pp1.size () + m_pp2.size () <= m_scanner_thr) { + } else if (m_pp1.size () + m_pp2.size () <= m_scanner_thr || m_pp2.size () <= m_scanner_thr1 || m_pp1.size () <= m_scanner_thr1) { // below m_scanner_thr elements use the brute force approach which is faster in that case diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index 64efb2aaf..83acc1182 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -187,6 +187,17 @@ Cell::clear (unsigned int index) } } +void +Cell::clear (unsigned int index, unsigned int types) +{ + shapes_map::iterator s = m_shapes_map.find(index); + if (s != m_shapes_map.end() && ! s->second.empty ()) { + mp_layout->invalidate_bboxes (index); // HINT: must come before the change is done! + s->second.clear (types); + m_bbox_needs_update = true; + } +} + Cell::shapes_type & Cell::shapes (unsigned int index) { @@ -344,6 +355,20 @@ Cell::copy (unsigned int src, unsigned int dest) } } +void +Cell::copy (unsigned int src, unsigned int dest, unsigned int types) +{ + if (src != dest) { + shapes (dest).insert (shapes (src), types); + } else { + // When duplicating the layer, first create a copy to avoid problems with non-stable containers + // Hint: using the assignment and not the copy ctor does not copy the db::Manager association. + db::Shapes shape_copy; + shape_copy.insert (shapes (src), types); + shapes (dest).insert (shape_copy); + } +} + void Cell::move (unsigned int src, unsigned int dest) { @@ -353,6 +378,15 @@ Cell::move (unsigned int src, unsigned int dest) } } +void +Cell::move (unsigned int src, unsigned int dest, unsigned int types) +{ + if (src != dest) { + copy (src, dest, types); + clear (src, types); + } +} + void Cell::swap (unsigned int i1, unsigned int i2) { diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index 00319ee08..2d04bc0ba 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -182,6 +182,13 @@ public: */ void copy (unsigned int src, unsigned int dest); + /** + * @brief Copy the shapes from layer src to dest (only shapes from given classes) + * + * The target layer is not overwritten. Instead, the shapes are added to the target layer's shapes. + */ + void copy (unsigned int src, unsigned int dest, unsigned int types); + /** * @brief Move the shapes from layer src to dest * @@ -189,6 +196,13 @@ public: */ void move (unsigned int src, unsigned int dest); + /** + * @brief Move the shapes from layer src to dest (only shapes from given classes) + * + * The target layer is not overwritten. Instead, the shapes are added to the target layer's shapes. + */ + void move (unsigned int src, unsigned int dest, unsigned int types); + /** * @brief Swap the layers given */ @@ -199,7 +213,12 @@ public: */ void clear (unsigned int index); - /** + /** + * @brief Clear the shapes on the given layer (only the shapes from the given classes) + */ + void clear (unsigned int index, unsigned int types); + + /** * @brief Erase a cell instance given by a instance proxy * * Erasing a cell instance will destroy the sorting order and invalidate diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index 0d7d725de..bed425c3d 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -962,10 +962,31 @@ DeepRegion::xor_with (const Region &other, db::PropertyConstraint property_const // Implement XOR as (A-B)+(B-A) - only this implementation // is compatible with the local processor scheme - DeepLayer n1 (and_or_not_with (other_deep, false, property_constraint)); - DeepLayer n2 (other_deep->and_or_not_with (this, false, property_constraint)); + // Prepare a version of "other_deep" that is mapped into the hierarchy space + // of "this" + std::unique_ptr other_deep_mapped; + if (&other_deep->deep_layer ().layout () == &deep_layer ().layout ()) { + // shallow copy for reconfiguration (progress etc.) + other_deep_mapped.reset (new DeepRegion (other_deep->deep_layer ())); + } else { + // deep copy with mapped hierarchy + other_deep_mapped.reset (new DeepRegion (deep_layer ().derived ())); + other_deep_mapped->deep_layer ().add_from (other_deep->deep_layer ()); + } + + other_deep_mapped->set_strict_handling (strict_handling ()); + other_deep_mapped->set_base_verbosity (base_verbosity ()); + if (report_progress ()) { + other_deep_mapped->enable_progress (progress_desc () + tl::to_string (tr (" - reverse part"))); + } else { + other_deep_mapped->disable_progress (); + } + + DeepLayer n1 (and_or_not_with (other_deep_mapped.get (), false, property_constraint)); + DeepLayer n2 (other_deep_mapped->and_or_not_with (this, false, property_constraint)); n1.add_from (n2); + return new DeepRegion (n1); } diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index e69057b84..9987451e2 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -64,6 +64,11 @@ cronology::events::event_collection +static void dump_cell_contexts (local_processor_contexts &contexts, const db::Layout *subject_layout, const db::Layout *intruder_layout) +{ + for (auto cc = contexts.begin (); cc != contexts.end (); ++cc) { + tl::info << "Cell " << subject_layout->cell_name (cc->first->cell_index ()) << ":"; + int i = 0; + for (auto c = cc->second.begin (); c != cc->second.end (); ++c) { + tl::info << " Context #" << ++i; + tl::info << " Instances:"; + for (auto i = c->first.first.begin (); i != c->first.first.end (); ++i) { + const db::CellInstArray &ci = *i; + tl::info << " " << intruder_layout->cell_name (ci.object ().cell_index ()) << " @ " << ci.complex_trans (*ci.begin ()).to_string () << " (" << ci.size () << ")"; + } + tl::info << " Shapes:"; + for (auto i = c->first.second.begin (); i != c->first.second.end (); ++i) { + for (auto s = i->second.begin (); s != i->second.end (); ++s) { + tl::info << " " << intruder_layout->get_properties (i->first).to_string () << ": " << s->to_string (); + } + } + } + } +} + // --------------------------------------------------------------------------------------------- // LocalProcessorCellContext implementation @@ -1208,7 +1239,8 @@ private: void collect_instance_interactions (const db::CellInstArray *inst1, const db::CellInstArray *inst2) { - // TODO: this algorithm is not in particular effective for identical arrays + // TODO: this algorithm is not in particular effective for identical arrays or for arrays + // vs. single instances const db::Cell &cell1 = mp_subject_layout->cell (inst1->object ().cell_index ()); const db::Cell &cell2 = mp_intruder_layout->cell (inst2->object ().cell_index ()); @@ -1247,7 +1279,7 @@ private: db::Box ibox2 = tn2 * cell2.bbox (m_intruder_layer).enlarged (db::Vector (m_dist, m_dist)); db::Box cbox = ibox1 & ibox2; - if (! cbox.empty () && cell1.has_shapes_touching (m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1))) { + if (! cbox.empty () && (cbox == ibox1 || cell1.has_shapes_touching (m_subject_layer, safe_box_enlarged (tni1 * cbox, -1, -1)))) { db::ICplxTrans tn21 = tni1 * tn2; @@ -1282,9 +1314,11 @@ private: 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 ()) { + // do not recurse further if we're overlapping with shapes from the intruder + // or the intruder cell is not much bigger than the region of interest (cbox) + if (intruder_cell.bbox (m_intruder_layer).area () < area_ratio_for_recursion * cbox.area () + || ! 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 interactions.push_back (std::make_pair (intruder_cell.cell_index (), tn21)); return; @@ -1782,6 +1816,11 @@ template void local_processor::compute_results (local_processor_contexts &contexts, const local_operation *op, const std::vector &output_layers) const { +#if 0 + // debugging + dump_cell_contexts (contexts, mp_subject_layout, mp_intruder_layout ? mp_intruder_layout : mp_subject_layout); +#endif + tl::SelfTimer timer (tl::verbosity () > m_base_verbosity + 10, tl::to_string (tr ("Computing results for ")) + description (op)); // avoids updates while we work on the layout diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 0b53b70a1..32bba3042 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1971,7 +1971,19 @@ Layout::move_layer (unsigned int src, unsigned int dest) } } -void +void +Layout::move_layer (unsigned int src, unsigned int dest, unsigned int flags) +{ + tl_assert (m_layers.layer_state (src) != LayoutLayers::Free); + tl_assert (m_layers.layer_state (dest) != LayoutLayers::Free); + + // move the shapes + for (iterator c = begin (); c != end (); ++c) { + c->move (src, dest, flags); + } +} + +void Layout::copy_layer (unsigned int src, unsigned int dest) { tl_assert (m_layers.layer_state (src) != LayoutLayers::Free); @@ -1983,7 +1995,19 @@ Layout::copy_layer (unsigned int src, unsigned int dest) } } -void +void +Layout::copy_layer (unsigned int src, unsigned int dest, unsigned int flags) +{ + tl_assert (m_layers.layer_state (src) != LayoutLayers::Free); + tl_assert (m_layers.layer_state (dest) != LayoutLayers::Free); + + // copy the shapes + for (iterator c = begin (); c != end (); ++c) { + c->copy (src, dest, flags); + } +} + +void Layout::clear_layer (unsigned int n) { tl_assert (m_layers.layer_state (n) != LayoutLayers::Free); @@ -1994,7 +2018,18 @@ Layout::clear_layer (unsigned int n) } } -void +void +Layout::clear_layer (unsigned int n, unsigned int flags) +{ + tl_assert (m_layers.layer_state (n) != LayoutLayers::Free); + + // clear the shapes + for (iterator c = begin (); c != end (); ++c) { + c->clear (n, flags); + } +} + +void Layout::delete_layer (unsigned int n) { tl_assert (m_layers.layer_state (n) != LayoutLayers::Free); diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index cb79d2a8a..353ad2ad9 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -1377,7 +1377,15 @@ public: */ void move_layer (unsigned int src, unsigned int dest); - /** + /** + * @brief Move a layer (selected shape types only) + * + * Move a layer from the source to the target. The target is not cleared before, so that this method + * merges shapes from the source with the target layer. The source layer is empty after that operation. + */ + void move_layer (unsigned int src, unsigned int dest, unsigned int flags); + + /** * @brief Copy a layer * * Copy a layer from the source to the target. The target is not cleared before, so that this method @@ -1385,14 +1393,29 @@ public: */ void copy_layer (unsigned int src, unsigned int dest); - /** + /** + * @brief Copy a layer (selected shape types only) + * + * Copy a layer from the source to the target. The target is not cleared before, so that this method + * merges shapes from the source with the target layer. + */ + void copy_layer (unsigned int src, unsigned int dest, unsigned int flags); + + /** * @brief Clear a layer * * Clears the layer: removes all shapes. */ void clear_layer (unsigned int n); - /** + /** + * @brief Clear a layer (selected shapes only) + * + * Clears the layer: removes the shapes of the type given the flags (ShapeIterator::shapes_type) + */ + void clear_layer (unsigned int n, unsigned int flags); + + /** * @brief Delete a layer * * This does free the shapes of the cells and remembers the diff --git a/src/db/db/dbLayoutDiff.cc b/src/db/db/dbLayoutDiff.cc index d52ec900e..be1e005e7 100644 --- a/src/db/db/dbLayoutDiff.cc +++ b/src/db/db/dbLayoutDiff.cc @@ -69,8 +69,10 @@ collect_cells (const db::Layout &l, const db::Cell *top, std::map &cci, std::vector &insts) +collect_insts_of_unmapped_cells (const db::Layout & /*l*/, const db::Cell *cell, unsigned int /*flags*/, const std::map &cci, std::vector &insts, bool no_duplicates) { + size_t n_before = insts.size (); + for (db::Cell::const_iterator i = cell->begin (); !i.at_end (); ++i) { std::map ::const_iterator ccii = cci.find (i->cell_index ()); @@ -81,6 +83,13 @@ collect_insts_of_unmapped_cells (const db::Layout & /*l*/, const db::Cell *cell, } } + + if (no_duplicates) { + + std::sort (insts.begin () + n_before, insts.end ()); + insts.erase (std::unique (insts.begin () + n_before, insts.end ()), insts.end ()); + + } } static void @@ -102,7 +111,7 @@ rewrite_instances_to (std::vector &insts, unsi } static void -collect_insts (const db::Layout & /*l*/, const db::Cell *cell, unsigned int flags, const std::map &cci, std::vector &insts, PropertyMapper &pn) +collect_insts (const db::Layout & /*l*/, const db::Cell *cell, unsigned int flags, const std::map &cci, std::vector &insts, PropertyMapper &pn, bool no_duplicates) { insts.clear (); @@ -148,6 +157,10 @@ collect_insts (const db::Layout & /*l*/, const db::Cell *cell, unsigned int flag } std::sort (insts.begin (), insts.end ()); + + if (no_duplicates) { + insts.erase (std::unique (insts.begin (), insts.end ()), insts.end ()); + } } /** @@ -178,10 +191,10 @@ int compare_seq (I b1, I e1, I b2, I e2, Op op) /** * @brief Reduces two vectors to the common objects as determined by the compare operator * If the iterate parameter is true, the reduction is repeated until no more reduction can be - * achieved. This is useful with tolerances since the sorted is not strict in that case. + * achieved. This is useful with tolerances since the sorting is not strict in that case. */ template -void reduce (std::vector &a, std::vector &b, Op op, bool iterate) +void reduce (std::vector &a, std::vector &b, Op op, bool iterate, bool no_duplicates) { do { @@ -196,12 +209,29 @@ void reduce (std::vector &a, std::vector &b, Op op, bool iterate) while (ra != a.end () && rb != b.end ()) { if (op (*ra, *rb)) { - *wa++ = *ra++; + typename std::vector::const_iterator r = ra++; + *wa = *r; + while (no_duplicates && ra != a.end () && !op (*ra, *r) && !op(*r, *ra)) { + ++ra; + } + ++wa; } else if (op (*rb, *ra)) { - *wb++ = *rb++; + typename std::vector::const_iterator r = rb++; + *wb = *r; + while (no_duplicates && rb != b.end () && !op (*rb, *r) && !op(*r, *rb)) { + ++rb; + } + ++wb; } else { - ++ra; - ++rb; + typename std::vector::const_iterator r; + r = ra++; + while (no_duplicates && ra != a.end () && !op (*ra, *r) && !op(*r, *ra)) { + ++ra; + } + r = rb++; + while (no_duplicates && rb != b.end () && !op (*rb, *r) && !op(*r, *rb)) { + ++rb; + } } } @@ -211,14 +241,22 @@ void reduce (std::vector &a, std::vector &b, Op op, bool iterate) if (ra != wa) { while (ra != a.end ()) { - *wa++ = *ra++; + typename std::vector::const_iterator r = ra++; + *wa++ = *r; + while (no_duplicates && ra != a.end () && !op (*ra, *r) && !op(*r, *ra)) { + ++ra; + } } a.erase (wa, a.end ()); } if (rb != wb) { while (rb != b.end ()) { - *wb++ = *rb++; + typename std::vector::const_iterator r = rb++; + *wb++ = *r; + while (no_duplicates && rb != b.end () && !op (*rb, *r) && !op(*r, *rb)) { + ++rb; + } } b.erase (wb, b.end ()); } @@ -405,7 +443,7 @@ struct PolygonCompareOpWithTolerance m_eb.push_back (*e); } - reduce (m_ea, m_eb, EdgeCompareOpWithTolerance (m_tolerance), m_tolerance > 0); + reduce (m_ea, m_eb, EdgeCompareOpWithTolerance (m_tolerance), m_tolerance > 0, false); return compare_seq (m_ea.begin (), m_ea.end (), m_eb.begin (), m_eb.end (), EdgeCompareOpWithTolerance (m_tolerance)) < 0; } @@ -665,6 +703,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout } bool verbose = (flags & layout_diff::f_verbose); + bool no_duplicates = (flags & layout_diff::f_ignore_duplicates); db::Layout n, na, nb; na.properties_repository () = a.properties_repository (); @@ -897,20 +936,20 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout r.bbox_differs (cell_a->bbox (), cell_b->bbox ()); } - collect_insts (a, cell_a, flags, common_cell_indices_a, insts_a, prop_normalize_a); - collect_insts (b, cell_b, flags, common_cell_indices_b, insts_b, prop_normalize_b); + collect_insts (a, cell_a, flags, common_cell_indices_a, insts_a, prop_normalize_a, no_duplicates); + collect_insts (b, cell_b, flags, common_cell_indices_b, insts_b, prop_normalize_b, no_duplicates); std::vector anotb; std::set_difference (insts_a.begin (), insts_a.end (), insts_b.begin (), insts_b.end (), std::back_inserter (anotb)); rewrite_instances_to (anotb, flags, common_cells_a, prop_remap_to_a); - collect_insts_of_unmapped_cells (a, cell_a, flags, common_cell_indices_a, anotb); + collect_insts_of_unmapped_cells (a, cell_a, flags, common_cell_indices_a, anotb, no_duplicates); std::vector bnota; std::set_difference (insts_b.begin (), insts_b.end (), insts_a.begin (), insts_a.end (), std::back_inserter (bnota)); rewrite_instances_to (bnota, flags, common_cells_b, prop_remap_to_b); - collect_insts_of_unmapped_cells (b, cell_b, flags, common_cell_indices_b, bnota); + collect_insts_of_unmapped_cells (b, cell_b, flags, common_cell_indices_b, bnota, no_duplicates); if (! anotb.empty () || ! bnota.empty ()) { @@ -979,7 +1018,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_polygons (b, cell_b, layer_b, flags, polygons_b, prop_normalize_b); } - reduce (polygons_a, polygons_b, make_polygon_compare_func (tolerance), tolerance > 0); + reduce (polygons_a, polygons_b, make_polygon_compare_func (tolerance), tolerance > 0, no_duplicates); if (!polygons_a.empty () || !polygons_b.empty ()) { differs = true; @@ -1007,7 +1046,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_paths (b, cell_b, layer_b, flags, paths_b, prop_normalize_b); } - reduce (paths_a, paths_b, make_path_compare_func (tolerance), tolerance > 0); + reduce (paths_a, paths_b, make_path_compare_func (tolerance), tolerance > 0, no_duplicates); if (!paths_a.empty () || !paths_b.empty ()) { differs = true; @@ -1034,7 +1073,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_texts (b, cell_b, layer_b, flags, texts_b, prop_normalize_b); } - reduce (texts_a, texts_b, make_text_compare_func (tolerance), tolerance > 0); + reduce (texts_a, texts_b, make_text_compare_func (tolerance), tolerance > 0, no_duplicates); if (!texts_a.empty () || !texts_b.empty ()) { differs = true; @@ -1061,7 +1100,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_boxes (b, cell_b, layer_b, flags, boxes_b, prop_normalize_b); } - reduce (boxes_a, boxes_b, make_box_compare_func (tolerance), tolerance > 0); + reduce (boxes_a, boxes_b, make_box_compare_func (tolerance), tolerance > 0, no_duplicates); if (!boxes_a.empty () || !boxes_b.empty ()) { differs = true; @@ -1088,7 +1127,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_edges (b, cell_b, layer_b, flags, edges_b, prop_normalize_b); } - reduce (edges_a, edges_b, make_edge_compare_func (tolerance), tolerance > 0); + reduce (edges_a, edges_b, make_edge_compare_func (tolerance), tolerance > 0, no_duplicates); if (!edges_a.empty () || !edges_b.empty ()) { differs = true; @@ -1113,7 +1152,7 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout collect_edge_pairs (b, cell_b, layer_b, flags, edge_pairs_b, prop_normalize_b); } - reduce (edge_pairs_a, edge_pairs_b, make_edge_pair_compare_func (tolerance), tolerance > 0); + reduce (edge_pairs_a, edge_pairs_b, make_edge_pair_compare_func (tolerance), tolerance > 0, no_duplicates); if (!edge_pairs_a.empty () || !edge_pairs_b.empty ()) { differs = true; diff --git a/src/db/db/dbLayoutDiff.h b/src/db/db/dbLayoutDiff.h index 865883af1..56f0114b6 100644 --- a/src/db/db/dbLayoutDiff.h +++ b/src/db/db/dbLayoutDiff.h @@ -74,12 +74,15 @@ const unsigned int f_paths_as_polygons = 0x100; // Derive smart cell mapping instead of name mapping (available only if top cells are specified) const unsigned int f_smart_cell_mapping = 0x200; -// Don't summarize missing layers +// Don't summarize missing layers - print them in detail const unsigned int f_dont_summarize_missing_layers = 0x400; // Ignore text details (font, size, presentation) const unsigned int f_no_text_details = 0x800; +// Ignore duplicate instances or shapes +const unsigned int f_ignore_duplicates = 0x1000; + } /** diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index d8b439404..48736115d 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -81,6 +81,12 @@ inline bool needs_translate (object_tag /*tag*/) return tl::is_equal_type::can_deref, tl::True> () || tl::is_equal_type::is_array, tl::True> (); } +inline bool type_mask_applies (const db::LayerBase *layer, unsigned int flags) +{ + unsigned int tm = layer->type_mask (); + return (((flags & db::ShapeIterator::Properties) == 0 || (tm & db::ShapeIterator::Properties) != 0) && (flags & tm) != 0); +} + // --------------------------------------------------------------------------------------- // layer_op implementation @@ -214,7 +220,13 @@ Shapes::insert (const Shapes &d) } void -Shapes::do_insert (const Shapes &d) +Shapes::insert (const Shapes &d, unsigned int flags) +{ + do_insert (d, flags); +} + +void +Shapes::do_insert (const Shapes &d, unsigned int flags) { // shortcut for "nothing to do" if (d.empty ()) { @@ -228,10 +240,12 @@ Shapes::do_insert (const Shapes &d) m_layers.reserve (d.m_layers.size ()); for (tl::vector::const_iterator l = d.m_layers.begin (); l != d.m_layers.end (); ++l) { - m_layers.push_back ((*l)->clone ()); - if (manager () && manager ()->transacting ()) { - check_is_editable_for_undo_redo (); - manager ()->queue (this, new FullLayerOp (true, m_layers.back ())); + if (type_mask_applies (*l, flags)) { + m_layers.push_back ((*l)->clone ()); + if (manager () && manager ()->transacting ()) { + check_is_editable_for_undo_redo (); + manager ()->queue (this, new FullLayerOp (true, m_layers.back ())); + } } } @@ -239,7 +253,9 @@ Shapes::do_insert (const Shapes &d) } else { for (tl::vector::const_iterator l = d.m_layers.begin (); l != d.m_layers.end (); ++l) { - (*l)->insert_into (this); + if (type_mask_applies (*l, flags)) { + (*l)->insert_into (this); + } } } @@ -247,14 +263,18 @@ Shapes::do_insert (const Shapes &d) // the target is standalone - dereference for (tl::vector::const_iterator l = d.m_layers.begin (); l != d.m_layers.end (); ++l) { - (*l)->deref_into (this); + if (type_mask_applies (*l, flags)) { + (*l)->deref_into (this); + } } } else { // both shape containers are in separate spaces - translate for (tl::vector::const_iterator l = d.m_layers.begin (); l != d.m_layers.end (); ++l) { - (*l)->translate_into (this, shape_repository (), array_repository ()); + if (type_mask_applies (*l, flags)) { + (*l)->translate_into (this, shape_repository (), array_repository ()); + } } } @@ -1046,6 +1066,41 @@ Shapes::clear () } } +void +Shapes::clear (unsigned int flags) +{ + if (!m_layers.empty ()) { + + invalidate_state (); // HINT: must come before the change is done! + + tl::vector new_layers; + + for (tl::vector::const_iterator l = m_layers.end (); l != m_layers.begin (); ) { + + // because the undo stack will do a push, we need to remove layers from the back (this is the last undo + // element to be executed) + --l; + + if (type_mask_applies (*l, flags)) { + + if (manager () && manager ()->transacting ()) { + check_is_editable_for_undo_redo (); + manager ()->queue (this, new FullLayerOp (false, (*l))); + } else { + delete *l; + } + + } else { + new_layers.push_back (*l); + } + + } + + m_layers.swap (new_layers); + + } +} + void Shapes::reset_bbox_dirty () { set_dirty (false); diff --git a/src/db/db/dbShapes.h b/src/db/db/dbShapes.h index b8114e360..bffb08315 100644 --- a/src/db/db/dbShapes.h +++ b/src/db/db/dbShapes.h @@ -628,6 +628,18 @@ public: */ void insert (const Shapes &d); + /** + * @brief Insert all shapes from another container using the given shape types only + * + * This method insert all shapes from the given shape container. + * + * HINT: This method can duplicate shape containers from one layout to another. + * The current implementation does not translate property Id's. + * It is mainly intended for 1-to-1 copies of layouts where the whole + * property repository is copied. + */ + void insert (const Shapes &d, unsigned int types); + /** * @brief Assignment operator with transformation * @@ -1219,6 +1231,11 @@ public: */ void clear (); + /** + * @brief Clears the collection (given shape types only) + */ + void clear (unsigned int types); + /** * @brief Report the type mask of the objects stored herein * @@ -1515,7 +1532,7 @@ private: db::Cell *mp_cell; // HINT: contains "dirty" in bit 0 and "editable" in bit 1 void invalidate_state (); - void do_insert (const Shapes &d); + void do_insert (const Shapes &d, unsigned int flags = db::ShapeIterator::All); void check_is_editable_for_undo_redo () const; // gets the layers array diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 8285bc5fa..9da66889d 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1682,29 +1682,53 @@ Class decl_Layout ("db", "Layout", "@param a The first of the layers to swap.\n" "@param b The second of the layers to swap.\n" ) + - gsi::method ("move_layer", &db::Layout::move_layer, gsi::arg ("src"), gsi::arg ("dest"), + gsi::method ("move_layer", static_cast (&db::Layout::move_layer), gsi::arg ("src"), gsi::arg ("dest"), "@brief Moves a layer\n" "\n" - "This method was introduced in version 0.19.\n" - "\n" - "Move a layer from the source to the target. The target is not cleared before, so that this method \n" - "merges shapes from the source with the target layer. The source layer is empty after that operation.\n" + "Moves a layer from the source to the destination layer. The target is not cleared before, so that this method \n" + "merges shapes from the source with the destination layer. The source layer is empty after that operation.\n" "\n" "@param src The layer index of the source layer.\n" "@param dest The layer index of the destination layer.\n" + "\n" + "This method was introduced in version 0.19.\n" ) + - gsi::method ("copy_layer", &db::Layout::copy_layer, gsi::arg ("src"), gsi::arg ("dest"), + gsi::method ("move_layer", static_cast (&db::Layout::move_layer), gsi::arg ("src"), gsi::arg ("dest"), gsi::arg ("flags"), + "@brief Moves a layer (selected shape types only)\n" + "\n" + "Moves a layer from the source to the destination layer. The target is not cleared before, so that this method \n" + "merges shapes from the source with the destination layer. The copied shapes are removed from the source layer.\n" + "\n" + "@param src The layer index of the source layer.\n" + "@param dest The layer index of the destination layer.\n" + "@param flags A combination of the shape type flags from \\Shapes, S... constants\n" + "\n" + "This method variant has been introduced in version 0.28.9.\n" + ) + + gsi::method ("copy_layer", static_cast (&db::Layout::copy_layer), gsi::arg ("src"), gsi::arg ("dest"), "@brief Copies a layer\n" "\n" - "This method was introduced in version 0.19.\n" - "\n" - "Copy a layer from the source to the target. The target is not cleared before, so that this method \n" - "merges shapes from the source with the target layer.\n" + "Copies a layer from the source to the destination layer. The destination layer is not cleared before, so that this method \n" + "merges shapes from the source with the destination layer.\n" "\n" "@param src The layer index of the source layer.\n" "@param dest The layer index of the destination layer.\n" + "\n" + "This method was introduced in version 0.19.\n" ) + - gsi::method ("clear_layer", &db::Layout::clear_layer, gsi::arg ("layer_index"), + gsi::method ("copy_layer", static_cast (&db::Layout::copy_layer), gsi::arg ("src"), gsi::arg ("dest"), gsi::arg ("flags"), + "@brief Copies a layer (selected shape types only)\n" + "\n" + "Copies a layer from the source to the destination layer. The destination layer is not cleared before, so that this method \n" + "merges shapes from the source with the destination layer.\n" + "\n" + "@param src The layer index of the source layer.\n" + "@param dest The layer index of the destination layer.\n" + "@param flags A combination of the shape type flags from \\Shapes, S... constants\n" + "\n" + "This method variant has been introduced in version 0.28.9.\n" + ) + + gsi::method ("clear_layer", static_cast (&db::Layout::clear_layer), gsi::arg ("layer_index"), "@brief Clears a layer\n" "\n" "Clears the layer: removes all shapes.\n" @@ -1712,6 +1736,16 @@ Class decl_Layout ("db", "Layout", "This method was introduced in version 0.19.\n" "\n" "@param layer_index The index of the layer to delete.\n" + ) + + gsi::method ("clear_layer", static_cast (&db::Layout::clear_layer), gsi::arg ("layer_index"), gsi::arg ("flags"), + "@brief Clears a layer (given shape types only)\n" + "\n" + "Clears the layer: removes all shapes for the given shape types.\n" + "\n" + "This method was introduced in version 0.28.9.\n" + "\n" + "@param layer_index The index of the layer to delete.\n" + "@param flags The type selector for the shapes to delete (see \\Shapes class, S... constants).\n" ) + gsi::method ("delete_layer", &db::Layout::delete_layer, gsi::arg ("layer_index"), "@brief Deletes a layer\n" diff --git a/src/db/db/gsiDeclDbLayoutDiff.cc b/src/db/db/gsiDeclDbLayoutDiff.cc index 1d50c6857..1ed1335ca 100644 --- a/src/db/db/gsiDeclDbLayoutDiff.cc +++ b/src/db/db/gsiDeclDbLayoutDiff.cc @@ -397,6 +397,10 @@ static unsigned int f_silent () { return db::layout_diff::f_silent; } +static unsigned int f_ignore_duplicates () { + return db::layout_diff::f_ignore_duplicates; +} + static unsigned int f_no_text_orientation () { return db::layout_diff::f_no_text_orientation; } @@ -448,6 +452,13 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "This constant can be used for the flags parameter of \\compare_layouts and \\compare_cells. It can be " "compared with other constants to form a flag set." ) + + gsi::constant ("IgnoreDuplicates", &f_ignore_duplicates, + "@brief Ignore duplicate instances or shapes\n" + "With this option present, duplicate instances or shapes are ignored and " + "duplication does not count as a difference.\n" + "\n" + "This option has been introduced in version 0.28.9." + ) + gsi::constant ("NoTextOrientation", &f_no_text_orientation, "@brief Ignore text orientation\n" "This constant can be used for the flags parameter of \\compare_layouts and \\compare_cells. It can be " diff --git a/src/db/db/gsiDeclDbShapes.cc b/src/db/db/gsiDeclDbShapes.cc index e711fe0c6..33f1a90c5 100644 --- a/src/db/db/gsiDeclDbShapes.cc +++ b/src/db/db/gsiDeclDbShapes.cc @@ -108,47 +108,47 @@ static gsi::layout_locking_iterator1 begin (const db return gsi::layout_locking_iterator1 (s->layout (), s->begin (flags)); } -static gsi::layout_locking_iterator1begin_all (const db::Shapes *s) +static gsi::layout_locking_iterator1 begin_all (const db::Shapes *s) { return gsi::layout_locking_iterator1 (s->layout (), s->begin (db::ShapeIterator::All)); } -static gsi::layout_locking_iterator1begin_overlapping (const db::Shapes *s, unsigned int flags, const db::Box ®ion) +static gsi::layout_locking_iterator1 begin_overlapping (const db::Shapes *s, unsigned int flags, const db::Box ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_overlapping (region, flags)); } -static gsi::layout_locking_iterator1begin_doverlapping (const db::Shapes *s, unsigned int flags, const db::DBox ®ion) +static gsi::layout_locking_iterator1 begin_doverlapping (const db::Shapes *s, unsigned int flags, const db::DBox ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_overlapping (db::CplxTrans (shapes_dbu (s)).inverted () * region, flags)); } -static gsi::layout_locking_iterator1begin_overlapping_all (const db::Shapes *s, const db::Box ®ion) +static gsi::layout_locking_iterator1 begin_overlapping_all (const db::Shapes *s, const db::Box ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_overlapping (region, db::ShapeIterator::All)); } -static gsi::layout_locking_iterator1begin_doverlapping_all (const db::Shapes *s, const db::DBox ®ion) +static gsi::layout_locking_iterator1 begin_doverlapping_all (const db::Shapes *s, const db::DBox ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_overlapping (db::CplxTrans (shapes_dbu (s)).inverted () * region, db::ShapeIterator::All)); } -static gsi::layout_locking_iterator1begin_touching (const db::Shapes *s, unsigned int flags, const db::Box ®ion) +static gsi::layout_locking_iterator1 begin_touching (const db::Shapes *s, unsigned int flags, const db::Box ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_touching (region, flags)); } -static gsi::layout_locking_iterator1begin_dtouching (const db::Shapes *s, unsigned int flags, const db::DBox ®ion) +static gsi::layout_locking_iterator1 begin_dtouching (const db::Shapes *s, unsigned int flags, const db::DBox ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_touching (db::CplxTrans (shapes_dbu (s)).inverted () * region, flags)); } -static gsi::layout_locking_iterator1begin_touching_all (const db::Shapes *s, const db::Box ®ion) +static gsi::layout_locking_iterator1 begin_touching_all (const db::Shapes *s, const db::Box ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_touching (region, db::ShapeIterator::All)); } -static gsi::layout_locking_iterator1begin_dtouching_all (const db::Shapes *s, const db::DBox ®ion) +static gsi::layout_locking_iterator1 begin_dtouching_all (const db::Shapes *s, const db::DBox ®ion) { return gsi::layout_locking_iterator1 (s->layout (), s->begin_touching (db::CplxTrans (shapes_dbu (s)).inverted () * region, db::ShapeIterator::All)); } @@ -251,12 +251,7 @@ static void insert_shapes (db::Shapes *sh, const db::Shapes &s) static void insert_shapes_with_flags (db::Shapes *sh, const db::Shapes &s, unsigned int flags) { - // NOTE: if the source (r) is from the same layout than the shapes live in, we better - // lock the layout against updates while inserting - db::LayoutLocker locker (sh->layout ()); - for (db::Shapes::shape_iterator i = s.begin (flags); !i.at_end(); ++i) { - sh->insert (*i); - } + sh->insert (s, flags); } static void insert_shapes_with_trans (db::Shapes *sh, const db::Shapes &s, const db::ICplxTrans &trans) @@ -1264,9 +1259,15 @@ Class decl_Shapes ("db", "Shapes", "@brief Returns a value indicating whether the shapes container is empty\n" "This method has been introduced in version 0.20.\n" ) + - gsi::method ("clear", &db::Shapes::clear, + gsi::method ("clear", static_cast (&db::Shapes::clear), "@brief Clears the shape container\n" - "This method has been introduced in version 0.16. It can only be used in editable mode." + "This method has been introduced in version 0.16." + ) + + gsi::method ("clear", static_cast (&db::Shapes::clear), gsi::arg ("flags"), + "@brief Clears certain shape types from the shape container\n" + "Only shapes matching the shape types from 'flags' are removed. 'flags' is a combination of the S... constants.\n" + "\n" + "This method has been introduced in version 0.28.9." ) + gsi::method_ext ("size", &shapes_size, "@brief Gets the number of shapes in this container\n" @@ -1296,10 +1297,19 @@ Class decl_Shapes ("db", "Shapes", "returned for future references." ) + gsi::method ("SAll|#s_all", &s_all, - "@brief Indicates that all shapes shall be retrieved" + "@brief Indicates that all shapes shall be retrieved\n" + "You can use this constant to construct 'except' classes - e.g. " + "to specify 'all shape types except boxes' use\n" + "\n" + "@code SAll - SBoxes @/code\n" ) + gsi::method ("SAllWithProperties|#s_all_with_properties", &s_all_with_properties, - "@brief Indicates that all shapes with properties shall be retrieved" + "@brief Indicates that all shapes with properties shall be retrieved\n" + "Using this selector means to retrieve only shapes with properties." + "You can use this constant to construct 'except' classes - e.g. " + "to specify 'all shape types with properties except boxes' use\n" + "\n" + "@code SAllWithProperties - SBoxes @/code\n" ) + gsi::method ("SPolygons|#s_polygons", &s_polygons, "@brief Indicates that polygons shall be retrieved" @@ -1333,7 +1343,10 @@ Class decl_Shapes ("db", "Shapes", "@brief Indicates that user objects shall be retrieved" ) + gsi::method ("SProperties|#s_properties", &s_properties, - "@brief Indicates that only shapes with properties shall be retrieved" + "@brief Indicates that only shapes with properties shall be retrieved\n" + "You can or-combine this flag with the plain shape types to select a " + "certain shape type, but only those shapes with properties. For example to " + "select boxes with properties, use 'SProperties | SBoxes'." ) + gsi::method_ext ("dump_mem_statistics", &dump_mem_statistics, gsi::arg ("detailed", false), "@hide" diff --git a/src/db/unit_tests/dbBoxScannerTests.cc b/src/db/unit_tests/dbBoxScannerTests.cc index 4d6de4959..9d772f340 100644 --- a/src/db/unit_tests/dbBoxScannerTests.cc +++ b/src/db/unit_tests/dbBoxScannerTests.cc @@ -938,6 +938,7 @@ TEST(two_1) db::box_convert bc1; db::box_convert bc2; bs.set_scanner_threshold (0); + bs.set_scanner_threshold1 (0); bs.process (tr, 1, bc1, bc2); EXPECT_EQ (tr.str, "[i](2-12)(2-14)(4-12)(4-14)(2-15)(4-15)(5-12)(5-14)(5-15)(2-13)(4-13)(3-12)(3-14)(3-13)(3-15)(5-13)(0-10)<2><5><4><3><12><15><14><13>(0-11)(1-10)(1-11)<0><1><10><11>[f]"); } @@ -974,6 +975,7 @@ TEST(two_1a) db::box_convert bc1; db::box_convert bc2; bs.set_scanner_threshold (0); + bs.set_scanner_threshold1 (0); bs.process (tr, 1, bc1, bc2); EXPECT_EQ (tr.str, "[i](2-11)(2-12)(1-11)(1-12)<1><2><11><12>(0-10)<0><10>[f]"); } @@ -1010,6 +1012,7 @@ TEST(two_1b) db::box_convert bc1; db::box_convert bc2; bs.set_scanner_threshold (0); + bs.set_scanner_threshold1 (0); EXPECT_EQ (bs.process (tr, 1, bc1, bc2), true); EXPECT_EQ (tr.str, "[i](1-12)(2-12)(1-11)(2-11)<1><2><11><12>(0-10)<0><10>[f]"); @@ -1046,14 +1049,15 @@ TEST(two_1c) db::box_convert bc1; db::box_convert bc2; bs.set_scanner_threshold (0); + bs.set_scanner_threshold1 (0); EXPECT_EQ (bs.process (tr, 1, bc1, bc2), true); EXPECT_EQ (tr.str, "[i]<0><10>(1-12)(2-12)(1-11)(2-11)<1><2><12><11>[f]"); } -void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, bool touch = true) +void run_test2_two (tl::TestBase *_this, size_t n1, size_t n2, double ff, db::Coord spread, bool touch = true, bool no_shortcut = true) { std::vector bb; - for (size_t i = 0; i < n; ++i) { + for (size_t i = 0; i < n1; ++i) { db::Coord x = rand () % spread; db::Coord y = rand () % spread; bb.push_back (db::Box (x, y, x + 100, y + 100)); @@ -1061,7 +1065,7 @@ void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, } std::vector bb2; - for (size_t i = 0; i < n; ++i) { + for (size_t i = 0; i < n2; ++i) { db::Coord x = rand () % spread; db::Coord y = rand () % spread; bb2.push_back (db::SimplePolygon (db::Box (x, y, x + 100, y + 100))); @@ -1082,7 +1086,10 @@ void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, db::box_convert bc2; { tl::SelfTimer timer ("box-scanner"); - bs.set_scanner_threshold (0); + if (no_shortcut) { + bs.set_scanner_threshold (0); + bs.set_scanner_threshold1 (0); + } bs.process (tr, touch ? 1 : 0, bc1, bc2); } @@ -1118,45 +1125,60 @@ void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, TEST(two_2a) { - run_test2_two(_this, 10, 0.0, 1000); + run_test2_two(_this, 10, 10, 0.0, 1000); + run_test2_two(_this, 10, 10, 0.0, 1000, true, false /*sub-threshold*/); } TEST(two_2b) { - run_test2_two(_this, 10, 0.0, 100); + run_test2_two(_this, 10, 10, 0.0, 100); + run_test2_two(_this, 10, 10, 0.0, 100, true, false /*sub-threshold*/); } TEST(two_2c) { - run_test2_two(_this, 10, 0.0, 10); + run_test2_two(_this, 10, 10, 0.0, 10); + run_test2_two(_this, 10, 10, 0.0, 10, true, false /*sub-threshold*/); } TEST(two_2d) { - run_test2_two(_this, 1000, 0.0, 1000); + run_test2_two(_this, 1000, 1000, 0.0, 1000); } TEST(two_2e) { - run_test2_two(_this, 1000, 2, 1000); + run_test2_two(_this, 1000, 1000, 2, 1000); } TEST(two_2f) { - run_test2_two(_this, 1000, 2, 1000, false); + run_test2_two(_this, 1000, 1000, 2, 1000, false); } TEST(two_2g) { - run_test2_two(_this, 1000, 2, 500); + run_test2_two(_this, 1000, 1000, 2, 500); } TEST(two_2h) { - run_test2_two(_this, 1000, 2, 100); + run_test2_two(_this, 1000, 1000, 2, 100); } TEST(two_2i) { - run_test2_two(_this, 10000, 2, 10000); + run_test2_two(_this, 10000, 1000, 2, 10000); +} + +TEST(two_2j) +{ + run_test2_two(_this, 3, 1000, 0.0, 1000); + run_test2_two(_this, 3, 1000, 0.0, 1000, true, false /*sub-threshold*/); +} + +TEST(two_2k) +{ + run_test2_two(_this, 1000, 3, 0.0, 1000); + run_test2_two(_this, 1000, 3, 0.0, 1000, true, false /*sub-threshold*/); } diff --git a/src/db/unit_tests/dbHierProcessorTests.cc b/src/db/unit_tests/dbHierProcessorTests.cc index 7e9fe165f..504f36c2e 100644 --- a/src/db/unit_tests/dbHierProcessorTests.cc +++ b/src/db/unit_tests/dbHierProcessorTests.cc @@ -1278,3 +1278,9 @@ TEST(FlatOperation) run_test_bool22_flat (_this, "hlp17_flat.oas", TMAndNot, 100, 101); } +TEST(Arrays) +{ + // Large arrays, NOT + run_test_bool2 (_this, "hlp18.oas", TMNot, 100); +} + diff --git a/src/db/unit_tests/dbLayoutDiffTests.cc b/src/db/unit_tests/dbLayoutDiffTests.cc index 011d2be8d..739b9a77d 100644 --- a/src/db/unit_tests/dbLayoutDiffTests.cc +++ b/src/db/unit_tests/dbLayoutDiffTests.cc @@ -105,6 +105,8 @@ TestDifferenceReceiver::print_cell_inst (const db::CellInstArrayWithProperties & } if (ci.properties_id () != 0) { m_os << " [" << ci.properties_id () << "]" << std::endl; + } else { + m_os << "" << std::endl; } } @@ -489,7 +491,9 @@ TEST(1) " c4 m45 *1 -10,20\n" " c4 m45 *1 -10,20\n" "Not in b but in a:\n" - " c5x r0 *1 10,-20 c5x m45 *1 -10,20Not in a but in b:\n" + " c5x r0 *1 10,-20\n" + " c5x m45 *1 -10,20\n" + "Not in a but in b:\n" ); g = h; @@ -951,7 +955,7 @@ TEST(3) c2h.shapes (0).insert (db::Polygon (db::Box (1, 2, 1003, 1006))); r.clear (); - eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 0, r); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 0, r); EXPECT_EQ (eq, false); EXPECT_EQ (r.text (), @@ -965,7 +969,7 @@ TEST(3) ); r.clear (); - eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 1, r); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 1, r); EXPECT_EQ (eq, false); EXPECT_EQ (r.text (), @@ -1499,4 +1503,205 @@ TEST(7) EXPECT_EQ (r.text (), ""); } +TEST(8) +{ + db::Layout g; + g.insert_layer (0); + g.set_properties (0, db::LayerProperties (17, 0)); + g.insert_layer (1); + g.set_properties (1, db::LayerProperties (42, 1)); + + db::cell_index_type c1i = g.add_cell ("c1"); + db::cell_index_type c2i = g.add_cell ("c2x"); + db::cell_index_type c3i = g.add_cell ("c3"); + db::cell_index_type c4i = g.add_cell ("c4"); + db::cell_index_type c5i = g.add_cell ("c5x"); + + { + + db::Cell &c1 (g.cell (c1i)); + db::Cell &c2 (g.cell (c2i)); + db::Cell &c3 (g.cell (c3i)); + db::Cell &c4 (g.cell (c4i)); + db::Cell &c5 (g.cell (c5i)); + c2.shapes (0).insert (db::Box (0, 1, 2, 3)); + + db::FTrans f (1, true); + db::Vector p (-10, 20); + db::Trans t (f.rot (), p); + db::Vector pp (10, -20); + db::Trans tt (0, pp); + + // c4->c1 (aref) + c4.insert (db::array (db::CellInst (c1.cell_index ()), t, db::Vector(1, 1), db::Vector (0, 2), 2, 3)); + // c5->c1 + c5.insert (db::array (db::CellInst (c1.cell_index ()), t)); + // c3->c5 (3x) + c3.insert (db::array (db::CellInst (c5.cell_index ()), t)); + c3.insert (db::array (db::CellInst (c5.cell_index ()), tt)); + c3.insert (db::array (db::CellInst (c5.cell_index ()), t)); + // c4->c3 + c4.insert (db::array (db::CellInst (c3.cell_index ()), t)); + // c4->c1 + c4.insert (db::array (db::CellInst (c1.cell_index ()), tt)); + // c2->c1 (2x) + c2.insert (db::array (db::CellInst (c1.cell_index ()), t)); + c2.insert (db::array (db::CellInst (c1.cell_index ()), tt)); + // c2->c4 (2x) + c2.insert (db::array (db::CellInst (c4.cell_index ()), t)); + c2.insert (db::array (db::CellInst (c4.cell_index ()), t)); + + } + + db::Layout h = g; + + TestDifferenceReceiver r; + bool eq; + + g.cell (c2i).shapes (0).insert (db::Box (1, 2, 1001, 1002)); + g.cell (c2i).shapes (0).insert (db::Box (2, 3, 1002, 1003)); + g.cell (c2i).shapes (0).insert (db::Box (2, 3, 1002, 1003)); + g.cell (c2i).shapes (0).insert (db::Box (3, 4, 1003, 1004)); + g.cell (c2i).shapes (0).insert (db::Box (3, 4, 1003, 1004)); + + h.cell (c2i).shapes (0).insert (db::Box (1, 2, 1001, 1002)); + h.cell (c2i).shapes (0).insert (db::Box (1, 2, 1001, 1002)); + h.cell (c2i).shapes (0).insert (db::Box (2, 3, 1002, 1003)); + h.cell (c2i).shapes (0).insert (db::Box (4, 5, 1004, 1005)); + h.cell (c2i).shapes (0).insert (db::Box (4, 5, 1004, 1005)); + + r.clear (); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 0, r); + + EXPECT_EQ (eq, false); + EXPECT_EQ (r.text (), + "layout_diff: boxes differ for layer 17/0 in cell c2x\n" + "Not in b but in a:\n" + " (2,3;1002,1003)\n" + " (3,4;1003,1004)\n" + " (3,4;1003,1004)\n" + "Not in a but in b:\n" + " (1,2;1001,1002)\n" + " (4,5;1004,1005)\n" + " (4,5;1004,1005)\n" + ); + + r.clear (); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose + db::layout_diff::f_ignore_duplicates, 0, r); + + EXPECT_EQ (eq, false); + EXPECT_EQ (r.text (), + "layout_diff: boxes differ for layer 17/0 in cell c2x\n" + "Not in b but in a:\n" + " (3,4;1003,1004)\n" + "Not in a but in b:\n" + " (4,5;1004,1005)\n" + ); + + // duplicate instances + { + db::FTrans f (1, true); + db::Vector p (-10, 20); + db::Trans t (f.rot (), p); + + h.cell(c4i).insert (db::array (db::CellInst (c1i), t, db::Vector(1, 1), db::Vector (0, 2), 2, 3)); + h.cell(c4i).insert (db::array (db::CellInst (c1i), t)); + h.cell(c4i).insert (db::array (db::CellInst (c1i), t)); + + g.cell(c5i).insert (db::array (db::CellInst (c1i), t)); + g.cell(c5i).insert (db::array (db::CellInst (c1i), t, db::Vector(1, 1), db::Vector (0, 2), 2, 3)); + g.cell(c5i).insert (db::array (db::CellInst (c1i), t, db::Vector(1, 1), db::Vector (0, 2), 2, 3)); + + db::cell_index_type c6i = g.add_cell ("c6"); + g.cell(c5i).insert (db::array (db::CellInst (c6i), t)); + g.cell(c5i).insert (db::array (db::CellInst (c6i), t)); + + } + + r.clear (); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose, 0, r); + + EXPECT_EQ (eq, false); + EXPECT_EQ (r.text (), + "layout_diff: cell c6 is not present in layout b, but in a\n" + "layout_diff: boxes differ for layer 17/0 in cell c2x\n" + "Not in b but in a:\n" + " (2,3;1002,1003)\n" + " (3,4;1003,1004)\n" + " (3,4;1003,1004)\n" + "Not in a but in b:\n" + " (1,2;1001,1002)\n" + " (4,5;1004,1005)\n" + " (4,5;1004,1005)\n" + "layout_diff: instances differ in cell c4\n" + "list for a:\n" + " c1 r0 *1 10,-20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c3 m45 *1 -10,20\n" + "list for b:\n" + " c1 r0 *1 10,-20\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c3 m45 *1 -10,20\n" + "Not in b but in a:\n" + "Not in a but in b:\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + "layout_diff: instances differ in cell c5x\n" + "list for a:\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + "list for b:\n" + " c1 m45 *1 -10,20\n" + "Not in b but in a:\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c6 m45 *1 -10,20\n" + " c6 m45 *1 -10,20\n" + "Not in a but in b:\n" + ); + + r.clear (); + eq = db::compare_layouts (g, h, db::layout_diff::f_verbose + db::layout_diff::f_ignore_duplicates, 0, r); + + EXPECT_EQ (eq, false); + EXPECT_EQ (r.text (), + "layout_diff: cell c6 is not present in layout b, but in a\n" + "layout_diff: boxes differ for layer 17/0 in cell c2x\n" + "Not in b but in a:\n" + " (3,4;1003,1004)\n" + "Not in a but in b:\n" + " (4,5;1004,1005)\n" + "layout_diff: instances differ in cell c4\n" + "list for a:\n" + " c1 r0 *1 10,-20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c3 m45 *1 -10,20\n" + "list for b:\n" + " c1 r0 *1 10,-20\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c3 m45 *1 -10,20\n" + "Not in b but in a:\n" + "Not in a but in b:\n" + " c1 m45 *1 -10,20\n" + "layout_diff: instances differ in cell c5x\n" + "list for a:\n" + " c1 m45 *1 -10,20\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + "list for b:\n" + " c1 m45 *1 -10,20\n" + "Not in b but in a:\n" + " c1 m45 *1 -10,20[a=1,1, b=0,2, na=2, nb=3]\n" + " c6 m45 *1 -10,20\n" + "Not in a but in b:\n" + ); +} + diff --git a/src/db/unit_tests/dbShapesTests.cc b/src/db/unit_tests/dbShapesTests.cc index b2fce8945..b18e02bf3 100644 --- a/src/db/unit_tests/dbShapesTests.cc +++ b/src/db/unit_tests/dbShapesTests.cc @@ -1925,6 +1925,51 @@ TEST(7) } } +// copy, move, clear with shape types +TEST(8) +{ + db::Manager m (true); + + db::Layout layout (true, &m); + unsigned int lindex1 = layout.insert_layer (); + unsigned int lindex2 = layout.insert_layer (); + + db::Cell &topcell = layout.cell (layout.add_cell ("TOP")); + + topcell.shapes (lindex1).insert (db::Box (1, 2, 3, 4)); + topcell.shapes (lindex1).insert (db::Polygon (db::Box (1, 2, 3, 4))); + + { + db::Transaction trans (&m, "T1"); + topcell.shapes (lindex2).insert (topcell.shapes (lindex1)); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), "polygon (1,2;1,4;3,4;3,2) #0\nbox (1,2;3,4) #0\n"); + } + + m.undo (); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), ""); + + { + db::Transaction trans (&m, "T1"); + topcell.shapes (lindex2).insert (topcell.shapes (lindex1), db::ShapeIterator::Boxes); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), "box (1,2;3,4) #0\n"); + } + + m.undo (); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), ""); + + topcell.shapes (lindex2).insert (topcell.shapes (lindex1)); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), "polygon (1,2;1,4;3,4;3,2) #0\nbox (1,2;3,4) #0\n"); + + { + db::Transaction trans (&m, "T1"); + topcell.shapes (lindex2).clear (db::ShapeIterator::Polygons); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), "box (1,2;3,4) #0\n"); + } + + m.undo (); + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex2)), shapes_to_string (_this, topcell.shapes (lindex1))); +} + TEST(10A) { if (db::default_editable_mode ()) { diff --git a/src/doc/doc/about/drc_ref_global.xml b/src/doc/doc/about/drc_ref_global.xml index 91fa1a06e..3deac6889 100644 --- a/src/doc/doc/about/drc_ref_global.xml +++ b/src/doc/doc/about/drc_ref_global.xml @@ -1182,6 +1182,36 @@ See Netter#netlist for a descrip

See Netter for more details

+

"new_report" - Creates a new report database object for use in "output"

+ +

Usage:

+
    +
  • new_report(description [, filename [, cellname ] ])
  • +
+

+This method creates an independent report object. This object +can be used in "output" to send a layer to a different report than +the default report or target. +

+Arguments are the same than for report. +

+See Layer#output for details about this feature. +

+

"new_target" - Creates a new layout target object for use in "output"

+ +

Usage:

+
    +
  • new_target(what [, cellname])
  • +
+

+This method creates an independent target object. This object +can be used in "output" to send a layer to a different layout file than +the default report or target. +

+Arguments are the same than for target. +

+See Layer#output for details about this feature. +

"no_borders" - Reset the tile borders

Usage:

@@ -1413,6 +1443,21 @@ See
Source#polygons for a descr The primary input of the universal DRC function is the layer the Layer#drc function is called on.

+

"profile" - Profiles the script and provides a runtime + memory statistics

+ +

Usage:

+
    +
  • profile
  • +
  • profile(n)
  • +
+

+Turns profiling on or off (default). In profiling mode, the +system will collect statistics about rules executed, their execution time +and memory information. The argument specifies how many operations to +print at the end of the run. Without an argument, all operations are +printed. Passing "false" for the argument will disable profiling. This is the +default. +

"props_copy" - Specifies "copy properties" on operations supporting user properties constraints

diff --git a/src/doc/doc/about/drc_ref_layer.xml b/src/doc/doc/about/drc_ref_layer.xml index 4dbd218fa..6efff2012 100644 --- a/src/doc/doc/about/drc_ref_layer.xml +++ b/src/doc/doc/about/drc_ref_layer.xml @@ -2217,6 +2217,26 @@ a single LayerInfo object.

See report and target on how to configure output to a target layout or report database. +

+See also new_target and new_report on how to create additional +targets for output. This allows saving certain layers to different files than +the standard target or report. To do so, create a new target or report using one +of these functions and pass that object to the corresponding "output" call as +an additional argument. +

+Example: +

+

+check1 = ...
+check2 = ...
+check3 = ...
+
+second_report = new_report("Only for check2", "check2.lyrdb")
+
+check1.output("Check 1")
+check2.output("Check 2", second_report)
+check3.output("Check 3")
+

"outside" - Selects edges or polygons of self which are outside edges or polygons from the other layer

diff --git a/src/doc/doc/manual/drc_runsets.xml b/src/doc/doc/manual/drc_runsets.xml index 971bf9b79..7642c4c16 100644 --- a/src/doc/doc/manual/drc_runsets.xml +++ b/src/doc/doc/manual/drc_runsets.xml @@ -185,7 +185,7 @@ l.output("OUT") l.output(17, 0, "OUT")

- Output can be sent to other layouts using the "target" function: + Output can be sent to other layouts using the target function:

@@ -196,7 +196,7 @@ target("@2")
 target("out.gds", "OUT_TOP")

- Output can also be sent to a report database: + Output can also be sent to a report database using the report function:

@@ -234,6 +234,38 @@ l.output("check1", "The first check")
unpredictable.

+

+ It is possible to open "side" reports and targets and send layers to these + outputs without closing the default output. +

+ +

+ To open a "side report", use new_report + in the same way you use "report". Instead of switching the output, this function will return a + new report object that can be included in the argument list of "output" + for the layer that is to be sent to that side report: +

+ +
+# opens a new side report
+side_report = new_report("Another report")
+...
+# Send data from layer l to new category "check1" to the side report
+l.output(side_report, "check1", "The first check")
+ +

+ In the same way, "side targets" can be opened using new_target. + Such side targets open a way to write certain layers to other layout files. + This is very handy for debugging: +

+ +
+# opens a new side target for debugging
+debug_out = new_target("debug.gds")
+...
+# Send data from layer l to the debug output, layer 100/0
+l.output(debug_out, 100, 0)
+

Dimension specifications

@@ -710,12 +742,12 @@ overlaps = layer.size(0.2).raw.merged(2)

-  layer = input(1, 0)
-  layer.raw.sized(0.1).output(100, 0)
+layer = input(1, 0)
+layer.raw.sized(0.1).output(100, 0)
 
-  # this check will now be done on a raw layer, since the 
-  # previous raw call was putting the layer into raw mode
-  layer.width(0.2).ouput(101, 0)
+# this check will now be done on a raw layer, since the +# previous raw call was putting the layer into raw mode +layer.width(0.2).ouput(101, 0)

The following two images show the effect of raw and clean mode: @@ -792,20 +824,18 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  drc_w = input(1, 0).width(0.2)
-  ...
-  
+... +drc_w = input(1, 0).width(0.2) +...

can be written as:

-  ...
-  drc_w = input(1, 0).drc(width < 0.2)
-  ...
-  
+... +drc_w = input(1, 0).drc(width < 0.2) +...

The drc method is the "universal DRC" method. @@ -845,12 +875,11 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  l1 = input(1, 0)
-  l2 = input(2, 0)
-  drc_sep = l1.drc(separation(l2) <= 0.5)
-  ...
-  
+... +l1 = input(1, 0) +l2 = input(2, 0) +drc_sep = l1.drc(separation(l2) <= 0.5) +...

Options are also specified together with the measurement and follow the same notation @@ -858,10 +887,9 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  drc_w = input(1, 0).drc(width(projection) < 0.2)
-  ...
-  
+... +drc_w = input(1, 0).drc(width(projection) < 0.2) +...

However, the universal DRC is much more than a convenient way to write checks: @@ -879,10 +907,9 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  drc_ws = input(1, 0).drc((width < 0.2) & (space < 0.3))
-  ...
-  
+... +drc_ws = input(1, 0).drc((width < 0.2) & (space < 0.3)) +...

The boolean AND is computed between the edges on the primary shape and returns the @@ -891,12 +918,11 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  drc_ws1 = input(1, 0).width(0.2).edges
-  drc_ws2 = input(1, 0).space(0.3).edges
-  drc_ws = drc_ws1 & drc_ws2
-  ...
-  
+... +drc_ws1 = input(1, 0).width(0.2).edges +drc_ws2 = input(1, 0).space(0.3).edges +drc_ws = drc_ws1 & drc_ws2 +...

The reason is that performing the boolean computation in the local loop can be @@ -933,11 +959,10 @@ overlaps = layer.size(0.2).raw.merged(2)

-  ...
-  drc_w = input(1, 0).width(0.2)
-  log("Number of width violations: #{drc_w.data.size}")
-  ...
-  
+... +drc_w = input(1, 0).width(0.2) +log("Number of width violations: #{drc_w.data.size}") +...

The error function can be used to output error messages @@ -946,11 +971,23 @@ overlaps = layer.size(0.2).raw.merged(2)

-  log_file("drc_log.txt")
-  verbose(true)
-  info("This message will be sent to the log file")
-  ...
-  
+log_file("drc_log.txt") +verbose(true) +info("This message will be sent to the log file") +... + +

+ The profile function will collect profiling + information during the DRC run. At the end of the script, the operations are printed to the log + output, sorted by their CPU time and approximate memory footprint. "profile" can be given a + numerical argument indicating the number of operations to print. Lower-ranking operations are + skipped in that case. By default, all operations are printed. +

+ +
+# enables profiling
+profile
+...

The tiling option

@@ -1003,8 +1040,7 @@ threads(4) # Disable tiling flat -... non-tiled operations ... - +... non-tiled operations ...

Some operations implicitly specify a tile border. If the tile border is known (see length example above), explicit borders diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 17edb9e2e..6bc5f4c72 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -13,6 +13,125 @@ module DRC end end + class OutputChannel + def initialize(engine) + @engine = engine + end + def is_rdb? + false + end + def cellname=(cellname) + # reimplement + end + def cell + # reimplement + end + def write + # reimplement + end + def finish(final) + # reimplement + end + def write + # reimplement + end + def layout + nil + end + def rdb + nil + end + end + + class LayoutOutputChannel < OutputChannel + + def initialize(engine, layout, cell, file_name) + super(engine) + @layout = layout + @cell = cell + @file_name = file_name + end + + def cellname=(cellname) + @cell = @layout.cell(cellname) || @layout.create_cell(cellname) + end + + def is_rdb? + false + end + + def finish(final, view) + write + end + + def write + if @file_name + opt = RBA::SaveLayoutOptions::new + gzip = opt.set_format_from_filename(@file_name) + @engine.info("Writing layout file: #{@file_name} ..") + @layout.write(@file_name, gzip, opt) + end + end + + def layout + @layout + end + + def cell + @cell + end + + end + + class RDBOutputChannel < OutputChannel + + def initialize(engine, rdb, rdb_index, cellname, file_name) + super(engine) + @rdb = rdb + @rdb_index = rdb_index + @cell = cellname && rdb.create_cell(cellname) + @file_name = file_name + end + + def cellname=(cellname) + @cell = nil + @rdb.each_cell do |c| + if c.name == cellname + @cell = c + end + end + @cell ||= @rdb.create_cell(cellname) + end + + def is_rdb? + true + end + + def finish(final, view) + write + if final && view + view.show_rdb(@rdb_index, view.active_cellview_index) + end + end + + def write + if @file_name + rdb_file = @engine._make_path(@file_name) + @engine.info("Writing report database: #{rdb_file} ..") + @rdb.save(rdb_file) + end + end + + def rdb + @rdb + end + + def cell + @cell + end + + end + # The DRC engine # %DRC% @@ -40,11 +159,9 @@ module DRC @def_source = nil @dbu_read = false use_dbu(@def_layout && @def_layout.dbu) - @output_layout = nil - @output_rdb = nil - @output_rdb_file = nil - @output_rdb_cell = nil @show_l2ndb = nil + @def_output = nil + @other_outputs = [] @output_l2ndb_file = nil @target_netlist_file = nil @target_netlist_format = nil @@ -70,6 +187,9 @@ module DRC dss._destroy @verbose = false + @profile = false + @profile_n = 0 + @profile_info = {} @in_context = nil @@ -711,6 +831,26 @@ module DRC end end + # %DRC% + # @name profile + # @brief Profiles the script and provides a runtime + memory statistics + # @synopsis profile + # @synopsis profile(n) + # Turns profiling on or off (default). In profiling mode, the + # system will collect statistics about rules executed, their execution time + # and memory information. The argument specifies how many operations to + # print at the end of the run. Without an argument, all operations are + # printed. Passing "false" for the argument will disable profiling. This is the + # default. + + def profile(n = 0) + if !n.is_a?(1.class) && n != nil && n != false + raise("Argument to 'profile' must be either an integer number or nil") + end + @profile = !!n + @profile_n = [n || 0, 0].max + end + # %DRC% # @name verbose? # @brief Returns true, if verbose mode is enabled @@ -1340,46 +1480,42 @@ module DRC self._context("report") do - @output_rdb_file = filename + # finish what we got so far + _finish(false) - name = filename && File::basename(filename) - name ||= "DRC" - - @output_rdb_index = nil - - view = RBA::LayoutView::current - if view - if self._rdb_index - @output_rdb = RBA::ReportDatabase::new("") # reuse existing name - @output_rdb_index = view.replace_rdb(self._rdb_index, @output_rdb) - else - @output_rdb = RBA::ReportDatabase::new(name) - @output_rdb_index = view.add_rdb(@output_rdb) - end - else - @output_rdb = RBA::ReportDatabase::new(name) - end - - @output_layout = nil - @output_cell = nil - @output_layout_file = nil - - cn = nil - cn ||= @def_cell && @def_cell.name - cn ||= source && source.cell_name - cn ||= cellname - - cn || raise("No cell name specified - either the source was not specified before 'report' or there is no default source. In the latter case, specify a cell name as the third parameter") - - @output_rdb_cell = @output_rdb.create_cell(cn) - @output_rdb.generator = self._generator - @output_rdb.top_cell_name = cn - @output_rdb.description = description + @def_output = nil + @def_output = _make_report(description, filename, cellname) end end + # %DRC% + # @name new_report + # @brief Creates a new report database object for use in "output" + # @synopsis new_report(description [, filename [, cellname ] ]) + # + # This method creates an independent report object. This object + # can be used in "output" to send a layer to a different report than + # the default report or target. + # + # Arguments are the same than for \global#report. + # + # See \Layer#output for details about this feature. + + def new_report(description, filename = nil, cellname = nil) + + output = nil + + self._context("new_report") do + output = _make_report(description, filename, cellname) + @other_outputs << output + end + + output + + end + # %DRC% # @name report_netlist # @brief Specifies an extracted netlist report for output @@ -1456,25 +1592,13 @@ module DRC # finish what we got so far _flush - if @output_rdb - - cell = nil - @output_rdb.each_cell do |c| - if c.name == cellname - cell = c - end + if ! @def_output + if @def_layout + # establish a new default output from the default layout on this occasion + @def_output = LayoutOutputChannel::new(self, @def_layout, cellname.to_s, nil) end - - cell ||= @output_rdb.create_cell(cellname) - @output_rdb_cell = cell - else - - @output_layout ||= @def_layout - if @output_layout - @output_cell = @output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s) - end - + @def_output.cellname = cellname.to_s end end @@ -1512,51 +1636,40 @@ module DRC # finish what we got so far _finish(false) - - if arg.is_a?(String) - - if arg =~ /^@(\d+|\+)/ - view = RBA::LayoutView::current - view || raise("No view open") - if $1 == "+" - n = view.create_layout(true) - cellname ||= (@def_cell ? @def_cell.name : "TOP") - else - n = $1.to_i - 1 - end - (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") - cv = view.cellview(n) - cv.is_valid? || raise("Invalid layout @#{n + 1}") - @output_layout = cv.layout - @output_cell = cellname ? (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) : cv.cell - cv.cell = @output_cell - @output_layout_file = nil - else - @output_layout = RBA::Layout::new - @output_cell = cellname && @output_layout.create_cell(cellname.to_s) - @output_layout_file = arg - end - - elsif arg.is_a?(RBA::Layout) - - @output_layout = arg - @output_cell = cellname && (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) - @output_layout_file = nil - - elsif arg.is_a?(RBA::Cell) - - @output_layout = arg.layout - @output_cell = arg - @output_layout_file = nil - else - raise("Invalid argument '" + arg.inspect + "'") - end - + @def_output = nil + @def_output = _make_target(arg, cellname) + end end - + + # %DRC% + # @name new_target + # @brief Creates a new layout target object for use in "output" + # @synopsis new_target(what [, cellname]) + # + # This method creates an independent target object. This object + # can be used in "output" to send a layer to a different layout file than + # the default report or target. + # + # Arguments are the same than for \global#target. + # + # See \Layer#output for details about this feature. + + def new_target(arg, cellname = nil) + + output = nil + + self._context("new_target") do + output = _make_target(arg, cellname) + @other_outputs << output + end + + output + + end + # %DRC% # @name box # @brief Creates a box object @@ -2267,13 +2380,16 @@ CODE end t = RBA::Timer::new + t.start self._process_events - if @force_gc || Time::now - @time > 0.5 + if @force_gc || Time::now - @time > 0.5 || @profile GC.start # force a garbage collection before the operation to free unused memory @time = Time::now end + mem_before = RBA::Timer::memory_size res = yield + mem_after = RBA::Timer::memory_size t.stop begin @@ -2283,15 +2399,25 @@ CODE # Report result statistics _result_info(res, 1) - mem = RBA::Timer::memory_size - if mem > 0 - info("Elapsed: #{'%.3f'%(t.sys+t.user)}s Memory: #{'%.2f'%(mem/(1024*1024))}M", 1) + if mem_after > 0 + info("Elapsed: #{'%.3f'%(t.sys+t.user)}s Memory: #{'%.2f'%(mem_after/(1024*1024))}M", 1) else info("Elapsed: #{'%.3f'%(t.sys+t.user)}s", 1) end end + if @profile + + # calls, sys time (in sec), user time (in sec), memory added (in bytes) + info = (@profile_info[desc] ||= [ 0, 0.0, 0.0, 0 ]) + info[0] += 1 + info[1] += t.sys + info[2] += t.user + info[3] += mem_after - mem_before + + end + ensure # disable progress again @@ -2477,8 +2603,8 @@ CODE end def _output_layout - if @output_layout - output = @output_layout + if @def_output && @def_output.layout + output = @def_output.layout else output = @def_layout output || raise("No output layout specified") @@ -2487,19 +2613,79 @@ CODE end def _output_cell - if @output_layout - if @output_cell - output_cell = @output_cell + if @def_output && @def_output.layout + if @def_output.cell + output_cell = @def_output.cell elsif @def_cell - output_cell = @output_layout.cell(@def_cell.name) || @output_layout.create_cell(@def_cell.name) + output_layout = @def_output.layout + output_cell = output_layout.cell(@def_cell.name) || output_layout.create_cell(@def_cell.name) end output_cell || raise("No output cell specified (see 'target' instruction)") else - output_cell = @output_cell || @def_cell + output_cell = @def_cell output_cell || raise("No output cell specified") end output_cell end + + def _dump_profile + + if @profile_info.empty? + return + end + + desc_title = "Operation" + calls_title = "# calls" + time_title = "Time (s)" + memory_title = "Memory (k)" + titles = [ desc_title, calls_title, time_title, memory_title ] + + max_len_desc = [ @profile_info.keys.collect { |s| s.size }.max, desc_title.size ].max + max_len_calls = [ @profile_info.values.collect { |v| v[0].to_s.size }.max, calls_title.size ].max + max_len_time = [ @profile_info.values.collect { |v| ("%.3f" % (v[1] + v[2])).to_s.size }.max, time_title.size ].max + max_len_mem = [ @profile_info.values.collect { |v| v[3].to_s.size }.max, memory_title.size ].max + + fmt = "%-" + max_len_desc.to_s + "s " + + "%-" + max_len_calls.to_s + "d " + + "%-" + max_len_time.to_s + ".3f " + + "%-" + max_len_mem.to_s + ".0f" + + fmt_title = "%-" + max_len_desc.to_s + "s " + + "%-" + max_len_calls.to_s + "s " + + "%-" + max_len_time.to_s + "s " + + "%-" + max_len_mem.to_s + "s" + + pi = @profile_info.keys.collect { |s| [s] + @profile_info[s] }.collect do |desc,calls,sys_time,user_time,memory| + [ desc, calls, sys_time + user_time, memory.to_f / 1024.0 ] + end + + self.log("") + self.log("Operations by execution time\n") + self.log(fmt_title % titles) + n = 1 + pi.sort { |a,b| b[2] <=> a[2] }.each do |info| + self.log(fmt % info) + n += 1 + if @profile_n > 0 && n > @profile_n + self.log("... (%d entries skipped)" % (pi.size - @profile_n)) + break + end + end + + self.log("") + self.log("Operations by memory adder\n") + self.log(fmt_title % titles) + n = 1 + pi.sort { |a,b| b[3] <=> a[3] }.each do |info| + self.log(fmt % info) + n += 1 + if @profile_n > 0 && n > @profile_n + self.log("... (%d entries skipped)" % (pi.size - @profile_n)) + break + end + end + + end def _start(job_description) @@ -2528,39 +2714,32 @@ CODE begin _flush - + view = RBA::LayoutView::current - # save the report database if requested - if @output_rdb_file && final - rdb_file = _make_path(@output_rdb_file) - info("Writing report database: #{rdb_file} ..") - @output_rdb.save(rdb_file) + @def_output && @def_output.finish(final, view) + + if final + @other_outputs.each do |output| + output.finish(final, view) + end end - if @output_rdb && final && view - view.show_rdb(@output_rdb_index, view.active_cellview_index) - end - - # save the output file if requested - if @output_layout && @output_layout_file - opt = RBA::SaveLayoutOptions::new - gzip = opt.set_format_from_filename(@output_layout_file) - info("Writing layout file: #{@output_layout_file} ..") - @output_layout.write(@output_layout_file, gzip, opt) + + # dump the profile information + if @profile && final + _dump_profile end # create the new layers as visual layers if necessary if view - output = @output_layout || @def_layout + output = ( @def_output && @def_output.layout ) || @def_layout cv_index = nil view.cellviews.times do |cvi| view.cellview(cvi).layout == output && cv_index = cvi end if cv_index - view = RBA::LayoutView::current - # clear selection view.cancel @@ -2634,13 +2813,10 @@ CODE ensure @output_layers = [] - @output_layout = nil - @output_layout_file = nil - @output_cell = nil - @output_rdb_file = nil - @output_rdb_cell = nil - @output_rdb = nil - @output_rdb_index = nil + @def_output = nil + if final + @other_outputs = [] + end @show_l2ndb = nil @output_l2ndb_file = nil @@ -2933,21 +3109,36 @@ CODE def _output(data, *args) - if @output_rdb + channel = args.find { |a| a.is_a?(OutputChannel) } + if ! channel + if ! @def_output + @def_output = LayoutOutputChannel::new(self, self._output_layout, self._output_cell, nil) + end + channel = @def_output + end + + args = args.select { |a| !a.is_a?(OutputChannel) } + + if channel.rdb if args.size < 1 raise("Invalid number of arguments - category name and optional description expected") end - cat = @output_rdb.create_category(args[0].to_s) + output_rdb = channel.rdb + output_cell = channel.cell + + cat = output_rdb.create_category(args[0].to_s) args[1] && cat.description = args[1] - cat.scan_collection(@output_rdb_cell, RBA::CplxTrans::new(self.dbu), data) + cat.scan_collection(output_cell, RBA::CplxTrans::new(self.dbu), data) - else + end - output = self._output_layout - output_cell = self._output_cell + if channel.layout + + output = channel.layout + output_cell = channel.cell info = nil if args.size == 1 @@ -2977,14 +3168,16 @@ CODE begin - if !@used_output_layers[li] - @output_layers.push(li) - # Note: to avoid issues with output onto the input layer, we - # output to a temp layer and later swap both. The simple implementation - # did a clear here and the effect of that was that the data potentially - # got invalidated. - tmp = output.insert_layer(RBA::LayerInfo::new) - @used_output_layers[li] = true + if channel == @def_output + if !@used_output_layers[li] + @output_layers.push(li) + # Note: to avoid issues with output onto the input layer, we + # output to a temp layer and later swap both. The simple implementation + # did a clear here and the effect of that was that the data potentially + # got invalidated. + tmp = output.insert_layer(RBA::LayerInfo::new) + @used_output_layers[li] = true + end end # insert the data into the output layer @@ -3020,7 +3213,111 @@ CODE @layout_sources[name] = src src end + + def _make_report(description, filename, cellname) + output_rdb_file = filename + + name = filename && File::basename(filename) + name ||= "DRC" + + output_rdb_index = nil + + view = RBA::LayoutView::current + if view + if self._rdb_index + output_rdb = RBA::ReportDatabase::new("") # reuse existing name + output_rdb_index = view.replace_rdb(self._rdb_index, output_rdb) + else + output_rdb = RBA::ReportDatabase::new(name) + output_rdb_index = view.add_rdb(output_rdb) + end + else + output_rdb = RBA::ReportDatabase::new(name) + end + + cn = cellname && cellname.to_s + cn ||= @def_cell && @def_cell.name + cn ||= source && source.cell_name + + cn || raise("No cell name specified - either the source was not specified before 'report' or there is no default source. In the latter case, specify a cell name as the third parameter") + + output_rdb.generator = self._generator + output_rdb.top_cell_name = cn + output_rdb.description = description + + RDBOutputChannel::new(self, output_rdb, output_rdb_index, cn, output_rdb_file) + + end + + def _make_target(arg, cellname = nil) + + if arg.is_a?(String) + + if arg =~ /^@(\d+|\+)/ + + view = RBA::LayoutView::current + view || raise("No view open") + if $1 == "+" + n = view.create_layout(true) + cellname ||= (@def_cell ? @def_cell.name : "TOP") + else + n = $1.to_i - 1 + end + + (n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}") + + cv = view.cellview(n) + cv.is_valid? || raise("Invalid layout @#{n + 1}") + + output_layout = cv.layout + output_cell = cellname ? (output_layout.cell(cellname.to_s) || output_layout.create_cell(cellname.to_s)) : cv.cell + cv.cell = output_cell + output_layout_file = nil + + else + + cn = cellname && cellname.to_s + cn ||= @def_cell && @def_cell.name + cn ||= @def_source && @def_source.cell_obj && @def_source.cell_obj.name + + cn || raise("No cell name specified - either the source was not specified before 'report' or there is no default source. In the latter case, specify a cell name as the third parameter") + + output_layout = RBA::Layout::new + output_cell = output_layout.create_cell(cn) + output_layout_file = arg + + end + + elsif arg.is_a?(RBA::Layout) + + output_layout = arg + + output_cell = cellname && (output_layout.cell(cellname.to_s) || output_layout.create_cell(cellname.to_s)) + if ! output_cell + begin + output_cell = output_layout.top_cell + rescue + raise("No cell name specified and the layout does not have a unique top cell - specify the name of the top cell to write the output to") + end + end + + output_layout_file = nil + + elsif arg.is_a?(RBA::Cell) + + output_layout = arg.layout + output_cell = arg + output_layout_file = nil + + else + raise("Invalid argument '" + arg.inspect + "'") + end + + output = LayoutOutputChannel::new(self, output_layout, output_cell, output_layout_file) + + end + end end diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index e434c9d1d..30ea3a692 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -5007,6 +5007,26 @@ CODE # # See \global#report and \global#target on how to configure output to a target layout # or report database. + # + # See also \global#new_target and \global#new_report on how to create additional + # targets for output. This allows saving certain layers to different files than + # the standard target or report. To do so, create a new target or report using one + # of these functions and pass that object to the corresponding "output" call as + # an additional argument. + # + # Example: + # + # @code + # check1 = ... + # check2 = ... + # check3 = ... + # + # second_report = new_report("Only for check2", "check2.lyrdb") + # + # check1.output("Check 1") + # check2.output("Check 2", second_report) + # check3.output("Check 3") + # @/code def output(*args) @engine._context("output") do diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 8b24c918d..c0825e751 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -731,6 +731,75 @@ TEST(14_SwitchingTargets) db::compare_layouts (_this, layout2, au2, db::NoNormalization); } +TEST(14b_SideTargetsAndReports) +{ + std::string rs = tl::testdata (); + rs += "/drc/drcSimpleTests_14b.drc"; + + std::string input = tl::testdata (); + input += "/drc/drcSimpleTests_14b.gds"; + + std::string au = tl::testdata (); + au += "/drc/drcSimpleTests_au14b.gds"; + + std::string au2 = tl::testdata (); + au2 += "/drc/drcSimpleTests_au14b_2.gds"; + + std::string au_report = tl::testdata (); + au_report += "/drc/drcSimpleTests_au14b.lyrdb"; + + std::string au_report2 = tl::testdata (); + au_report2 += "/drc/drcSimpleTests_au14b_2.lyrdb"; + + std::string output = this->tmp_file ("tmp.gds"); + std::string output2 = this->tmp_file ("tmp2.gds"); + std::string report = this->tmp_file ("tmp.lydrc"); + std::string report2 = this->tmp_file ("tmp2.lydrc"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_force_gc = true\n" + "$drc_test_source = '%s'\n" + "$drc_test_target = '%s'\n" + "$drc_test_target2 = '%s'\n" + "$drc_test_report = '%s'\n" + "$drc_test_report2 = '%s'\n" + , input, output, output2, report, report2) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + db::compare_layouts (_this, layout, au, db::NoNormalization); + + db::Layout layout2; + + { + tl::InputStream stream (output2); + db::Reader reader (stream); + reader.read (layout2); + } + + db::compare_layouts (_this, layout2, au2, db::NoNormalization); + + compare_text_files (report, au_report); + compare_text_files (report2, au_report2); +} + TEST(15_issue548) { std::string rs = tl::testdata (); diff --git a/src/lay/lay/layGSIHelpProvider.cc b/src/lay/lay/layGSIHelpProvider.cc index eda190c67..c79df181f 100644 --- a/src/lay/lay/layGSIHelpProvider.cc +++ b/src/lay/lay/layGSIHelpProvider.cc @@ -118,6 +118,10 @@ std::string escape_xml_with_formatting (const std::string &s, bool &in_code) r += ""; } else if (sc.test ("@/u")) { r += ""; + } else if (sc.test ("@tt")) { + r += ""; + } else if (sc.test ("@/tt")) { + r += ""; } else if (sc.test ("@i")) { r += ""; } else if (sc.test ("@/i")) { diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.cc index 8167b02d5..64253e5e0 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.cc @@ -127,11 +127,19 @@ GDS2Reader::get_record () return rec_id; } +void +GDS2Reader::record_underflow_error () +{ + error (tl::to_string (tr ("Record too short"))); +} + inline int GDS2Reader::get_int () { unsigned char *b = mp_rec_buf + m_recptr; - m_recptr += 4; + if ((m_recptr += 4) > m_reclen) { + record_underflow_error (); + } int32_t l = *((int32_t *)b); gds2h (l); @@ -142,7 +150,9 @@ inline short GDS2Reader::get_short () { unsigned char *b = mp_rec_buf + m_recptr; - m_recptr += 2; + if ((m_recptr += 2) > m_reclen) { + record_underflow_error (); + } int16_t s = *((int16_t *)b); gds2h (s); @@ -153,7 +163,9 @@ inline unsigned short GDS2Reader::get_ushort () { unsigned char *b = mp_rec_buf + m_recptr; - m_recptr += 2; + if ((m_recptr += 2) > m_reclen) { + record_underflow_error (); + } uint16_t s = *((uint16_t *)b); gds2h ((int16_t &) s); @@ -164,7 +176,9 @@ inline double GDS2Reader::get_double () { unsigned char *b = mp_rec_buf + m_recptr; - m_recptr += 8; + if ((m_recptr += 8) > m_reclen) { + record_underflow_error (); + } uint32_t l0 = ((uint32_t *)b) [0]; gds2h ((int32_t &) l0); diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.h b/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.h index 33cce1401..17eb2b504 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2Reader.h @@ -106,6 +106,8 @@ private: virtual void get_time (unsigned int *mod_time, unsigned int *access_time); virtual GDS2XY *get_xy_data (unsigned int &length); virtual void progress_checkpoint (); + + void record_underflow_error (); }; } diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc index 90e84d65d..ad6eed607 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc @@ -278,6 +278,10 @@ GDS2ReaderBase::do_read (db::Layout &layout) get_string (m_cellname); + if (m_cellname.empty ()) { + error (tl::to_string (tr ("Empty cell name"))); + } + // if the first cell is the dummy cell containing the context information // read this cell in a special way and store the context information separately. if (first_cell && m_cellname == "$$$CONTEXT_INFO$$$") { diff --git a/src/plugins/tools/diff/lay_plugin/DiffToolDialog.ui b/src/plugins/tools/diff/lay_plugin/DiffToolDialog.ui index a14df4d1a..1995401fc 100644 --- a/src/plugins/tools/diff/lay_plugin/DiffToolDialog.ui +++ b/src/plugins/tools/diff/lay_plugin/DiffToolDialog.ui @@ -1,76 +1,73 @@ - + + DiffToolDialog - - + + 0 0 - 498 + 503 404 - + Diff Tool - - - 9 - - + + 6 + + 9 + - - + + Input - - + + 9 - + 6 - - - + + + Layout A - - - + + + Layout B - - - - - 7 - 5 + + + + 0 0 - + QComboBox::AdjustToContentsOnFirstShow - - - - - 7 - 5 + + + + 0 0 - + QComboBox::AdjustToContentsOnFirstShow @@ -79,77 +76,84 @@ - - + + Options - - - 9 - - + + 6 + + 9 + - - + + Don't use names to match cells (use geometrical properties) - + true - - + + Run XOR on differences - - + + Summarize missing layers - + true - - + + Detailed information - + true - - + + Expand cell arrays (compare single instance by instance) - - + + Exact compare (includes properties, text orientation and similar) + + + + Ignore duplicate instances and shapes + + + - + Qt::Vertical - + 472 16 @@ -158,12 +162,12 @@ - - + + Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -187,11 +191,11 @@ DiffToolDialog accept() - + 248 254 - + 157 274 @@ -203,11 +207,11 @@ DiffToolDialog reject() - + 316 260 - + 286 274 diff --git a/src/plugins/tools/diff/lay_plugin/layDiffToolDialog.cc b/src/plugins/tools/diff/lay_plugin/layDiffToolDialog.cc index 1285f72bb..5ae9dcafb 100644 --- a/src/plugins/tools/diff/lay_plugin/layDiffToolDialog.cc +++ b/src/plugins/tools/diff/lay_plugin/layDiffToolDialog.cc @@ -46,6 +46,7 @@ std::string cfg_diff_smart ("diff-smart"); std::string cfg_diff_summarize ("diff-summarize"); std::string cfg_diff_expand_cell_arrays ("diff-expand-cell-arrays"); std::string cfg_diff_exact ("diff-exact"); +std::string cfg_diff_ignore_duplicates ("diff-ignore-duplicates"); // ------------------------------------------------------------------------------ // RdbDifferenceReceiver definition @@ -650,6 +651,9 @@ DiffToolDialog::exec_dialog (lay::LayoutViewBase *view) if (config_root->config_get (cfg_diff_exact, f)) { mp_ui->exact_cbx->setChecked (f); } + if (config_root->config_get (cfg_diff_ignore_duplicates, f)) { + mp_ui->ignore_duplicates_cbx->setChecked (f); + } update (); @@ -686,6 +690,7 @@ BEGIN_PROTECTED config_root->config_set (cfg_diff_summarize, mp_ui->summarize_cbx->isChecked ()); config_root->config_set (cfg_diff_expand_cell_arrays, mp_ui->expand_cell_arrays_cbx->isChecked ()); config_root->config_set (cfg_diff_exact, mp_ui->exact_cbx->isChecked ()); + config_root->config_set (cfg_diff_ignore_duplicates, mp_ui->ignore_duplicates_cbx->isChecked ()); config_root->config_end (); QDialog::accept (); @@ -712,6 +717,7 @@ DiffToolDialog::run_diff () bool summarize = !run_xor && mp_ui->summarize_cbx->isChecked (); bool expand_cell_arrays = !run_xor && mp_ui->expand_cell_arrays_cbx->isChecked (); bool exact = !run_xor && mp_ui->exact_cbx->isChecked (); + bool ignore_duplicates = mp_ui->ignore_duplicates_cbx->isChecked (); int cv_index_a = mp_ui->layouta->current_cv_index (); int cv_index_b = mp_ui->layoutb->current_cv_index (); @@ -740,6 +746,9 @@ DiffToolDialog::run_diff () if (smart) { flags |= db::layout_diff::f_smart_cell_mapping; } + if (ignore_duplicates) { + flags |= db::layout_diff::f_ignore_duplicates; + } // TODO: make an parameter db::Coord tolerance = 0; diff --git a/src/pya/pya/pya.cc b/src/pya/pya/pya.cc index 53d0d9496..44f31d88a 100644 --- a/src/pya/pya/pya.cc +++ b/src/pya/pya/pya.cc @@ -34,6 +34,7 @@ #include "gsiDecl.h" #include "gsiDeclBasic.h" #include "tlLog.h" +#include "tlEnv.h" #include "tlStream.h" #include "tlTimer.h" #include "tlFileUtils.h" @@ -140,6 +141,16 @@ public: virtual size_t scope_index () const { + static int consider_scope = -1; + + // disable scoped debugging (e.g. DRC script lines) if $KLAYOUT_PYA_DEBUG_SCOPE is set. + if (consider_scope < 0) { + consider_scope = tl::app_flag ("pya-debug-scope") ? 0 : 1; + } + if (! consider_scope) { + return 0; + } + if (! m_scope.empty ()) { for (size_t i = 0; i < m_stack_trace.size (); ++i) { if (m_stack_trace [i].file == m_scope) { diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index 27af10ed0..00826361c 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -111,7 +111,7 @@ RubyStackTraceProvider::scope_index (const std::vector &bt // disable scoped debugging (e.g. DRC script lines) if $KLAYOUT_RBA_DEBUG_SCOPE is set. if (consider_scope < 0) { - consider_scope = tl::has_env ("KLAYOUT_RBA_DEBUG_SCOPE") ? 0 : 1; + consider_scope = tl::app_flag ("rba-debug-scope") ? 0 : 1; } if (! consider_scope) { return 0; diff --git a/testdata/algo/hlp18.oas b/testdata/algo/hlp18.oas new file mode 100644 index 000000000..2c45f1226 Binary files /dev/null and b/testdata/algo/hlp18.oas differ diff --git a/testdata/drc/drcSimpleTests_14b.drc b/testdata/drc/drcSimpleTests_14b.drc new file mode 100644 index 000000000..0d4daa56a --- /dev/null +++ b/testdata/drc/drcSimpleTests_14b.drc @@ -0,0 +1,24 @@ + +source($drc_test_source) + +target($drc_test_target) + +l1 = input(1, 0) +l2 = input(2, 0) + +tcopy = new_target($drc_test_target2) +rcopy = new_report("Report 2", $drc_test_report2) + +l1.output(tcopy, 101, 0) +l2.output(tcopy, 102, 0) + +l1.output(1, 0) +l1.space(1.0.um).output(100, 0) + +report("Report 1", $drc_test_report) + +l2.space(1.0.um).output("l2 space < 1µm") +l1.width(1.0.um).output("l1 width < 1µm") + +l1.sep(l2, 1.0.um).output(rcopy, "l1 sep l2 < 1µm") + diff --git a/testdata/drc/drcSimpleTests_14b.gds b/testdata/drc/drcSimpleTests_14b.gds new file mode 100644 index 000000000..c43c2fae9 Binary files /dev/null and b/testdata/drc/drcSimpleTests_14b.gds differ diff --git a/testdata/drc/drcSimpleTests_au14b.gds b/testdata/drc/drcSimpleTests_au14b.gds new file mode 100644 index 000000000..af3ebbcfb Binary files /dev/null and b/testdata/drc/drcSimpleTests_au14b.gds differ diff --git a/testdata/drc/drcSimpleTests_au14b.lyrdb b/testdata/drc/drcSimpleTests_au14b.lyrdb new file mode 100644 index 000000000..0b6c00a9d --- /dev/null +++ b/testdata/drc/drcSimpleTests_au14b.lyrdb @@ -0,0 +1,88 @@ + + + Report 1 + + drc: script='.drc' + TOP + + + + + l2 space < 1µm + + + + + + l1 width < 1µm + + + + + + + + TOP + + + + + + + + + 'l2 space < 1\302\265m' + TOP + false + 1 + + + edge-pair: (-0.2,0.7;1,0.7)|(1,1.1;-0.2,1.1) + + + + + 'l1 width < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0,0;0,0.9)|(0.3,0.9;0.3,0) + + + + + 'l1 width < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0.3,0;0,0)|(0,0.9;0.3,0.9) + + + + + 'l1 width < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0.5,0;0.5,0.9)|(0.8,0.9;0.8,0) + + + + + 'l1 width < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0.8,0;0.5,0)|(0.5,0.9;0.8,0.9) + + + + diff --git a/testdata/drc/drcSimpleTests_au14b_2.gds b/testdata/drc/drcSimpleTests_au14b_2.gds new file mode 100644 index 000000000..b3a512645 Binary files /dev/null and b/testdata/drc/drcSimpleTests_au14b_2.gds differ diff --git a/testdata/drc/drcSimpleTests_au14b_2.lyrdb b/testdata/drc/drcSimpleTests_au14b_2.lyrdb new file mode 100644 index 000000000..f3430532f --- /dev/null +++ b/testdata/drc/drcSimpleTests_au14b_2.lyrdb @@ -0,0 +1,49 @@ + + + Report 2 + + drc: script='.drc' + TOP + + + + + l1 sep l2 < 1µm + + + + + + + + TOP + + + + + + + + + 'l1 sep l2 < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0,0.9;0.3,0.9)/(1,1.1;-0.2,1.1) + + + + + 'l1 sep l2 < 1\302\265m' + TOP + false + 1 + + + edge-pair: (0.5,0.9;0.8,0.9)/(1,1.1;-0.2,1.1) + + + + diff --git a/testdata/pymod/import_db.py b/testdata/pymod/import_db.py index 2c1881260..154bd7198 100755 --- a/testdata/pymod/import_db.py +++ b/testdata/pymod/import_db.py @@ -41,6 +41,12 @@ class BasicTest(unittest.TestCase): v.read(os.path.join(os.path.dirname(__file__), "..", "gds", "t10.gds")) self.assertEqual(v.top_cell().name, "RINGO") + def test_4(self): + # gds2_text plugin loaded? (issue #1393) + v = db.Layout() + v.read(os.path.join(os.path.dirname(__file__), "..", "gds2_txt", "read.txt")) + self.assertEqual(v.top_cell().name, "RINGO") + # run unit tests if __name__ == '__main__': suite = unittest.TestSuite() diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index dca1b4b8b..c6734efb3 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -1971,6 +1971,66 @@ class DBLayoutTests1_TestClass < TestBase end + def test_23 + + # layer operations with shape types + + m = RBA::Manager::new + + l = RBA::Layout.new(m) + l1 = l.insert_layer(RBA::LayerInfo.new(1, 0)) + l2 = l.insert_layer(RBA::LayerInfo.new(2, 0)) + c0 = l.cell(l.add_cell("c0")) + c1 = l.cell(l.add_cell("c1")) + + c0.shapes(l1).insert(RBA::Box::new(1, 2, 3, 4)) + c1.shapes(l1).insert(RBA::Polygon::new(RBA::Box::new(1, 2, 3, 4))) + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:polygon (1,2;1,4;3,4;3,2)") + + m.transaction("T") + l.clear_layer(l1, RBA::Shapes::SPolygons) + m.commit + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:") + + m.undo + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:polygon (1,2;1,4;3,4;3,2)") + + m.transaction("T") + l.move_layer(l1, l2, RBA::Shapes::SPolygons) + m.commit + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:") + + str2 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l2).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str2, "c0:\nc1:polygon (1,2;1,4;3,4;3,2)") + + m.undo + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:polygon (1,2;1,4;3,4;3,2)") + + str2 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l2).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str2, "c0:\nc1:") + + m.transaction("T") + l.copy_layer(l1, l2, RBA::Shapes::SPolygons) + m.commit + + str1 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l1).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str1, "c0:box (1,2;3,4)\nc1:polygon (1,2;1,4;3,4;3,2)") + + str2 = l.each_cell.collect { |c| c.name + ":" + c.shapes(l2).each.collect { |sh| sh.to_s }.join(";") }.join("\n") + assert_equal(str2, "c0:\nc1:polygon (1,2;1,4;3,4;3,2)") + + end + # Iterating while flatten def test_issue200 diff --git a/testdata/ruby/dbShapesTest.rb b/testdata/ruby/dbShapesTest.rb index 76bb1df85..a55ffa4f8 100644 --- a/testdata/ruby/dbShapesTest.rb +++ b/testdata/ruby/dbShapesTest.rb @@ -1595,6 +1595,34 @@ class DBShapes_TestClass < TestBase end + # Shapes with shape-type specific insert and clear + def test_11 + + s = RBA::Shapes::new + s.insert(RBA::Box::new(1, 2, 3, 4)) + s.insert(RBA::Polygon::new(RBA::Box::new(1, 2, 3, 4))) + + assert_equal(s.each.collect { |sh| sh.to_s }.join("; "), "polygon (1,2;1,4;3,4;3,2); box (1,2;3,4)") + + s2 = RBA::Shapes::new + s2.insert(s) + + assert_equal(s2.each.collect { |sh| sh.to_s }.join("; "), "polygon (1,2;1,4;3,4;3,2); box (1,2;3,4)") + + s2.clear(RBA::Shapes::SPolygons) + + assert_equal(s2.each.collect { |sh| sh.to_s }.join("; "), "box (1,2;3,4)") + + s2.clear + + assert_equal(s2.each.collect { |sh| sh.to_s }.join("; "), "") + + s2.insert(s, RBA::Shapes::SPolygons) + + assert_equal(s2.each.collect { |sh| sh.to_s }.join("; "), "polygon (1,2;1,4;3,4;3,2)") + + end + end load("test_epilogue.rb")