From 5712c8a85c142b495c6505b673e260c9f4409dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6fferlein?= Date: Sun, 11 Feb 2024 10:32:56 +0100 Subject: [PATCH] Issue 1609 (#1613) * Fixed issue #1609 (Cell.read doesn't read LayoutMetaInfo) This also includes some more functions: - Layout#merge_meta_info, Layout#copy_meta_info - Layout#clear_all_meta_info - Cell#merge_meta_info, Cell#copy_meta_info In addition, meta info is merged when importing a layout from another file (Layout/Import -> Other Layouts into current). * Meta info support in layout diff (for testing), implemented meta info merge for GDS and OASIS readers with special conflict resolution modes * Undo support for meta info - this way we do not loose meta info when we delete a cell and undo --------- Co-authored-by: Matthias Koefferlein --- src/db/db/dbCommonReader.cc | 24 ++- src/db/db/dbCommonReader.h | 4 +- src/db/db/dbLayout.cc | 178 +++++++++++++++++- src/db/db/dbLayout.h | 49 +++++ src/db/db/dbLayoutDiff.cc | 72 +++++++ src/db/db/dbLayoutDiff.h | 5 + src/db/db/dbLayoutUtils.cc | 3 + src/db/db/dbTestSupport.cc | 1 + src/db/db/dbTestSupport.h | 3 +- src/db/db/gsiDeclDbCell.cc | 32 ++++ src/db/db/gsiDeclDbLayout.cc | 42 +++++ src/db/db/gsiDeclDbLayoutDiff.cc | 64 +++++-- src/db/unit_tests/dbLayoutDiffTests.cc | 56 ++++++ .../{dbGDS2Reader.cc => dbGDS2ReaderTests.cc} | 8 +- .../{dbGDS2Writer.cc => dbGDS2WriterTests.cc} | 0 .../streamers/gds2/unit_tests/unit_tests.pro | 4 +- testdata/gds/collect_add_au.gds | Bin 12990 -> 13406 bytes testdata/gds/collect_added.gds | Bin 4990 -> 5086 bytes testdata/gds/collect_basic.gds | Bin 9360 -> 8878 bytes testdata/gds/collect_overwrite_au.gds | Bin 8008 -> 8424 bytes testdata/gds/collect_rename_au.gds | Bin 13248 -> 13822 bytes testdata/gds/collect_skip_au.gds | Bin 9598 -> 10010 bytes testdata/ruby/dbLayoutTests2.rb | 173 +++++++++++++++++ 23 files changed, 689 insertions(+), 29 deletions(-) rename src/plugins/streamers/gds2/unit_tests/{dbGDS2Reader.cc => dbGDS2ReaderTests.cc} (98%) rename src/plugins/streamers/gds2/unit_tests/{dbGDS2Writer.cc => dbGDS2WriterTests.cc} (100%) diff --git a/src/db/db/dbCommonReader.cc b/src/db/db/dbCommonReader.cc index c184501e9..dc87204c9 100644 --- a/src/db/db/dbCommonReader.cc +++ b/src/db/db/dbCommonReader.cc @@ -164,7 +164,7 @@ CommonReaderBase::rename_cell (db::Layout &layout, size_t id, const std::string // Both cells already exist and are not identical: merge ID-declared cell into the name-declared one layout.force_update (); - merge_cell (layout, iname->second.second, iid->second.second); + merge_cell (layout, iname->second.second, iid->second.second, true); iid->second.second = iname->second.second; } @@ -239,7 +239,7 @@ CommonReaderBase::cell_for_instance (db::Layout &layout, const std::string &cn) } void -CommonReaderBase::merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index) const +CommonReaderBase::merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const { const db::Cell &src_cell = layout.cell (src_cell_index); db::Cell &target_cell = layout.cell (target_cell_index); @@ -253,11 +253,11 @@ CommonReaderBase::merge_cell (db::Layout &layout, db::cell_index_type target_cel } } - merge_cell_without_instances (layout, target_cell_index, src_cell_index); + merge_cell_without_instances (layout, target_cell_index, src_cell_index, with_meta); } void -CommonReaderBase::merge_cell_without_instances (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index) const +CommonReaderBase::merge_cell_without_instances (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const { const db::Cell &src_cell = layout.cell (src_cell_index); db::Cell &target_cell = layout.cell (target_cell_index); @@ -272,6 +272,16 @@ CommonReaderBase::merge_cell_without_instances (db::Layout &layout, db::cell_ind // replace all instances of the new cell with the original one layout.replace_instances_of (src_cell.cell_index (), target_cell.cell_index ()); + // merge meta info + if (with_meta) { + auto ib = layout.begin_meta (src_cell.cell_index ()); + auto ie = layout.end_meta (src_cell.cell_index ()); + for (auto i = ib; i != ie; ++i) { + layout.add_meta_info (target_cell.cell_index (), i->first, i->second); + } + } + layout.clear_meta (src_cell.cell_index ()); + // finally delete the new cell layout.delete_cell (src_cell.cell_index ()); } @@ -375,7 +385,7 @@ CommonReaderBase::finish (db::Layout &layout) layout.cell (ci_org).clear_shapes (); - merge_cell (layout, ci_org, ci_new); + merge_cell (layout, ci_org, ci_new, true); } else if (m_cc_resolution == SkipNewCell && ! layout.cell (ci_org).is_ghost_cell ()) { @@ -383,11 +393,11 @@ CommonReaderBase::finish (db::Layout &layout) layout.cell (ci_new).clear_shapes (); // NOTE: ignore instances -> this saves us a layout update - merge_cell_without_instances (layout, ci_org, ci_new); + merge_cell_without_instances (layout, ci_org, ci_new, false); } else { - merge_cell (layout, ci_org, ci_new); + merge_cell (layout, ci_org, ci_new, m_cc_resolution != SkipNewCell); } diff --git a/src/db/db/dbCommonReader.h b/src/db/db/dbCommonReader.h index 45afd6cc5..90e98d339 100644 --- a/src/db/db/dbCommonReader.h +++ b/src/db/db/dbCommonReader.h @@ -242,12 +242,12 @@ protected: /** * @brief Merge (and delete) the src_cell into target_cell */ - void merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index) const; + void merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const; /** * @brief Merge (and delete) the src_cell into target_cell without instances */ - void merge_cell_without_instances (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index) const; + void merge_cell_without_instances (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const; /** * @brief Gets the layer name map diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 8ab1e8717..4e29d27ef 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -257,6 +257,83 @@ private: bool m_insert; }; +struct SetLayoutMetaInfoOp + : public LayoutOp +{ + SetLayoutMetaInfoOp (db::Layout::meta_info_name_id_type name_id, const db::MetaInfo *f, const db::MetaInfo *t) + : m_name_id (name_id), m_has_from (f != 0), m_has_to (t != 0) + { + if (f) { + m_from = *f; + } + if (t) { + m_to = *t; + } + } + + virtual void redo (db::Layout *layout) const + { + if (! m_has_to) { + layout->remove_meta_info (m_name_id); + } else { + layout->add_meta_info (m_name_id, m_to); + } + } + + virtual void undo (db::Layout *layout) const + { + if (! m_has_from) { + layout->remove_meta_info (m_name_id); + } else { + layout->add_meta_info (m_name_id, m_from); + } + } + +private: + db::Layout::meta_info_name_id_type m_name_id; + bool m_has_from, m_has_to; + db::MetaInfo m_from, m_to; +}; + +struct SetCellMetaInfoOp + : public LayoutOp +{ + SetCellMetaInfoOp (db::cell_index_type ci, db::Layout::meta_info_name_id_type name_id, const db::MetaInfo *f, const db::MetaInfo *t) + : m_ci (ci), m_name_id (name_id), m_has_from (f != 0), m_has_to (t != 0) + { + if (f) { + m_from = *f; + } + if (t) { + m_to = *t; + } + } + + virtual void redo (db::Layout *layout) const + { + if (! m_has_to) { + layout->remove_meta_info (m_ci, m_name_id); + } else { + layout->add_meta_info (m_ci, m_name_id, m_to); + } + } + + virtual void undo (db::Layout *layout) const + { + if (! m_has_from) { + layout->remove_meta_info (m_ci, m_name_id); + } else { + layout->add_meta_info (m_ci, m_name_id, m_from); + } + } + +private: + db::cell_index_type m_ci; + db::Layout::meta_info_name_id_type m_name_id; + bool m_has_from, m_has_to; + db::MetaInfo m_from, m_to; +}; + // ----------------------------------------------------------------- // Implementation of the ProxyContextInfo class @@ -848,6 +925,9 @@ Layout::delete_cells (const std::set &cells_to_delete) // cell child objects that must remain. for (std::set::const_iterator c = cells_to_delete.begin (); c != cells_to_delete.end (); ++c) { + // supports undo + clear_meta (*c); + if (manager () && manager ()->transacting ()) { // note the "take" method - this takes out the cell @@ -917,9 +997,12 @@ Layout::delete_cell (cell_index_type id) // a backup container for the cell. This is necessary since the ID's within manager are given to // cell child objects that must remain. + // supports undo + clear_meta (id); + if (manager () && manager ()->transacting ()) { - // not the "take" method - this takes out the cell + // note the "take" method - this takes out the cell std::string cn (cell_name (id)); manager ()->queue (this, new NewRemoveCellOp (id, cn, true /*remove*/, take_cell (id))); @@ -1869,18 +1952,36 @@ Layout::meta_info_name_id (const std::string &name) const void Layout::clear_meta () { + if (manager () && manager ()->transacting ()) { + for (auto i = m_meta_info.begin (); i != m_meta_info.end (); ++i) { + manager ()->queue (this, new SetLayoutMetaInfoOp (i->first, &i->second, 0)); + } + } + m_meta_info.clear (); } void Layout::add_meta_info (meta_info_name_id_type name_id, const MetaInfo &i) { + if (manager () && manager ()->transacting ()) { + auto e = m_meta_info.find (name_id); + manager ()->queue (this, new SetLayoutMetaInfoOp (name_id, e != m_meta_info.end () ? &e->second : 0, &i)); + } + m_meta_info[name_id] = i; } void Layout::remove_meta_info (meta_info_name_id_type name_id) { + if (manager () && manager ()->transacting ()) { + auto e = m_meta_info.find (name_id); + if (e != m_meta_info.end ()) { + manager ()->queue (this, new SetLayoutMetaInfoOp (name_id, &e->second, 0)); + } + } + m_meta_info.erase (name_id); } @@ -1901,12 +2002,41 @@ Layout::has_meta_info (meta_info_name_id_type name_id) const void Layout::clear_meta (db::cell_index_type ci) { + if (manager () && manager ()->transacting ()) { + auto ib = begin_meta (ci); + auto ie = end_meta (ci); + for (auto i = ib; i != ie; ++i) { + manager ()->queue (this, new SetCellMetaInfoOp (ci, i->first, &i->second, 0)); + } + } + m_meta_info_by_cell.erase (ci); } +void +Layout::clear_all_meta () +{ + clear_meta (); + while (! m_meta_info_by_cell.empty ()) { + clear_meta (m_meta_info_by_cell.begin ()->first); + } +} + void Layout::add_meta_info (db::cell_index_type ci, meta_info_name_id_type name_id, const MetaInfo &i) { + if (manager () && manager ()->transacting ()) { + const MetaInfo *from = 0; + auto c = m_meta_info_by_cell.find (ci); + if (c != m_meta_info_by_cell.end ()) { + auto e = c->second.find (name_id); + if (e != c->second.end ()) { + from = &e->second; + } + } + manager ()->queue (this, new SetCellMetaInfoOp (ci, name_id, from, &i)); + } + m_meta_info_by_cell[ci][name_id] = i; } @@ -1914,6 +2044,18 @@ void Layout::remove_meta_info (db::cell_index_type ci, meta_info_name_id_type name_id) { auto c = m_meta_info_by_cell.find (ci); + + if (manager () && manager ()->transacting ()) { + const MetaInfo *from = 0; + if (c != m_meta_info_by_cell.end ()) { + auto e = c->second.find (name_id); + if (e != c->second.end ()) { + from = &e->second; + } + } + manager ()->queue (this, new SetCellMetaInfoOp (ci, name_id, from, 0)); + } + if (c != m_meta_info_by_cell.end ()) { c->second.erase (name_id); } @@ -1945,6 +2087,40 @@ Layout::has_meta_info (db::cell_index_type ci, meta_info_name_id_type name_id) c } } +void +Layout::merge_meta_info (const db::Layout &other) +{ + for (auto mi = other.begin_meta (); mi != other.end_meta (); ++mi) { + add_meta_info (other.meta_info_name (mi->first), mi->second); + } +} + +void +Layout::merge_meta_info (db::cell_index_type into_cell, const db::Layout &other, db::cell_index_type other_cell) +{ + auto mi_begin = other.begin_meta (other_cell); + auto mi_end = other.end_meta (other_cell); + for (auto mi = mi_begin; mi != mi_end; ++mi) { + add_meta_info (into_cell, other.meta_info_name (mi->first), mi->second); + } +} + +void +Layout::merge_meta_info (const db::Layout &other, const db::CellMapping &cm) +{ + for (auto i = cm.begin (); i != cm.end (); ++i) { + merge_meta_info (i->second, other, i->first); + } +} + +void +Layout::copy_meta_info (const db::Layout &other, const db::CellMapping &cm) +{ + for (auto i = cm.begin (); i != cm.end (); ++i) { + copy_meta_info (i->second, other, i->first); + } +} + void Layout::swap_layers (unsigned int a, unsigned int b) { diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index 7d2598da6..59a4ad19f 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -1993,6 +1993,11 @@ public: */ void clear_meta (db::cell_index_type ci); + /** + * @brief Clears all meta information (cells and layout) + */ + void clear_all_meta (); + /** * @brief Adds meta information for a given cell * The given meta information object is to the meta information list for the given cell. @@ -2021,6 +2026,50 @@ public: } } + /** + * @brief Merges meta information from the other layout into self + * This applies to the layout-only meta information. Same keys get overwritten, new ones are added. + */ + void merge_meta_info (const db::Layout &other); + + /** + * @brief Copies meta information from the other layout into self + * This applies to the layout-only meta information. All keys are replaced. + */ + void copy_meta_info (const db::Layout &other) + { + clear_meta (); + merge_meta_info (other); + } + + /** + * @brief Merges meta information from the other cell into the target cell from sel. + * This applies to the cell-specific meta information. Same keys get overwritten, new ones are added. + */ + void merge_meta_info (db::cell_index_type into_cell, const db::Layout &other, db::cell_index_type other_cell); + + /** + * @brief Copies meta information from the other cell into the target cell from sel. + * This applies to the cell-specific meta information. All keys are replaced. + */ + void copy_meta_info (db::cell_index_type into_cell, const db::Layout &other, db::cell_index_type other_cell) + { + clear_meta (into_cell); + merge_meta_info (into_cell, other, other_cell); + } + + /** + * @brief Merges meta information from the other cell into the target cell from sel using the given cell mapping. + * The cell mapping specifies which meta information to merge from which cell into which cell. + */ + void merge_meta_info (const db::Layout &other, const db::CellMapping &cm); + + /** + * @brief Copies meta information from the other cell into the target cell from sel using the given cell mapping. + * The cell mapping specifies which meta information to copy from which cell into which cell. + */ + void copy_meta_info (const db::Layout &other, const db::CellMapping &cm); + /** * @brief Gets a value indicating whether a meta info with the given name is present for the given cell */ diff --git a/src/db/db/dbLayoutDiff.cc b/src/db/db/dbLayoutDiff.cc index bc35d4c62..b52e71b02 100644 --- a/src/db/db/dbLayoutDiff.cc +++ b/src/db/db/dbLayoutDiff.cc @@ -702,6 +702,29 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout r.dbu_differs (a.dbu (), b.dbu ()); } + if ((flags & layout_diff::f_with_meta) != 0) { + std::map > mi; + for (auto i = a.begin_meta (); i != a.end_meta (); ++i) { + if (i->second.persisted) { + mi [a.meta_info_name (i->first)].first = i->second.value; + } + } + for (auto i = b.begin_meta (); i != b.end_meta (); ++i) { + if (i->second.persisted) { + mi [b.meta_info_name (i->first)].second = i->second.value; + } + } + for (auto i = mi.begin (); i != mi.end (); ++i) { + if (i->second.first != i->second.second) { + differs = true; + if (flags & layout_diff::f_silent) { + return false; + } + r.layout_meta_info_differs (i->first, i->second.first, i->second.second); + } + } + } + bool verbose = (flags & layout_diff::f_verbose); bool no_duplicates = (flags & layout_diff::f_ignore_duplicates); @@ -928,6 +951,33 @@ do_compare_layouts (const db::Layout &a, const db::Cell *top_a, const db::Layout r.begin_cell (common_cells [cci], common_cells_a [cci], common_cells_b [cci]); + if ((flags & layout_diff::f_with_meta) != 0) { + std::map > mi; + auto ib = a.begin_meta (common_cells_a [cci]); + auto ie = a.end_meta (common_cells_a [cci]); + for (auto i = ib; i != ie; ++i) { + if (i->second.persisted) { + mi [a.meta_info_name (i->first)].first = i->second.value; + } + } + ib = b.begin_meta (common_cells_b [cci]); + ie = b.end_meta (common_cells_b [cci]); + for (auto i = ib; i != ie; ++i) { + if (i->second.persisted) { + mi [b.meta_info_name (i->first)].second = i->second.value; + } + } + for (auto i = mi.begin (); i != mi.end (); ++i) { + if (i->second.first != i->second.second) { + differs = true; + if (flags & layout_diff::f_silent) { + return false; + } + r.cell_meta_info_differs (i->first, i->second.first, i->second.second); + } + } + } + if (!verbose && cell_a->bbox () != cell_b->bbox ()) { differs = true; if (flags & layout_diff::f_silent) { @@ -1215,6 +1265,7 @@ public: } void dbu_differs (double dbu_a, double dbu_b); + void layout_meta_info_differs (const std::string &name, const tl::Variant &a, const tl::Variant &b); void layer_in_a_only (const db::LayerProperties &la); void layer_in_b_only (const db::LayerProperties &lb); void layer_name_differs (const db::LayerProperties &la, const db::LayerProperties &lb); @@ -1222,6 +1273,7 @@ public: void cell_in_b_only (const std::string &cellname, db::cell_index_type ci); void cell_name_differs (const std::string &cellname_a, db::cell_index_type cia, const std::string &cellname_b, db::cell_index_type cib); void begin_cell (const std::string &cellname, db::cell_index_type cia, db::cell_index_type cib); + void cell_meta_info_differs (const std::string &name, const tl::Variant &a, const tl::Variant &b); void bbox_differs (const db::Box &ba, const db::Box &bb); void begin_inst_differences (); void instances_in_a (const std::vector &insts_a, const std::vector &cell_names, const db::PropertiesRepository &props); @@ -1384,6 +1436,16 @@ PrintingDifferenceReceiver::dbu_differs (double dbu_a, double dbu_b) } } +void +PrintingDifferenceReceiver::layout_meta_info_differs (const std::string &name, const tl::Variant &a, const tl::Variant &b) +{ + try { + enough (tl::error) << "Global meta info differs - [" << name << "]: " << a << " vs. " << b; + } catch (tl::CancelException &) { + // ignore cancel exceptions + } +} + void PrintingDifferenceReceiver::layer_in_a_only (const db::LayerProperties &la) { @@ -1461,6 +1523,16 @@ PrintingDifferenceReceiver::begin_cell (const std::string &cellname, db::cell_in m_cellname = cellname; } +void +PrintingDifferenceReceiver::cell_meta_info_differs (const std::string &name, const tl::Variant &a, const tl::Variant &b) +{ + try { + enough (tl::error) << "Meta info differs in cell " << m_cellname << " - [" << name << "]: " << a << " vs. " << b; + } catch (tl::CancelException &) { + // ignore cancel exceptions + } +} + void PrintingDifferenceReceiver::begin_inst_differences () { diff --git a/src/db/db/dbLayoutDiff.h b/src/db/db/dbLayoutDiff.h index ce39f5988..223ddd4f4 100644 --- a/src/db/db/dbLayoutDiff.h +++ b/src/db/db/dbLayoutDiff.h @@ -56,6 +56,9 @@ const unsigned int f_no_text_orientation = 0x02; // Ignore properties const unsigned int f_no_properties = 0x04; +// With meta info +const unsigned int f_with_meta = 0x08; + // Do not compare layer names const unsigned int f_no_layer_names = 0x10; @@ -94,6 +97,7 @@ public: virtual ~DifferenceReceiver () { } virtual void dbu_differs (double /*dbu_a*/, double /*dbu_b*/) { } + virtual void layout_meta_info_differs (const std::string & /*name*/, const tl::Variant & /*value_a*/, const tl::Variant & /*value_b*/) { } virtual void layer_in_a_only (const db::LayerProperties & /*la*/) { } virtual void layer_in_b_only (const db::LayerProperties & /*lb*/) { } virtual void layer_name_differs (const db::LayerProperties & /*la*/, const db::LayerProperties & /*lb*/) { } @@ -102,6 +106,7 @@ public: virtual void cell_in_b_only (const std::string & /*cellname*/, db::cell_index_type /*ci*/) { } virtual void bbox_differs (const db::Box & /*ba*/, const db::Box & /*bb*/) { } virtual void begin_cell (const std::string & /*cellname*/, db::cell_index_type /*cia*/, db::cell_index_type /*cib*/) { } + virtual void cell_meta_info_differs (const std::string & /*name*/, const tl::Variant & /*value_a*/, const tl::Variant & /*value_b*/) { } virtual void begin_inst_differences () { } virtual void instances_in_a (const std::vector & /*insts_a*/, const std::vector & /*cell_names*/, const db::PropertiesRepository & /*props*/) { } virtual void instances_in_b (const std::vector & /*insts_b*/, const std::vector & /*cell_names*/, const db::PropertiesRepository & /*props*/) { } diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 1c4a7e0d1..2fe5174fb 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -252,6 +252,9 @@ merge_layouts (db::Layout &target, const db::Cell &source_cell = source.cell (*c); db::Cell &target_cell = target.cell (target_cell_index); + // merge meta info + target.merge_meta_info (target_cell_index, source, *c); + // NOTE: this implementation employs the safe but cumbersome "local transformation" feature. // This means, all cells are transformed according to the given transformation and their // references are transformed to account for that effect. This will lead to somewhat strange diff --git a/src/db/db/dbTestSupport.cc b/src/db/db/dbTestSupport.cc index c93757909..871e66396 100644 --- a/src/db/db/dbTestSupport.cc +++ b/src/db/db/dbTestSupport.cc @@ -139,6 +139,7 @@ void compare_layouts (tl::TestBase *_this, const db::Layout &layout, const std:: (n > 0 ? db::layout_diff::f_silent : db::layout_diff::f_verbose) | ((norm & AsPolygons) != 0 ? db::layout_diff::f_boxes_as_polygons + db::layout_diff::f_paths_as_polygons : 0) | ((norm & WithArrays) != 0 ? 0 : db::layout_diff::f_flatten_array_insts) + | ((norm & WithMeta) == 0 ? 0 : db::layout_diff::f_with_meta) /*| db::layout_diff::f_no_text_details | db::layout_diff::f_no_text_orientation*/ , tolerance, 100 /*max diff lines*/); if (equal && n > 0) { diff --git a/src/db/db/dbTestSupport.h b/src/db/db/dbTestSupport.h index 4963b2ec4..5369a6e90 100644 --- a/src/db/db/dbTestSupport.h +++ b/src/db/db/dbTestSupport.h @@ -58,7 +58,8 @@ enum NormalizationMode NormFileMask = 7, // bits the extract for file mode NoContext = 8, // write tmp file without context AsPolygons = 16, // paths and boxes are treated as polygons - WithArrays = 32 // do not flatten arrays + WithArrays = 32, // do not flatten arrays + WithMeta = 64 // with meta info }; /** diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index dc256ce0d..09ae74ac2 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1004,6 +1004,22 @@ static void cell_add_meta_info (db::Cell *cell, const MetaInfo &mi) } } +static void cell_merge_meta_info (db::Cell *cell, const db::Cell *source) +{ + if (! source || ! cell->layout () || ! source->layout ()) { + return; + } + cell->layout ()->merge_meta_info (cell->cell_index (), *source->layout (), source->cell_index ()); +} + +static void cell_copy_meta_info (db::Cell *cell, const db::Cell *source) +{ + if (! source || ! cell->layout () || ! source->layout ()) { + return; + } + cell->layout ()->copy_meta_info (cell->cell_index (), *source->layout (), source->cell_index ()); +} + static const tl::Variant &cell_meta_info_value (db::Cell *cell, const std::string &name) { if (! cell->layout ()) { @@ -1755,6 +1771,7 @@ read_options (db::Cell *cell, const std::string &path, const db::LoadLayoutOptio db::CellMapping cm; std::vector new_cells = cm.create_single_mapping_full (*cell->layout (), cell->cell_index (), tmp, *tmp.begin_top_down ()); cell->move_tree_shapes (tmp.cell (*tmp.begin_top_down ()), cm); + cell->layout ()->merge_meta_info (tmp, cm); return new_cells; } @@ -1834,6 +1851,21 @@ Class decl_Cell ("db", "Cell", "\n" "This method has been introduced in version 0.28.8." ) + + gsi::method_ext ("merge_meta_info", &cell_merge_meta_info, gsi::arg ("other"), + "@brief Merges the meta information from the other cell into this cell\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "Existing keys in this cell will be overwritten by the respective values from the other cell.\n" + "New keys will be added.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + + gsi::method_ext ("copy_meta_info", &cell_copy_meta_info, gsi::arg ("other"), + "@brief Copies the meta information from the other cell into this cell\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "The meta information from this cell will be replaced by the meta information from the other cell.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + gsi::method_ext ("clear_meta_info", &cell_clear_meta_info, "@brief Clears the meta information of the cell\n" "See \\LayoutMetaInfo for details about cells and meta information.\n" diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index d85fe3687..db8c78233 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1105,12 +1105,54 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.25." ) + + gsi::method ("merge_meta_info", static_cast (&db::Layout::merge_meta_info), gsi::arg ("other"), + "@brief Merges the meta information from the other layout into this layout\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "Existing keys in this layout will be overwritten by the respective values from the other layout.\n" + "New keys will be added.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + + gsi::method ("merge_meta_info", static_cast (&db::Layout::merge_meta_info), gsi::arg ("other"), gsi::arg ("cm"), + "@brief Merges the meta information from the other layout into this layout for the cells given by the cell mapping\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "This method will use the source/target cell pairs from the cell mapping object and merge the meta information " + "from each source cell from the 'other' layout into the mapped cell inside self.\n" + "This method can be used with '\\copy_tree_shapes' and similar to copy meta information in addition to the shapes.\n" + "Existing cell-specific keys in this layout will be overwritten by the respective values from the other layout.\n" + "New keys will be added.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + + gsi::method ("copy_meta_info", static_cast (&db::Layout::copy_meta_info), gsi::arg ("other"), + "@brief Copies the meta information from the other layout into this layout\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "The meta information from this layout will be replaced by the meta information from the other layout.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + + gsi::method ("copy_meta_info", static_cast (&db::Layout::copy_meta_info), gsi::arg ("other"), gsi::arg ("cm"), + "@brief Copies the meta information from the other layout into this layout for the cells given by the cell mapping\n" + "See \\LayoutMetaInfo for details about cells and meta information.\n" + "This method will use the source/target cell pairs from the cell mapping object and merge the meta information " + "from each source cell from the 'other' layout into the mapped cell inside self.\n" + "This method can be used with '\\copy_tree_shapes' and similar to copy meta information in addition to the shapes.\n" + "All cell-specific keys in this layout will be replaced by the respective values from the other layout.\n" + "\n" + "This method has been introduced in version 0.28.16." + ) + gsi::method ("clear_meta_info", static_cast (&db::Layout::clear_meta), "@brief Clears the meta information of the layout\n" "See \\LayoutMetaInfo for details about layouts and meta information." "\n" "This method has been introduced in version 0.28.8." ) + + gsi::method ("clear_all_meta_info", static_cast (&db::Layout::clear_all_meta), + "@brief Clears all meta information of the layout (cell specific and global)\n" + "See \\LayoutMetaInfo for details about layouts and meta information." + "\n" + "This method has been introduced in version 0.28.16." + ) + gsi::method ("remove_meta_info", static_cast (&db::Layout::remove_meta_info), gsi::arg ("name"), "@brief Removes meta information from the layout\n" "See \\LayoutMetaInfo for details about layouts and meta information." diff --git a/src/db/db/gsiDeclDbLayoutDiff.cc b/src/db/db/gsiDeclDbLayoutDiff.cc index 84ac3217e..3e8098750 100644 --- a/src/db/db/gsiDeclDbLayoutDiff.cc +++ b/src/db/db/gsiDeclDbLayoutDiff.cc @@ -90,6 +90,11 @@ public: dbu_differs_event (dbu_a, dbu_b); } + virtual void layout_meta_info_differs (const std::string &name, const tl::Variant &value_a, const tl::Variant &value_b) + { + layout_meta_info_differs_event (name, value_a, value_b); + } + virtual void layer_in_a_only (const db::LayerProperties &la) { layer_in_a_only_event (la); @@ -132,6 +137,11 @@ public: begin_cell_event (mp_cell_a, mp_cell_b); } + virtual void cell_meta_info_differs (const std::string &name, const tl::Variant &value_a, const tl::Variant &value_b) + { + cell_meta_info_differs_event (name, value_a, value_b); + } + virtual void begin_inst_differences () { begin_inst_differences_event (); @@ -343,6 +353,7 @@ public: } tl::event dbu_differs_event; + tl::event layout_meta_info_differs_event; tl::event layer_in_a_only_event; tl::event layer_in_b_only_event; tl::event layer_name_differs_event; @@ -351,6 +362,7 @@ public: tl::event cell_in_b_only_event; tl::event bbox_differs_event; tl::event begin_cell_event; + tl::event cell_meta_info_differs_event; tl::Event begin_inst_differences_event; tl::event instance_in_a_only_event; tl::event instance_in_b_only_event; @@ -409,6 +421,10 @@ static unsigned int f_no_properties () { return db::layout_diff::f_no_properties; } +static unsigned int f_with_meta () { + return db::layout_diff::f_with_meta; +} + static unsigned int f_no_layer_names () { return db::layout_diff::f_no_layer_names; } @@ -450,7 +466,7 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "full compare.\n" "\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("IgnoreDuplicates", &f_ignore_duplicates, "@brief Ignore duplicate instances or shapes\n" @@ -462,17 +478,25 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", 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 " - "compared with other constants to form a flag set." + "combined with other constants to form a flag set." ) + gsi::constant ("NoProperties", &f_no_properties, "@brief Ignore properties\n" "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." + "combined with other constants to form a flag set." + ) + + gsi::constant ("WithMetaInfo", &f_with_meta, + "@brief Ignore meta info\n" + "This constant can be used for the flags parameter of \\compare_layouts and \\compare_cells. It can be " + "combined with other constants to form a flag set. If present, this option tells the compare algorithm " + "to include persisted meta info in the compare.\n" + "\n" + "This flag has been introduced in version 0.28.16." ) + gsi::constant ("NoLayerNames", &f_no_layer_names, "@brief Do not compare layer names\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("Verbose", &f_verbose, "@brief Enables verbose mode (gives details about the differences)\n" @@ -480,22 +504,22 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "See the event descriptions for details about the differences in verbose and non-verbose mode.\n" "\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("BoxesAsPolygons", &f_boxes_as_polygons, "@brief Compare boxes to polygons\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("FlattenArrayInsts", &f_flatten_array_insts, "@brief Compare array instances instance by instance\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("PathsAsPolygons", &f_paths_as_polygons, "@brief Compare paths to polygons\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("SmartCellMapping", &f_smart_cell_mapping, "@brief Derive smart cell mapping instead of name mapping (available only if top cells are specified)\n" @@ -503,7 +527,7 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "cells are compared (with \\LayoutDiff#compare with cells instead of layout objects).\n" "\n" "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.\n" + "combined with other constants to form a flag set.\n" ) + gsi::constant ("DontSummarizeMissingLayers", &f_dont_summarize_missing_layers, "@brief Don't summarize missing layers\n" @@ -511,12 +535,12 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "layer will be reported as difference.\n" "\n" "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." + "combined with other constants to form a flag set." ) + gsi::constant ("NoTextDetails", &f_no_text_details, "@brief Ignore text details (font, size, presentation)\n" "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." + "combined with other constants to form a flag set." ) + gsi::method ("compare", &LayoutDiff::compare_layouts, gsi::arg("a"), @@ -593,6 +617,14 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", gsi::event ("on_dbu_differs", &LayoutDiff::dbu_differs_event, gsi::arg ("dbu_a"), gsi::arg ("dbu_b"), "@brief This signal indicates a difference in the database units of the layouts\n" ) + + gsi::event ("on_layout_meta_info_differs", &LayoutDiff::layout_meta_info_differs_event, gsi::arg ("name"), gsi::arg ("a"), gsi::arg ("b"), + "@brief This signal indicates that global meta info differs\n" + "Meta information is only compared when \\WithMetaInfo is added to the compare flags.\n" + "'a' and 'b' are the values for the first and second layout. 'nil' is passed to these values to indicate " + "missing meta information on one side.\n" + "\n" + "This event has been added in version 0.28.16." + ) + gsi::event ("on_layer_in_a_only", &LayoutDiff::layer_in_a_only_event, gsi::arg ("a"), "@brief This signal indicates a layer that is present only in the first layout\n" ) + @@ -619,9 +651,17 @@ gsi::Class decl_LayoutDiff ("db", "LayoutDiff", "In verbose mode detailed events will be issued indicating the differences.\n" ) + gsi::event ("on_begin_cell", &LayoutDiff::begin_cell_event, gsi::arg ("ca"), gsi::arg ("cb"), - "@brief This signal initiates the sequence of events for a cell pair\n" + "@brief This signal indicates the sequence of events for a cell pair\n" "All cell specific events happen between \\begin_cell_event and \\end_cell_event signals." ) + + gsi::event ("on_cell_meta_info_differs", &LayoutDiff::cell_meta_info_differs_event, gsi::arg ("name"), gsi::arg ("a"), gsi::arg ("b"), + "@brief This signal indicates that meta info between the current cells differs\n" + "Meta information is only compared when \\WithMetaInfo is added to the compare flags.\n" + "'a' and 'b' are the values for the first and second layout. 'nil' is passed to these values to indicate " + "missing meta information on one side.\n" + "\n" + "This event has been added in version 0.28.16." + ) + gsi::event ("on_begin_inst_differences", &LayoutDiff::begin_inst_differences_event, "@brief This signal indicates differences in the cell instances\n" "In verbose mode (see \\Verbose) more events will follow that indicate the instances that are present only " diff --git a/src/db/unit_tests/dbLayoutDiffTests.cc b/src/db/unit_tests/dbLayoutDiffTests.cc index 87ef5f1ba..6fb0d413b 100644 --- a/src/db/unit_tests/dbLayoutDiffTests.cc +++ b/src/db/unit_tests/dbLayoutDiffTests.cc @@ -38,6 +38,7 @@ public: std::string text () const { return m_os.str (); } void clear () { m_os.str (std::string ()); } void dbu_differs (double dbu_a, double dbu_b); + void layout_meta_info_differs (const std::string &, const tl::Variant &, const tl::Variant &); void layer_in_a_only (const db::LayerProperties &la); void layer_in_b_only (const db::LayerProperties &lb); void layer_name_differs (const db::LayerProperties &la, const db::LayerProperties &lb); @@ -45,6 +46,7 @@ public: void cell_in_b_only (const std::string &cellname, db::cell_index_type ci); void cell_name_differs (const std::string &cellname_a, db::cell_index_type cia, const std::string &cellname_b, db::cell_index_type cib); void begin_cell (const std::string &cellname, db::cell_index_type cia, db::cell_index_type cib); + void cell_meta_info_differs (const std::string &, const tl::Variant &, const tl::Variant &); void bbox_differs (const db::Box &ba, const db::Box &bb); void begin_inst_differences (); void instances_in_a (const std::vector &insts_a, const std::vector &cell_names, const db::PropertiesRepository &props); @@ -155,6 +157,12 @@ TestDifferenceReceiver::dbu_differs (double dbu_a, double dbu_b) m_os << "layout_diff: database units differ " << dbu_a << " vs. " << dbu_b << std::endl; } +void +TestDifferenceReceiver::layout_meta_info_differs (const std::string &name, const tl::Variant &va, const tl::Variant &vb) +{ + m_os << "layout_diff: global meta info differs " << name << ": " << va.to_string () << " vs. " << vb.to_string () << std::endl; +} + void TestDifferenceReceiver::layer_in_a_only (const db::LayerProperties &la) { @@ -204,6 +212,12 @@ TestDifferenceReceiver::begin_cell (const std::string &cellname, db::cell_index_ m_cellname = cellname; } +void +TestDifferenceReceiver::cell_meta_info_differs (const std::string &name, const tl::Variant &va, const tl::Variant &vb) +{ + m_os << "layout_diff: cell meta info differs for cell " << m_cellname << " - " << name << ": " << va.to_string () << " vs. " << vb.to_string () << std::endl; +} + void TestDifferenceReceiver::begin_inst_differences () { @@ -1704,4 +1718,46 @@ TEST(8) ); } +// meta info +TEST(9) +{ + db::Layout a; + db::cell_index_type caa = a.add_cell ("A"); + db::cell_index_type cab = a.add_cell ("B"); + db::Layout b; + db::cell_index_type cba = b.add_cell ("A"); + db::cell_index_type cbb = b.add_cell ("B"); + + a.add_meta_info ("x", db::MetaInfo ("", 17.0, true)); + a.add_meta_info ("y", db::MetaInfo ("", -1.0, false)); // not persisted + a.add_meta_info ("z", db::MetaInfo ("", -1.0, true)); + a.add_meta_info (caa, "a1", db::MetaInfo ("", "a", true)); + a.add_meta_info (caa, "a2", db::MetaInfo ("", 42, false)); // not persisted + a.add_meta_info (caa, "a3", db::MetaInfo ("", 41, true)); + a.add_meta_info (cab, "b1", db::MetaInfo ("", "b", true)); + a.add_meta_info (cab, "b2", db::MetaInfo ("", 3, false)); // not persisted + a.add_meta_info (cab, "b3", db::MetaInfo ("", "q", true)); + + b.add_meta_info ("x", db::MetaInfo ("", 21.0, true)); + b.add_meta_info ("y", db::MetaInfo ("", -1.0, true)); + b.add_meta_info (cba, "a1", db::MetaInfo ("", "aa", true)); + b.add_meta_info (cba, "a2", db::MetaInfo ("", 42, true)); + b.add_meta_info (cbb, "b1", db::MetaInfo ("", "bb", true)); + b.add_meta_info (cbb, "b2", db::MetaInfo ("", 3, true)); + + TestDifferenceReceiver r; + bool eq = db::compare_layouts (a, b, db::layout_diff::f_verbose | db::layout_diff::f_with_meta, 0, r); + EXPECT_EQ (eq, false); + EXPECT_EQ (r.text (), + "layout_diff: global meta info differs x: 17 vs. 21\n" + "layout_diff: global meta info differs y: nil vs. -1\n" + "layout_diff: global meta info differs z: -1 vs. nil\n" + "layout_diff: cell meta info differs for cell A - a1: a vs. aa\n" + "layout_diff: cell meta info differs for cell A - a2: nil vs. 42\n" + "layout_diff: cell meta info differs for cell A - a3: 41 vs. nil\n" + "layout_diff: cell meta info differs for cell B - b1: b vs. bb\n" + "layout_diff: cell meta info differs for cell B - b2: nil vs. 3\n" + "layout_diff: cell meta info differs for cell B - b3: q vs. nil\n" + ); +} diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2Reader.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2ReaderTests.cc similarity index 98% rename from src/plugins/streamers/gds2/unit_tests/dbGDS2Reader.cc rename to src/plugins/streamers/gds2/unit_tests/dbGDS2ReaderTests.cc index 4e3774736..c503bab0a 100644 --- a/src/plugins/streamers/gds2/unit_tests/dbGDS2Reader.cc +++ b/src/plugins/streamers/gds2/unit_tests/dbGDS2ReaderTests.cc @@ -538,7 +538,7 @@ TEST(4_CollectModeRename) } std::string fn_au (tl::testdata () + "/gds/collect_rename_au.gds"); - db::compare_layouts (_this, layout, fn_au, db::WriteGDS2, 1); + db::compare_layouts (_this, layout, fn_au, db::NormalizationMode (db::WriteGDS2 | db::WithMeta), 1); } TEST(4_CollectModeRenameWithGhost) @@ -634,7 +634,7 @@ TEST(4_CollectModeOverwrite) } std::string fn_au (tl::testdata () + "/gds/collect_overwrite_au.gds"); - db::compare_layouts (_this, layout, fn_au, db::WriteGDS2, 1); + db::compare_layouts (_this, layout, fn_au, db::NormalizationMode (db::WriteGDS2 | db::WithMeta), 1); } TEST(4_CollectModeSkip) @@ -658,7 +658,7 @@ TEST(4_CollectModeSkip) } std::string fn_au (tl::testdata () + "/gds/collect_skip_au.gds"); - db::compare_layouts (_this, layout, fn_au, db::WriteGDS2, 1); + db::compare_layouts (_this, layout, fn_au, db::NormalizationMode (db::WriteGDS2 | db::WithMeta), 1); } TEST(4_CollectModeAdd) @@ -682,7 +682,7 @@ TEST(4_CollectModeAdd) } std::string fn_au (tl::testdata () + "/gds/collect_add_au.gds"); - db::compare_layouts (_this, layout, fn_au, db::WriteGDS2, 1); + db::compare_layouts (_this, layout, fn_au, db::NormalizationMode (db::WriteGDS2 | db::WithMeta), 1); } // border case with multiple padding 0 for SNAME and STRING records diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2Writer.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc similarity index 100% rename from src/plugins/streamers/gds2/unit_tests/dbGDS2Writer.cc rename to src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc diff --git a/src/plugins/streamers/gds2/unit_tests/unit_tests.pro b/src/plugins/streamers/gds2/unit_tests/unit_tests.pro index cc5d39a61..396a6273e 100644 --- a/src/plugins/streamers/gds2/unit_tests/unit_tests.pro +++ b/src/plugins/streamers/gds2/unit_tests/unit_tests.pro @@ -6,8 +6,8 @@ TARGET = gds2_tests include($$PWD/../../../../lib_ut.pri) SOURCES = \ - dbGDS2Reader.cc \ - dbGDS2Writer.cc \ + dbGDS2ReaderTests.cc \ + dbGDS2WriterTests.cc INCLUDEPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common DEPENDPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common diff --git a/testdata/gds/collect_add_au.gds b/testdata/gds/collect_add_au.gds index ca0206610f735d387d7be8c495ec574f7d97e7bd..ec52fb206812965fa59b229753e871bc520c7aa4 100644 GIT binary patch literal 13406 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 diff --git a/testdata/gds/collect_added.gds b/testdata/gds/collect_added.gds index e4882b6d8354f758abd8171d00e5d3c2dcc31d6d..2e366a6a0f34b1d5d6c07e515570949492679cde 100644 GIT binary patch literal 5086 zcmeHL&ref95T5s1mRbuzB*u^)Odym-0_BH@CN(Ko6JiApB!a)10tK3sVrZk7c=Y1U zlNUXBF!3KS#)Bsh#y^FV@!-jL@SEA)_LWDa5KJ`bCVjK-&Ca}U_nY0BS5oAhJHLnV(xs zFD+(g=Wfj7mVzN7)!`DZoqQeY3gFsYQx{!w==_knm0nB@Zf)=6$KuJD$Qb-VOVn$$ z8^!IFToG#B8H-Wy3{fyl6z*1xsDFYYo_Di#Cv^^~g_(3FljITNv4gtsY_O8wt-#rB z^PY%|9Nfxn#FKq}qcZX|_3+4I`C)FgP!dH#qY5)-XmU^TJMrXQkypn?68FXKl(s9B z9_4q+`FL_YS1jj66QU^Ft#6kq*-~x`x>&igmgRecqg~_RCjgQVE2aJA*VnVf#|$qq zJgRZD)ii4W!kWY-u?f~RtHx$h+XOtCO>Hx!HLljI`seHDHCrj<%3`6%6JtdrC8u-c z!Ya7f$H&w$_(-K%7wWBc$`k zG1$OibyBBQiw%=KA|{)UBG(>0pPwKjt$c16;r%hcFy>zB;;Ka|OTmZG5Nz%A$0|3hY&B^sTU60f**^7Dmr~!l zpCHzVtfZH?x4)n0W_L+KweR8OI&AgHQ3p_qrVg`Bb$gx5!`nkPpBu zkO~qY6!{r)3D3V82^4~kCC2a!suHN3AwAA9b}}RQt;ADTy zhDdY`o@SU(O5N7E<=}6VW_;K<9QUzV^s%`wo&+gavr?D?TUCiZ#~3pxvBrIlLo3lW z?I=hmt4JNQV=96a@gxn>MmeuLH(6Uo%4^M%;}2`jF~w;4UgZ86a-8vuI4*e>-1K{z zK@5IUsk(2+`frtHe1!Bl;hXb6zt;{q|FAac`q-^M!)0yo)r~V3F)kTeSTj4hs!_cT zIEh5trr!%YW~_QEY&WyQK(btqq`qSIAOV{V-q(GPK~%F6XNr^`^&x8#F*$*2lKolX zf9IRyn6o?olX6fc>{(^Rg^%w;R!)1z8K&a`e2RyzO^FGQZ)loeu&K9A*tc1xP5gP@ s);L~5ziRQRG22!NCmtc^71HGKRmjD0& literal 4990 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 literal 9360 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 WC$V#csaF) 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 literal 13248 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 diff --git a/testdata/ruby/dbLayoutTests2.rb b/testdata/ruby/dbLayoutTests2.rb index b5dbb9a50..bf5349645 100644 --- a/testdata/ruby/dbLayoutTests2.rb +++ b/testdata/ruby/dbLayoutTests2.rb @@ -23,6 +23,10 @@ end load("test_prologue.rb") +def mi2s(obj) + obj.each_meta_info.collect { |mi| mi.name + ":" + mi.value.to_s }.sort.join(";") +end + class DBLayoutTests2_TestClass < TestBase # LayerInfo @@ -1253,6 +1257,175 @@ class DBLayoutTests2_TestClass < TestBase end + # Cell#read and meta info (issue #1609) + def test_16 + + tmp = File::join($ut_testtmp, "test16.gds") + + ly1 = RBA::Layout::new + a = ly1.create_cell("A") + b = ly1.create_cell("B") + a.insert(RBA::DCellInstArray::new(b, RBA::Trans::new)) + + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", 42.0, "", true)) + a.add_meta_info(RBA::LayoutMetaInfo::new("am2", "u", "", true)) + assert_equal(mi2s(a), "am1:42.0;am2:u") + + b.add_meta_info(RBA::LayoutMetaInfo::new("bm1", 17, "", true)) + assert_equal(mi2s(b), "bm1:17") + + ly1.add_meta_info(RBA::LayoutMetaInfo::new("lm1", -2.0, "", true)) + ly1.add_meta_info(RBA::LayoutMetaInfo::new("lm2", "v", "", true)) + assert_equal(mi2s(ly1), "lm1:-2.0;lm2:v") + + ly1.write(tmp) + + ly2 = RBA::Layout::new + top = ly2.create_cell("TOP") + a = ly2.create_cell("A") + c = ly2.create_cell("C") + top.insert(RBA::DCellInstArray::new(a, RBA::Trans::new)) + a.insert(RBA::DCellInstArray::new(c, RBA::Trans::new)) + + top.add_meta_info(RBA::LayoutMetaInfo::new("topm1", "abc")) + assert_equal(mi2s(top), "topm1:abc") + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", "a number")) + a.add_meta_info(RBA::LayoutMetaInfo::new("am3", 0)) + assert_equal(mi2s(a), "am1:a number;am3:0") + c.add_meta_info(RBA::LayoutMetaInfo::new("cm1", 3)) + assert_equal(mi2s(c), "cm1:3") + + ly2.add_meta_info(RBA::LayoutMetaInfo::new("lm1", 5)) + assert_equal(mi2s(ly2), "lm1:5") + + a.read(tmp) + # not modified + assert_equal(mi2s(ly2), "lm1:5") + # merged + assert_equal(mi2s(a), "am1:42.0;am2:u;am3:0") + # not modified + assert_equal(mi2s(c), "cm1:3") + + b2 = ly2.cell("B") + # imported + assert_equal(mi2s(b2), "bm1:17") + + puts "done." + + end + + # Layout, meta info diverse + def test_17 + + manager = RBA::Manager::new + + ly = RBA::Layout::new(manager) + a = ly.create_cell("A") + + manager.transaction("trans") + ly.add_meta_info(RBA::LayoutMetaInfo::new("lm1", 17)) + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", "u")) + manager.commit + + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.undo + assert_equal(mi2s(ly), "") + assert_equal(mi2s(a), "") + + manager.redo + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.transaction("trans") + ly.add_meta_info(RBA::LayoutMetaInfo::new("lm1", 117)) + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", "v")) + manager.commit + + assert_equal(mi2s(ly), "lm1:117") + assert_equal(mi2s(a), "am1:v") + + manager.undo + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.redo + assert_equal(mi2s(ly), "lm1:117") + assert_equal(mi2s(a), "am1:v") + + manager.undo + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.transaction("trans") + ly.remove_meta_info("lm1") + a.remove_meta_info("am1") + a.remove_meta_info("doesnotexist") + manager.commit + + assert_equal(mi2s(ly), "") + assert_equal(mi2s(a), "") + + manager.undo + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.transaction("trans") + ly.clear_all_meta_info + manager.commit + + assert_equal(mi2s(ly), "") + assert_equal(mi2s(a), "") + + manager.undo + assert_equal(mi2s(ly), "lm1:17") + assert_equal(mi2s(a), "am1:u") + + manager.redo + assert_equal(mi2s(ly), "") + assert_equal(mi2s(a), "") + + ly2 = RBA::Layout::new + ly.add_meta_info(RBA::LayoutMetaInfo::new("lm1", 17)) + ly2.add_meta_info(RBA::LayoutMetaInfo::new("lm2", 42)) + assert_equal(mi2s(ly), "lm1:17") + ly.merge_meta_info(ly2) + assert_equal(mi2s(ly), "lm1:17;lm2:42") + ly.copy_meta_info(ly2) + assert_equal(mi2s(ly), "lm2:42") + + a = ly.create_cell("A") + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", "u")) + b = ly2.create_cell("B") + b.add_meta_info(RBA::LayoutMetaInfo::new("bm1", "v")) + + assert_equal(mi2s(a), "am1:u") + a.merge_meta_info(b) + assert_equal(mi2s(a), "am1:u;bm1:v") + a.copy_meta_info(b) + assert_equal(mi2s(a), "bm1:v") + + ly = RBA::Layout::new + ly2 = RBA::Layout::new + + a = ly.create_cell("A") + a.add_meta_info(RBA::LayoutMetaInfo::new("am1", "u")) + ly2.create_cell("X") + b = ly2.create_cell("B") + b.add_meta_info(RBA::LayoutMetaInfo::new("bm1", "v")) + + cm = RBA::CellMapping::new + cm.map(b.cell_index, a.cell_index) + + assert_equal(mi2s(a), "am1:u") + ly.merge_meta_info(ly2, cm) + assert_equal(mi2s(a), "am1:u;bm1:v") + ly.copy_meta_info(ly2, cm) + assert_equal(mi2s(a), "bm1:v") + + end + end load("test_epilogue.rb")