diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index ec140eb71..5fd475e00 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -93,7 +93,7 @@ Cell::box_type Cell::ms_empty_box = Cell::box_type (); Cell::Cell (cell_index_type ci, db::Layout &l) : db::Object (l.manager ()), m_cell_index (ci), mp_layout (&l), m_instances (this), m_prop_id (0), m_hier_levels (0), - m_bbox_needs_update (false), m_ghost_cell (false), + m_bbox_needs_update (false), m_locked (false), m_ghost_cell (false), mp_last (0), mp_next (0) { // .. nothing yet @@ -125,6 +125,7 @@ Cell::operator= (const Cell &d) } m_ghost_cell = d.m_ghost_cell; + m_locked = d.m_locked; m_instances = d.m_instances; m_bbox = d.m_bbox; m_bboxes = d.m_bboxes; @@ -138,6 +139,7 @@ Cell::operator= (const Cell &d) Cell::~Cell () { + m_locked = false; clear_shapes (); } @@ -180,6 +182,8 @@ Cell::empty () const void Cell::clear (unsigned int index) { + check_locked (); + 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! @@ -191,6 +195,8 @@ Cell::clear (unsigned int index) void Cell::clear (unsigned int index, unsigned int types) { + check_locked (); + 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! @@ -241,6 +247,8 @@ Cell::index_of_shapes (const Cell::shapes_type *shapes) const void Cell::clear_shapes () { + check_locked (); + mp_layout->invalidate_bboxes (std::numeric_limits::max ()); // HINT: must come before the change is done! clear_shapes_no_invalidate (); } @@ -345,6 +353,8 @@ Cell::update_bbox (unsigned int layers) void Cell::copy (unsigned int src, unsigned int dest) { + check_locked (); + if (src != dest) { shapes (dest).insert (shapes (src)); } else { @@ -359,6 +369,8 @@ Cell::copy (unsigned int src, unsigned int dest) void Cell::copy (unsigned int src, unsigned int dest, unsigned int types) { + check_locked (); + if (src != dest) { shapes (dest).insert (shapes (src), types); } else { @@ -373,6 +385,8 @@ Cell::copy (unsigned int src, unsigned int dest, unsigned int types) void Cell::move (unsigned int src, unsigned int dest) { + check_locked (); + if (src != dest) { copy (src, dest); clear (src); @@ -382,6 +396,8 @@ Cell::move (unsigned int src, unsigned int dest) void Cell::move (unsigned int src, unsigned int dest, unsigned int types) { + check_locked (); + if (src != dest) { copy (src, dest, types); clear (src, types); @@ -391,6 +407,8 @@ Cell::move (unsigned int src, unsigned int dest, unsigned int types) void Cell::swap (unsigned int i1, unsigned int i2) { + check_locked (); + if (i1 != i2) { if (manager () && manager ()->transacting ()) { @@ -784,6 +802,14 @@ Cell::set_name (const std::string &name) layout ()->rename_cell (cell_index (), name.c_str ()); } +void +Cell::check_locked () const +{ + if (m_locked) { + throw tl::Exception (tl::to_string (tr ("Cell '%s' cannot be modified as it is locked")), get_basic_name ()); + } +} + void Cell::copy_shapes (const db::Cell &source_cell, const db::LayerMapping &layer_mapping) { @@ -800,6 +826,8 @@ Cell::copy_shapes (const db::Cell &source_cell, const db::LayerMapping &layer_ma throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + if (target_layout != source_layout) { db::PropertyMapper pm (target_layout, source_layout); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); @@ -824,6 +852,8 @@ Cell::copy_shapes (const db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout"))); } + check_locked (); + if (target_layout != source_cell.layout ()) { if (! source_cell.layout ()) { throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); @@ -848,6 +878,8 @@ Cell::copy_instances (const db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout"))); } + check_locked (); + for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) { insert (*i); } @@ -869,6 +901,8 @@ Cell::copy_tree (const db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::CellMapping cm; @@ -900,6 +934,8 @@ Cell::copy_tree_shapes (const db::Cell &source_cell, const db::CellMapping &cm) throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); db::LayerMapping lm; @@ -926,6 +962,8 @@ Cell::copy_tree_shapes (const db::Cell &source_cell, const db::CellMapping &cm, throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); std::vector source_cells; @@ -949,6 +987,8 @@ Cell::move_shapes (db::Cell &source_cell, const db::LayerMapping &layer_mapping) throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + if (target_layout != source_layout) { db::PropertyMapper pm (target_layout, source_layout); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); @@ -975,6 +1015,8 @@ Cell::move_shapes (db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Cell does not reside in a layout"))); } + check_locked (); + if (target_layout != source_cell.layout ()) { if (! source_cell.layout ()) { throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); @@ -1000,6 +1042,8 @@ Cell::move_instances (db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Cells do not reside in the same layout"))); } + check_locked (); + for (db::Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) { insert (*i); } @@ -1023,6 +1067,8 @@ Cell::move_tree (db::Cell &source_cell) throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::PropertyMapper pm (target_layout, source_layout); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); @@ -1057,6 +1103,8 @@ Cell::move_tree_shapes (db::Cell &source_cell, const db::CellMapping &cm) throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::PropertyMapper pm (target_layout, source_layout); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); @@ -1084,6 +1132,8 @@ Cell::move_tree_shapes (db::Cell &source_cell, const db::CellMapping &cm, const throw tl::Exception (tl::to_string (tr ("Source cell does not reside in a layout"))); } + check_locked (); + db::PropertyMapper pm (target_layout, source_layout); db::ICplxTrans trans (source_layout->dbu () / target_layout->dbu ()); diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index 13759f409..a321e3e36 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -832,6 +832,13 @@ public: */ virtual void update (ImportLayerMapping * /*layer_mapping*/ = 0) { } + /** + * @brief Checks if the cell is locked + * + * This method throws an exception if the cell is locked. + */ + void check_locked () const; + /** * @brief Tell, if this cell is a proxy cell * @@ -903,6 +910,27 @@ public: m_ghost_cell = g; } + /** + * @brief Gets a value indicating whether the cell is locked + * + * A locked cell cannot be modified in terms of instances or shapes. + * The name of a locked cell can be changed though. + */ + bool is_locked () const + { + return m_locked; + } + + /** + * @brief Sets the locked state of the cell + * + * See \is_locked for details about locked state. + */ + void set_locked (bool f) + { + m_locked = f; + } + /** * @brief Returns a value indicating whether the cell is empty * @@ -1076,8 +1104,9 @@ private: db::properties_id_type m_prop_id; // packed fields - unsigned int m_hier_levels : 30; + unsigned int m_hier_levels : 29; bool m_bbox_needs_update : 1; + bool m_locked : 1; bool m_ghost_cell : 1; static box_type ms_empty_box; diff --git a/src/db/db/dbInstances.cc b/src/db/db/dbInstances.cc index c8ff86b5a..4acbc7a5a 100644 --- a/src/db/db/dbInstances.cc +++ b/src/db/db/dbInstances.cc @@ -1035,8 +1035,10 @@ Instances::layout () const void Instances::invalidate_insts () { - if (cell ()) { - cell ()->invalidate_insts (); + db::Cell *cp = cell (); + if (cp) { + cp->check_locked (); + cp->invalidate_insts (); } set_instance_by_cell_index_needs_made (true); diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index f3a38ad34..a864ecaff 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -873,6 +873,7 @@ Layout::delete_cells (const std::set &cells_to_delete) std::set pcs; for (std::set::const_iterator c = cells_to_delete.begin (); c != cells_to_delete.end (); ++c) { const db::Cell &cref = cell (*c); + cref.check_locked (); for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) { pcs.insert (*pc); } @@ -950,6 +951,7 @@ void Layout::delete_cell (cell_index_type id) { db::Cell &cref = cell (id); + cref.check_locked (); std::vector pcs; for (db::Cell::parent_cell_iterator pc = cref.begin_parent_cells (); pc != cref.end_parent_cells (); ++pc) { @@ -1140,6 +1142,8 @@ Layout::flatten (const db::Cell &source_cell, db::Cell &target_cell, const db::I void Layout::flatten (db::Cell &cell_to_flatten, int levels, bool prune) { + cell_to_flatten.check_locked (); + std::set direct_children; if (prune) { // save direct children diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index 76f773ae2..326e5bba5 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -297,12 +297,16 @@ Shapes::array_repository () const void Shapes::invalidate_state () { + db::Cell *cp = cell (); + if (cp) { + cp->check_locked (); + } if (! is_dirty ()) { set_dirty (true); - if (layout () && cell ()) { - unsigned int index = cell ()->index_of_shapes (this); + if (cp && cp->layout ()) { + unsigned int index = cp->index_of_shapes (this); if (index != std::numeric_limits::max ()) { - layout ()->invalidate_bboxes (index); + cp->layout ()->invalidate_bboxes (index); } } } diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 98d33a9ab..09594321c 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -2923,6 +2923,28 @@ Class decl_Cell ("db", "Cell", "\n" "@return A list of cell indices.\n" ) + + gsi::method ("is_locked?", &db::Cell::is_locked, + "@brief Gets a value indicating whether the cell is locked\n" + "\n" + "Locked cells cannot be modified in terms of instances (children) and shapes. " + "Locked cells can still be renamed, but cannot be deleted or cleared.\n" + "Among other things, these features are disabled too: layer operations, copy of instances or shapes, " + "flattening or pruning.\n" + "\n" + "However, wiping the layout entirely with \\Layout#clear is always possible, even if cells are locked.\n" + "\n" + "Use \\locked= to set the locked state of the cell.\n" + "\n" + "The lock feature has been introduced in version 0.29.11." + ) + + gsi::method ("locked=", &db::Cell::set_locked, gsi::arg ("l"), + "@brief Locks or unlocks the cell\n" + "\n" + "Set this predicate to 'true' to lock the cell and to 'false' to unlock it.\n" + "See \\is_locked? for details about the lock feature.\n" + "\n" + "The lock feature has been introduced in version 0.29.11." + ) + gsi::method ("bbox", (const db::Cell::box_type &(db::Cell::*) () const) &db::Cell::bbox, "@brief Gets the bounding box of the cell\n" "\n" diff --git a/testdata/ruby/dbCellTests.rb b/testdata/ruby/dbCellTests.rb index f72149747..dec702c68 100644 --- a/testdata/ruby/dbCellTests.rb +++ b/testdata/ruby/dbCellTests.rb @@ -124,6 +124,230 @@ class DBCellTests_TestClass < TestBase end + # locking + def test_4 + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + other = ly.create_cell("OTHER") + child = ly.create_cell("CHILD") + + l1 = ly.layer(1, 0) + l2 = ly.layer(2, 0) + shape = top.shapes(l1).insert(RBA::Box::new(0, 0, 100, 200)) + inst = top.insert(RBA::CellInstArray::new(child, RBA::Trans::new(RBA::Vector::new(100, 200)))) + other.insert(RBA::CellInstArray::new(child, RBA::Trans::new(RBA::Vector::new(10, 20)))) + + assert_equal(top.is_locked?, false) + top.locked = false + assert_equal(top.is_locked?, false) + top.locked = true + assert_equal(top.is_locked?, true) + + # rename is still possible + top.name = "TOP2" + + # instances of the cell can still be created + toptop = ly.create_cell("TOPTOP") + toptop.insert(RBA::CellInstArray::new(top, RBA::Trans::new)) + assert_equal(top.each_parent_inst.to_a[0].child_inst.to_s, "cell_index=0 r0 0,0") + + begin + # forbidden now + top.shapes(l2).insert(RBA::Box::new(0, 0, 100, 200)) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shapes::insert") + end + + begin + # forbidden now + shape.box = RBA::Box::new(1, 2, 101, 202) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::box=") + end + + begin + # forbidden now + shape.prop_id = 1 + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::prop_id=") + end + + begin + # forbidden now + shape.polygon = RBA::Polygon::new(RBA::Box::new(0, 0, 200, 100)) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shape::polygon=") + end + + begin + # forbidden now + inst.cell_index = other.cell_index + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Instance::cell_index=") + end + + begin + # forbidden now + inst.prop_id = 1 + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Instance::prop_id=") + end + + begin + # also forbidden + top.insert(RBA::CellInstArray::new(child, RBA::Trans::new)) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::insert") + end + + begin + # clear is forbidding + top.clear + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::clear") + end + + begin + # clear layer is forbidden + top.shapes(l1).clear + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Shapes::clear") + end + + begin + # clear layer is forbidden + top.clear(l1) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::clear") + end + + begin + # layer copy is forbidden + top.copy(l1, l2) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy") + end + + begin + # layer move is forbidden + top.move(l1, l2) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move") + end + + begin + # layer swap is forbidden + top.swap(l1, l2) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::swap") + end + + begin + # copy_instances is forbidden + top.copy_instances(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_instances") + end + + begin + # move_instances is forbidden + top.move_instances(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_instances") + end + + begin + # copy_tree is forbidden + top.copy_tree(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_tree") + end + + begin + # move_tree is forbidden + top.move_tree(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_tree") + end + + begin + # copy_shapes is forbidden + top.copy_shapes(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::copy_shapes") + end + + begin + # move_shapes is forbidden + top.move_shapes(other) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::move_shapes") + end + + begin + # flatten is forbidden + top.flatten(true) + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::flatten") + end + + begin + # cell cannot be deleted + top.delete + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::delete") + end + + # locked attribute is copied + ly2 = ly.dup + assert_equal(ly2.cell("TOP2").is_locked?, true) + + begin + # cell cannot be deleted + ly2.cell("TOP2").delete + assert_equal(true, false) + rescue => ex + assert_equal(ex.to_s, "Cell 'TOP2' cannot be modified as it is locked in Cell::delete") + end + + # clear and _destroy is always possible + ly2.clear + + ly2 = ly.dup + ly2._destroy + + # resetting the locked flag enables things again (not all tested here) + top.locked = false + + inst.cell_index = other.cell_index + top.shapes(l2).insert(RBA::Box::new(0, 0, 100, 200)) + shape.box = RBA::Box::new(1, 2, 101, 202) + top.delete + + end + end load("test_epilogue.rb")