Taking care of complex library reference scenarios where libraries self-reference and under the presence of stale references. Main issue is order of resolution and indirect references due to replication. Solution is to repeat resolution until saturated. A test is supplied.

This commit is contained in:
Matthias Koefferlein 2026-03-31 21:35:33 +02:00
parent 062567f206
commit 7cec679d39
20 changed files with 349 additions and 91 deletions

View File

@ -37,6 +37,11 @@ FileBasedLibrary::FileBasedLibrary (const std::string &path, const std::string &
: db::Library (), m_name (name), m_path (path), m_is_loaded (false)
{
set_description (tl::filename (path));
// preliminary name, may be replaced later
if (! name.empty ()) {
set_name (name);
}
}
void

View File

@ -2987,6 +2987,14 @@ Layout::fill_meta_info_from_context (cell_index_type cell_index, const LayoutOrC
void
Layout::restore_proxies (ImportLayerMapping *layer_mapping)
{
if (restore_proxies_without_cleanup (layer_mapping)) {
cleanup ();
}
}
bool
Layout::restore_proxies_without_cleanup (ImportLayerMapping *layer_mapping)
{
std::vector<db::ColdProxy *> cold_proxies;
@ -3004,9 +3012,7 @@ Layout::restore_proxies (ImportLayerMapping *layer_mapping)
}
}
if (needs_cleanup) {
cleanup ();
}
return needs_cleanup;
}
bool

View File

@ -1115,7 +1115,16 @@ public:
* Library updates may enabled lost connections which are help in cold proxies. This method will recover
* these connections.
*/
void restore_proxies(ImportLayerMapping *layer_mapping = 0);
void restore_proxies (ImportLayerMapping *layer_mapping = 0);
/**
* @brief Restores proxies as far as possible, no cleanup included
*
* This method is equivalent to "restore_proxies", but does not include a cleanup.
* Instead it returns a value of true, indicating that something got changed
* and a cleanup is required.
*/
bool restore_proxies_without_cleanup (ImportLayerMapping *layer_mapping = 0);
/**
* @brief Replaces the given cell index with the new cell

View File

@ -208,110 +208,128 @@ Library::remap_to (db::Library *other, db::Layout *original_layout)
// Hint: in the loop over the referrers we might unregister (delete from m_referrers) a referrer because no more cells refer to us.
// Hence we must not directly iterate of m_referrers.
std::vector<std::pair<db::Layout *, int> > referrers;
std::vector<db::Layout *> referrers;
for (std::map<db::Layout *, int>::const_iterator r = m_referrers.begin (); r != m_referrers.end (); ++r) {
referrers.push_back (*r);
referrers.push_back (r->first);
}
// Sort for deterministic order of resolution
std::sort (referrers.begin (), referrers.end (), tl::sort_by_id ());
// Remember the layouts that will finally need a cleanup
std::set<db::Layout *> needs_cleanup;
for (std::vector<std::pair<db::Layout *, int> >::const_iterator r = referrers.begin (); r != referrers.end (); ++r) {
// NOTE: resolution may create new references due to replicas.
// Hence, loop until no further references are resolved.
bool any = true;
while (any) {
std::vector<std::pair<db::LibraryProxy *, db::PCellVariant *> > pcells_to_map;
std::vector<db::LibraryProxy *> lib_cells_to_map;
any = false;
for (auto c = r->first->begin (); c != r->first->end (); ++c) {
for (std::vector<db::Layout *>::const_iterator r = referrers.begin (); r != referrers.end (); ++r) {
db::LibraryProxy *lib_proxy = dynamic_cast<db::LibraryProxy *> (c.operator-> ());
if (lib_proxy && lib_proxy->lib_id () == get_id ()) {
std::vector<std::pair<db::LibraryProxy *, db::PCellVariant *> > pcells_to_map;
std::vector<db::LibraryProxy *> lib_cells_to_map;
for (auto c = (*r)->begin (); c != (*r)->end (); ++c) {
db::LibraryProxy *lib_proxy = dynamic_cast<db::LibraryProxy *> (c.operator-> ());
if (lib_proxy && lib_proxy->lib_id () == get_id ()) {
if (! original_layout->is_valid_cell_index (lib_proxy->library_cell_index ())) {
// safety feature, should not happen
continue;
}
db::Cell *lib_cell = &original_layout->cell (lib_proxy->library_cell_index ());
db::PCellVariant *lib_pcell = dynamic_cast <db::PCellVariant *> (lib_cell);
if (lib_pcell) {
pcells_to_map.push_back (std::make_pair (lib_proxy, lib_pcell));
} else {
lib_cells_to_map.push_back (lib_proxy);
}
needs_cleanup.insert (*r);
any = true;
db::Cell *lib_cell = &original_layout->cell (lib_proxy->library_cell_index ());
db::PCellVariant *lib_pcell = dynamic_cast <db::PCellVariant *> (lib_cell);
if (lib_pcell) {
pcells_to_map.push_back (std::make_pair (lib_proxy, lib_pcell));
} else {
lib_cells_to_map.push_back (lib_proxy);
}
needs_cleanup.insert (r->first);
}
}
// We do PCell resolution before the library proxy resolution. The reason is that
// PCells may generate library proxies in their instantiation. Hence we must instantiate
// the PCells before we can resolve them.
for (std::vector<std::pair<db::LibraryProxy *, db::PCellVariant *> >::const_iterator lp = pcells_to_map.begin (); lp != pcells_to_map.end (); ++lp) {
// We do PCell resolution before the library proxy resolution. The reason is that
// PCells may generate library proxies in their instantiation. Hence we must instantiate
// the PCells before we can resolve them.
for (std::vector<std::pair<db::LibraryProxy *, db::PCellVariant *> >::const_iterator lp = pcells_to_map.begin (); lp != pcells_to_map.end (); ++lp) {
db::cell_index_type ci = lp->first->Cell::cell_index ();
db::PCellVariant *lib_pcell = lp->second;
db::cell_index_type ci = lp->first->Cell::cell_index ();
db::PCellVariant *lib_pcell = lp->second;
std::pair<bool, pcell_id_type> pn (false, 0);
if (other) {
pn = other->layout ().pcell_by_name (original_layout->cell (lp->first->library_cell_index ()).get_basic_name ().c_str ());
}
std::pair<bool, pcell_id_type> pn (false, 0);
if (other) {
pn = other->layout ().pcell_by_name (original_layout->cell (lp->first->library_cell_index ()).get_basic_name ().c_str ());
}
if (! pn.first) {
// substitute by a cold proxy
db::LayoutOrCellContextInfo info;
r->first->get_context_info (ci, info);
r->first->create_cold_proxy_as (info, ci);
} else {
const db::PCellDeclaration *old_pcell_decl = original_layout->pcell_declaration (lib_pcell->pcell_id ());
const db::PCellDeclaration *new_pcell_decl = other->layout ().pcell_declaration (pn.second);
if (! old_pcell_decl || ! new_pcell_decl) {
if (! pn.first) {
// substitute by a cold proxy
db::LayoutOrCellContextInfo info;
r->first->get_context_info (ci, info);
r->first->create_cold_proxy_as (info, ci);
(*r)->get_context_info (ci, info);
(*r)->create_cold_proxy_as (info, ci);
} else {
db::pcell_parameters_type new_parameters = new_pcell_decl->map_parameters (lib_pcell->parameters_by_name ());
const db::PCellDeclaration *old_pcell_decl = original_layout->pcell_declaration (lib_pcell->pcell_id ());
const db::PCellDeclaration *new_pcell_decl = other->layout ().pcell_declaration (pn.second);
if (! old_pcell_decl || ! new_pcell_decl) {
// substitute by a cold proxy
db::LayoutOrCellContextInfo info;
(*r)->get_context_info (ci, info);
(*r)->create_cold_proxy_as (info, ci);
} else {
db::pcell_parameters_type new_parameters = new_pcell_decl->map_parameters (lib_pcell->parameters_by_name ());
// coerce the new parameters if requested
try {
db::pcell_parameters_type plist = new_parameters;
new_pcell_decl->coerce_parameters (other->layout (), plist);
plist.swap (new_parameters);
} catch (tl::Exception &ex) {
// ignore exception - we will do that again on update() to establish an error message
tl::error << ex.msg ();
}
lp->first->remap (other->get_id (), other->layout ().get_pcell_variant (pn.second, new_parameters));
// coerce the new parameters if requested
try {
db::pcell_parameters_type plist = new_parameters;
new_pcell_decl->coerce_parameters (other->layout (), plist);
plist.swap (new_parameters);
} catch (tl::Exception &ex) {
// ignore exception - we will do that again on update() to establish an error message
tl::error << ex.msg ();
}
lp->first->remap (other->get_id (), other->layout ().get_pcell_variant (pn.second, new_parameters));
}
}
}
for (std::vector<db::LibraryProxy *>::const_iterator lp = lib_cells_to_map.begin (); lp != lib_cells_to_map.end (); ++lp) {
for (std::vector<db::LibraryProxy *>::const_iterator lp = lib_cells_to_map.begin (); lp != lib_cells_to_map.end (); ++lp) {
db::cell_index_type ci = (*lp)->Cell::cell_index ();
db::cell_index_type ci = (*lp)->Cell::cell_index ();
std::pair<bool, cell_index_type> cn (false, 0);
if (other) {
cn = other->layout ().cell_by_name (original_layout->cell_name ((*lp)->library_cell_index ()));
}
std::pair<bool, cell_index_type> cn (false, 0);
if (other) {
cn = other->layout ().cell_by_name (original_layout->cell_name ((*lp)->library_cell_index ()));
}
if (! cn.first) {
if (! cn.first) {
// substitute by a cold proxy
db::LayoutOrCellContextInfo info;
(*r)->get_context_info (ci, info);
(*r)->create_cold_proxy_as (info, ci);
// substitute by a cold proxy
db::LayoutOrCellContextInfo info;
r->first->get_context_info (ci, info);
r->first->create_cold_proxy_as (info, ci);
} else {
} else {
(*lp)->remap (other->get_id (), cn.second);
(*lp)->remap (other->get_id (), cn.second);
}
}

View File

@ -249,13 +249,38 @@ LibraryManager::register_lib (Library *library)
// "restore_proxies" takes care not to re-substitute cold proxies.
const tl::weak_collection<db::ColdProxy> &cold_proxies = db::ColdProxy::cold_proxies_per_lib_name (library->get_name ());
std::set<db::Layout *> to_refresh;
std::set<db::Layout *> to_refresh_set;
for (tl::weak_collection<db::ColdProxy>::const_iterator p = cold_proxies.begin (); p != cold_proxies.end (); ++p) {
to_refresh.insert (const_cast<db::Layout *> (p->layout ()));
to_refresh_set.insert (const_cast<db::Layout *> (p->layout ()));
}
for (std::set<db::Layout *>::const_iterator l = to_refresh.begin (); l != to_refresh.end (); ++l) {
(*l)->restore_proxies (0);
// Sort for deterministic order of resolution
std::vector<db::Layout *> to_refresh (to_refresh_set.begin (), to_refresh_set.end ());
std::sort (to_refresh.begin (), to_refresh.end (), tl::sort_by_id ());
std::set<db::Layout *> needs_cleanup;
// NOTE: "restore proxies" can create new proxies, because indirect references.
// Hence we need to repeat the process.
bool any = true;
while (any) {
any = false;
for (auto l = to_refresh.begin (); l != to_refresh.end (); ++l) {
if ((*l)->restore_proxies_without_cleanup ()) {
any = true;
needs_cleanup.insert (*l);
}
}
}
// do the cleanup
for (auto l = needs_cleanup.begin (); l != needs_cleanup.end (); ++l) {
(*l)->cleanup ();
}
// issue the change notification

View File

@ -135,13 +135,52 @@ void compare_layouts (tl::TestBase *_this, const db::Layout &layout, const std::
db::Reader reader (stream);
reader.read (layout_au, options);
equal = db::compare_layouts (*subject, layout_au,
(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 ((norm & WithoutCellNames) != 0) {
// in this case, the layouts need to have one top cell
db::cell_index_type top_subject = 0, top_au = 0;
size_t n_top_subject = 0, n_top_au = 0;
for (auto t = subject->begin_top_down (); t != subject->end_top_cells (); ++t) {
top_subject = *t;
++n_top_subject;
}
for (auto t = layout_au.begin_top_down (); t != layout_au.end_top_cells (); ++t) {
top_au = *t;
++n_top_au;
}
if (n_top_subject != 1) {
throw tl::Exception (tl::sprintf ("With smart cell mapping, the subject layout must have a single top cell"));
}
if (n_top_au != 1) {
throw tl::Exception (tl::sprintf ("With smart cell mapping, the reference layout must have a single top cell"));
}
equal = db::compare_layouts (*subject, top_subject, layout_au, top_au,
(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_smart_cell_mapping)
/*| db::layout_diff::f_no_text_details | db::layout_diff::f_no_text_orientation*/
, tolerance, 100 /*max diff lines*/);
} else {
equal = db::compare_layouts (*subject, layout_au,
(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) {
tl::info << tl::sprintf ("Found match on golden reference variant %s", fn);
}

View File

@ -59,7 +59,8 @@ enum NormalizationMode
NoContext = 8, // write tmp file without context
AsPolygons = 16, // paths and boxes are treated as polygons
WithArrays = 32, // do not flatten arrays
WithMeta = 64 // with meta info
WithMeta = 64, // with meta info
WithoutCellNames = 128 // smart cell name mapping
};
/**

View File

@ -32,6 +32,8 @@
#include "dbReader.h"
#include "dbLayoutDiff.h"
#include "dbTestSupport.h"
#include "dbFileBasedLibrary.h"
#include "dbColdProxy.h"
#include "tlStream.h"
#include "tlStaticObjects.h"
#include "tlUnitTest.h"
@ -698,3 +700,137 @@ TEST(6_issue996)
CHECKPOINT ();
db::compare_layouts (this, ly, tl::testdata () + "/gds/lib_test6b.gds", db::NormalizationMode (db::WriteGDS2 + db::NoContext));
}
static size_t num_top_cells (const db::Layout &layout)
{
size_t n = 0;
for (auto t = layout.begin_top_down (); t != layout.end_top_cells (); ++t) {
++n;
}
return n;
}
static size_t num_cells (const db::Layout &layout)
{
size_t n = 0;
for (auto t = layout.begin_top_down (); t != layout.end_top_down (); ++t) {
++n;
}
return n;
}
static size_t num_defunct (const db::Layout &layout)
{
size_t ndefunct = 0;
for (auto c = layout.begin (); c != layout.end (); ++c) {
if (dynamic_cast<const db::ColdProxy *> (c.operator-> ())) {
++ndefunct;
}
}
return ndefunct;
}
// monster lib refresh issue
// (monster lib is a layout with manifold library references to existing and non-existing libraries)
TEST(7_monsterlib)
{
std::pair<bool, db::lib_id_type> lib;
// tabula rasa
lib = db::LibraryManager::instance ().lib_by_name ("EX");
if (lib.first) {
db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (lib.second));
}
lib = db::LibraryManager::instance ().lib_by_name ("EX2");
if (lib.first) {
db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (lib.second));
}
lib = db::LibraryManager::instance ().lib_by_name ("NOEX");
if (lib.first) {
db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (lib.second));
}
lib = db::LibraryManager::instance ().lib_by_name ("NOEX2");
if (lib.first) {
db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (lib.second));
}
// first, read the layout with only EX and EX2 in place
db::FileBasedLibrary *lib_ex = new db::FileBasedLibrary (tl::testsrc () + "/testdata/libman/libs/EX.gds", "EX");
lib_ex->load ();
db::LibraryManager::instance ().register_lib (lib_ex);
db::FileBasedLibrary *lib_ex2 = new db::FileBasedLibrary (tl::testsrc () + "/testdata/libman/libs/EX2.gds", "EX2");
lib_ex2->load ();
db::LibraryManager::instance ().register_lib (lib_ex2);
db::Layout layout;
layout.do_cleanup (true);
{
tl::InputStream is (tl::testsrc () + "/testdata/libman/design.gds");
db::Reader reader (is);
reader.read (layout);
}
// as NOEX and NOEX2 are not present, a number of references are defunct (aka cold proxies)
EXPECT_EQ (num_defunct (layout), size_t (15));
EXPECT_EQ (num_cells (layout), size_t (46));
EXPECT_EQ (num_top_cells (layout), size_t (1));
// NOTE: normalization would spoil the layout, so don't do it
// The golden layout is a spoiled version that uses preliminary cell versions for the
// unresolved references of NOEX and NOEX2. This is intentional to test the replication.
// Also note, that the golden file has static cells.
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au1.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
// then, establish NOEX and NOEX2 too - this will update the libraries in the layout that was read
db::FileBasedLibrary *lib_noex = new db::FileBasedLibrary (tl::testsrc () + "/testdata/libman/libs/NOEX.gds", "NOEX");
lib_noex->load ();
db::LibraryManager::instance ().register_lib (lib_noex);
db::FileBasedLibrary *lib_noex2 = new db::FileBasedLibrary (tl::testsrc () + "/testdata/libman/libs/NOEX2.gds", "NOEX2");
lib_noex2->load ();
db::LibraryManager::instance ().register_lib (lib_noex2);
// all references now need to be resolved
EXPECT_EQ (num_defunct (layout), size_t (0));
EXPECT_EQ (num_cells (layout), size_t (34));
EXPECT_EQ (num_top_cells (layout), size_t (1));
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au2.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
// refresh must not change the layout
lib_ex->refresh ();
lib_ex2->refresh ();
lib_noex->refresh ();
lib_noex2->refresh ();
// all references now need to be resolved
EXPECT_EQ (num_defunct (layout), size_t (0));
EXPECT_EQ (num_cells (layout), size_t (29));
EXPECT_EQ (num_top_cells (layout), size_t (1));
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au3.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
db::LibraryManager::instance ().delete_lib (lib_noex);
db::LibraryManager::instance ().delete_lib (lib_noex2);
// after removing the libraries, we have defunct cells again
EXPECT_EQ (num_defunct (layout), size_t (8));
EXPECT_EQ (num_cells (layout), size_t (29));
EXPECT_EQ (num_top_cells (layout), size_t (1));
// but the layout did not change
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au4.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
db::LibraryManager::instance ().delete_lib (lib_ex);
db::LibraryManager::instance ().delete_lib (lib_ex2);
// after removing all libraries, we have even more defunct cells (i.e. all, except top)
EXPECT_EQ (num_defunct (layout), size_t (16));
EXPECT_EQ (num_cells (layout), size_t (29));
EXPECT_EQ (num_top_cells (layout), size_t (1));
// but the layout did not change
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au5.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
}

View File

@ -289,22 +289,30 @@ LibraryController::sync_files ()
}
for (std::map<std::string, LibInfo>::const_iterator lf = m_lib_files.begin (); lf != m_lib_files.end (); ++lf) {
std::pair<bool, db::lib_id_type> li = db::LibraryManager::instance ().lib_by_name (lf->second.name, lf->second.tech);
if (! li.first) {
continue; // should not happen
}
db::Library *lib = db::LibraryManager::instance ().lib (li.second);
if (new_names.find (lf->second.name) == new_names.end ()) {
try {
std::pair<bool, db::lib_id_type> li = db::LibraryManager::instance ().lib_by_name (lf->second.name, lf->second.tech);
if (li.first) {
if (! lf->second.tech.empty ()) {
tl::log << "Unregistering lib '" << lf->second.name << "' for technology '" << *lf->second.tech.begin () << "' as the file no longer exists: " << lf->first;
} else {
tl::log << "Unregistering lib '" << lf->second.name << "' as the file no longer exists: " << lf->first;
}
db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (li.second));
if (! lf->second.tech.empty ()) {
tl::log << "Unregistering lib '" << lf->second.name << "' for technology '" << *lf->second.tech.begin () << "' as the file no longer exists: " << lf->first;
} else {
tl::log << "Unregistering lib '" << lf->second.name << "' as the file no longer exists: " << lf->first;
}
db::LibraryManager::instance ().delete_lib (lib);
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (...) {
}
}
}
// establish the new libraries

View File

@ -75,6 +75,17 @@ inline id_type id_of (const UniqueId *o)
return o ? o->m_id : 0;
}
/**
* @brief A sorting operator of pointers by ID
*/
struct sort_by_id
{
bool operator () (const UniqueId *a, const UniqueId *b) const
{
return id_of (a) < id_of (b);
}
};
} // namespace tl
#endif

BIN
testdata/libman/design.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/design_au1.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/design_au2.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/design_au3.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/design_au4.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/design_au5.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/libs/EX.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/libs/EX2.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/libs/NOEX.gds vendored Normal file

Binary file not shown.

BIN
testdata/libman/libs/NOEX2.gds vendored Normal file

Binary file not shown.