Merge remote-tracking branch 'origin/bugfix/issue-2350' into wip

This commit is contained in:
Matthias Koefferlein 2026-05-25 20:51:42 +02:00
commit be8b165b6a
11 changed files with 246 additions and 64 deletions

View File

@ -600,30 +600,7 @@ CommonReader::read (db::Layout &layout, const db::LoadLayoutOptions &options)
// A cleanup may be necessary because of the following scenario: if library proxies contain subcells
// which are proxies themselves, the proxy update may make them orphans (the proxies are regenerated).
// The cleanup will removed these.
// Adressing issue #1835 (reading proxy-only GDS file renders empty layout) we do not delete
// the first (non-cold) proxy if there are only proxy top cells.
// We never clean up the top cell if there is a single one. This catches the case of having
// defunct proxies for top cells.
std::set<db::cell_index_type> keep;
if (layout.end_top_cells () - layout.begin_top_down () == 1) {
keep.insert (*layout.begin_top_down ());
} else {
for (auto c = layout.begin_top_down (); c != layout.end_top_cells (); ++c) {
const db::Cell *cptr = &layout.cell (*c);
if (cptr->is_proxy ()) {
if (! dynamic_cast <const db::ColdProxy *> (cptr) && keep.empty ()) {
keep.insert (*c);
}
} else {
keep.clear ();
break;
}
}
}
layout.cleanup (keep);
layout.cleanup ();
return layer_map_out ();
}

View File

@ -1389,7 +1389,6 @@ namespace
{
public:
DeepShapeStoreToShapeTransformer (const DeepShapeStore &dss, const db::Layout &layout)
: mp_layout (& layout)
{
// gets the text annotation property ID -
// this is how the texts are passed for annotating the net names
@ -1445,7 +1444,6 @@ namespace
private:
std::pair<bool, db::property_names_id_type> m_text_annot_name_id;
const db::Layout *mp_layout;
};
}

View File

@ -119,7 +119,6 @@ public:
virtual bool wants_variants () const { return false; }
private:
bool m_inverted;
db::EdgeAngleChecker m_checker;
};

View File

@ -244,7 +244,6 @@ struct DB_PUBLIC EdgeOrientationFilter
}
private:
bool m_inverse;
db::MagnificationAndOrientationReducer m_vars;
EdgeAngleChecker m_checker;
};

View File

@ -1723,6 +1723,84 @@ Layout::refresh ()
}
}
std::set<db::cell_index_type>
Layout::cells_to_cleanup (const std::set<db::cell_index_type> &keep_always) const
{
std::set<db::cell_index_type> to_clean;
// Adressing issue #1835 (reading proxy-only GDS file renders empty layout) we do not delete
// the first (non-cold) proxy if there are only proxy top cells.
// We never clean up the top cell if there is a single one. This catches the case of having
// defunct proxies for top cells.
if (end_top_cells () - begin_top_down () > 1) {
std::set<db::cell_index_type> keep;
for (auto c = begin_top_down (); c != end_top_cells (); ++c) {
const db::Cell *cptr = &cell (*c);
if (cptr->is_proxy ()) {
if (! dynamic_cast <const db::ColdProxy *> (cptr) && keep.empty ()) {
keep.insert (*c);
}
} else {
keep.clear ();
break;
}
}
for (auto c = begin_top_down (); c != end_top_cells (); ++c) {
if (cell (*c).is_proxy () && keep.find (*c) == keep.end () && keep_always.find (*c) == keep_always.end ()) {
to_clean.insert (*c);
}
}
}
// determine all cells that can be deleted as well because they are called
// by cells registered for cleanup and nowhere else
if (! to_clean.empty ()) {
// collect the called cells
std::set <db::cell_index_type> all_called;
for (auto c = to_clean.begin (); c != to_clean.end (); ++c) {
cell (*c).collect_called_cells (all_called, -1);
}
// remove all called cells which are not proxies - we don't want to
// clean up those and their children.
std::set <db::cell_index_type> called;
for (auto c = all_called.begin (); c != all_called.end (); ++c) {
if (cell (*c).is_proxy ()) {
called.insert (*c);
}
}
// From these cells erase all cells that have parents outside the subtree of our cell.
// Make sure this is done recursively by doing this top-down.
for (auto c = begin_top_down (); c != end_top_down (); ++c) {
if (called.find (*c) != called.end ()) {
const db::Cell &ccref = cell (*c);
for (db::Cell::parent_cell_iterator pc = ccref.begin_parent_cells (); pc != ccref.end_parent_cells (); ++pc) {
if (to_clean.find (*pc) == to_clean.end () && called.find (*pc) == called.end ()) {
// we have a parent outside the subset considered currently (either the cell was never in or
// it was removed itself already): remove this cell from the set of valid subcells.
called.erase (*c);
break;
}
}
}
}
to_clean.insert (called.begin (), called.end ());
}
return to_clean;
}
void
Layout::cleanup (const std::set<db::cell_index_type> &keep)
{
@ -1801,28 +1879,17 @@ Layout::cleanup (const std::set<db::cell_index_type> &keep)
}
std::set<cell_index_type> cells_to_delete;
// deleting cells may create new top cells which need to be deleted as well, hence we iterate
// until there are no more cells to delete
while (true) {
// Remove all cells that are targeted for cleanup
// delete all cells that are top cells and are proxies. Those cells are proxies no longer required.
for (top_down_iterator c = begin_top_down (); c != end_top_cells (); ++c) {
if (cell (*c).is_proxy () && keep.find (*c) == keep.end ()) {
cells_to_delete.insert (*c);
}
{
std::set<cell_index_type> cells_to_delete = cells_to_cleanup (keep);
if (! cells_to_delete.empty ()) {
delete_cells (cells_to_delete);
}
if (cells_to_delete.empty ()) {
break;
}
delete_cells (cells_to_delete);
cells_to_delete.clear ();
}
// Try to ensure that cell names reflect the library cell names. The latter is good for LVS for example.
for (auto c = m_lib_proxy_map.begin (); c != m_lib_proxy_map.end (); ++c) {
@ -2965,12 +3032,7 @@ Layout::has_context_info (cell_index_type cell_index) const
}
}
const db::Cell &cref = cell (cell_index);
if (cref.is_proxy () && ! cref.is_top ()) {
return true;
} else {
return false;
}
return cell (cell_index).is_proxy ();
}
bool

View File

@ -1682,6 +1682,19 @@ public:
*/
void cleanup (const std::set<db::cell_index_type> &keep = std::set<db::cell_index_type> ());
/**
* @brief Gets a list of cells which are cleanup candidates
*
* The returned list also includes cells that are indirectly cleaned up.
* The cleanup only removes top cells which represent unused proxies
* with some exceptions - for example proxies that are single top cells
* are retained.
*
* However, removing the top cells should not produce new top cells
* of proxies, so these cells need to be removed as well.
*/
std::set<db::cell_index_type> cells_to_cleanup (const std::set<db::cell_index_type> &keep = std::set<db::cell_index_type> ()) const;
/**
* @brief Calls "update" on all cells of the layout
*

View File

@ -372,12 +372,19 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set <db::cell_index
if (m_all_cells) {
// issue #2350: don't write cells that are to be cleanup, but in a sensitive manner
// - this does a kind of virtual cleanup, but with the same rules as applied
// during the normal cleanup.
std::set<db::cell_index_type> cleaned_cells = layout.cells_to_cleanup ();
bool has_skipped_replica = false;
// check if we have skipped replicas
if (has_context) {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) {
has_skipped_replica = cell->can_skip_replica ();
if (cell->can_skip_replica () && cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) {
has_skipped_replica = true;
}
}
}
@ -386,7 +393,7 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set <db::cell_index
if (has_skipped_replica) {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
if (cell->is_top ()) {
if (cell->is_top () && cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) {
cells.insert (cell->cell_index ());
collect_called_cells_unskipped (cell->cell_index (), layout, cells);
}
@ -394,8 +401,10 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set <db::cell_index
} else {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) {
cells.insert (cell->cell_index ());
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
if (cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) {
cells.insert (cell->cell_index ());
}
}
}

View File

@ -1040,3 +1040,134 @@ TEST(101_CopyTreeDoesNotModifyPolygons)
EXPECT_EQ (l2s (l), "begin_lib 0.001\nbegin_cell {TOP}\nboundary 1 0 {0 0} {0 1000} {500 1000} {1500 1000} {1000 1000} {1000 0} {0 0}\nend_cell\nend_lib\n");
}
namespace
{
class LIBT_L
: public db::Library
{
public:
LIBT_L (tl::TestBase *_this)
: Library ()
{
set_name("L");
set_description("A test library.");
layout ().dbu (0.001);
db::LayerProperties p;
p.layer = 23;
p.datatype = 0;
unsigned int l_cont = layout ().insert_layer (p);
p.layer = 16;
p.datatype = 0;
unsigned int l_gate = layout ().insert_layer (p);
db::Cell &cell_a = layout ().cell (layout ().add_cell ("A"));
cell_a.shapes(l_cont).insert(db::Box (50, 50, 150, 150));
cell_a.shapes(l_gate).insert(db::Box (0, 0, 200, 1000));
db::Cell &top = layout ().cell (layout ().add_cell ("TOP"));
top.insert (db::CellInstArray (db::CellInst (cell_a.cell_index ()), db::Trans (db::Vector (0, 0))));
}
~LIBT_L()
{
// .. nothing yet ..
}
};
}
static std::string cells2string (const db::Layout &layout, bool top = true)
{
std::string s;
auto cto = top ? layout.end_top_cells () : layout.end_top_down ();
for (auto c = layout.begin_top_down (); c != cto; ++c) {
if (! s.empty ()) {
s += ",";
}
if (layout.cell (*c).is_proxy ()) {
s += "*";
}
s += layout.cell_name (*c);
}
return s;
}
// issue #2350
TEST(101_CleanupBehavior)
{
std::unique_ptr<LIBT_L> lib (new LIBT_L (_this));
db::LibraryManager::instance ().register_lib (lib.get ());
db::Manager m (true);
db::Layout layout (&m);
layout.do_cleanup (true);
layout.dbu (0.001);
db::Cell &top = layout.cell (layout.add_cell ("TOPTOP"));
db::cell_index_type lib_top = lib->layout ().cell_by_name ("TOP").second;
db::cell_index_type lp1 = layout.get_lib_proxy (lib.get (), lib_top);
db::Instance i1 = top.insert (db::CellInstArray (db::CellInst (lp1), db::Trans (db::Vector (0, 0))));
db::Layout layout2 = layout;
layout2.do_cleanup (true);
EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A");
EXPECT_EQ (cells2string (layout2), "TOPTOP");
layout2.cleanup ();
// cleanup does not change anything as the top cell is not a proxy
EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A");
EXPECT_EQ (cells2string (layout2), "TOPTOP");
top.erase (i1);
layout2 = layout;
layout2.do_cleanup (true);
EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A");
EXPECT_EQ (cells2string (layout2), "TOPTOP,*TOP");
layout2.cleanup ();
// cleanup removes the proxy and subcell
EXPECT_EQ (cells2string (layout2, false), "TOPTOP");
EXPECT_EQ (cells2string (layout2), "TOPTOP");
// now we borrow *A (LIB.A) and place it in TOPTOP
db::cell_index_type ci_a = layout.cell_by_name ("A").second;
EXPECT_EQ (layout.cell (ci_a).is_proxy (), true);
top.insert (db::CellInstArray (db::CellInst (ci_a), db::Trans (db::Vector (0, 0))));
layout2 = layout;
layout2.do_cleanup (true);
EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A");
EXPECT_EQ (cells2string (layout2), "TOPTOP,*TOP");
layout2.cleanup ();
// cleanup removes the *TOP proxy, but not *A as it is used by TOPTOP
EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*A");
EXPECT_EQ (cells2string (layout2), "TOPTOP");
layout2 = layout;
layout2.delete_cell (top.cell_index ()); // layout and layout2 use the same cell indexes
layout2.do_cleanup (true);
EXPECT_EQ (cells2string (layout2, false), "*TOP,*A");
EXPECT_EQ (cells2string (layout2), "*TOP");
layout2.cleanup ();
// cleanup does not remove the proxy as it is the only top cell
EXPECT_EQ (cells2string (layout2, false), "*TOP,*A");
EXPECT_EQ (cells2string (layout2), "*TOP");
}

View File

@ -582,7 +582,7 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
// don't write ghost cells unless they are not empty (any more)
// also don't write proxy cells which are not employed
if (! cref.is_real_ghost_cell () && (! cref.is_proxy () || ! cref.is_top ())) {
if (! cref.is_real_ghost_cell ()) {
try {
bool skip_body = options.write_context_info () && cref.can_skip_replica ();

View File

@ -1437,12 +1437,6 @@ OASISWriter::write_layername_table (size_t &layernames_table_pos, const std::vec
end_table (layernames_table_pos);
}
static bool must_write_cell (const db::Cell &cref)
{
// Don't write proxy cells which are not employed
return ! cref.is_proxy () || ! cref.is_top ();
}
void
OASISWriter::create_cell_nstrings (const db::Layout &layout, const std::set <db::cell_index_type> &cell_set)
{
@ -1511,13 +1505,13 @@ OASISWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::Save
cells_by_index.reserve (cell_set.size ());
for (db::Layout::bottom_up_const_iterator cell = layout.begin_bottom_up (); cell != layout.end_bottom_up (); ++cell) {
if (cell_set.find (*cell) != cell_set.end () && must_write_cell (layout.cell (*cell))) {
if (cell_set.find (*cell) != cell_set.end ()) {
cells.push_back (*cell);
}
}
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
if (cell_set.find (cell->cell_index ()) != cell_set.end () && must_write_cell (layout.cell (cell->cell_index ()))) {
if (cell_set.find (cell->cell_index ()) != cell_set.end ()) {
cells_by_index.push_back (cell->cell_index ());
}
}

Binary file not shown.