diff --git a/src/db/db/db.pro b/src/db/db/db.pro
index ce2f3cc3e..bcae249bf 100644
--- a/src/db/db/db.pro
+++ b/src/db/db/db.pro
@@ -34,6 +34,7 @@ SOURCES = \
dbEdgeProcessor.cc \
dbEdges.cc \
dbEdgesLocalOperations.cc \
+ dbFileBasedLibrary.cc \
dbFillTool.cc \
dbFuzzyCellMapping.cc \
dbGenericShapeIterator.cc \
@@ -275,6 +276,7 @@ HEADERS = \
dbEdges.h \
dbEdgesLocalOperations.h \
dbEdgesToContours.h \
+ dbFileBasedLibrary.h \
dbFillTool.h \
dbFuzzyCellMapping.h \
dbGenericShapeIterator.h \
diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h
index c616a105d..ac3430020 100644
--- a/src/db/db/dbCell.h
+++ b/src/db/db/dbCell.h
@@ -857,7 +857,7 @@ public:
void check_locked () const;
/**
- * @brief Tell, if this cell is a proxy cell
+ * @brief Gets a value indicating if this cell is a proxy cell
*
* Proxy cells are such whose layout represents a snapshot of another entity.
* Such cells can be PCell variants or library references for example.
@@ -867,6 +867,17 @@ public:
return false;
}
+ /**
+ * @brief Gets a value indicating that this cell is a replica that can be skipped
+ *
+ * This attribute is evaluated by file writers to skip cell replicas for
+ * library cells that do not want to replicated.
+ */
+ virtual bool can_skip_replica () const
+ {
+ return false;
+ }
+
/**
* @brief Sets the cell name
*
diff --git a/src/db/db/dbColdProxy.cc b/src/db/db/dbColdProxy.cc
index 486e84688..08da24b28 100644
--- a/src/db/db/dbColdProxy.cc
+++ b/src/db/db/dbColdProxy.cc
@@ -60,14 +60,36 @@ ColdProxy::ColdProxy (db::cell_index_type ci, db::Layout &layout, const LayoutOr
}
i->second->push_back (this);
}
+
+ layout.register_cold_proxy (this);
}
ColdProxy::~ColdProxy ()
{
+ if (layout ()) {
+ layout ()->unregister_cold_proxy (this);
+ }
+
delete mp_context_info;
mp_context_info = 0;
}
+void
+ColdProxy::unregister ()
+{
+ if (layout ()) {
+ layout ()->unregister_cold_proxy (this);
+ }
+}
+
+void
+ColdProxy::reregister ()
+{
+ if (layout ()) {
+ layout ()->register_cold_proxy (this);
+ }
+}
+
Cell *
ColdProxy::clone (Layout &layout) const
{
diff --git a/src/db/db/dbColdProxy.h b/src/db/db/dbColdProxy.h
index 69fb84511..8d6d2fcd3 100644
--- a/src/db/db/dbColdProxy.h
+++ b/src/db/db/dbColdProxy.h
@@ -81,6 +81,16 @@ public:
return true;
}
+ /**
+ * @brief Reimplemented from Cell: unregisters the proxy at the layout
+ */
+ virtual void unregister ();
+
+ /**
+ * @brief Reimplemented from Cell: reregisters the proxy at the layout
+ */
+ virtual void reregister ();
+
/**
* @brief Gets a list of cold proxies for a given library name
*/
diff --git a/src/db/db/dbCommonReader.cc b/src/db/db/dbCommonReader.cc
index 2924d5685..617ea3f1f 100644
--- a/src/db/db/dbCommonReader.cc
+++ b/src/db/db/dbCommonReader.cc
@@ -692,6 +692,11 @@ public:
return false;
}
+ virtual bool supports_context () const
+ {
+ return false;
+ }
+
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement ("common",
diff --git a/src/db/db/dbFileBasedLibrary.cc b/src/db/db/dbFileBasedLibrary.cc
new file mode 100644
index 000000000..e13fd927f
--- /dev/null
+++ b/src/db/db/dbFileBasedLibrary.cc
@@ -0,0 +1,146 @@
+
+/*
+
+ KLayout Layout Viewer
+ Copyright (C) 2006-2026 Matthias Koefferlein
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+
+#include "dbFileBasedLibrary.h"
+#include "dbReader.h"
+#include "dbCellMapping.h"
+
+#include "tlFileUtils.h"
+#include "tlStream.h"
+
+namespace db
+{
+
+// -------------------------------------------------------------------------------------------
+
+FileBasedLibrary::FileBasedLibrary (const std::string &path, const std::string &name)
+ : 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
+FileBasedLibrary::merge_with_other_layout (const std::string &path)
+{
+ m_other_paths.push_back (path);
+ if (m_is_loaded) {
+ merge_impl (path);
+ }
+}
+
+bool
+FileBasedLibrary::is_for_path (const std::string &path)
+{
+ return m_other_paths.empty () && m_path == path;
+}
+
+bool
+FileBasedLibrary::is_for_paths (const std::vector &paths)
+{
+ if (paths.size () != m_other_paths.size () + 1) {
+ return false;
+ }
+
+ // this check is purely based on path strings
+ std::set p1 (paths.begin (), paths.end ());
+ std::set p2;
+ p2.insert (m_path);
+ p2.insert (m_other_paths.begin (), m_other_paths.end ());
+ return p1 == p2;
+}
+
+std::string
+FileBasedLibrary::load ()
+{
+ if (! m_is_loaded) {
+ return reload ();
+ } else {
+ return get_name ();
+ }
+}
+
+std::string
+FileBasedLibrary::reload ()
+{
+ std::string name = m_name.empty () ? tl::basename (m_path) : m_name;
+
+ layout ().clear ();
+
+ tl::InputStream stream (m_path);
+ db::Reader reader (stream);
+ reader.read (layout ());
+
+ // Use the libname if there is one
+ if (m_name.empty ()) {
+ db::Layout::meta_info_name_id_type libname_name_id = layout ().meta_info_name_id ("libname");
+ for (db::Layout::meta_info_iterator m = layout ().begin_meta (); m != layout ().end_meta (); ++m) {
+ if (m->first == libname_name_id && ! m->second.value.is_nil ()) {
+ name = m->second.value.to_string ();
+ break;
+ }
+ }
+ }
+
+ for (auto p = m_other_paths.begin (); p != m_other_paths.end (); ++p) {
+ merge_impl (*p);
+ }
+
+ m_is_loaded = true;
+
+ return name;
+}
+
+void
+FileBasedLibrary::merge_impl (const std::string &path)
+{
+ db::Layout ly;
+
+ tl::InputStream stream (path);
+ db::Reader reader (stream);
+ reader.read (ly);
+
+ std::vector target_cells, source_cells;
+
+ // collect the cells to pull in (all top cells of the library layout)
+ // NOTE: cells are not overwritten - the first layout wins, in terms
+ // of cell names and also in terms of database unit.
+ for (auto c = ly.begin_top_down (); c != ly.end_top_cells (); ++c) {
+ std::string cn = ly.cell_name (*c);
+ if (! layout ().has_cell (cn.c_str ())) {
+ source_cells.push_back (*c);
+ target_cells.push_back (layout ().add_cell (cn.c_str ()));
+ }
+ }
+
+ db::CellMapping cm;
+ cm.create_multi_mapping_full (layout (), target_cells, ly, source_cells);
+ layout ().copy_tree_shapes (ly, cm);
+}
+
+}
+
diff --git a/src/db/db/dbFileBasedLibrary.h b/src/db/db/dbFileBasedLibrary.h
new file mode 100644
index 000000000..3a9ddaee9
--- /dev/null
+++ b/src/db/db/dbFileBasedLibrary.h
@@ -0,0 +1,115 @@
+
+/*
+
+ KLayout Layout Viewer
+ Copyright (C) 2006-2026 Matthias Koefferlein
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+
+#ifndef HDR_dbFileBasedLibrary
+#define HDR_dbFileBasedLibrary
+
+#include "dbLibrary.h"
+#include "dbCommon.h"
+
+#include
+#include
+
+namespace db
+{
+
+/**
+ * @brief A Library specialization that ties a library to a file
+ *
+ * This object supports loading a library from multiple files and merging them into
+ * a single library (e.g. for loading a directory of cell files).
+ */
+class DB_PUBLIC FileBasedLibrary
+ : public db::Library
+{
+public:
+ /**
+ * @brief Creates a file-based library object
+ *
+ * @param path The file path
+ * @param name The library name
+ *
+ * If the library name is an empty string, the library name is taken from
+ * a GDS LIBNAME or from the file name in the path.
+ *
+ * Note that you need to call "load" in order to actually load the file.
+ */
+ FileBasedLibrary (const std::string &path, const std::string &name = std::string ());
+
+ /**
+ * @brief Merges another file into this library
+ *
+ * If the library was not loaded already, the merge requests are postponed
+ * until "load" is called.
+ */
+ void merge_with_other_layout (const std::string &path);
+
+ /**
+ * @brief Loads the files
+ *
+ * If the files are already loaded, this method does nothing.
+ * It returns the name of the library derived from the first file
+ * or the name given in the constructor. The constructor name has
+ * priority.
+ */
+ std::string load ();
+
+ /**
+ * @brief Implements the reload feature
+ */
+ virtual std::string reload ();
+
+ /**
+ * @brief Set the paths
+ * This method is provided for test purposes only.
+ */
+ void set_paths (const std::string &path, const std::list &other_paths = std::list ())
+ {
+ m_path = path;
+ m_other_paths = other_paths;
+ }
+
+ /**
+ * @brief Gets a value indicating whether the library is for the given path
+ */
+ bool is_for_path (const std::string &path);
+
+ /**
+ * @brief Gets a value indicating whether the library is for the given path
+ */
+ bool is_for_paths (const std::vector &paths);
+
+private:
+ std::string m_name;
+ std::string m_path;
+ std::list m_other_paths;
+ bool m_is_loaded;
+
+ void merge_impl (const std::string &path);
+};
+
+}
+
+#endif
+
+
diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc
index 40557f375..fb9cbbc04 100644
--- a/src/db/db/dbLayout.cc
+++ b/src/db/db/dbLayout.cc
@@ -337,6 +337,37 @@ private:
// -----------------------------------------------------------------
// Implementation of the ProxyContextInfo class
+bool
+LayoutOrCellContextInfo::operator== (const LayoutOrCellContextInfo &other) const
+{
+ return lib_name == other.lib_name &&
+ cell_name == other.cell_name &&
+ pcell_name == other.pcell_name &&
+ pcell_parameters == other.pcell_parameters &&
+ meta_info == other.meta_info;
+}
+
+bool
+LayoutOrCellContextInfo::operator< (const LayoutOrCellContextInfo &other) const
+{
+ if (lib_name != other.lib_name) {
+ return lib_name < other.lib_name;
+ }
+ if (cell_name != other.cell_name) {
+ return cell_name < other.cell_name;
+ }
+ if (pcell_name != other.pcell_name) {
+ return pcell_name < other.pcell_name;
+ }
+ if (pcell_parameters != other.pcell_parameters) {
+ return pcell_parameters < other.pcell_parameters;
+ }
+ if (meta_info != other.meta_info) {
+ return meta_info < other.meta_info;
+ }
+ return false;
+}
+
LayoutOrCellContextInfo
LayoutOrCellContextInfo::deserialize (std::vector::const_iterator from, std::vector::const_iterator to)
{
@@ -540,6 +571,7 @@ Layout::clear ()
m_pcell_ids.clear ();
m_lib_proxy_map.clear ();
+ m_cold_proxy_map.clear ();
m_meta_info.clear ();
}
@@ -568,6 +600,7 @@ Layout::operator= (const Layout &d)
}
m_lib_proxy_map = d.m_lib_proxy_map;
+ m_cold_proxy_map = d.m_cold_proxy_map;
m_cell_ptrs.resize (d.m_cell_ptrs.size (), 0);
@@ -809,6 +842,7 @@ Layout::mem_stat (MemStatistics *stat, MemStatistics::purpose_t purpose, int cat
db::mem_stat (stat, purpose, cat, m_pcells, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_pcell_ids, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_lib_proxy_map, true, (void *) this);
+ db::mem_stat (stat, purpose, cat, m_cold_proxy_map, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_meta_info, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_shape_repository, true, (void *) this);
db::mem_stat (stat, purpose, cat, m_array_repository, true, (void *) this);
@@ -1505,6 +1539,11 @@ Layout::register_cell_name (const char *name, cell_index_type ci)
void
Layout::rename_cell (cell_index_type id, const char *name)
{
+ static const char *anonymous_name = "";
+ if (! name) {
+ name = anonymous_name;
+ }
+
tl_assert (id < m_cell_names.size ());
if (strcmp (m_cell_names [id], name) != 0) {
@@ -1521,7 +1560,10 @@ Layout::rename_cell (cell_index_type id, const char *name)
delete [] m_cell_names [id];
m_cell_names [id] = cp;
- m_cell_map.insert (std::make_pair (cp, id));
+ // NOTE: anonymous cells (empty name string) are not registered in the cell name map
+ if (*cp != 0) {
+ m_cell_map.insert (std::make_pair (cp, id));
+ }
// to enforce a redraw and a rebuild
cell_name_changed ();
@@ -1690,27 +1732,120 @@ Layout::cleanup (const std::set &keep)
return;
}
+ if (tl::verbosity () >= 30) {
+ tl::info << "Cleaning up layout ..";
+ }
+
+ // Do some polishing of the proxies - sometimes, specifically when resolving indirect library references,
+ // different proxies to the same library object exist. We can identify them and clean them up, so there
+ // is a single reference. We can also try to ensure that cell names reflect the library cell names.
+ // The latter is good for LVS for example.
+
+ {
+ update();
+ db::LayoutLocker locker (this);
+
+ // join library proxies pointing to the same object
+
+ for (auto c = m_lib_proxy_map.begin (); c != m_lib_proxy_map.end (); ) {
+
+ auto c0 = c++;
+ size_t n = 1;
+ while (c != m_lib_proxy_map.end () && c->first == c0->first) {
+ ++n;
+ ++c;
+ }
+
+ if (n > 1) {
+ auto cc = c0;
+ ++cc;
+ while (cc != c) {
+ if (keep.find (cc->second) == keep.end ()) {
+ if (tl::verbosity () >= 30) {
+ tl::info << "Joining lib proxy " << cell_name (cc->second) << " into " << cell_name (c0->second);
+ }
+ replace_instances_of (cc->second, c0->second);
+ }
+ ++cc;
+ }
+ }
+
+ }
+
+ // join cold proxies pointing to the same object
+
+ for (auto c = m_cold_proxy_map.begin (); c != m_cold_proxy_map.end (); ) {
+
+ auto c0 = c++;
+ size_t n = 1;
+ while (c != m_cold_proxy_map.end () && c->first == c0->first) {
+ ++n;
+ ++c;
+ }
+
+ if (n > 1) {
+ auto cc = c0;
+ ++cc;
+ while (cc != c) {
+ if (keep.find (cc->second) == keep.end ()) {
+ if (tl::verbosity () >= 30) {
+ tl::info << "Joining cold proxy " << cell_name (cc->second) << " into " << cell_name (c0->second);
+ }
+ replace_instances_of (cc->second, c0->second);
+ }
+ ++cc;
+ }
+ }
+
+ }
+
+ }
+
+ std::set 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) {
// delete all cells that are top cells and are proxies. Those cells are proxies no longer required.
- std::set cells_to_delete;
for (top_down_iterator c = begin_top_down (); c != end_top_cells (); ++c) {
- if (cell (*c).is_proxy ()) {
+ if (cell (*c).is_proxy () && keep.find (*c) == keep.end ()) {
cells_to_delete.insert (*c);
}
}
- for (std::set::const_iterator k = keep.begin (); k != keep.end (); ++k) {
- cells_to_delete.erase (*k);
- }
-
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) {
+
+ std::string bn = cell (c->second).get_basic_name ();
+ if (bn != cell_name (c->second) && ! cell_by_name (bn.c_str ()).first) {
+ if (tl::verbosity () >= 30) {
+ tl::info << "Renaming lib proxy " << cell_name (c->second) << " to " << bn;
+ }
+ rename_cell (c->second, bn.c_str ());
+ }
+
+ }
+
+ for (auto c = m_cold_proxy_map.begin (); c != m_cold_proxy_map.end (); ++c) {
+
+ std::string bn = cell (c->second).get_basic_name ();
+ if (bn != cell_name (c->second) && ! cell_by_name (bn.c_str ()).first) {
+ if (tl::verbosity () >= 30) {
+ tl::info << "Renaming cold proxy " << cell_name (c->second) << " to " << bn;
+ }
+ rename_cell (c->second, bn.c_str ());
+ }
}
}
@@ -2987,6 +3122,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 cold_proxies;
@@ -3004,9 +3147,7 @@ Layout::restore_proxies (ImportLayerMapping *layer_mapping)
}
}
- if (needs_cleanup) {
- cleanup ();
- }
+ return needs_cleanup;
}
bool
@@ -3145,13 +3286,32 @@ Layout::variant_name (cell_index_type cell_index) const
void
Layout::register_lib_proxy (db::LibraryProxy *lib_proxy)
{
- m_lib_proxy_map.insert (std::make_pair (std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()), lib_proxy->Cell::cell_index ()));
+ auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ());
+
+ auto l = m_lib_proxy_map.find (key);
+ while (l != m_lib_proxy_map.end () && l->first == key) {
+ if (l->second == lib_proxy->Cell::cell_index ()) {
+ return;
+ }
+ ++l;
+ }
+
+ m_lib_proxy_map.insert (std::make_pair (key, lib_proxy->Cell::cell_index ()));
}
void
Layout::unregister_lib_proxy (db::LibraryProxy *lib_proxy)
{
- m_lib_proxy_map.erase (std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()));
+ auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ());
+
+ auto l = m_lib_proxy_map.find (key);
+ while (l != m_lib_proxy_map.end () && l->first == key) {
+ if (l->second == lib_proxy->Cell::cell_index ()) {
+ m_lib_proxy_map.erase (l);
+ break;
+ }
+ ++l;
+ }
}
void
@@ -3171,9 +3331,13 @@ Layout::get_lib_proxy_as (Library *lib, cell_index_type cell_index, cell_index_t
cell_index_type
Layout::get_lib_proxy (Library *lib, cell_index_type cell_index)
{
- lib_proxy_map::const_iterator lp = m_lib_proxy_map.find (std::make_pair (lib->get_id (), cell_index));
- if (lp != m_lib_proxy_map.end ()) {
+ auto key = std::make_pair (lib->get_id (), cell_index);
+
+ lib_proxy_map::const_iterator lp = m_lib_proxy_map.find (key);
+ if (lp != m_lib_proxy_map.end () && lp->first == key) {
+
return lp->second;
+
} else {
// create a new unique name
@@ -3204,35 +3368,82 @@ Layout::get_lib_proxy (Library *lib, cell_index_type cell_index)
}
}
+void
+Layout::register_cold_proxy (db::ColdProxy *cold_proxy)
+{
+ auto l = m_cold_proxy_map.find (cold_proxy->context_info ());
+ while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) {
+ if (l->second == cold_proxy->Cell::cell_index ()) {
+ return;
+ }
+ ++l;
+ }
+
+ m_cold_proxy_map.insert (std::make_pair (cold_proxy->context_info (), cold_proxy->Cell::cell_index ()));
+}
+
+void
+Layout::unregister_cold_proxy (db::ColdProxy *cold_proxy)
+{
+ auto l = m_cold_proxy_map.find (cold_proxy->context_info ());
+ while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) {
+ if (l->second == cold_proxy->Cell::cell_index ()) {
+ m_cold_proxy_map.erase (l);
+ break;
+ }
+ ++l;
+ }
+}
+
+std::pair
+Layout::find_cold_proxy (const db::LayoutOrCellContextInfo &info)
+{
+ cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info);
+ if (lp != m_cold_proxy_map.end () && lp->first == info) {
+ return std::make_pair (true, lp->second);
+ } else {
+ return std::make_pair (false, 0);
+ }
+}
+
cell_index_type
Layout::create_cold_proxy (const db::LayoutOrCellContextInfo &info)
{
- // create a new unique name
- std::string b;
- if (! info.cell_name.empty ()) {
- b = info.cell_name;
- } else if (! info.pcell_name.empty ()) {
- b = info.pcell_name;
+ cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info);
+ if (lp != m_cold_proxy_map.end () && lp->first == info) {
+
+ return lp->second;
+
+ } else {
+
+ // create a new unique name
+ std::string b;
+ if (! info.cell_name.empty ()) {
+ b = info.cell_name;
+ } else if (! info.pcell_name.empty ()) {
+ b = info.pcell_name;
+ }
+ if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) {
+ b = uniquify_cell_name (b.c_str ());
+ }
+
+ // create a new cell (a LibraryProxy)
+ cell_index_type new_index = allocate_new_cell ();
+
+ ColdProxy *proxy = new ColdProxy (new_index, *this, info);
+ m_cells.push_back_ptr (proxy);
+ m_cell_ptrs [new_index] = proxy;
+
+ // enter its index and cell_name
+ register_cell_name (b.c_str (), new_index);
+
+ if (manager () && manager ()->transacting ()) {
+ manager ()->queue (this, new NewRemoveCellOp (new_index, m_cell_names [new_index], false /*new*/, 0));
+ }
+
+ return new_index;
+
}
- if (m_cell_map.find (b.c_str ()) != m_cell_map.end ()) {
- b = uniquify_cell_name (b.c_str ());
- }
-
- // create a new cell (a LibraryProxy)
- cell_index_type new_index = allocate_new_cell ();
-
- ColdProxy *proxy = new ColdProxy (new_index, *this, info);
- m_cells.push_back_ptr (proxy);
- m_cell_ptrs [new_index] = proxy;
-
- // enter its index and cell_name
- register_cell_name (b.c_str (), new_index);
-
- if (manager () && manager ()->transacting ()) {
- manager ()->queue (this, new NewRemoveCellOp (new_index, m_cell_names [new_index], false /*new*/, 0));
- }
-
- return new_index;
}
void
diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h
index ed62a60ab..b9bc0f1c9 100644
--- a/src/db/db/dbLayout.h
+++ b/src/db/db/dbLayout.h
@@ -62,6 +62,7 @@ class PCellDeclaration;
class PCellHeader;
class Library;
class LibraryProxy;
+class ColdProxy;
class CellMapping;
class LayerMapping;
class Region;
@@ -427,6 +428,9 @@ struct DB_PUBLIC LayoutOrCellContextInfo
std::map pcell_parameters;
std::map > meta_info;
+ bool operator== (const LayoutOrCellContextInfo &other) const;
+ bool operator< (const LayoutOrCellContextInfo &other) const;
+
static LayoutOrCellContextInfo deserialize (std::vector::const_iterator from, std::vector::const_iterator to);
void serialize (std::vector &strings);
@@ -469,7 +473,8 @@ public:
typedef db::pcell_id_type pcell_id_type;
typedef std::map pcell_name_map;
typedef pcell_name_map::const_iterator pcell_iterator;
- typedef std::map, cell_index_type> lib_proxy_map;
+ typedef std::multimap, cell_index_type> lib_proxy_map;
+ typedef std::multimap cold_proxy_map;
typedef LayerIterator layer_iterator;
typedef size_t meta_info_name_id_type;
typedef std::map meta_info_map;
@@ -1003,6 +1008,12 @@ public:
*/
void get_lib_proxy_as (Library *lib, cell_index_type cell_index, cell_index_type target_cell_index, ImportLayerMapping *layer_mapping = 0, bool retain_layout = false);
+ /**
+ * @brief Find an existing cold proxy for a given context
+ * @return A pair of success flag and cell index of the proxy
+ */
+ std::pair find_cold_proxy (const db::LayoutOrCellContextInfo &info);
+
/**
* @brief Creates a cold proxy representing the given context information
*/
@@ -1115,7 +1126,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
@@ -1830,6 +1850,20 @@ public:
*/
void unregister_lib_proxy (db::LibraryProxy *lib_proxy);
+ /**
+ * @brief Register a cold proxy
+ *
+ * This method is used by ColdProxy to register itself.
+ */
+ void register_cold_proxy (db::ColdProxy *cold_proxy);
+
+ /**
+ * @brief Unregister a cold proxy
+ *
+ * This method is used by ColdProxy to unregister itself.
+ */
+ void unregister_cold_proxy (db::ColdProxy *cold_proxy);
+
/**
* @brief Gets the editable status of this layout
*
@@ -2144,6 +2178,7 @@ private:
std::vector m_pcells;
pcell_name_map m_pcell_ids;
lib_proxy_map m_lib_proxy_map;
+ cold_proxy_map m_cold_proxy_map;
bool m_do_cleanup;
bool m_editable;
std::map m_meta_info_name_map;
diff --git a/src/db/db/dbLibrary.cc b/src/db/db/dbLibrary.cc
index 0af31331c..d82ba22b6 100644
--- a/src/db/db/dbLibrary.cc
+++ b/src/db/db/dbLibrary.cc
@@ -26,20 +26,24 @@
#include "dbPCellDeclaration.h"
#include "dbPCellVariant.h"
#include "dbLibraryManager.h"
+#include "tlTimer.h"
#include
namespace db
{
-Library::Library()
- : m_id (std::numeric_limits::max ()), m_layout (true)
+Library::Library ()
+ : m_id (std::numeric_limits::max ()), m_layout (true), m_replicate (true)
{
m_layout.set_library (this);
}
-Library::Library(const Library &d)
- : gsi::ObjectBase (), tl::Object (), m_name (d.m_name), m_description (d.m_description), m_id (std::numeric_limits::max ()), m_layout (d.m_layout)
+Library::Library (const Library &d)
+ : gsi::ObjectBase (), tl::Object (),
+ m_name (d.m_name), m_description (d.m_description),
+ m_id (std::numeric_limits::max ()), m_layout (d.m_layout),
+ m_replicate (d.m_replicate)
{
m_layout.set_library (this);
}
@@ -64,6 +68,12 @@ Library::for_technologies () const
return ! m_technologies.empty ();
}
+void
+Library::set_technologies (const std::set &t)
+{
+ m_technologies = t;
+}
+
void
Library::set_technology (const std::string &t)
{
@@ -108,10 +118,12 @@ Library::unregister_proxy (db::LibraryProxy *lib_proxy, db::Layout *ly)
if (! --c->second) {
db::cell_index_type ci = c->first;
m_refcount.erase (c);
- // remove cells which are itself proxies and are no longer used
- db::Cell *lib_cell = &layout ().cell (ci);
- if (lib_cell && lib_cell->is_proxy () && lib_cell->parent_cells () == 0) {
- layout ().delete_cell (ci);
+ if (layout ().is_valid_cell_index (ci)) {
+ // remove cells which are itself proxies and are no longer used
+ db::Cell *lib_cell = &layout ().cell (ci);
+ if (lib_cell && lib_cell->is_proxy () && lib_cell->parent_cells () == 0) {
+ layout ().delete_cell (ci);
+ }
}
}
retired_state_changed_event ();
@@ -145,6 +157,12 @@ Library::is_retired (const db::cell_index_type library_cell_index) const
return (i != m_refcount.end () && j != m_retired_count.end () && i->second == j->second);
}
+void
+Library::set_replicate (bool f)
+{
+ m_replicate = f;
+}
+
void
Library::rename (const std::string &name)
{
@@ -164,122 +182,175 @@ Library::rename (const std::string &name)
void
Library::refresh ()
{
- std::string name = reload ();
- rename (name);
+ refresh_without_restore ();
- layout ().refresh ();
+ // proxies need to be restored in a special case when a library has a proxy to itself.
+ // The layout readers will not be able to create library references in this case.
+ layout ().restore_proxies ();
+}
+
+void
+Library::refresh_without_restore ()
+{
+ db::LibraryManager::instance ().unregister_lib (this);
+
+ try {
+
+ m_name = reload ();
+
+ } catch (...) {
+
+ // fallback - leave the library, but with empty layout
+ layout ().clear ();
+ db::LibraryManager::instance ().register_lib (this);
+
+ throw;
+
+ }
+
+ // re-register, potentially under the new name
+ db::LibraryManager::instance ().register_lib (this);
+
+ // This is basically only needed for coerce_parameters in a persistent way.
+ // During "register_lib", this is already called in PCell update, but cannot be persisted then.
remap_to (this);
}
void
-Library::remap_to (db::Library *other)
+Library::remap_to (db::Library *other, db::Layout *original_layout)
{
+ if (! original_layout) {
+ original_layout = &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 > referrers;
+ std::vector referrers;
for (std::map::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 needs_cleanup;
- for (std::vector >::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 > pcells_to_map;
- std::vector lib_cells_to_map;
+ any = false;
- for (db::Layout::iterator c = r->first->begin (); c != r->first->end (); ++c) {
+ for (std::vector::const_iterator r = referrers.begin (); r != referrers.end (); ++r) {
- db::LibraryProxy *lib_proxy = dynamic_cast (&*c);
- if (lib_proxy && lib_proxy->lib_id () == get_id ()) {
+ std::vector > pcells_to_map;
+ std::vector lib_cells_to_map;
+
+ for (auto c = (*r)->begin (); c != (*r)->end (); ++c) {
+
+ db::LibraryProxy *lib_proxy = dynamic_cast (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 (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);
- db::Cell *lib_cell = &layout ().cell (lib_proxy->library_cell_index ());
- db::PCellVariant *lib_pcell = dynamic_cast (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 >::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 >::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 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 pn (false, 0);
- if (other) {
- pn = other->layout ().pcell_by_name (lp->first->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 = 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::const_iterator lp = lib_cells_to_map.begin (); lp != lib_cells_to_map.end (); ++lp) {
- for (std::vector::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 cn (false, 0);
+ if (other) {
+ cn = other->layout ().cell_by_name (original_layout->cell_name ((*lp)->library_cell_index ()));
+ }
- std::pair cn (false, 0);
- if (other) {
- cn = other->layout ().cell_by_name ((*lp)->get_basic_name ().c_str ());
- }
+ 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 {
+ if ((*lp)->lib_id () != other->get_id () || (*lp)->library_cell_index () != cn.second) {
+ // we potentially need another iteration
+ any = true;
+ }
- (*lp)->remap (other->get_id (), cn.second);
+ (*lp)->remap (other->get_id (), cn.second);
+
+ }
}
diff --git a/src/db/db/dbLibrary.h b/src/db/db/dbLibrary.h
index cd3513a4d..76bde3406 100644
--- a/src/db/db/dbLibrary.h
+++ b/src/db/db/dbLibrary.h
@@ -53,12 +53,12 @@ public:
/**
* @brief The constructor
*/
- Library();
+ Library ();
/**
* @brief Copy constructor
*/
- Library(const Library &);
+ Library (const Library &);
/**
* @brief The destructor
@@ -138,6 +138,13 @@ public:
*/
bool for_technologies () const;
+ /**
+ * @brief Sets the technology names this library is associated with
+ *
+ * This will reset the list of technologies to this set.
+ */
+ void set_technologies (const std::set &t);
+
/**
* @brief Sets the technology name this library is associated with
*
@@ -172,6 +179,27 @@ public:
m_description = description;
}
+ /**
+ * @brief Sets a value indicating whether the library produces replicas
+ *
+ * If this value is true (the default), layout written will include the
+ * actual layout of a library cell (replica). With this, it is possible
+ * to regenerate the layout without actually having the library at the
+ * cost of additional bytes in the file.
+ *
+ * Setting this flag to false avoids this replication, but a layout
+ * cannot be regenerated without having this library.
+ */
+ void set_replicate (bool f);
+
+ /**
+ * @brief Gets a value indicating whether the library produces replicas
+ */
+ bool replicate () const
+ {
+ return m_replicate;
+ }
+
/**
* @brief Getter for the library Id property
*/
@@ -225,6 +253,13 @@ public:
*/
void refresh ();
+ /**
+ * @brief Refreshes the library on all clients without restoring proxies
+ *
+ * This method is intended to be used internally for bulk refreshes.
+ */
+ void refresh_without_restore ();
+
/**
* @brief Renames the library
*
@@ -237,8 +272,11 @@ public:
* @brief Remap the library proxies to a different library
*
* After remapping, "other" can replace "this".
+ * When calling with "other=this", a pointer to the original
+ * layout needs to be supplied, because in that case, the
+ * layout of "this" is already replaced.
*/
- void remap_to (db::Library *other);
+ void remap_to (db::Library *other, Layout *original_layout = 0);
/**
* @brief This event is fired if proxies get retired on unretired
@@ -253,6 +291,7 @@ private:
db::Layout m_layout;
std::map m_referrers;
std::map m_refcount, m_retired_count;
+ bool m_replicate;
// no copying.
Library &operator=(const Library &);
diff --git a/src/db/db/dbLibraryManager.cc b/src/db/db/dbLibraryManager.cc
index e181855a3..8181694b4 100644
--- a/src/db/db/dbLibraryManager.cc
+++ b/src/db/db/dbLibraryManager.cc
@@ -38,7 +38,7 @@ namespace db
static LibraryManager *sp_instance (0);
-LibraryManager &
+DB_PUBLIC LibraryManager &
LibraryManager::instance ()
{
if (sp_instance == 0) {
@@ -249,13 +249,38 @@ LibraryManager::register_lib (Library *library)
// "restore_proxies" takes care not to re-substitute cold proxies.
const tl::weak_collection &cold_proxies = db::ColdProxy::cold_proxies_per_lib_name (library->get_name ());
- std::set to_refresh;
+
+ std::set to_refresh_set;
for (tl::weak_collection::const_iterator p = cold_proxies.begin (); p != cold_proxies.end (); ++p) {
- to_refresh.insert (const_cast (p->layout ()));
+ to_refresh_set.insert (const_cast (p->layout ()));
}
- for (std::set::const_iterator l = to_refresh.begin (); l != to_refresh.end (); ++l) {
- (*l)->restore_proxies (0);
+ // Sort for deterministic order of resolution
+ std::vector to_refresh (to_refresh_set.begin (), to_refresh_set.end ());
+ std::sort (to_refresh.begin (), to_refresh.end (), tl::sort_by_id ());
+
+ std::set 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
@@ -284,9 +309,27 @@ LibraryManager::lib_internal (lib_id_type id) const
void
LibraryManager::refresh_all ()
{
+ // NOTE: libraries may get deleted during the refresh, so better use weak pointers here
+ // to track lifetime. Libraries appearing are not considered.
+
+ std::vector > libs;
+ libs.reserve (m_libs.size ());
+ for (auto l = m_libs.begin (); l != m_libs.end (); ++l) {
+ libs.push_back (tl::weak_ptr (*l));
+ }
+
+ for (auto l = libs.begin (); l != libs.end (); ++l) {
+ if (l->get ()) {
+ (*l)->refresh_without_restore ();
+ }
+ }
+
+ // restore proxies on all libraries - as the refresh happens in random order, dependencies
+ // are not necessarily resolved. This is done here.
+
for (std::vector::iterator l = m_libs.begin (); l != m_libs.end (); ++l) {
if (*l) {
- (*l)->refresh ();
+ (*l)->layout ().restore_proxies ();
}
}
}
diff --git a/src/db/db/dbLibraryProxy.cc b/src/db/db/dbLibraryProxy.cc
index 8a4e043f2..84317d341 100644
--- a/src/db/db/dbLibraryProxy.cc
+++ b/src/db/db/dbLibraryProxy.cc
@@ -22,6 +22,7 @@
#include "dbLibraryProxy.h"
+#include "dbColdProxy.h"
#include "dbLibraryManager.h"
#include "dbLibrary.h"
#include "dbLayout.h"
@@ -187,25 +188,6 @@ LibraryProxy::get_layer_indices (db::Layout &layout, db::ImportLayerMapping *lay
return m_layer_indices;
}
-class LibraryCellIndexMapper
-{
-public:
- LibraryCellIndexMapper (Layout &layout, Library *lib)
- : mp_lib (lib), mp_layout (&layout)
- {
- // .. nothing yet ..
- }
-
- cell_index_type operator() (cell_index_type cell_index_in_lib)
- {
- return mp_layout->get_lib_proxy (mp_lib, cell_index_in_lib);
- }
-
-private:
- Library *mp_lib;
- Layout *mp_layout;
-};
-
void
LibraryProxy::update (db::ImportLayerMapping *layer_mapping)
{
@@ -215,32 +197,65 @@ LibraryProxy::update (db::ImportLayerMapping *layer_mapping)
Library *lib = LibraryManager::instance ().lib (lib_id ());
const db::Cell &source_cell = lib->layout ().cell (library_cell_index ());
- db::ICplxTrans tr;
- bool need_transform = false;
- if (fabs (layout ()->dbu () - lib->layout ().dbu ()) > 1e-6) {
- need_transform = true;
- tr = db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ());
- }
-
clear_shapes ();
clear_insts ();
for (unsigned int l = 0; l < lib->layout ().layers (); ++l) {
if (layer_indices [l] >= 0) {
- shapes ((unsigned int) layer_indices [l]).assign_transformed (source_cell.shapes (l), tr);
+ shapes ((unsigned int) layer_indices [l]).assign_transformed (source_cell.shapes (l), db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ()));
}
}
- LibraryCellIndexMapper cell_index_mapper (*layout (), lib);
+ for (Cell::const_iterator i = source_cell.begin (); ! i.at_end (); ++i) {
- for (Cell::const_iterator inst = source_cell.begin (); !inst.at_end (); ++inst) {
- db::Instance new_inst = insert (*inst, cell_index_mapper);
- if (need_transform) {
- replace (new_inst, new_inst.cell_inst ().transformed_into (tr));
+ db::CellInstArray inst = i->cell_inst ();
+
+ Library *real_lib = lib;
+ cell_index_type real_cil = inst.object ().cell_index ();
+
+ // use the "final lib", so we refer to the actual lib instead
+ // of building chains of lib references
+ LibraryProxy *lp;
+ while ((lp = dynamic_cast (&real_lib->layout ().cell (real_cil))) != 0) {
+ real_cil = lp->library_cell_index ();
+ real_lib = db::LibraryManager::instance ().lib (lp->lib_id ());
}
+
+ ColdProxy *cp = dynamic_cast (&real_lib->layout ().cell (real_cil));
+ if (cp) {
+
+ // The final item is a cold proxy ("" cell) - treat it like
+ // a library proxy as it may become one in the future, but replace it
+ // by a cold proxy now.
+
+ auto p = layout ()->find_cold_proxy (cp->context_info ());
+ if (p.first) {
+ // reuse existing proxy
+ inst.object ().cell_index (p.second);
+ } else {
+ // create a new proxy, reusing the first library proxy's replica
+ inst.object ().cell_index (layout ()->get_lib_proxy (real_lib, real_cil));
+ layout ()->create_cold_proxy_as (cp->context_info (), inst.object ().cell_index ());
+ }
+
+ } else {
+ inst.object ().cell_index (layout ()->get_lib_proxy (real_lib, real_cil));
+ }
+
+ inst.transform_into (db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ()));
+
+ insert (inst);
+
}
}
+bool
+LibraryProxy::can_skip_replica () const
+{
+ const Library *lib = LibraryManager::instance ().lib (lib_id ());
+ return lib && ! lib->replicate ();
+}
+
std::string
LibraryProxy::get_basic_name () const
{
diff --git a/src/db/db/dbLibraryProxy.h b/src/db/db/dbLibraryProxy.h
index 233348daa..029a4ad64 100644
--- a/src/db/db/dbLibraryProxy.h
+++ b/src/db/db/dbLibraryProxy.h
@@ -93,6 +93,14 @@ public:
return true;
}
+ /**
+ * @brief Gets a value indicating that this cell is a replica that can be skipped
+ *
+ * This attribute is evaluated by file writers to skip cell replicas for
+ * library cells that do not want to replicated.
+ */
+ virtual bool can_skip_replica () const;
+
/**
* @brief Gets the basic name
*
@@ -129,12 +137,12 @@ public:
/**
* @brief Reimplemented from Cell: unregisters the proxy at the layout
*/
- void unregister ();
+ virtual void unregister ();
/**
* @brief Reimplemented from Cell: reregisters the proxy at the layout
*/
- void reregister ();
+ virtual void reregister ();
private:
lib_id_type m_lib_id;
diff --git a/src/db/db/dbSaveLayoutOptions.cc b/src/db/db/dbSaveLayoutOptions.cc
index 688b34704..236a3f38c 100644
--- a/src/db/db/dbSaveLayoutOptions.cc
+++ b/src/db/db/dbSaveLayoutOptions.cc
@@ -57,7 +57,7 @@ SaveLayoutOptions::operator= (const SaveLayoutOptions &d)
m_format = d.m_format;
m_layers = d.m_layers;
m_cells = d.m_cells;
- m_implied_childred = d.m_implied_childred;
+ m_implied_children = d.m_implied_children;
m_all_layers = d.m_all_layers;
m_all_cells = d.m_all_cells;
m_dbu = d.m_dbu;
@@ -190,7 +190,7 @@ SaveLayoutOptions::add_cell (db::cell_index_type cell_index)
{
m_all_cells = false;
m_cells.insert (cell_index);
- m_implied_childred.insert (cell_index);
+ m_implied_children.insert (cell_index);
}
void
@@ -205,7 +205,7 @@ SaveLayoutOptions::clear_cells ()
{
m_all_cells = false;
m_cells.clear ();
- m_implied_childred.clear ();
+ m_implied_children.clear ();
}
void
@@ -213,7 +213,7 @@ SaveLayoutOptions::select_all_cells ()
{
m_all_cells = true;
m_cells.clear ();
- m_implied_childred.clear ();
+ m_implied_children.clear ();
}
void
@@ -341,21 +341,75 @@ SaveLayoutOptions::get_valid_layers (const db::Layout &layout, std::vector &called)
+{
+ const db::Cell &c = layout.cell (ci);
+ if (c.can_skip_replica ()) {
+ return;
+ }
+
+ for (auto cc = c.begin_child_cells (); ! cc.at_end (); ++cc) {
+ if (called.find (*cc) == called.end () && layout.is_valid_cell_index (*cc)) {
+ called.insert (*cc);
+ collect_called_cells_unskipped (*cc, layout, called);
+ }
+ }
+}
+
void
SaveLayoutOptions::get_cells (const db::Layout &layout, std::set &cells, const std::vector > &valid_layers, bool require_unique_names) const
{
+ bool has_context = m_write_context_info;
+ for (tl::Registrar::iterator fmt = tl::Registrar::begin (); fmt != tl::Registrar::end (); ++fmt) {
+ if (fmt->format_name () == m_format) {
+ if (! fmt->supports_context ()) {
+ has_context = false;
+ }
+ break;
+ }
+ }
+
if (m_all_cells) {
- for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
- cells.insert (cell->cell_index ());
+ 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 ();
+ }
+ }
+
+ // with skipped replicas start again and skip child cells of such cells
+ // unless they are needed in other places.
+ if (has_skipped_replica) {
+
+ for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
+ if (cell->is_top ()) {
+ cells.insert (cell->cell_index ());
+ collect_called_cells_unskipped (cell->cell_index (), layout, cells);
+ }
+ }
+
+ } else {
+
+ for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) {
+ cells.insert (cell->cell_index ());
+ }
+
}
} else {
for (std::set ::const_iterator c = m_cells.begin (); c != m_cells.end (); ++c) {
cells.insert (*c);
- if (m_implied_childred.find (*c) != m_implied_childred.end ()) {
- layout.cell (*c).collect_called_cells (cells);
+ if (m_implied_children.find (*c) != m_implied_children.end ()) {
+ if (has_context) {
+ collect_called_cells_unskipped (*c, layout, cells);
+ } else {
+ layout.cell (*c).collect_called_cells (cells);
+ }
}
}
diff --git a/src/db/db/dbSaveLayoutOptions.h b/src/db/db/dbSaveLayoutOptions.h
index 6c04970bd..585e7921e 100644
--- a/src/db/db/dbSaveLayoutOptions.h
+++ b/src/db/db/dbSaveLayoutOptions.h
@@ -449,7 +449,7 @@ private:
std::string m_format;
std::map m_layers;
std::set m_cells;
- std::set m_implied_childred;
+ std::set m_implied_children;
bool m_all_layers;
bool m_all_cells;
double m_dbu;
diff --git a/src/db/db/dbStream.h b/src/db/db/dbStream.h
index ed217adbc..b6c1c4381 100644
--- a/src/db/db/dbStream.h
+++ b/src/db/db/dbStream.h
@@ -109,6 +109,11 @@ public:
*/
virtual bool can_write () const = 0;
+ /**
+ * @brief Returns true, when the format supports a context (e.g. PCell parameters, Library references)
+ */
+ virtual bool supports_context () const = 0;
+
/**
* @brief Delivers the XMLElement object that represents the reader options within a technology XML tree
*
diff --git a/src/db/db/dbTestSupport.cc b/src/db/db/dbTestSupport.cc
index c2f12e19a..f055a5980 100644
--- a/src/db/db/dbTestSupport.cc
+++ b/src/db/db/dbTestSupport.cc
@@ -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);
}
diff --git a/src/db/db/dbTestSupport.h b/src/db/db/dbTestSupport.h
index 368c1bad8..df013f8a1 100644
--- a/src/db/db/dbTestSupport.h
+++ b/src/db/db/dbTestSupport.h
@@ -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
};
/**
diff --git a/src/db/db/gsiDeclDbLibrary.cc b/src/db/db/gsiDeclDbLibrary.cc
index 4da1335f2..69e9280c0 100644
--- a/src/db/db/gsiDeclDbLibrary.cc
+++ b/src/db/db/gsiDeclDbLibrary.cc
@@ -29,6 +29,7 @@
#include "dbPCellDeclaration.h"
#include "dbLibrary.h"
#include "dbLibraryManager.h"
+#include "dbFileBasedLibrary.h"
#include "tlLog.h"
namespace gsi
@@ -156,11 +157,91 @@ static LibraryImpl *new_lib ()
return new LibraryImpl ();
}
+static db::Library *library_from_file (const std::string &path, const std::string &name, const std::string &for_technology)
+{
+ // Check if a library with this specification already is installed and reuse in that case.
+ if (! name.empty ()) {
+
+ db::FileBasedLibrary *old_lib = dynamic_cast (library_by_name (name, for_technology));
+ if (old_lib && old_lib->is_for_path (path)) {
+ old_lib->load ();
+ return old_lib;
+ }
+
+ }
+
+ std::unique_ptr lib (new db::FileBasedLibrary (path, name));
+ lib->set_technology (for_technology);
+
+ std::string n = lib->load ();
+ db::Library *ret = lib.get ();
+ register_lib (lib.release (), n);
+
+ return ret;
+}
+
+static db::Library *library_from_files (const std::vector &paths, const std::string &name, const std::string &for_technology)
+{
+ if (paths.empty ()) {
+ throw tl::Exception (tl::to_string (tr ("At least one path must be given")));
+ }
+
+ // Check if a library with this specification already is installed and reuse in that case.
+ if (! name.empty ()) {
+
+ db::FileBasedLibrary *old_lib = dynamic_cast (library_by_name (name, for_technology));
+ if (old_lib && old_lib->is_for_paths (paths)) {
+ old_lib->load ();
+ return old_lib;
+ }
+
+ }
+
+ std::unique_ptr lib (new db::FileBasedLibrary (paths.front (), name));
+ for (auto i = paths.begin () + 1; i != paths.end (); ++i) {
+ lib->merge_with_other_layout (*i);
+ }
+
+ lib->set_technology (for_technology);
+
+ std::string n = lib->load ();
+ db::Library *ret = lib.get ();
+ register_lib (lib.release (), n);
+
+ return ret;
+}
+
/**
* @brief A basic implementation of the library
*/
LibraryClass decl_Library ("db", "LibraryBase",
+ gsi::method ("library_from_file", &library_from_file, gsi::arg ("path"), gsi::arg ("name", std::string (), "auto"), gsi::arg ("for_technology", std::string (), "none"),
+ "@brief Creates a library from a file\n"
+ "@param path The path to the file from which to create the library from.\n"
+ "@param name The name of the library. If empty, the name will be derived from the GDS LIBNAME or the file name.\n"
+ "@return The library object created. It is already registered with the name given or derived from the file.\n"
+ "\n"
+ "This method will create a \\Library object which is tied to a specific file. This object supports "
+ "automatic reloading when the \\Library#refresh method is called.\n"
+ "\n"
+ "If a file-based library with the same name and path is registered already, this method will not reload again "
+ "and return the library that was already registered.\n"
+ "\n"
+ "This convenience method has been added in version 0.30.8.\n"
+ ) +
+ gsi::method ("library_from_files", &library_from_files, gsi::arg ("paths"), gsi::arg ("name", std::string (), "auto"), gsi::arg ("for_technology", std::string (), "none"),
+ "@brief Creates a library from a set of files\n"
+ "@param paths The paths to the files from which to create the library from. At least one file needs to be given.\n"
+ "@param name The name of the library. If empty, the name will be derived from the GDS LIBNAME or the file name.\n"
+ "@return The library object created. It is already registered with the name given or derived from the file.\n"
+ "\n"
+ "This method will create a \\Library object which is tied to several files. This object supports "
+ "automatic reloading when the \\Library#refresh method is called. The content of the files is merged "
+ "into the library. This is useful for example to create one library from a collection of files.\n"
+ "\n"
+ "This convenience method has been added in version 0.30.8.\n"
+ ) +
gsi::method ("library_by_name", &library_by_name, gsi::arg ("name"), gsi::arg ("for_technology", std::string (), "unspecific"),
"@brief Gets a library by name\n"
"Returns the library object for the given name. If the name is not a valid library name, nil is returned.\n"
@@ -213,6 +294,26 @@ LibraryClass decl_Library ("db", "LibraryBase",
"\n"
"This method has been introduced in version 0.30.5."
) +
+ gsi::method ("replicate=", &db::Library::set_replicate, gsi::arg ("flag"),
+ "@brief Sets a value indicating whether the library produces replicas\n"
+ "\n"
+ "If this value is true (the default), layout written will include the\n"
+ "actual layout of a library cell (replica). With this, it is possible\n"
+ "to regenerate the layout without actually having the library at the\n"
+ "cost of additional bytes in the file.\n"
+ "\n"
+ "Setting this flag to false avoids this replication, but a layout\n"
+ "cannot be regenerated without having this library.\n"
+ "\n"
+ "This attribute has been introduced in version 0.30.8."
+ ) +
+ gsi::method ("replicate", &db::Library::replicate,
+ "@brief Gets a value indicating whether the library produces replicas\n"
+ "\n"
+ "See \\replicate= for a description of this attribute.\n"
+ "\n"
+ "This attribute has been introduced in version 0.30.8."
+ ) +
gsi::method ("rename", &db::Library::rename, gsi::arg ("name"),
"@brief Renames the library\n"
"\n"
diff --git a/src/db/unit_tests/dbLibrariesTests.cc b/src/db/unit_tests/dbLibrariesTests.cc
index 1d48e852e..65ca6e4f9 100644
--- a/src/db/unit_tests/dbLibrariesTests.cc
+++ b/src/db/unit_tests/dbLibrariesTests.cc
@@ -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 (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 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 (6));
+ EXPECT_EQ (num_cells (layout), size_t (25));
+ 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 (25));
+ 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 (25));
+ 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 (6));
+ EXPECT_EQ (num_cells (layout), size_t (25));
+ 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 (12));
+ EXPECT_EQ (num_cells (layout), size_t (25));
+ 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));
+}
diff --git a/src/db/unit_tests/dbLibraryTests.cc b/src/db/unit_tests/dbLibraryTests.cc
new file mode 100644
index 000000000..3959e677f
--- /dev/null
+++ b/src/db/unit_tests/dbLibraryTests.cc
@@ -0,0 +1,92 @@
+
+/*
+
+ KLayout Layout Viewer
+ Copyright (C) 2006-2026 Matthias Koefferlein
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+#include "dbLibraryManager.h"
+#include "dbLibrary.h"
+#include "dbFileBasedLibrary.h"
+#include "tlUnitTest.h"
+#include "tlFileUtils.h"
+
+// map library to different file (aka reload/refresh)
+TEST(1)
+{
+ std::string pa = tl::testsrc () + "/testdata/gds/lib_a.gds";
+ std::string pb = tl::testsrc () + "/testdata/gds/lib_b.gds";
+
+ db::FileBasedLibrary *lib = new db::FileBasedLibrary (pa);
+ lib->load ();
+ lib->set_name ("LIB");
+ db::LibraryManager::instance ().register_lib (lib);
+
+ db::Layout l;
+ auto top_cell_index = l.add_cell ("TOP");
+ auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("A").second);
+
+ l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
+
+ EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;2000,2000)");
+
+ // switch to a different file and refresh
+ lib->set_paths (pb);
+ lib->refresh ();
+
+ EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(-1000,-1000;1000,1000)");
+
+ db::LibraryManager::instance ().delete_lib (lib);
+}
+
+// merge multiple files into one library
+TEST(2)
+{
+ std::string pa = tl::testsrc () + "/testdata/gds/lib_a.gds";
+ std::string pb = tl::testsrc () + "/testdata/gds/lib_b.gds";
+
+ db::FileBasedLibrary *lib = new db::FileBasedLibrary (pa);
+ lib->merge_with_other_layout (pb);
+ lib->load ();
+ lib->set_name ("LIB");
+ db::LibraryManager::instance ().register_lib (lib);
+
+ {
+ db::Layout l;
+ auto top_cell_index = l.add_cell ("TOP");
+ auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("A").second);
+
+ l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
+
+ // A comes from the first layout
+ EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;2000,2000)");
+ }
+
+ {
+ db::Layout l;
+ auto top_cell_index = l.add_cell ("TOP");
+ auto lib_proxy = l.get_lib_proxy (lib, lib->layout ().cell_by_name ("Z").second);
+
+ l.cell (top_cell_index).insert (db::CellInstArray (db::CellInst (lib_proxy), db::Trans ()));
+
+ // Z comes from the second layout
+ EXPECT_EQ (l.cell (top_cell_index).bbox ().to_string (), "(0,0;100,100)");
+ }
+
+ db::LibraryManager::instance ().delete_lib (lib);
+}
diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro
index a51884427..fa26f60fb 100644
--- a/src/db/unit_tests/unit_tests.pro
+++ b/src/db/unit_tests/unit_tests.pro
@@ -11,6 +11,7 @@ SOURCES = \
dbCompoundOperationTests.cc \
dbEdgeNeighborhoodTests.cc \
dbFillToolTests.cc \
+ dbLibraryTests.cc \
dbLogTests.cc \
dbObjectWithPropertiesTests.cc \
dbPLCConvexDecompositionTests.cc \
diff --git a/src/doc/doc/about/about_libraries.xml b/src/doc/doc/about/about_libraries.xml
index 70ee02d31..c41e52844 100644
--- a/src/doc/doc/about/about_libraries.xml
+++ b/src/doc/doc/about/about_libraries.xml
@@ -6,6 +6,8 @@
About Libraries
+
+
Starting with version 0.22, KLayout offers a library concept.
@@ -50,6 +52,22 @@
name of the library file minus the extension.
+
Layout files by declaration:
+ Starting with version 0.30.8, KLayout supports declaration of static libraries by
+ a library definition file. This is an alternative mechanism to the "libraries" folder
+ and has a number of advantages:
+
+
+
Libraries can be referenced from any place in the file system
+
Library definition files can include other files and include environment variables
+
Additional attributes are available for declaration
+
+
+ This feature is limited to static libraries - i.e. libraries declared by library
+ definition files cannot include PCells. PCells are an exclusive feature of coded
+ libraries.
+
+
Coded libraries:
Such libraries are provided by code, either through shared objects/DLL's or through Ruby code.
Basically such code has to provide a layout object containing the library cells.
@@ -65,5 +83,186 @@
about packages.
+
+ Static libraries, whether defined by a library declaration file or implicitly from the
+ "libraries" folder, are automatically reloaded, when the file changes. If this feature
+ is not desired, you can disable automatic reload in the Setup dialog and manually refresh
+ the libraries if required.
+
+
+
How libraries work
+
+
+ Unlike other systems, libraries are not external references for KLayout. Instead, a library
+ is essentially a copy source. When you use a library cell, it is copied into your
+ working layout and is weakly linked to the original place. You can refresh the library,
+ which will also refresh the copy. This process is called "replication".
+
+
+
+ This approach implies a memory adder, but has a number of benefits:
+
+
+
+
The working layout is "whole" - i.e. if a library vanishes or is not available
+ when transferring the layout to someone else, the layout is still functional. The
+ copy acts as a snapshot.
+
+
+ The independent copy allows for compensating database unit differences and layer details,
+ so the libraries can be made with a different database unit than the working layout for example.
+
+
+ This design greatly simplifies the database organisation, as the internal
+ structure does not need to care about layer index translations and all information
+ needed to build lookup trees is available in a single layout object. It is easy
+ to temporarily detach a layout object from the libraries without destroying the
+ structure.
+
+
+
+
+ The design also implies that libraries are read-only. You edit the working
+ copy only - library cells pulled into the layout are copies that cannot and
+ should not be edited, as they are restored from their original source
+ occasionally. Libraries should be thought of as external deliveries similar
+ to Python modules for example.
+
+
+
+ When saving a working layout, the copies are stored in the layout file too.
+ This feature can be disabled in a per-library basis in the library declaration
+ files (only available for static libraries). See the description of the
+ "replicate" attribute below. Note that when you use this feature, you can
+ only restore the entire layout, if you have these libraries installed.
+
+
+
Library declaration files
+
+
+ By default, library declaration files are called "klayout.lib" and are looked up in
+ all places of the KLayout search path - this includes the home folder, the technology
+ folders and the package folders. You can override this mechanism by setting the
+ "KLAYOUT_LIB" environment variable to a specific file. In that case, only this
+ file is loaded.
+
+
+
+ The format of the library declaration file is "KLayout expressions". This means: function
+ calls need to be with round brackets and expressions are separated
+ by ";". For more information about expressions, see .
+
+
+
+ Two functions are available: "define" and "include". These are used to build
+ the declaration files.
+
+
+
"include" function
+
+
Usage:
+
+
include(path);
+
+
Description:
+
+
+ Includes the lib file given by the path. The path is
+ resolved relative to the current lib file.
+
+ Defines a library. In the path-only version, the library name
+ is taken from the layout file's LIBNAME record (GDS) or the
+ file name. In the name/path version, "name" is the name the
+ library is registered under.
+
+
+
+ The path is resolved relative to the current lib file.
+ If you want to include an environment variable, use the "env" expression
+ function:
+
+
+
define("A", env("HOME") + "/mylibs/a.gds");
+
+
+ Note that the order of library definitions matters. Dependencies
+ need to be defined already when a library is loaded. For example
+ if a library "B" uses components from library "A", the order needs
+ to be:
+
+
+
define("A", "a.gds");
+define("B", "b.gds");
+
+
+ The following options are available:
+
+
+
+
+
+
+
technology: binds a library to a technology - i.e. it is only
+ available in the editor when that technology is selected:
+
+
define("A", "a.gds", technology="SG13A");
+
+
+ By default, libraries are not bound to a technology, except if
+ the library file is loaded in the context of a technology (i.e.
+ sits in a technology folder). You can explicitly enable a library
+ for all technologies using:
+
+
+
define("A", "a.gds", technology="*");
+
+
+
+
+
+
technologies: binds a library to several technologies. The
+ argument is a list:
replicate: this flag indicates that components of the library
+ are replicated in the layout files using a library element.
+ This allows loading that file without actually having the library.
+ However, it comes with a size overhead, that can be avoided
+ by skipping this replication:
+
+
+
define("A", "a.gds", replicate=false);
+
+
By default, replication is enabled.
+
+
+
+
+
+
description: Specifies a description for that library.
+