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
+5
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
+9 -3
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
+10 -1
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
+85 -67
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);
}
}
+29 -4
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
+46 -7
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);
}
+2 -1
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
};
/**
+136
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));
}
+16 -8
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
+11
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
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.