diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f87194b2c..d3b1fba45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,9 @@ jobs: - os: "ubuntu-latest" cibuild: "*musllinux*" cibw_arch: "musllinux" + - os: "ubuntu-24.04-arm" # aarch64 manylinux on ARM runner + cibuild: "*manylinux*" + cibw_arch: "aarch64" steps: - name: Free Disk Space (Ubuntu) if: matrix.os == 'ubuntu-latest' @@ -48,29 +51,44 @@ jobs: uses: styfle/cancel-workflow-action@0.12.1 - uses: actions/checkout@v4 - name: ccache + if: matrix.os != 'ubuntu-24.04-arm' uses: hendrikmuhs/ccache-action@v1.2 with: key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.cibuild }} # Make cache specific to OS max-size: "5G" - name: Install dependencies + if: matrix.os != 'ubuntu-24.04-arm' run: | env export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH HOST_CCACHE_DIR="$(ccache -k cache_dir)" mkdir -p $HOST_CCACHE_DIR - - name: Build wheels # check https://cibuildwheel.readthedocs.io/en/stable/setup/#github-actions - uses: pypa/cibuildwheel@v2.23.0 - # to supply options, put them in 'env', like: - # env: - # CIBW_SOME_OPTION: value + - name: Build wheels (ARM) + if: matrix.os == 'ubuntu-24.04-arm' + uses: pypa/cibuildwheel@v2.23.3 + env: + # override the default CentOS “yum install … ccache” and drop ccache + CIBW_BEFORE_ALL_LINUX: | + yum install -y \ + zlib-devel \ + curl-devel \ + expat-devel \ + libpng-devel + CIBW_BEFORE_BUILD_LINUX: "true" + CIBW_BUILD: ${{ matrix.cibuild }} + CIBW_ARCHS_LINUX: ${{ matrix.cibw_arch }} + + - name: Build wheels (all other platforms) + if: matrix.os != 'ubuntu-24.04-arm' + uses: pypa/cibuildwheel@v2.23.3 env: CIBW_BUILD: ${{ matrix.cibuild }} CIBW_ARCHS_MACOS: ${{ matrix.macos-arch }} CIBW_DEPENDENCY_VERSIONS_MACOS: cibw_constraints.txt - name: Download Cache from Docker (linux only) - if: ${{ runner.os == 'Linux' }} + if: runner.os == 'Linux' && matrix.os != 'ubuntu-24.04-arm' # hack until https://github.com/pypa/cibuildwheel/issues/1030 is fixed run: | env diff --git a/Changelog b/Changelog index c4450b33d..1138f86d5 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,23 @@ +0.30.2 (2025-xx-xx): +* Enhancement: %GITHUB%/issues/2016 Lazy evaluation of PCell also when changing guiding shape properties + +0.30.1 (2025-04-27): +* Bugfix: %GITHUB%/issues/2038 DEdgePairWithProperties not working properly in scripts +* Bugfix: %GITHUB%/issues/2011 Some DRC bugs fixed +* Bug: %GITHUB%/issues/2014 Bug fixes in LEF/DEF reader +* Enhancement: %GITHUB%/issues/2019 Support for Qt 6.9 +* Bugfix: %GITHUB%/issues/2020 Strict weak ordering issue fixed in edge processor +* Enhancement: %GITHUB%/issues/2024 Option to configure grid density +* Bugfix: %GITHUB%/issues/2025 Brackets get added in List type PCell parameter edit field +* Bugfix: %GITHUB%/issues/2026 Display is dead after opening 2.5d view +* Bugfix/Enhancement: some updates of "strmxor" tool + - strmxor was giving wrong results if cell variants are + present where one variant is covered entirely by a large shape + - Parallelization now happens on a per-layer basis (same as for + XOR tool in KLayout) + - Shape count was not consistent in deep mode + - All buddy tools print total runtime with -d11 + 0.30.0 (2025-03-25): * Bug: %GITHUB%/issues/1996 More robust triangulation * Bug: %GITHUB%/issues/2002 Path to polygon conversion issue diff --git a/Changelog.Debian b/Changelog.Debian index 1d2c2b3c3..a69eb136e 100644 --- a/Changelog.Debian +++ b/Changelog.Debian @@ -1,3 +1,10 @@ +klayout (0.30.1-1) unstable; urgency=low + + * New features and bugfixes + - See changelog + + -- Matthias Köfferlein Sun, 27 Apr 2025 14:26:50 +0200 + klayout (0.30.0-1) unstable; urgency=low * New features and bugfixes diff --git a/macbuild/build4mac_env.py b/macbuild/build4mac_env.py index 3be1c22de..e3527edf6 100755 --- a/macbuild/build4mac_env.py +++ b/macbuild/build4mac_env.py @@ -192,7 +192,7 @@ RubySequoia = { 'exe': '/System/Library/Frameworks/Ruby.framework/Versions # install with 'sudo port install ruby33' # [Key Type Name] = 'MP33' Ruby33MacPorts = { 'exe': '/opt/local/bin/ruby3.3', - 'inc': '/opt/local/include/ruby-3.3.7', + 'inc': '/opt/local/include/ruby-3.3.8', 'lib': '/opt/local/lib/libruby.3.3.dylib' } diff --git a/macbuild/makeDMG4mac.py b/macbuild/makeDMG4mac.py index 972a36374..b7e7aa9da 100755 --- a/macbuild/makeDMG4mac.py +++ b/macbuild/makeDMG4mac.py @@ -424,7 +424,7 @@ def CheckPkgDirectory(): #------------------------------------------------------ # [5] Check the occupied disk space #------------------------------------------------------ - command = "\du -sm %s" % DefaultBundleName + command = r"\du -sm %s" % DefaultBundleName sizeApp = int( os.popen(command).read().strip("\n").split("\t")[0] ) #------------------------------------------------------ @@ -671,14 +671,14 @@ def MakeTargetDMGFile(msg=""): imageDest = "%s/.background" % MountDir if not os.path.isdir(imageDest): os.mkdir(imageDest) - command = "\cp -p %s %s/%s" % (imageSrc, imageDest, BackgroundPNG) + command = r"\cp -p %s %s/%s" % (imageSrc, imageDest, BackgroundPNG) os.system(command) #-------------------------------------------------------- # (6) Create a symbolic link to /Applications #-------------------------------------------------------- print( ">>> (6) Creating a symbolic link to /Applications..." ) - command = "\ln -s %s %s/%s" % (RootApplications, MountDir, RootApplications) + command = r"\ln -s %s %s/%s" % (RootApplications, MountDir, RootApplications) os.system(command) #-------------------------------------------------------- @@ -702,7 +702,7 @@ def MakeTargetDMGFile(msg=""): print( ">>> (8) Copying the volume icon..." ) iconsSrc = "macbuild/Resources/%s" % VolumeIcons iconsDest = "%s/.VolumeIcon.icns" % MountDir - command1 = "\cp -p %s %s" % (iconsSrc, iconsDest) + command1 = r"\cp -p %s %s" % (iconsSrc, iconsDest) command2 = "SetFile -c icnC %s" % iconsDest os.system(command1) sleep(2) @@ -713,7 +713,7 @@ def MakeTargetDMGFile(msg=""): # (9) Change the permission #-------------------------------------------------------- print( ">>> (9) Changing permission to 755..." ) - command = "\chmod -Rf 755 %s &> /dev/null" % MountDir + command = r"\chmod -Rf 755 %s &> /dev/null" % MountDir os.system(command) #-------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index eafccb77f..aee59ff0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + [tool.cibuildwheel] build-verbosity = "3" test-command = [ diff --git a/scripts/mkqtdecl6/mkqtdecl.conf b/scripts/mkqtdecl6/mkqtdecl.conf index 427141e2b..4b7c15fa5 100644 --- a/scripts/mkqtdecl6/mkqtdecl.conf +++ b/scripts/mkqtdecl6/mkqtdecl.conf @@ -547,7 +547,7 @@ drop_method "QDebug", /QDebug::operator\s*<<\((?!const\s+QString\s*&)/ # don't m drop_method "", /::operator\s*<<\(QDebug\s*\w*\s*,\s*(?!const\s+QString\s*&)/ # don't map the others right now - too many (TODO: how to map?) drop_method "QNoDebug", /QNoDebug::operator<= 11, tl::to_string (tr ("Total"))); + { db::LoadLayoutOptions load_options; generic_reader_options.configure (load_options); diff --git a/src/buddies/src/bd/bdReaderOptions.cc b/src/buddies/src/bd/bdReaderOptions.cc index 53133d373..752d20edd 100644 --- a/src/buddies/src/bd/bdReaderOptions.cc +++ b/src/buddies/src/bd/bdReaderOptions.cc @@ -120,7 +120,9 @@ GenericReaderOptions::GenericReaderOptions () m_lefdef_separate_groups = load_options.get_option_by_name ("lefdef_config.separate_groups").to_bool (); m_lefdef_joined_paths = load_options.get_option_by_name ("lefdef_config.joined_paths").to_bool (); m_lefdef_map_file = load_options.get_option_by_name ("lefdef_config.map_file").to_string (); - m_lefdef_macro_resolution_mode = load_options.get_option_by_name ("lefdef_config.macro_resolution_mode").to_int (); + // Don't take the default, as in practice, it's more common to substitute LEF macros by layouts + // m_lefdef_macro_resolution_mode = load_options.get_option_by_name ("lefdef_config.macro_resolution_mode").to_int (); + m_lefdef_macro_resolution_mode = 2; // "assume FOREIGN always" } void diff --git a/src/buddies/src/bd/strmclip.cc b/src/buddies/src/bd/strmclip.cc index ca36b2507..917b5a24d 100644 --- a/src/buddies/src/bd/strmclip.cc +++ b/src/buddies/src/bd/strmclip.cc @@ -29,6 +29,7 @@ #include "dbSaveLayoutOptions.h" #include "tlLog.h" #include "tlCommandLineParser.h" +#include "tlTimer.h" struct ClipData @@ -200,6 +201,8 @@ BD_PUBLIC int strmclip (int argc, char *argv[]) cmd.parse (argc, argv); + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (tr ("Total"))); + clip (data); return 0; diff --git a/src/buddies/src/bd/strmcmp.cc b/src/buddies/src/bd/strmcmp.cc index 70cc712e9..ccc31c55c 100644 --- a/src/buddies/src/bd/strmcmp.cc +++ b/src/buddies/src/bd/strmcmp.cc @@ -25,6 +25,7 @@ #include "dbLayoutDiff.h" #include "dbReader.h" #include "tlCommandLineParser.h" +#include "tlTimer.h" BD_PUBLIC int strmcmp (int argc, char *argv[]) { @@ -141,6 +142,8 @@ BD_PUBLIC int strmcmp (int argc, char *argv[]) throw tl::Exception ("Both -ta|--top-a and -tb|--top-b top cells must be given"); } + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (tr ("Total"))); + db::Layout layout_a; db::Layout layout_b; diff --git a/src/buddies/src/bd/strmrun.cc b/src/buddies/src/bd/strmrun.cc index 8f5faf784..de42220c0 100644 --- a/src/buddies/src/bd/strmrun.cc +++ b/src/buddies/src/bd/strmrun.cc @@ -28,6 +28,7 @@ #include "tlLog.h" #include "tlCommandLineParser.h" #include "tlFileUtils.h" +#include "tlTimer.h" #include "rba.h" #include "pya.h" #include "gsi.h" @@ -98,5 +99,8 @@ BD_PUBLIC int strmrun (int argc, char *argv[]) lym::Macro macro; macro.load_from (script); macro.set_file_path (script); + + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (tr ("Total"))); + return macro.run (); } diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 94ff7da1d..1a5965e83 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -32,6 +32,8 @@ #include "gsiExpression.h" #include "tlCommandLineParser.h" #include "tlThreads.h" +#include "tlThreadedWorkers.h" +#include "tlTimer.h" namespace { @@ -319,7 +321,8 @@ struct XORData dont_summarize_missing_layers (false), silent (false), no_summary (false), threads (0), tile_size (0.0), heal_results (false), - output_layout (0), output_cell (0) + output_layout (0), output_cell (0), + layers_missing (0) { } db::Layout *layout_a, *layout_b; @@ -336,6 +339,8 @@ struct XORData db::cell_index_type output_cell; std::map, db::LPLogicalLessFunc> l2l_map; std::map, ResultDescriptor> *results; + mutable int layers_missing; + mutable tl::Mutex lock; }; } @@ -455,6 +460,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) } } + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (tr ("Total"))); + db::Layout layout_a; db::Layout layout_b; @@ -572,14 +579,22 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) if (! silent && ! no_summary) { if (result) { - tl::info << "No differences found"; + tl::info << tl::to_string (tr ("No differences found")); } else { const char *line_format = " %-10s %-12s %s"; - const char *sep = " -------------------------------------------------------"; - tl::info << "Result summary (layers without differences are not shown):" << tl::endl; - tl::info << tl::sprintf (line_format, "Layer", "Output", "Differences (shape count)") << tl::endl << sep; + std::string headline; + if (deep) { + headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (hierarchical shape count)"))); + } else { + headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)"))); + } + + const char *sep = " ----------------------------------------------------------------"; + + tl::info << tl::to_string (tr ("Result summary (layers without differences are not shown):")) << tl::endl; + tl::info << headline << tl::endl << sep; int ti = -1; for (std::map, ResultDescriptor>::const_iterator r = results.begin (); r != results.end (); ++r) { @@ -587,17 +602,17 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) if (r->first.first != ti) { ti = r->first.first; if (tolerances[ti] > db::epsilon) { - tl::info << tl::endl << "Tolerance " << tl::micron_to_string (tolerances[ti]) << ":" << tl::endl; - tl::info << tl::sprintf (line_format, "Layer", "Output", "Differences (shape count)") << tl::endl << sep; + tl::info << tl::endl << tl::to_string (tr ("Tolerance ")) << tl::micron_to_string (tolerances[ti]) << ":" << tl::endl; + tl::info << headline << tl::endl << sep; } } std::string out ("-"); std::string value; if (r->second.layer_a < 0 && ! dont_summarize_missing_layers) { - value = "(no such layer in first layout)"; + value = tl::to_string (tr ("(no such layer in first layout)")); } else if (r->second.layer_b < 0 && ! dont_summarize_missing_layers) { - value = "(no such layer in second layout)"; + value = tl::to_string (tr ("(no such layer in second layout)")); } else if (! r->second.is_empty ()) { if (r->second.layer_output >= 0 && r->second.layout) { out = r->second.layout->get_properties (r->second.layer_output).to_string (); @@ -758,15 +773,174 @@ bool run_tiled_xor (const XORData &xor_data) return result; } -bool run_deep_xor (const XORData &xor_data) -{ - db::DeepShapeStore dss; - dss.set_threads (xor_data.threads); +class XORJob + : public tl::JobBase +{ +public: + XORJob (int nworkers) + : tl::JobBase (nworkers) + { + } + + virtual tl::Worker *create_worker (); +}; + +class XORWorker + : public tl::Worker +{ +public: + XORWorker (XORJob *job); + void perform_task (tl::Task *task); + + db::DeepShapeStore &dss () + { + return m_dss; + } + +private: + XORJob *mp_job; + db::DeepShapeStore m_dss; +}; + +class XORTask + : public tl::Task +{ +public: + XORTask (const XORData *xor_data, const db::LayerProperties &layer_props, int la, int lb, double dbu) + : mp_xor_data (xor_data), m_layer_props (layer_props), m_la (la), m_lb (lb), m_dbu (dbu) + { + // .. nothing yet .. + } + + void run (XORWorker *worker) const + { + if ((m_la < 0 || m_lb < 0) && ! mp_xor_data->dont_summarize_missing_layers) { + + if (m_la < 0) { + (mp_xor_data->silent ? tl::log : tl::warn) << "Layer " << m_layer_props.to_string () << " is not present in first layout, but in second"; + } else { + (mp_xor_data->silent ? tl::log : tl::warn) << "Layer " << m_layer_props.to_string () << " is not present in second layout, but in first"; + } + + tl::MutexLocker locker (&mp_xor_data->lock); + + mp_xor_data->layers_missing += 1; + + int tol_index = 0; + for (std::vector::const_iterator t = mp_xor_data->tolerances.begin (); t != mp_xor_data->tolerances.end (); ++t) { + + ResultDescriptor &result = mp_xor_data->results->insert (std::make_pair (std::make_pair (tol_index, m_layer_props), ResultDescriptor ())).first->second; + result.layer_a = m_la; + result.layer_b = m_lb; + result.layout = mp_xor_data->output_layout; + result.top_cell = mp_xor_data->output_cell; + + ++tol_index; + + } + + } else { + + tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + m_layer_props.to_string ()); + + db::RecursiveShapeIterator ri_a, ri_b; + + if (m_la >= 0) { + ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); + } else { + ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), std::vector ()); + } + ri_a.set_for_merged_input (true); + + if (m_lb >= 0) { + ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); + } else { + ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), std::vector ()); + } + ri_b.set_for_merged_input (true); + + db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); + db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + + db::Region xor_res; + { + tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ()); + xor_res = in_a ^ in_b; + } + + int tol_index = 0; + for (std::vector::const_iterator t = mp_xor_data->tolerances.begin (); t != mp_xor_data->tolerances.end (); ++t) { + + db::LayerProperties lp = m_layer_props; + if (lp.layer >= 0) { + lp.layer += tol_index * mp_xor_data->tolerance_bump; + } + + if (*t > db::epsilon) { + tl::SelfTimer timer (tl::verbosity () >= 21, "Tolerance " + tl::to_string (*t) + " on layer " + m_layer_props.to_string ()); + xor_res.size (-db::coord_traits::rounded (0.5 * *t / m_dbu)); + xor_res.size (db::coord_traits::rounded (0.5 * *t / m_dbu)); + } + + { + tl::MutexLocker locker (&mp_xor_data->lock); + + ResultDescriptor &result = mp_xor_data->results->insert (std::make_pair (std::make_pair (tol_index, m_layer_props), ResultDescriptor ())).first->second; + result.layer_a = m_la; + result.layer_b = m_lb; + result.layout = mp_xor_data->output_layout; + result.top_cell = mp_xor_data->output_cell; + + if (mp_xor_data->output_layout) { + result.layer_output = result.layout->insert_layer (lp); + xor_res.insert_into (mp_xor_data->output_layout, mp_xor_data->output_cell, result.layer_output); + } else { + result.shape_count = xor_res.hier_count (); + } + } + + ++tol_index; + + } + + } + } + +private: + const XORData *mp_xor_data; + const db::LayerProperties &m_layer_props; + int m_la; + int m_lb; + double m_dbu; +}; + +XORWorker::XORWorker (XORJob *job) + : tl::Worker (), mp_job (job) +{ // TODO: this conflicts with the "set_for_merged_input" optimization below. // It seems not to be very effective then. Why? - dss.set_wants_all_cells (true); // saves time for less cell mapping operations + m_dss.set_wants_all_cells (true); // saves time for less cell mapping operations +} +void +XORWorker::perform_task (tl::Task *task) +{ + XORTask *xor_task = dynamic_cast (task); + if (xor_task) { + xor_task->run (this); + } +} + +tl::Worker * +XORJob::create_worker () +{ + return new XORWorker (this); +} + + +bool run_deep_xor (const XORData &xor_data) +{ double dbu = std::min (xor_data.layout_a->dbu (), xor_data.layout_b->dbu ()); if (tl::verbosity () >= 20) { @@ -779,98 +953,18 @@ bool run_deep_xor (const XORData &xor_data) xor_data.output_layout->dbu (dbu); } - bool result = true; - - int index = 1; + XORJob job (xor_data.threads); for (std::map >::const_iterator ll = xor_data.l2l_map.begin (); ll != xor_data.l2l_map.end (); ++ll) { - - if ((ll->second.first < 0 || ll->second.second < 0) && ! xor_data.dont_summarize_missing_layers) { - - if (ll->second.first < 0) { - (xor_data.silent ? tl::log : tl::warn) << "Layer " << ll->first.to_string () << " is not present in first layout, but in second"; - } else { - (xor_data.silent ? tl::log : tl::warn) << "Layer " << ll->first.to_string () << " is not present in second layout, but in first"; - } - - result = false; - - int tol_index = 0; - for (std::vector::const_iterator t = xor_data.tolerances.begin (); t != xor_data.tolerances.end (); ++t) { - - ResultDescriptor &result = xor_data.results->insert (std::make_pair (std::make_pair (tol_index, ll->first), ResultDescriptor ())).first->second; - result.layer_a = ll->second.first; - result.layer_b = ll->second.second; - result.layout = xor_data.output_layout; - result.top_cell = xor_data.output_cell; - - ++tol_index; - - } - - } else { - - tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + ll->first.to_string ()); - - db::RecursiveShapeIterator ri_a, ri_b; - - if (ll->second.first >= 0) { - ri_a = db::RecursiveShapeIterator (*xor_data.layout_a, xor_data.layout_a->cell (xor_data.cell_a), ll->second.first); - ri_a.set_for_merged_input (true); - } - - if (ll->second.second >= 0) { - ri_b = db::RecursiveShapeIterator (*xor_data.layout_b, xor_data.layout_b->cell (xor_data.cell_b), ll->second.second); - ri_b.set_for_merged_input (true); - } - - db::Region in_a (ri_a, dss, db::ICplxTrans (xor_data.layout_a->dbu () / dbu)); - db::Region in_b (ri_b, dss, db::ICplxTrans (xor_data.layout_b->dbu () / dbu)); - - db::Region xor_res; - { - tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + ll->first.to_string ()); - xor_res = in_a ^ in_b; - } - - int tol_index = 0; - for (std::vector::const_iterator t = xor_data.tolerances.begin (); t != xor_data.tolerances.end (); ++t) { - - db::LayerProperties lp = ll->first; - if (lp.layer >= 0) { - lp.layer += tol_index * xor_data.tolerance_bump; - } - - ResultDescriptor &result = xor_data.results->insert (std::make_pair (std::make_pair (tol_index, ll->first), ResultDescriptor ())).first->second; - result.layer_a = ll->second.first; - result.layer_b = ll->second.second; - result.layout = xor_data.output_layout; - result.top_cell = xor_data.output_cell; - - if (*t > db::epsilon) { - tl::SelfTimer timer (tl::verbosity () >= 21, "Tolerance " + tl::to_string (*t) + " on layer " + ll->first.to_string ()); - xor_res.size (-db::coord_traits::rounded (0.5 * *t / dbu)); - xor_res.size (db::coord_traits::rounded (0.5 * *t / dbu)); - } - - if (xor_data.output_layout) { - result.layer_output = result.layout->insert_layer (lp); - xor_res.insert_into (xor_data.output_layout, xor_data.output_cell, result.layer_output); - } else { - result.shape_count = xor_res.count (); - } - - ++tol_index; - - } - - } - - ++index; - + job.schedule (new XORTask (&xor_data, ll->first, ll->second.first, ll->second.second, dbu)); } - // Determines the output status + job.start (); + job.wait (); + + // Determine the output status + + bool result = (xor_data.layers_missing == 0); for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end () && result; ++r) { result = r->second.is_empty (); } diff --git a/src/buddies/unit_tests/bdStrmxorTests.cc b/src/buddies/unit_tests/bdStrmxorTests.cc index cde4afde0..c065bbb43 100644 --- a/src/buddies/unit_tests/bdStrmxorTests.cc +++ b/src/buddies/unit_tests/bdStrmxorTests.cc @@ -105,7 +105,7 @@ TEST(1A_Flat) "Result summary (layers without differences are not shown):\n" "\n" " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" + " ----------------------------------------------------------------\n" " 3/0 3/0 30\n" " 6/0 6/0 41\n" " 8/1 8/1 1\n" @@ -146,8 +146,8 @@ TEST(1A_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" + " Layer Output Differences (hierarchical shape count)\n" + " ----------------------------------------------------------------\n" " 3/0 3/0 3\n" " 6/0 6/0 314\n" " 8/1 8/1 1\n" @@ -177,7 +177,7 @@ TEST(1B_Flat) "Result summary (layers without differences are not shown):\n" "\n" " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" + " ----------------------------------------------------------------\n" " 3/0 - 30\n" " 6/0 - 41\n" " 8/1 - 1\n" @@ -206,9 +206,9 @@ TEST(1B_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" - " 3/0 - 30\n" + " Layer Output Differences (hierarchical shape count)\n" + " ----------------------------------------------------------------\n" + " 3/0 - 3\n" " 6/0 - 314\n" " 8/1 - 1\n" " 10/0 - (no such layer in first layout)\n" @@ -417,7 +417,7 @@ TEST(3_FlatCount) "Result summary (layers without differences are not shown):\n" "\n" " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" + " ----------------------------------------------------------------\n" " 3/0 - 31\n" " 6/0 - 217\n" " 8/1 - 168\n" @@ -483,7 +483,7 @@ TEST(3_FlatCountHeal) "Result summary (layers without differences are not shown):\n" "\n" " Layer Output Differences (shape count)\n" - " -------------------------------------------------------\n" + " ----------------------------------------------------------------\n" " 3/0 - 30\n" " 6/0 - 41\n" " 8/1 - 1\n" @@ -756,3 +756,42 @@ TEST(6_Deep) "Layer 10/0 is not present in first layout, but in second\n" ); } + +TEST(7_OptimizeDeep) +{ + tl::CaptureChannel cap; + + std::string input_a = tl::testdata (); + input_a += "/bd/strmxor_covered1.gds"; + + std::string input_b = tl::testdata (); + input_b += "/bd/strmxor_covered2.gds"; + + std::string au = tl::testdata (); + au += "/bd/strmxor_au7d.oas"; + + std::string output = this->tmp_file ("tmp.oas"); + + const char *argv[] = { "x", "-u", input_a.c_str (), input_b.c_str (), output.c_str () }; + + EXPECT_EQ (strmxor (sizeof (argv) / sizeof (argv[0]), (char **) argv), 1); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + db::compare_layouts (this, layout, au, db::NormalizationMode (db::NoNormalization | db::AsPolygons)); + EXPECT_EQ (cap.captured_text (), + "Result summary (layers without differences are not shown):\n" + "\n" + " Layer Output Differences (hierarchical shape count)\n" + " ----------------------------------------------------------------\n" + " 2/0 2/0 1\n" + " 3/0 3/0 8\n" + "\n" + ); +} diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index a4f51760f..ec8d16ef6 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -1571,6 +1571,17 @@ struct array_iterator } } + /** + * @brief Gets a value indicating whether the iterator is a synthetic one + * + * "is_singular" is true, if the iterator was default-created or with a single + * transformation. + */ + bool is_singular () const + { + return mp_base == 0; + } + private: trans_type m_trans; basic_array_iterator *mp_base; diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index b3bdd4e6a..7bc3dc712 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -354,6 +354,13 @@ CellMapping::do_create_missing_mapping (db::Layout &layout_a, const db::Layout & std::vector &new_cells = *(new_cells_ptr ? new_cells_ptr : &new_cells_int); std::vector new_cells_b; + std::vector > all_a2b; + for (std::vector::const_iterator b = cell_index_b.begin (); b != cell_index_b.end (); ++b) { + auto m = m_b2a_mapping.find (*b); + tl_assert (m != m_b2a_mapping.end ()); + all_a2b.push_back (std::make_pair (m->second, *b)); + } + std::set called_b; for (std::vector::const_iterator i = cell_index_b.begin (); i != cell_index_b.end (); ++i) { layout_b.cell (*i).collect_called_cells (called_b); @@ -368,6 +375,7 @@ CellMapping::do_create_missing_mapping (db::Layout &layout_a, const db::Layout & db::cell_index_type new_cell = layout_a.add_cell (layout_b, *b); new_cells.push_back (new_cell); new_cells_b.push_back (*b); + all_a2b.push_back (std::make_pair (new_cell, *b)); if (mapped_pairs) { mapped_pairs->push_back (std::make_pair (*b, new_cell)); @@ -378,34 +386,34 @@ CellMapping::do_create_missing_mapping (db::Layout &layout_a, const db::Layout & } } - if (! new_cells.empty ()) { + if (all_a2b.empty ()) { + return; + } - // Note: this avoids frequent cell index table rebuilds if source and target layout are identical - db::LayoutLocker locker (&layout_a); + // Note: this avoids frequent cell index table rebuilds if source and target layout are identical + db::LayoutLocker locker (&layout_a); - // Create instances for the new cells in layout A according to their instantiation in layout B - double mag = layout_b.dbu () / layout_a.dbu (); - for (size_t i = 0; i < new_cells.size (); ++i) { + // Create instances for the new cells in layout A according to their instantiation in layout B + double mag = layout_b.dbu () / layout_a.dbu (); + for (auto i = all_a2b.begin (); i != all_a2b.end (); ++i) { - const db::Cell &b = layout_b.cell (new_cells_b [i]); - for (db::Cell::parent_inst_iterator pb = b.begin_parent_insts (); ! pb.at_end (); ++pb) { + const db::Cell &b = layout_b.cell (i->second); + for (db::Cell::parent_inst_iterator pb = b.begin_parent_insts (); ! pb.at_end (); ++pb) { - if (called_b.find (pb->parent_cell_index ()) != called_b.end ()) { + if (called_b.find (pb->parent_cell_index ()) != called_b.end ()) { - db::Cell &pa = layout_a.cell (m_b2a_mapping [pb->parent_cell_index ()]); + db::Cell &pa = layout_a.cell (m_b2a_mapping [pb->parent_cell_index ()]); - db::Instance bi = pb->child_inst (); + db::Instance bi = pb->child_inst (); - db::CellInstArray bci = bi.cell_inst (); - bci.object ().cell_index (new_cells [i]); - bci.transform_into (db::ICplxTrans (mag), &layout_a.array_repository ()); - - if (bi.has_prop_id ()) { - pa.insert (db::CellInstArrayWithProperties (bci, bi.prop_id ())); - } else { - pa.insert (bci); - } + db::CellInstArray bci = bi.cell_inst (); + bci.object ().cell_index (i->first); + bci.transform_into (db::ICplxTrans (mag), &layout_a.array_repository ()); + if (bi.has_prop_id ()) { + pa.insert (db::CellInstArrayWithProperties (bci, bi.prop_id ())); + } else { + pa.insert (bci); } } diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index 7593067c9..41938c618 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -311,6 +311,11 @@ const db::EdgePair *DeepEdgePairs::nth (size_t) const throw tl::Exception (tl::to_string (tr ("Random access to edge pairs is available only for flat edge pair collections"))); } +db::properties_id_type DeepEdgePairs::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to edge pairs is available only for flat edge pair collections"))); +} + bool DeepEdgePairs::has_valid_edge_pairs () const { return false; diff --git a/src/db/db/dbDeepEdgePairs.h b/src/db/db/dbDeepEdgePairs.h index c55e7f97f..7ed036d6e 100644 --- a/src/db/db/dbDeepEdgePairs.h +++ b/src/db/db/dbDeepEdgePairs.h @@ -70,6 +70,7 @@ public: virtual Box bbox () const; virtual bool empty () const; virtual const db::EdgePair *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edge_pairs () const; virtual const db::RecursiveShapeIterator *iter () const; virtual void apply_property_translator (const db::PropertiesTranslator &pt); diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc index bd80f7ac9..3872386fe 100644 --- a/src/db/db/dbDeepEdges.cc +++ b/src/db/db/dbDeepEdges.cc @@ -438,6 +438,12 @@ DeepEdges::nth (size_t /*n*/) const throw tl::Exception (tl::to_string (tr ("Random access to edges is available only for flat edge collections"))); } +db::properties_id_type +DeepEdges::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to edges is available only for flat collections"))); +} + bool DeepEdges::has_valid_edges () const { diff --git a/src/db/db/dbDeepEdges.h b/src/db/db/dbDeepEdges.h index 6af2bf55a..b8cf9fc2e 100644 --- a/src/db/db/dbDeepEdges.h +++ b/src/db/db/dbDeepEdges.h @@ -76,6 +76,7 @@ public: virtual bool is_merged () const; virtual const db::Edge *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edges () const; virtual bool has_valid_merged_edges () const; diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index a86e2ef4a..aaa6e5fb3 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -1054,12 +1054,7 @@ DeepRegion::xor_with (const Region &other, db::PropertyConstraint property_const { const DeepRegion *other_deep = dynamic_cast (other.delegate ()); - if (empty ()) { - - // Nothing to do - return other.delegate ()->clone (); - - } else if (other.empty ()) { + if (other.empty ()) { // Nothing to do return clone (); @@ -1068,6 +1063,18 @@ DeepRegion::xor_with (const Region &other, db::PropertyConstraint property_const return AsIfFlatRegion::xor_with (other, property_constraint); + } else if (empty ()) { + + // Nothing to do, but to maintain the normal behavior, we have to map the other + // input to our layout if neccessary + if (&other_deep->deep_layer ().layout () == &deep_layer ().layout ()) { + return other.delegate ()->clone (); + } else { + std::unique_ptr other_deep_mapped (dynamic_cast (clone ())); + other_deep_mapped->deep_layer ().add_from (other_deep->deep_layer ()); + return other_deep_mapped.release (); + } + } else if (other_deep->deep_layer () == deep_layer () && pc_skip (property_constraint)) { return new DeepRegion (deep_layer ().derived ()); diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index e272e25ca..d15bb3601 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -332,6 +332,12 @@ const db::Text *DeepTexts::nth (size_t) const throw tl::Exception (tl::to_string (tr ("Random access to texts is available only for flat text collections"))); } +db::properties_id_type +DeepTexts::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to texts is available only for flat collections"))); +} + bool DeepTexts::has_valid_texts () const { return false; diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 7425df7cd..0c9a6eabb 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -71,6 +71,7 @@ public: virtual Box bbox () const; virtual bool empty () const; virtual const db::Text *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_texts () const; virtual const db::RecursiveShapeIterator *iter () const; virtual void apply_property_translator (const db::PropertiesTranslator &pt); diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index 271777473..0cffe8960 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -678,7 +678,7 @@ public: /** * @brief Returns the nth edge pair * - * This operation is available only for flat regions - i.e. such for which + * This operation is available only for flat edge pair collections - i.e. such for which * "has_valid_edge_pairs" is true. */ const db::EdgePair *nth (size_t n) const @@ -686,6 +686,17 @@ public: return mp_delegate->nth (n); } + /** + * @brief Returns the nth edge pair's property ID + * + * This operation is available only for flat edge pair collections - i.e. such for which + * "has_valid_edge_pairs" is true. + */ + db::properties_id_type nth_prop_id (size_t n) const + { + return mp_delegate->nth_prop_id (n); + } + /** * @brief Forces flattening of the edge pair collection * diff --git a/src/db/db/dbEdgePairsDelegate.h b/src/db/db/dbEdgePairsDelegate.h index d8dd18849..d4a1efd97 100644 --- a/src/db/db/dbEdgePairsDelegate.h +++ b/src/db/db/dbEdgePairsDelegate.h @@ -273,6 +273,7 @@ public: virtual EdgePairsDelegate *in (const EdgePairs &other, bool invert) const = 0; virtual const db::EdgePair *nth (size_t n) const = 0; + virtual db::properties_id_type nth_prop_id (size_t n) const = 0; virtual bool has_valid_edge_pairs () const = 0; virtual const db::RecursiveShapeIterator *iter () const = 0; diff --git a/src/db/db/dbEdgeProcessor.cc b/src/db/db/dbEdgeProcessor.cc index fd3ffb113..c26b5569f 100644 --- a/src/db/db/dbEdgeProcessor.cc +++ b/src/db/db/dbEdgeProcessor.cc @@ -1321,7 +1321,7 @@ struct edge_xmin_at_yinterval_double_compare { if (edge_xmax (a) < edge_xmin (b)) { return true; - } else if (edge_xmin (a) >= edge_xmax (b)) { + } else if (edge_xmin (a) > edge_xmax (b)) { return false; } else { C xa = edge_xmin_at_yinterval_double (a, m_y1, m_y2); diff --git a/src/db/db/dbEdges.h b/src/db/db/dbEdges.h index 6b507048a..31bdc35af 100644 --- a/src/db/db/dbEdges.h +++ b/src/db/db/dbEdges.h @@ -1440,13 +1440,24 @@ public: /** * @brief Returns the nth edge * - * This operation is available only for flat regions - i.e. such for which "has_valid_edges" is true. + * This operation is available only for flat edge collections - i.e. such for which "has_valid_edges" is true. */ const db::Edge *nth (size_t n) const { return mp_delegate->nth (n); } + /** + * @brief Returns the nth edge's property ID + * + * This operation is available only for flat edge collections - i.e. such for which + * "has_valid_edges" is true. + */ + db::properties_id_type nth_prop_id (size_t n) const + { + return mp_delegate->nth_prop_id (n); + } + /** * @brief Forces flattening of the edge collection * diff --git a/src/db/db/dbEdgesDelegate.h b/src/db/db/dbEdgesDelegate.h index 496b86931..91aea92ee 100644 --- a/src/db/db/dbEdgesDelegate.h +++ b/src/db/db/dbEdgesDelegate.h @@ -280,6 +280,7 @@ public: virtual std::pair in_and_out (const Edges &) const = 0; virtual const db::Edge *nth (size_t n) const = 0; + virtual db::properties_id_type nth_prop_id (size_t n) const = 0; virtual bool has_valid_edges () const = 0; virtual bool has_valid_merged_edges () const = 0; diff --git a/src/db/db/dbEdgesUtils.cc b/src/db/db/dbEdgesUtils.cc index 15dcb5a1b..68db12206 100644 --- a/src/db/db/dbEdgesUtils.cc +++ b/src/db/db/dbEdgesUtils.cc @@ -250,8 +250,8 @@ EdgeAngleChecker::EdgeAngleChecker (double angle_start, bool include_angle_start include_angle_start = true; } - m_t_start = db::CplxTrans(1.0, angle_start, false, db::DVector ()); - m_t_end = db::CplxTrans(1.0, angle_end, false, db::DVector ()); + m_t_start = db::ICplxTrans (1.0, angle_start, false, db::Vector ()); + m_t_end = db::ICplxTrans (1.0, angle_end, false, db::Vector ()); m_include_start = include_angle_start; m_include_end = include_angle_end; @@ -266,10 +266,10 @@ EdgeAngleChecker::EdgeAngleChecker (double angle_start, bool include_angle_start bool EdgeAngleChecker::check (const db::Vector &a, const db::Vector &b) const { - db::DVector vout (b); + db::Vector vout (b); - db::DVector v1 = m_t_start * a; - db::DVector v2 = m_t_end * a; + db::Vector v1 = m_t_start * a; + db::Vector v2 = m_t_end * a; int vps1 = db::vprod_sign (v1, vout); int vps2 = db::vprod_sign (v2, vout); @@ -306,12 +306,14 @@ EdgeOrientationFilter::EdgeOrientationFilter (double a, bool inverse, bool absol bool EdgeOrientationFilter::selected (const db::Edge &edge, db::properties_id_type) const { + db::Vector en = db::Vector (std::max (edge.dx_abs (), edge.dy_abs ()), 0); + // NOTE: this edge normalization confines the angle to a range between (-90 .. 90] (-90 excluded). // A horizontal edge has 0 degree, a vertical one has 90 degree. if (edge.dx () < 0 || (edge.dx () == 0 && edge.dy () < 0)) { - return m_checker (db::Vector (edge.ortho_length (), 0), -edge.d ()); + return m_checker (en, -edge.d ()); } else { - return m_checker (db::Vector (edge.ortho_length (), 0), edge.d ()); + return m_checker (en, edge.d ()); } } @@ -363,7 +365,7 @@ SpecialEdgeOrientationFilter::selected (const db::Edge &edge, properties_id_type } db::Vector en, ev; - en = db::Vector (edge.ortho_length (), 0); + en = db::Vector (std::max (edge.dx_abs (), edge.dy_abs ()), 0); // NOTE: this edge normalization confines the angle to a range between (-90 .. 90] (-90 excluded). // A horizontal edge has 0 degree, a vertical one has 90 degree. diff --git a/src/db/db/dbEdgesUtils.h b/src/db/db/dbEdgesUtils.h index d0082806d..769687b01 100644 --- a/src/db/db/dbEdgesUtils.h +++ b/src/db/db/dbEdgesUtils.h @@ -155,7 +155,7 @@ public: } private: - db::CplxTrans m_t_start, m_t_end; + db::ICplxTrans m_t_start, m_t_end; bool m_include_start, m_include_end; bool m_big_angle, m_all; bool m_inverse, m_absolute; diff --git a/src/db/db/dbEmptyEdgePairs.h b/src/db/db/dbEmptyEdgePairs.h index f63cfd1a8..92650b923 100644 --- a/src/db/db/dbEmptyEdgePairs.h +++ b/src/db/db/dbEmptyEdgePairs.h @@ -89,6 +89,7 @@ public: virtual EdgePairsDelegate *in (const EdgePairs &, bool) const { return new EmptyEdgePairs (); } virtual const db::EdgePair *nth (size_t) const { tl_assert (false); } + virtual db::properties_id_type nth_prop_id (size_t) const { tl_assert (false); } virtual bool has_valid_edge_pairs () const { return true; } virtual const db::RecursiveShapeIterator *iter () const { return 0; } diff --git a/src/db/db/dbEmptyEdges.h b/src/db/db/dbEmptyEdges.h index 84032b448..bf45753cd 100644 --- a/src/db/db/dbEmptyEdges.h +++ b/src/db/db/dbEmptyEdges.h @@ -119,6 +119,7 @@ public: virtual std::pair in_and_out (const Edges &) const { return std::make_pair (new EmptyEdges (), new EmptyEdges ()); } virtual const db::Edge *nth (size_t) const { tl_assert (false); } + virtual db::properties_id_type nth_prop_id (size_t) const { tl_assert (false); } virtual bool has_valid_edges () const { return true; } virtual bool has_valid_merged_edges () const { return true; } diff --git a/src/db/db/dbEmptyTexts.h b/src/db/db/dbEmptyTexts.h index 37cdd6725..202a72399 100644 --- a/src/db/db/dbEmptyTexts.h +++ b/src/db/db/dbEmptyTexts.h @@ -71,6 +71,7 @@ public: virtual TextsDelegate *in (const Texts &, bool) const { return new EmptyTexts (); } virtual const db::Text *nth (size_t) const { tl_assert (false); } + virtual db::properties_id_type nth_prop_id (size_t) const { tl_assert (false); } virtual bool has_valid_texts () const { return true; } virtual const db::RecursiveShapeIterator *iter () const { return 0; } diff --git a/src/db/db/dbFlatEdgePairs.cc b/src/db/db/dbFlatEdgePairs.cc index cb554a3ea..a42bce475 100644 --- a/src/db/db/dbFlatEdgePairs.cc +++ b/src/db/db/dbFlatEdgePairs.cc @@ -173,7 +173,46 @@ EdgePairsDelegate *FlatEdgePairs::add_in_place (const EdgePairs &other) const db::EdgePair *FlatEdgePairs::nth (size_t n) const { - return n < mp_edge_pairs->size () ? &mp_edge_pairs->get_layer ().begin () [n] : 0; + // NOTE: this assumes that we iterate over non-property edge pairs first and then over edges with properties + + if (n >= mp_edge_pairs->size ()) { + return 0; + } + + const db::layer &l = mp_edge_pairs->get_layer (); + if (n < l.size ()) { + return &l.begin () [n]; + } + n -= l.size (); + + const db::layer &lp = mp_edge_pairs->get_layer (); + if (n < lp.size ()) { + return &lp.begin () [n]; + } + + return 0; +} + +db::properties_id_type FlatEdgePairs::nth_prop_id (size_t n) const +{ + // NOTE: this assumes that we iterate over non-property edge pairs first and then over edges with properties + + if (n >= mp_edge_pairs->size ()) { + return 0; + } + + const db::layer &l = mp_edge_pairs->get_layer (); + if (n < l.size ()) { + return 0; + } + n -= l.size (); + + const db::layer &lp = mp_edge_pairs->get_layer (); + if (n < lp.size ()) { + return lp.begin () [n].properties_id (); + } + + return 0; } bool FlatEdgePairs::has_valid_edge_pairs () const diff --git a/src/db/db/dbFlatEdgePairs.h b/src/db/db/dbFlatEdgePairs.h index dc38dc088..ce203c4aa 100644 --- a/src/db/db/dbFlatEdgePairs.h +++ b/src/db/db/dbFlatEdgePairs.h @@ -75,6 +75,7 @@ public: virtual EdgePairsDelegate *add (const EdgePairs &other) const; virtual const db::EdgePair *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edge_pairs () const; virtual const db::RecursiveShapeIterator *iter () const; diff --git a/src/db/db/dbFlatEdges.cc b/src/db/db/dbFlatEdges.cc index 44299bc8c..27e323cae 100644 --- a/src/db/db/dbFlatEdges.cc +++ b/src/db/db/dbFlatEdges.cc @@ -361,7 +361,46 @@ EdgesDelegate *FlatEdges::add_in_place (const Edges &other) const db::Edge *FlatEdges::nth (size_t n) const { - return n < mp_edges->size () ? &mp_edges->get_layer ().begin () [n] : 0; + // NOTE: this assumes that we iterate over non-property edges first and then over edges with properties + + if (n >= mp_edges->size ()) { + return 0; + } + + const db::layer &l = mp_edges->get_layer (); + if (n < l.size ()) { + return &l.begin () [n]; + } + n -= l.size (); + + const db::layer &lp = mp_edges->get_layer (); + if (n < lp.size ()) { + return &lp.begin () [n]; + } + + return 0; +} + +db::properties_id_type FlatEdges::nth_prop_id (size_t n) const +{ + // NOTE: this assumes that we iterate over non-property polygons first and then over polygons with properties + + if (n >= mp_edges->size ()) { + return 0; + } + + const db::layer &l = mp_edges->get_layer (); + if (n < l.size ()) { + return 0; + } + n -= l.size (); + + const db::layer &lp = mp_edges->get_layer (); + if (n < lp.size ()) { + return lp.begin () [n].properties_id (); + } + + return 0; } bool FlatEdges::has_valid_edges () const diff --git a/src/db/db/dbFlatEdges.h b/src/db/db/dbFlatEdges.h index f5464be30..551c33ce4 100644 --- a/src/db/db/dbFlatEdges.h +++ b/src/db/db/dbFlatEdges.h @@ -89,6 +89,7 @@ public: virtual EdgesDelegate *add (const Edges &other) const; virtual const db::Edge *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edges () const; virtual bool has_valid_merged_edges () const; diff --git a/src/db/db/dbFlatTexts.cc b/src/db/db/dbFlatTexts.cc index e2e0e54a2..ef6dda20d 100644 --- a/src/db/db/dbFlatTexts.cc +++ b/src/db/db/dbFlatTexts.cc @@ -171,7 +171,46 @@ TextsDelegate *FlatTexts::add_in_place (const Texts &other) const db::Text *FlatTexts::nth (size_t n) const { - return n < mp_texts->size () ? &mp_texts->get_layer ().begin () [n] : 0; + // NOTE: this assumes that we iterate over non-property texts first and then over texts with properties + + if (n >= mp_texts->size ()) { + return 0; + } + + const db::layer &l = mp_texts->get_layer (); + if (n < l.size ()) { + return &l.begin () [n]; + } + n -= l.size (); + + const db::layer &lp = mp_texts->get_layer (); + if (n < lp.size ()) { + return &lp.begin () [n]; + } + + return 0; +} + +db::properties_id_type FlatTexts::nth_prop_id (size_t n) const +{ + // NOTE: this assumes that we iterate over non-property polygons first and then over polygons with properties + + if (n >= mp_texts->size ()) { + return 0; + } + + const db::layer &l = mp_texts->get_layer (); + if (n < l.size ()) { + return 0; + } + n -= l.size (); + + const db::layer &lp = mp_texts->get_layer (); + if (n < lp.size ()) { + return lp.begin () [n].properties_id (); + } + + return 0; } bool FlatTexts::has_valid_texts () const diff --git a/src/db/db/dbFlatTexts.h b/src/db/db/dbFlatTexts.h index f7e5ff16c..553dd58d5 100644 --- a/src/db/db/dbFlatTexts.h +++ b/src/db/db/dbFlatTexts.h @@ -76,6 +76,7 @@ public: virtual TextsDelegate *add (const Texts &other) const; virtual const db::Text *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_texts () const; virtual const db::RecursiveShapeIterator *iter () const; diff --git a/src/db/db/dbHierarchyBuilder.cc b/src/db/db/dbHierarchyBuilder.cc index ac3a31116..b54354858 100644 --- a/src/db/db/dbHierarchyBuilder.cc +++ b/src/db/db/dbHierarchyBuilder.cc @@ -152,15 +152,17 @@ static std::pair > compute_clip_variant (const db::Box & } HierarchyBuilder::HierarchyBuilder (db::Layout *target, unsigned int target_layer, const db::ICplxTrans &trans, HierarchyBuilderShapeReceiver *pipe) - : mp_target (target), m_initial_pass (true), m_cm_new_entry (false), m_target_layer (target_layer), m_wants_all_cells (false), m_trans (trans) + : mp_target (target), m_target_layer (target_layer), m_wants_all_cells (false), m_trans (trans) { set_shape_receiver (pipe); + reset (); } HierarchyBuilder::HierarchyBuilder (db::Layout *target, const db::ICplxTrans &trans, HierarchyBuilderShapeReceiver *pipe) - : mp_target (target), m_initial_pass (true), m_cm_new_entry (false), m_target_layer (0), m_wants_all_cells (false), m_trans (trans) + : mp_target (target), m_target_layer (0), m_wants_all_cells (false), m_trans (trans) { set_shape_receiver (pipe); + reset (); } HierarchyBuilder::~HierarchyBuilder () @@ -178,6 +180,8 @@ void HierarchyBuilder::reset () { m_initial_pass = true; + m_cm_new_entry = false; + mp_initial_cell = 0; m_cells_to_be_filled.clear (); @@ -186,7 +190,6 @@ HierarchyBuilder::reset () m_cells_seen.clear (); m_cell_stack.clear (); m_cm_entry = null_iterator; - m_cm_new_entry = false; } const std::pair & @@ -351,7 +354,6 @@ HierarchyBuilder::make_cell_variant (const HierarchyBuilder::CellMapKey &key, co if (! key.clip_region.empty ()) { cn += "$CLIP_VAR"; description += "CLIP"; - } if (key.inactive) { cn += "$DIS"; @@ -383,7 +385,7 @@ HierarchyBuilder::make_cell_variant (const HierarchyBuilder::CellMapKey &key, co } HierarchyBuilder::new_inst_mode -HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all) +HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all, bool skip_shapes) { if (all) { @@ -402,7 +404,7 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn } // To see the cell once, use NI_single. If we did see the cell already, skip the whole instance array. - return (m_cells_seen.find (key) == m_cells_seen.end ()) ? NI_single : NI_skip; + return (! skip_shapes && m_cells_seen.find (key) == m_cells_seen.end ()) ? NI_single : NI_skip; } else { @@ -413,7 +415,7 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn } bool -HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all) +HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes) { if (all) { @@ -441,7 +443,7 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db: } } - return (m_cells_seen.find (key) == m_cells_seen.end ()); + return ! skip_shapes && m_cells_seen.find (key) == m_cells_seen.end (); } } diff --git a/src/db/db/dbHierarchyBuilder.h b/src/db/db/dbHierarchyBuilder.h index 31a125f44..d1d056718 100644 --- a/src/db/db/dbHierarchyBuilder.h +++ b/src/db/db/dbHierarchyBuilder.h @@ -294,8 +294,8 @@ public: virtual void end (const RecursiveShapeIterator *iter); virtual void enter_cell (const RecursiveShapeIterator *iter, const db::Cell *cell, const db::Box ®ion, const box_tree_type *complex_region); virtual void leave_cell (const RecursiveShapeIterator *iter, const db::Cell *cell); - virtual new_inst_mode new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all); - virtual bool new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all); + virtual new_inst_mode new_inst (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes); + virtual bool new_inst_member (const RecursiveShapeIterator *iter, const db::CellInstArray &inst, const ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes); virtual void shape (const RecursiveShapeIterator *iter, const db::Shape &shape, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region); /** diff --git a/src/db/db/dbLayoutVsSchematic.cc b/src/db/db/dbLayoutVsSchematic.cc index 449f49d69..a31cfe333 100644 --- a/src/db/db/dbLayoutVsSchematic.cc +++ b/src/db/db/dbLayoutVsSchematic.cc @@ -25,6 +25,7 @@ #include "dbLayoutVsSchematic.h" #include "dbLayoutVsSchematicWriter.h" #include "dbLayoutVsSchematicReader.h" +#include "dbNetlistCompareUtils.h" namespace db { @@ -86,6 +87,53 @@ db::NetlistCrossReference *LayoutVsSchematic::make_cross_ref () return mp_cross_ref.get (); } +bool +LayoutVsSchematic::flag_missing_ports (const db::Circuit *circuit) +{ + if (! mp_cross_ref.get ()) { + return false; + } + + db::NetlistCrossReference::PerCircuitData *pcd = const_cast (mp_cross_ref->per_circuit_data_for (std::make_pair (circuit, circuit))); + if (! pcd) { + return false; + } + + bool error = false; + bool any = false; + + for (auto n = pcd->nets.begin (); n != pcd->nets.end (); ++n) { + + const db::Net *schem = n->pair.second; + const db::Net *layout = n->pair.first; + + if (schem && layout && schem->begin_pins () != schem->end_pins ()) { + + any = true; + + if (db::name_compare (layout, schem) != 0) { + + std::string msg = tl::sprintf (tl::to_string (tr ("Port mismatch '%s' vs. '%s'")), layout->expanded_name (), schem->expanded_name ()); + db::LogEntryData entry (db::Error, msg); + pcd->log_entries.push_back (entry); + + error = true; + + } + } + + } + + if (! any) { + + std::string msg = tl::to_string (tr ("No pins found in circuit during 'flag_missing_ports'")); + db::LogEntryData entry (db::Warning, msg); + pcd->log_entries.push_back (entry); + + } + + return !error; +} void LayoutVsSchematic::save (const std::string &path, bool short_format) { diff --git a/src/db/db/dbLayoutVsSchematic.h b/src/db/db/dbLayoutVsSchematic.h index 1e27ea954..6c2b242e8 100644 --- a/src/db/db/dbLayoutVsSchematic.h +++ b/src/db/db/dbLayoutVsSchematic.h @@ -144,6 +144,22 @@ public: */ db::NetlistCrossReference *make_cross_ref (); + /** + * @brief Checks top-level port names + * + * This method checks that every top-level pin has a corresponding + * schematic pin and their names are equivalent. This verifies that + * all pins are labelles properly. + * + * Errors are placed in the log file. The return values indicates + * if there are no errors. + * + * The circuit is either a schematic or layout circuit. + * + * See issue #2055. + */ + bool flag_missing_ports (const db::Circuit *circuit); + /** * @brief Saves the database to the given path * diff --git a/src/db/db/dbLocalOperationUtils.cc b/src/db/db/dbLocalOperationUtils.cc index c338dbe6d..51d98807d 100644 --- a/src/db/db/dbLocalOperationUtils.cc +++ b/src/db/db/dbLocalOperationUtils.cc @@ -39,7 +39,9 @@ PolygonRefToShapesGenerator::PolygonRefToShapesGenerator (db::Layout *layout, db void PolygonRefToShapesGenerator::put (const db::Polygon &polygon) { tl::MutexLocker locker (&mp_layout->lock ()); - if (m_prop_id != 0) { + if (polygon.is_empty ()) { + // ignore empty polygons + } else if (m_prop_id != 0) { mp_shapes->insert (db::PolygonRefWithProperties (db::PolygonRef (polygon, mp_layout->shape_repository ()), m_prop_id)); } else { mp_shapes->insert (db::PolygonRef (polygon, mp_layout->shape_repository ())); @@ -58,7 +60,9 @@ PolygonSplitter::PolygonSplitter (PolygonSink &sink, double max_area_ratio, size void PolygonSplitter::put (const db::Polygon &poly) { - if (db::suggest_split_polygon (poly, m_max_vertex_count, m_max_area_ratio)) { + if (poly.is_empty ()) { + // ignore empty polygons + } else if (db::suggest_split_polygon (poly, m_max_vertex_count, m_max_area_ratio)) { std::vector split_polygons; db::split_polygon (poly, split_polygons); diff --git a/src/db/db/dbOriginalLayerEdgePairs.cc b/src/db/db/dbOriginalLayerEdgePairs.cc index 301208ebc..5038a1885 100644 --- a/src/db/db/dbOriginalLayerEdgePairs.cc +++ b/src/db/db/dbOriginalLayerEdgePairs.cc @@ -190,6 +190,12 @@ OriginalLayerEdgePairs::nth (size_t) const throw tl::Exception (tl::to_string (tr ("Random access to edge pairs is available only for flat collections"))); } +db::properties_id_type +OriginalLayerEdgePairs::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to edge pairs is available only for flat collections"))); +} + bool OriginalLayerEdgePairs::has_valid_edge_pairs () const { diff --git a/src/db/db/dbOriginalLayerEdgePairs.h b/src/db/db/dbOriginalLayerEdgePairs.h index bcecf1f7c..41000da51 100644 --- a/src/db/db/dbOriginalLayerEdgePairs.h +++ b/src/db/db/dbOriginalLayerEdgePairs.h @@ -53,6 +53,7 @@ public: virtual bool empty () const; virtual const db::EdgePair *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edge_pairs () const; virtual const db::RecursiveShapeIterator *iter () const; diff --git a/src/db/db/dbOriginalLayerEdges.cc b/src/db/db/dbOriginalLayerEdges.cc index a01314308..3484b926e 100644 --- a/src/db/db/dbOriginalLayerEdges.cc +++ b/src/db/db/dbOriginalLayerEdges.cc @@ -233,6 +233,12 @@ OriginalLayerEdges::nth (size_t) const throw tl::Exception (tl::to_string (tr ("Random access to edges is available only for flat collections"))); } +db::properties_id_type +OriginalLayerEdges::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to edges is available only for flat collections"))); +} + bool OriginalLayerEdges::has_valid_edges () const { diff --git a/src/db/db/dbOriginalLayerEdges.h b/src/db/db/dbOriginalLayerEdges.h index 89d6fc647..53254e852 100644 --- a/src/db/db/dbOriginalLayerEdges.h +++ b/src/db/db/dbOriginalLayerEdges.h @@ -58,6 +58,7 @@ public: virtual bool is_merged () const; virtual const db::Edge *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_edges () const; virtual bool has_valid_merged_edges () const; diff --git a/src/db/db/dbOriginalLayerTexts.cc b/src/db/db/dbOriginalLayerTexts.cc index e4f53eb27..af633ebe5 100644 --- a/src/db/db/dbOriginalLayerTexts.cc +++ b/src/db/db/dbOriginalLayerTexts.cc @@ -190,6 +190,12 @@ OriginalLayerTexts::nth (size_t) const throw tl::Exception (tl::to_string (tr ("Random access to texts is available only for flat collections"))); } +db::properties_id_type +OriginalLayerTexts::nth_prop_id (size_t) const +{ + throw tl::Exception (tl::to_string (tr ("Random access to texts is available only for flat collections"))); +} + bool OriginalLayerTexts::has_valid_texts () const { diff --git a/src/db/db/dbOriginalLayerTexts.h b/src/db/db/dbOriginalLayerTexts.h index d294e2803..81971bd44 100644 --- a/src/db/db/dbOriginalLayerTexts.h +++ b/src/db/db/dbOriginalLayerTexts.h @@ -53,6 +53,7 @@ public: virtual bool empty () const; virtual const db::Text *nth (size_t n) const; + virtual db::properties_id_type nth_prop_id (size_t n) const; virtual bool has_valid_texts () const; virtual const db::RecursiveShapeIterator *iter () const; diff --git a/src/db/db/dbPolygon.h b/src/db/db/dbPolygon.h index 134412e8e..b56a36271 100644 --- a/src/db/db/dbPolygon.h +++ b/src/db/db/dbPolygon.h @@ -1771,6 +1771,14 @@ public: return true; } + /** + * @brief Returns a value indicating that the polygon is an empty one + */ + bool is_empty () const + { + return m_ctrs.size () == size_t (1) && m_ctrs[0].size () == 0; + } + /** * @brief Returns the number of points in the polygon */ @@ -1879,6 +1887,7 @@ public: for (typename contour_list_type::iterator h = m_ctrs.begin (); h != m_ctrs.end (); ++h) { h->transform (db::unit_trans (), true /*compress*/, remove_reflected); } + m_bbox = m_ctrs [0].bbox (); return *this; } @@ -2804,6 +2813,7 @@ public: { // compress the polygon by employing the transform method m_hull.transform (db::unit_trans (), true, remove_reflected); + m_bbox = m_hull.bbox (); return *this; } @@ -3022,6 +3032,14 @@ public: return m_hull.is_halfmanhattan (); } + /** + * @brief Returns a value indicating that the polygon is an empty one + */ + bool is_empty () const + { + return m_hull.size () == 0; + } + /** * @brief The number of holes * diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index c92cd4bc4..b3965f9ce 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -74,6 +74,8 @@ RecursiveShapeIterator &RecursiveShapeIterator::operator= (const RecursiveShapeI m_layer = d.m_layer; mp_cell = d.mp_cell; m_current_layer = d.m_current_layer; + m_skip_shapes = d.m_skip_shapes; + m_skip_shapes_member = d.m_skip_shapes_member; m_shape = d.m_shape; m_trans = d.m_trans; m_global_trans = d.m_global_trans; @@ -85,6 +87,7 @@ RecursiveShapeIterator &RecursiveShapeIterator::operator= (const RecursiveShapeI m_local_complex_region_stack = d.m_local_complex_region_stack; m_local_region_stack = d.m_local_region_stack; m_skip_shapes_stack = d.m_skip_shapes_stack; + m_skip_shapes_member_stack = d.m_skip_shapes_member_stack; m_needs_reinit = d.m_needs_reinit; m_inst_quad_id = d.m_inst_quad_id; m_inst_quad_id_stack = d.m_inst_quad_id_stack; @@ -469,6 +472,8 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const m_local_region_stack.push_back (m_global_trans.inverted () * m_region); m_skip_shapes_stack.clear (); m_skip_shapes_stack.push_back (false); + m_skip_shapes_member_stack.clear (); + m_skip_shapes_member_stack.push_back (false); m_local_complex_region_stack.clear (); if (mp_complex_region.get ()) { @@ -814,39 +819,6 @@ RecursiveShapeIterator::next_shape (RecursiveShapeReceiver *receiver) const bool RecursiveShapeIterator::down (RecursiveShapeReceiver *receiver) const { - bool skip_shapes = false; - - if (m_for_merged_input && ! m_skip_shapes_stack.back () && (! m_has_layers || m_layers.size () == 1)) { - - // Try some optimization: if the instance we're looking at is entirely covered - // by a rectangle (other objects are too expensive to check), then we skip it - // - // We check 10 shapes max. - - box_type inst_bx; - if (m_inst->size () == 1) { - inst_bx = m_inst->bbox (m_box_convert); - } else { - inst_bx = m_inst->complex_trans (*m_inst_array) * m_box_convert (m_inst->cell_inst ().object ()); - } - - unsigned int l = m_has_layers ? m_layers.front () : m_layer; - auto si = cell ()->shapes (l).begin_overlapping (inst_bx, m_shape_flags, mp_shape_prop_sel, m_shape_inv_prop_sel); - size_t nmax = 10; - while (! si.at_end () && nmax-- > 0) { - if (inst_bx.inside (si->rectangle ())) { - skip_shapes = true; - break; - } - ++si; - } - - } - - if (skip_shapes && (! receiver || ! receiver->wants_all_cells ())) { - return false; - } - tl_assert (mp_layout); m_trans_stack.push_back (m_trans); @@ -874,7 +846,8 @@ RecursiveShapeIterator::down (RecursiveShapeReceiver *receiver) const } m_local_region_stack.push_back (new_region); - m_skip_shapes_stack.push_back (m_skip_shapes_stack.back () || skip_shapes); + m_skip_shapes_stack.push_back (m_skip_shapes); + m_skip_shapes_member_stack.push_back (m_skip_shapes_member); if (! m_local_complex_region_stack.empty ()) { @@ -948,6 +921,8 @@ RecursiveShapeIterator::pop () const m_inst = m_inst_iterators.back (); m_inst_array = m_inst_array_iterators.back (); m_inst_quad_id = m_inst_quad_id_stack.back (); + m_skip_shapes = m_skip_shapes_stack.back (); + m_skip_shapes_member = m_skip_shapes_member_stack.back (); m_inst_iterators.pop_back (); m_inst_array_iterators.pop_back (); m_inst_quad_id_stack.pop_back (); @@ -958,6 +933,7 @@ RecursiveShapeIterator::pop () const m_cells.pop_back (); m_local_region_stack.pop_back (); m_skip_shapes_stack.pop_back (); + m_skip_shapes_member_stack.pop_back (); if (! m_local_complex_region_stack.empty ()) { m_local_complex_region_stack.pop_back (); } @@ -982,7 +958,7 @@ RecursiveShapeIterator::start_shapes () const void RecursiveShapeIterator::new_layer () const { - if (m_skip_shapes_stack.back () || int (m_trans_stack.size ()) < m_min_depth || int (m_trans_stack.size ()) > m_max_depth) { + if (skip_shapes () || int (m_trans_stack.size ()) < m_min_depth || int (m_trans_stack.size ()) > m_max_depth) { m_shape = shape_iterator (); } else if (! m_overlapping) { m_shape = cell ()->shapes (m_layer).begin_touching (m_local_region_stack.back (), m_shape_flags, mp_shape_prop_sel, m_shape_inv_prop_sel); @@ -1029,6 +1005,32 @@ RecursiveShapeIterator::new_cell (RecursiveShapeReceiver *receiver) const new_inst (receiver); } +bool +RecursiveShapeIterator::instance_is_covered (const box_type &inst_bx, unsigned int layer) const +{ + // Try some optimization: if the instance we're looking at is entirely covered + // by a rectangle (other objects are too expensive to check), then we skip it + // + // We check 10 shapes max. + + auto si = cell ()->shapes (layer).begin_overlapping (inst_bx, m_shape_flags, mp_shape_prop_sel, m_shape_inv_prop_sel); + size_t nmax = 10; + while (! si.at_end () && nmax-- > 0) { + if (inst_bx.inside (si->rectangle ())) { + return true; + } + ++si; + } + + return false; +} + +bool +RecursiveShapeIterator::skip_shapes () const +{ + return m_skip_shapes_stack.back () || m_skip_shapes_member_stack.back (); +} + void RecursiveShapeIterator::new_inst (RecursiveShapeReceiver *receiver) const { @@ -1055,9 +1057,19 @@ RecursiveShapeIterator::new_inst (RecursiveShapeReceiver *receiver) const all_of_instance = m_local_complex_region_stack.empty (); } + m_skip_shapes = skip_shapes (); + m_skip_shapes_member = false; + + if (m_for_merged_input && ! m_skip_shapes && (! m_has_layers || m_layers.size () == 1)) { + box_type inst_bx = m_inst->bbox (m_box_convert); + m_skip_shapes = instance_is_covered (inst_bx, m_has_layers ? m_layers.front () : m_layer); + } + RecursiveShapeReceiver::new_inst_mode ni = RecursiveShapeReceiver::NI_all; if (receiver) { - ni = receiver->new_inst (this, m_inst->cell_inst (), always_apply (), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), all_of_instance); + ni = receiver->new_inst (this, m_inst->cell_inst (), always_apply (), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), all_of_instance, m_skip_shapes); + } else if (m_skip_shapes) { + ni = RecursiveShapeReceiver::NI_skip; } if (ni == RecursiveShapeReceiver::NI_skip) { @@ -1095,7 +1107,7 @@ RecursiveShapeIterator::new_inst_member (RecursiveShapeReceiver *receiver) const // skip instance array members not part of the complex region while (! m_inst_array.at_end ()) { - db::Box ia_box = m_inst->complex_trans (*m_inst_array) * cell_bbox (m_inst->cell_index ()); + box_type ia_box = m_inst->complex_trans (*m_inst_array) * cell_bbox (m_inst->cell_index ()); if (! is_outside_complex_region (ia_box)) { break; } else { @@ -1105,12 +1117,31 @@ RecursiveShapeIterator::new_inst_member (RecursiveShapeReceiver *receiver) const } - while (! m_inst_array.at_end () && receiver) { - if (receiver->new_inst_member (this, m_inst->cell_inst (), always_apply (), m_inst->complex_trans (*m_inst_array), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), is_all_of_instance ())) { - break; - } else { - ++m_inst_array; + m_skip_shapes_member = false; + + while (! m_inst_array.at_end () && (m_for_merged_input || receiver)) { + + m_skip_shapes_member = m_skip_shapes; + if (m_for_merged_input && ! m_inst_array.is_singular () && ! m_skip_shapes && (! m_has_layers || m_layers.size () == 1)) { + + box_type ia_box = m_inst->complex_trans (*m_inst_array) * cell_bbox (m_inst->cell_index ()); + m_skip_shapes_member = instance_is_covered (ia_box, m_has_layers ? m_layers.front () : m_layer); + } + + bool skip = false; + if (receiver) { + skip = ! receiver->new_inst_member (this, m_inst->cell_inst (), always_apply (), m_inst->complex_trans (*m_inst_array), m_local_region_stack.back (), m_local_complex_region_stack.empty () ? 0 : &m_local_complex_region_stack.back (), is_all_of_instance (), m_skip_shapes_member); + } else { + skip = m_skip_shapes_member; + } + + if (skip) { + ++m_inst_array; + } else { + break; + } + } } diff --git a/src/db/db/dbRecursiveShapeIterator.h b/src/db/db/dbRecursiveShapeIterator.h index 26ee32b7e..b0e685686 100644 --- a/src/db/db/dbRecursiveShapeIterator.h +++ b/src/db/db/dbRecursiveShapeIterator.h @@ -868,6 +868,7 @@ private: mutable unsigned int m_layer; mutable const cell_type *mp_cell; mutable size_t m_current_layer; + mutable bool m_skip_shapes, m_skip_shapes_member; mutable shape_iterator m_shape; mutable cplx_trans_type m_trans; mutable std::vector m_trans_stack; @@ -876,7 +877,7 @@ private: mutable std::vector m_cells; mutable std::vector m_local_complex_region_stack; mutable std::vector m_local_region_stack; - mutable std::vector m_skip_shapes_stack; + mutable std::vector m_skip_shapes_stack, m_skip_shapes_member_stack; mutable bool m_needs_reinit; mutable size_t m_inst_quad_id; mutable std::vector m_inst_quad_id_stack; @@ -899,6 +900,8 @@ private: bool down (RecursiveShapeReceiver *receiver) const; void pop () const; + bool instance_is_covered (const box_type &inst_bx, unsigned int layer) const; + bool skip_shapes () const; bool is_outside_complex_region (const db::Box &box) const; void set_inactive (bool a) const @@ -1013,8 +1016,11 @@ public: * - NI_all: iterate all members through "new_inst_member" * - NI_single: iterate a single member (the first one) * - NI_skip: skips the whole array (not a single instance is iterated) + * + * The "skip_shapes" parameter indicates that the instance is visited with the + * purpose of skipping all shapes. This is used to implement the "for_merged" optimization. */ - virtual new_inst_mode new_inst (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return NI_all; } + virtual new_inst_mode new_inst (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/, bool /*skip_shapes*/) { return NI_all; } /** * @brief Enters a new array member of the instance @@ -1026,8 +1032,11 @@ public: * "all" is true, if an instance array is iterated in "all" mode (see new_inst). * * If this method returns false, this array instance (but not the whole array) is skipped and the cell is not entered. + * + * The "skip_shapes" parameter indicates that the instance member is visited with the + * purpose of skipping all shapes. This is used to implement the "for_merged" optimization. */ - virtual bool new_inst_member (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) { return true; } + virtual bool new_inst_member (const RecursiveShapeIterator * /*iter*/, const db::CellInstArray & /*inst*/, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/, bool /*skip_shapes*/) { return true; } /** * @brief Delivers a shape diff --git a/src/db/db/dbRegionLocalOperations.cc b/src/db/db/dbRegionLocalOperations.cc index 48d974563..64849b712 100644 --- a/src/db/db/dbRegionLocalOperations.cc +++ b/src/db/db/dbRegionLocalOperations.cc @@ -1618,18 +1618,34 @@ bool_and_or_not_local_operation::do_compute_local (db::Layout *layou } } + db::polygon_ref_generator pr (layout, result); + db::PolygonSplitter splitter (pr, proc->area_ratio (), proc->max_vertex_count ()); + for (auto i = interactions.begin (); i != interactions.end (); ++i) { const TR &subject = interactions.subject_shape (i->first); if (others.find (subject) != others.end ()) { + + // shortcut (and: keep, not: drop) + // Note that we still normalize and split the polygon, so we get a uniform + // behavior. if (m_is_and) { - result.insert (subject); + db::Polygon poly; + subject.instantiate (poly); + splitter.put (poly); } + } else if (i->second.empty ()) { + // shortcut (not: keep, and: drop) + // Note that we still normalize and split the polygon, so we get a uniform + // behavior. if (! m_is_and) { - result.insert (subject); + db::Polygon poly; + subject.instantiate (poly); + splitter.put (poly); } + } else { for (auto e = subject.begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p1); @@ -1649,8 +1665,6 @@ bool_and_or_not_local_operation::do_compute_local (db::Layout *layou } db::BooleanOp op (m_is_and ? db::BooleanOp::And : db::BooleanOp::ANotB); - db::polygon_ref_generator pr (layout, result); - db::PolygonSplitter splitter (pr, proc->area_ratio (), proc->max_vertex_count ()); db::PolygonGenerator pg (splitter, true, true); ep.set_base_verbosity (50); ep.process (pg, op); diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index a948f3366..51b05a5d6 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -495,7 +495,7 @@ public: /** * @brief Returns the nth text * - * This operation is available only for flat regions - i.e. such for which + * This operation is available only for flat text collections - i.e. such for which * "has_valid_texts" is true. */ const db::Text *nth (size_t n) const @@ -503,6 +503,17 @@ public: return mp_delegate->nth (n); } + /** + * @brief Returns the nth text's property ID + * + * This operation is available only for flat text collections - i.e. such for which + * "has_valid_texts" is true. + */ + db::properties_id_type nth_prop_id (size_t n) const + { + return mp_delegate->nth_prop_id (n); + } + /** * @brief Forces flattening of the text collection * diff --git a/src/db/db/dbTextsDelegate.h b/src/db/db/dbTextsDelegate.h index 4808e9679..895b1808e 100644 --- a/src/db/db/dbTextsDelegate.h +++ b/src/db/db/dbTextsDelegate.h @@ -109,6 +109,7 @@ public: virtual TextsDelegate *in (const Texts &other, bool invert) const = 0; virtual const db::Text *nth (size_t n) const = 0; + virtual db::properties_id_type nth_prop_id (size_t n) const = 0; virtual bool has_valid_texts () const = 0; virtual const db::RecursiveShapeIterator *iter () const = 0; diff --git a/src/db/db/gsiDeclDbEdgePair.cc b/src/db/db/gsiDeclDbEdgePair.cc index 8c34e5507..0ba631bde 100644 --- a/src/db/db/gsiDeclDbEdgePair.cc +++ b/src/db/db/gsiDeclDbEdgePair.cc @@ -370,7 +370,7 @@ static db::DEdgePairWithProperties *new_dedge_pair_with_properties2 (const db::D return new db::DEdgePairWithProperties (edge_pair, db::properties_id (db::PropertiesSet (properties.begin (), properties.end ()))); } -Class decl_DEdgePairWithProperties (decl_EdgePair, "db", "DEdgePairWithProperties", +Class decl_DEdgePairWithProperties (decl_DEdgePair, "db", "DEdgePairWithProperties", gsi::properties_support_methods () + constructor ("new", &new_dedge_pair_with_properties, gsi::arg ("edge_pair"), gsi::arg ("properties_id", db::properties_id_type (0)), "@brief Creates a new object from a property-less object and a properties ID." diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 2a50e6747..903aba802 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -783,6 +783,16 @@ static std::vector split_with_area2 (const db::EdgePairs *r, db:: return as_2edge_pairs_vector (r->split_filter (f)); } +static tl::Variant nth (const db::EdgePairs *edge_pairs, size_t n) +{ + const db::EdgePair *ep = edge_pairs->nth (n); + if (! ep) { + return tl::Variant (); + } else { + return tl::Variant (db::EdgePairWithProperties (*ep, edge_pairs->nth_prop_id (n))); + } +} + static db::generic_shape_iterator begin_edge_pairs (const db::EdgePairs *edge_pairs) { return db::generic_shape_iterator (db::make_wp_iter (edge_pairs->delegate ()->begin ())); @@ -1855,13 +1865,15 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "\n" "Starting with version 0.30, the iterator delivers EdgePairWithProperties objects." ) + - method ("[]", &db::EdgePairs::nth, gsi::arg ("n"), + method_ext ("[]", &nth, gsi::arg ("n"), "@brief Returns the nth edge pair\n" "\n" "This method returns nil if the index is out of range. It is available for flat edge pairs only - i.e. " "those for which \\has_valid_edge_pairs? is true. Use \\flatten to explicitly flatten an edge pair collection.\n" "\n" - "The \\each iterator is the more general approach to access the edge pairs." + "The \\each iterator is the more general approach to access the edge pairs.\n" + "\n" + "Since version 0.30.1, this method returns a \\EdgePairWithProperties object." ) + method ("flatten", &db::EdgePairs::flatten, "@brief Explicitly flattens an edge pair collection\n" diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index d4da722f4..0383d14ac 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -848,6 +848,16 @@ static std::vector split_interacting_with_region (const db::Edges *r, return as_2edges_vector (r->selected_interacting_differential (other, min_count, max_count)); } +static tl::Variant nth (const db::Edges *edges, size_t n) +{ + const db::Edge *e = edges->nth (n); + if (! e) { + return tl::Variant (); + } else { + return tl::Variant (db::EdgeWithProperties (*e, edges->nth_prop_id (n))); + } +} + static db::generic_shape_iterator begin_edges (const db::Edges *edges) { return db::generic_shape_iterator (db::make_wp_iter (edges->delegate ()->begin ())); @@ -2439,14 +2449,16 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "This method has been introduced in version 0.25." "Starting with version 0.30, the iterator delivers an EdgeWithProperties object." ) + - method ("[]", &db::Edges::nth, gsi::arg ("n"), + method_ext ("[]", &nth, gsi::arg ("n"), "@brief Returns the nth edge of the collection\n" "\n" "This method returns nil if the index is out of range. It is available for flat edge collections only - i.e. " "those for which \\has_valid_edges? is true. Use \\flatten to explicitly flatten an edge collection.\n" "This method returns the raw edge (not merged edges, even if merged semantics is enabled).\n" "\n" - "The \\each iterator is the more general approach to access the edges." + "The \\each iterator is the more general approach to access the edges.\n" + "\n" + "Since version 0.30.1, this method returns an \\EdgeWithProperties object." ) + method ("flatten", &db::Edges::flatten, "@brief Explicitly flattens an edge collection\n" diff --git a/src/db/db/gsiDeclDbLayoutVsSchematic.cc b/src/db/db/gsiDeclDbLayoutVsSchematic.cc index f17694359..87e1d6346 100644 --- a/src/db/db/gsiDeclDbLayoutVsSchematic.cc +++ b/src/db/db/gsiDeclDbLayoutVsSchematic.cc @@ -110,6 +110,18 @@ Class decl_dbLayoutVsSchematic (decl_dbLayoutToNetlist, " "\n" "See \\NetlistCrossReference for more details.\n" ) + + gsi::method ("flag_missing_ports", &db::LayoutVsSchematic::flag_missing_ports, gsi::arg ("circuit"), + "@brief Flags inconsistent port labels in the given circuit\n" + "@param circuit Either a layout or schematic circuit\n" + "@return True, if no errors were found\n" + "This method will check all schematic nets which have pins and tests whether the corresponding layout net " + "has the same name. This way, it is checked if the pins are properly labelled.\n" + "\n" + "The method must be called after the compare step was successful. Error messages will be added " + "to the log entries. If an error occured or the cross reference is not value, 'false' is returned.\n" + "\n" + "This method was introduced in version 0.30.2." + ) + gsi::method_ext ("write_l2n", &save_l2n, gsi::arg ("path"), gsi::arg ("short_format", false), "@brief Writes the \\LayoutToNetlist part of the object to a file.\n" "This method employs the native format of KLayout.\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 11b80b936..53da87933 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1379,6 +1379,16 @@ rasterize1 (const db::Region *region, const db::Point &origin, const db::Vector return rasterize2 (region, origin, pixel_size, pixel_size, nx, ny); } +static tl::Variant nth (const db::Region *region, size_t n) +{ + const db::Polygon *poly = region->nth (n); + if (! poly) { + return tl::Variant (); + } else { + return tl::Variant (db::PolygonWithProperties (*poly, region->nth_prop_id (n))); + } +} + static db::generic_shape_iterator begin_region (const db::Region *region) { return db::generic_shape_iterator (db::make_wp_iter (region->delegate ()->begin ())); @@ -4103,14 +4113,16 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "This returns the raw polygons if merged semantics is disabled or the merged ones if merged semantics is enabled.\n" "Starting with version 0.30, the iterator delivers a RegionWithProperties object." ) + - method ("[]", &db::Region::nth, gsi::arg ("n"), + method_ext ("[]", &nth, gsi::arg ("n"), "@brief Returns the nth polygon of the region\n" "\n" "This method returns nil if the index is out of range. It is available for flat regions only - i.e. " "those for which \\has_valid_polygons? is true. Use \\flatten to explicitly flatten a region.\n" "This method returns the raw polygon (not merged polygons, even if merged semantics is enabled).\n" "\n" - "The \\each iterator is the more general approach to access the polygons." + "The \\each iterator is the more general approach to access the polygons.\n" + "\n" + "Since version 0.30.1, this method returns a \\PolygonWithProperties object." ) + method ("flatten", &db::Region::flatten, "@brief Explicitly flattens a region\n" diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 66e1d34a6..17a9563c9 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -455,6 +455,16 @@ static db::Region pull_interacting (const db::Texts *r, const db::Region &other) return out; } +static tl::Variant nth (const db::Texts *texts, size_t n) +{ + const db::Text *t = texts->nth (n); + if (! t) { + return tl::Variant (); + } else { + return tl::Variant (db::TextWithProperties (*t, texts->nth_prop_id (n))); + } +} + static db::generic_shape_iterator begin_texts (const db::Texts *texts) { return db::generic_shape_iterator (db::make_wp_iter (texts->delegate ()->begin ())); @@ -846,13 +856,15 @@ Class decl_Texts (decl_dbShapeCollection, "db", "Texts", "\n" "Starting with version 0.30, the iterator delivers TextWithProperties objects." ) + - method ("[]", &db::Texts::nth, gsi::arg ("n"), + method_ext ("[]", &nth, gsi::arg ("n"), "@brief Returns the nth text\n" "\n" "This method returns nil if the index is out of range. It is available for flat texts only - i.e. " "those for which \\has_valid_texts? is true. Use \\flatten to explicitly flatten an text collection.\n" "\n" - "The \\each iterator is the more general approach to access the texts." + "The \\each iterator is the more general approach to access the texts.\n" + "\n" + "Since version 0.30.1, this method returns a \\TextWithProperties object." ) + method ("flatten", &db::Texts::flatten, "@brief Explicitly flattens an text collection\n" diff --git a/src/db/unit_tests/dbCellMappingTests.cc b/src/db/unit_tests/dbCellMappingTests.cc index b20c8ec66..f6ec6a767 100644 --- a/src/db/unit_tests/dbCellMappingTests.cc +++ b/src/db/unit_tests/dbCellMappingTests.cc @@ -485,8 +485,41 @@ TEST(7) cib.push_back (b1.cell_index ()); cib.push_back (b2.cell_index ()); cm.create_multi_mapping_full (h, cib, *g, cia); - EXPECT_EQ (m2s (cm, *g, h), "a0->b0;a1->b1;a2->b2;a3->a3;a4->a4;a5->a5"); + EXPECT_EQ (m2s (cm, h, *g), "b0->a0;b1->a1;b2->a2;a3->a3;a4->a4;a5->a5"); EXPECT_EQ (l2s (h), "b0#0:;b1#1:cell_index=3 r0 0,0,cell_index=4 r0 0,0;b2#2:cell_index=4 r0 0,0;a3#3:cell_index=4 r0 0,0,cell_index=5 r0 0,0;a4#4:;a5#5:"); } +// Issue #2014 +TEST(8) +{ + std::unique_ptr g (new db::Layout ()); + db::Cell &a (g->cell (g->add_cell ("a"))); + db::Cell &b (g->cell (g->add_cell ("b"))); + db::Cell &b1 (g->cell (g->add_cell ("b1"))); + db::Cell &b2 (g->cell (g->add_cell ("b2"))); + db::Cell &c (g->cell (g->add_cell ("c"))); + + b.insert (db::CellInstArray (db::CellInst (a.cell_index ()), db::Trans ())); + b.insert (db::CellInstArray (db::CellInst (c.cell_index ()), db::Trans ())); + b.insert (db::CellInstArray (db::CellInst (b1.cell_index ()), db::Trans ())); + b.insert (db::CellInstArray (db::CellInst (b2.cell_index ()), db::Trans ())); + + db::Layout h; + db::Cell &ha (h.cell (h.add_cell ("a"))); + db::Cell &hb (h.cell (h.add_cell ("b"))); + db::Cell &hc (h.cell (h.add_cell ("c"))); + + db::CellMapping cm; + std::vector cib, cia; + cia.push_back (a.cell_index ()); + cia.push_back (b.cell_index ()); + cia.push_back (c.cell_index ()); + cib.push_back (ha.cell_index ()); + cib.push_back (hb.cell_index ()); + cib.push_back (hc.cell_index ()); + cm.create_multi_mapping_full (h, cib, *g, cia); + EXPECT_EQ (m2s (cm, h, *g), "a->a;b->b;b1->b1;b2->b2;c->c"); + + EXPECT_EQ (l2s (h), "a#0:;b#1:cell_index=0 r0 0,0,cell_index=2 r0 0,0,cell_index=3 r0 0,0,cell_index=4 r0 0,0;c#2:;b1#3:;b2#4:"); +} diff --git a/src/db/unit_tests/dbEdgePairsTests.cc b/src/db/unit_tests/dbEdgePairsTests.cc index 1f6153c3d..1a8680fdd 100644 --- a/src/db/unit_tests/dbEdgePairsTests.cc +++ b/src/db/unit_tests/dbEdgePairsTests.cc @@ -279,3 +279,24 @@ TEST(6_add_with_properties) EXPECT_EQ ((ro1 + rf2).to_string (), "(10,20;-20,60)/(10,30;-20,70){net=>17};(-10,20;20,60)/(-10,30;20,70){net=>17}"); } +TEST(7_properties) +{ + db::PropertiesSet ps; + + ps.insert (tl::Variant ("id"), 1); + db::properties_id_type pid1 = db::properties_id (ps); + + db::EdgePairs edge_pairs; + db::Edge e1 (db::Point (0, 0), db::Point (10, 20)); + db::Edge e2 (db::Point (1, 2), db::Point (11, 22)); + edge_pairs.insert (db::EdgePairWithProperties (db::EdgePair (e1, e2), pid1)); + edge_pairs.insert (db::EdgePair (e1, e2)); + + EXPECT_EQ (edge_pairs.nth (0)->to_string (), "(0,0;10,20)/(1,2;11,22)"); + EXPECT_EQ (edge_pairs.nth (1)->to_string (), "(0,0;10,20)/(1,2;11,22)"); + EXPECT_EQ (edge_pairs.nth (2) == 0, true); + + EXPECT_EQ (edge_pairs.nth_prop_id (0), db::properties_id_type (0)); + EXPECT_EQ (edge_pairs.nth_prop_id (1), pid1); +} + diff --git a/src/db/unit_tests/dbEdgesTests.cc b/src/db/unit_tests/dbEdgesTests.cc index 15a4b562b..2c20bf043 100644 --- a/src/db/unit_tests/dbEdgesTests.cc +++ b/src/db/unit_tests/dbEdgesTests.cc @@ -261,6 +261,37 @@ TEST(4) db::EdgeOrientationFilter f1 (89.0, true, 90.0, false, false, false); EXPECT_EQ (r.filtered (f1).to_string (), ""); } + + // issue-2060 + { + db::EdgeOrientationFilter f1 (90.0, true, false); + db::EdgeOrientationFilter f2 (90.0, false, false); + db::EdgeOrientationFilter f45 (45.0, false, false); + db::SpecialEdgeOrientationFilter fs (db::SpecialEdgeOrientationFilter::Diagonal, false); + + db::Edges rr; + rr.insert (db::Box (db::Point (0, 0), db::Point (1000, 4000000))); + EXPECT_EQ (db::compare (rr.filtered (f1), "(1000,0;0,0);(0,4000000;1000,4000000)"), true); + + rr.clear (); + rr.insert (db::Box (db::Point (0, 0), db::Point (1000, 400000))); + EXPECT_EQ (db::compare (rr.filtered (f1), "(1000,0;0,0);(0,400000;1000,400000)"), true); + + rr.clear (); + rr.insert (db::Box (db::Point (0, -1000000000), db::Point (1000, 1000000000))); + EXPECT_EQ (db::compare (rr.filtered (f1), "(1000,-1000000000;0,-1000000000);(0,1000000000;1000,1000000000)"), true); + + rr.clear (); + rr.insert (db::Box (db::Point (0, -1000000000), db::Point (1000, 1000000000))); + EXPECT_EQ (db::compare (rr.filtered (f2), "(0,-1000000000;0,1000000000);(1000,1000000000;1000,-1000000000)"), true); + + EXPECT_EQ (f2.selected (db::Edge (db::Point (0, -1000000000), db::Point (0, 1000000000)), size_t (0)), true); + EXPECT_EQ (f2.selected (db::Edge (db::Point (0, -1000000000), db::Point (1, 1000000000)), size_t (0)), false); + EXPECT_EQ (f45.selected (db::Edge (db::Point (-1000000000, -1000000000), db::Point (1000000000, 1000000000)), size_t (0)), true); + EXPECT_EQ (f45.selected (db::Edge (db::Point (-1000000000, -1000000000), db::Point (1000000000, 1000000001)), size_t (0)), false); + EXPECT_EQ (fs.selected (db::Edge (db::Point (-1000000000, -1000000000), db::Point (1000000000, 1000000000)), size_t (0)), true); + EXPECT_EQ (fs.selected (db::Edge (db::Point (-1000000000, -1000000000), db::Point (1000000000, 1000000001)), size_t (0)), false); + } } TEST(5) @@ -1443,6 +1474,25 @@ TEST(32_add_with_properties) EXPECT_EQ ((ro1 + rf2).to_string (), "(10,20;40,60){net=>17};(-10,20;20,60){net=>17}"); } +TEST(33_properties) +{ + db::PropertiesSet ps; + + ps.insert (tl::Variant ("id"), 1); + db::properties_id_type pid1 = db::properties_id (ps); + + db::Edges edges; + edges.insert (db::EdgeWithProperties (db::Edge (db::Point (0, 0), db::Point (10, 20)), pid1)); + edges.insert (db::Edge (db::Point (0, 0), db::Point (10, 20))); + + EXPECT_EQ (edges.nth (0)->to_string (), "(0,0;10,20)"); + EXPECT_EQ (edges.nth (1)->to_string (), "(0,0;10,20)"); + EXPECT_EQ (edges.nth (2) == 0, true); + + EXPECT_EQ (edges.nth_prop_id (0), db::properties_id_type (0)); + EXPECT_EQ (edges.nth_prop_id (1), pid1); +} + // GitHub issue #72 (Edges/Region NOT issue) TEST(100) { diff --git a/src/db/unit_tests/dbPolygonTests.cc b/src/db/unit_tests/dbPolygonTests.cc index c9e277d77..dc518c7bd 100644 --- a/src/db/unit_tests/dbPolygonTests.cc +++ b/src/db/unit_tests/dbPolygonTests.cc @@ -69,6 +69,7 @@ TEST(1) EXPECT_EQ (empty == p, true); EXPECT_EQ (p.is_box (), false); + EXPECT_EQ (p.is_empty (), true); std::vector c1, c2, c3; c1.push_back (db::Point (0, 0)); @@ -76,6 +77,7 @@ TEST(1) c1.push_back (db::Point (100, 1000)); c1.push_back (db::Point (100, 0)); p.assign_hull (c1.begin (), c1.end ()); + EXPECT_EQ (p.is_empty (), false); b = p.box (); EXPECT_EQ (p.holes (), size_t (0)); EXPECT_EQ (p.area (), 1000*100); @@ -1404,3 +1406,30 @@ TEST(28) db::Polygon b (db::Box (-1000000000, -1000000000, 1000000000, 1000000000)); EXPECT_EQ (b.perimeter (), 8000000000.0); } + +TEST(29) +{ + // Degenerated boxes and compress + + db::Polygon b (db::Box (10, 20, 10, 20)); + EXPECT_EQ (b.is_empty (), false); + EXPECT_EQ (b == db::Polygon (), false); + EXPECT_EQ (b.to_string (), "(10,20;10,20;10,20;10,20)"); + EXPECT_EQ (double (b.area ()), 0.0); + + b.compress (true); + + EXPECT_EQ (b.is_empty (), true); + EXPECT_EQ (b == db::Polygon (), true); + + db::SimplePolygon sb (db::Box (10, 20, 10, 20)); + EXPECT_EQ (sb.is_empty (), false); + EXPECT_EQ (sb == db::SimplePolygon (), false); + EXPECT_EQ (sb.to_string (), "(10,20;10,20;10,20;10,20)"); + EXPECT_EQ (double (sb.area ()), 0.0); + + sb.compress (true); + + EXPECT_EQ (sb.is_empty (), true); + EXPECT_EQ (sb == db::SimplePolygon (), true); +} diff --git a/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc b/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc index 7c386f011..c7205fbf3 100644 --- a/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc +++ b/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc @@ -756,15 +756,25 @@ namespace { : public db::RecursiveShapeReceiver { public: - FlatPusher (std::set *boxes) : mp_boxes (boxes) { } + FlatPusher (std::set *boxes = 0) : mp_boxes (boxes ? boxes : &m_boxes) { } void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/) { mp_boxes->insert (trans * shape.bbox ()); } + std::string to_string () const + { + std::vector s; + for (auto i = mp_boxes->begin (); i != mp_boxes->end (); ++i) { + s.push_back (i->to_string ()); + } + return tl::join (s.begin (), s.end (), ";"); + } + private: std::set *mp_boxes; + std::set m_boxes; }; } @@ -1038,7 +1048,7 @@ public: m_text += std::string ("leave_cell(") + iter->layout ()->cell_name (cell->cell_index ()) + ")\n"; } - virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all) + virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all, bool /*skip_shapes*/) { m_text += std::string ("new_inst(") + iter->layout ()->cell_name (inst.object ().cell_index ()); if (all) { @@ -1048,7 +1058,7 @@ public: return NI_all; } - virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all) + virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool all, bool /*skip_shapes*/) { m_text += std::string ("new_inst_member(") + iter->layout ()->cell_name (inst.object ().cell_index ()) + "," + tl::to_string (always_apply * trans); if (all) { @@ -1073,9 +1083,9 @@ class ReceiverRejectingACellInstanceArray public: ReceiverRejectingACellInstanceArray (db::cell_index_type rejected) : m_rejected (rejected) { } - virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all) + virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes) { - LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all); + LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all, skip_shapes); return inst.object ().cell_index () != m_rejected ? NI_all : NI_skip; } @@ -1089,9 +1099,9 @@ class ReceiverRejectingACellInstanceArrayExceptOne public: ReceiverRejectingACellInstanceArrayExceptOne (db::cell_index_type rejected) : m_rejected (rejected) { } - virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all) + virtual new_inst_mode new_inst (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes) { - LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all); + LoggingReceiver::new_inst (iter, inst, always_apply, region, complex_region, all, skip_shapes); return inst.object ().cell_index () != m_rejected ? NI_all : NI_single; } @@ -1105,9 +1115,9 @@ class ReceiverRejectingACellInstance public: ReceiverRejectingACellInstance (db::cell_index_type rejected, const db::ICplxTrans &trans_rejected) : m_rejected (rejected), m_trans_rejected (trans_rejected) { } - virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all) + virtual bool new_inst_member (const db::RecursiveShapeIterator *iter, const db::CellInstArray &inst, const db::ICplxTrans &always_apply, const db::ICplxTrans &trans, const db::Box ®ion, const box_tree_type *complex_region, bool all, bool skip_shapes) { - LoggingReceiver::new_inst_member (iter, inst, always_apply, trans, region, complex_region, all); + LoggingReceiver::new_inst_member (iter, inst, always_apply, trans, region, complex_region, all, skip_shapes); return inst.object ().cell_index () != m_rejected || trans != m_trans_rejected; } @@ -1586,49 +1596,174 @@ TEST(12_ForMerged) db::RecursiveShapeIterator i1 (*g, c0, 0); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$2](0,100;1000,1200)/[$3](100,0;1100,1100)/[$4](1200,0;2200,1100)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); i1.set_for_merged_input (true); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); std::vector lv; lv.push_back (0); i1 = db::RecursiveShapeIterator (*g, c0, lv); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$2](0,100;1000,1200)/[$3](100,0;1100,1100)/[$4](1200,0;2200,1100)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); i1.set_for_merged_input (true); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); lv.push_back (1); // empty, but kills "for merged" optimization i1 = db::RecursiveShapeIterator (*g, c0, lv); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$2](0,100;1000,1200)/[$3](100,0;1100,1100)/[$4](1200,0;2200,1100)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000);(100,0;1100,1100);(1200,0;2200,1100);(0,100;1000,1200)"); + } i1.set_for_merged_input (true); x = collect(i1, *g); // no longer optimized EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$2](0,100;1000,1200)/[$3](100,0;1100,1100)/[$4](1200,0;2200,1100)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000);(100,0;1100,1100);(1200,0;2200,1100);(0,100;1000,1200)"); + } i1 = db::RecursiveShapeIterator (*g, c0, 0, db::Box (-100, 0, 100, 50)); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$3](100,0;1100,1100)/[$4](-1200,0;-100,1000)"); + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000);(100,0;1100,1100)"); + } + i1.set_for_merged_input (true); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000)"); + } i1 = db::RecursiveShapeIterator (*g, c0, 0, db::Box (-101, 0, 100, 50)); i1.set_overlapping (true); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000)"); + } i1.set_for_merged_input (true); x = collect(i1, *g); EXPECT_EQ (x, "[$1](0,0;3000,2000)/[$4](-1200,0;-100,1000)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(-1200,0;-100,1000);(0,0;3000,2000)"); + } } +TEST(12b_ForMerged) +{ + std::unique_ptr g (new db::Layout ()); + g->insert_layer (0); + g->insert_layer (1); + db::Cell &c0 (g->cell (g->add_cell ())); + db::Cell &c1 (g->cell (g->add_cell ())); + + db::Box b (0, 100, 1000, 1200); + c0.shapes (0).insert (db::Box (0, 0, 3000, 2200)); + c1.shapes (0).insert (b); + + db::Trans tt; + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), tt)); + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), db::Trans (db::Vector (2000, 1000)), db::Vector (0, 2000), db::Vector (2000, 0), 2l, 2l)); + + std::string x; + + db::RecursiveShapeIterator i1 (*g, c0, 0); + x = collect(i1, *g); + EXPECT_EQ (x, "[$1](0,0;3000,2200)/[$2](0,100;1000,1200)/[$2](2000,1100;3000,2200)/[$2](2000,3100;3000,4200)/[$2](4000,1100;5000,2200)/[$2](4000,3100;5000,4200)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(0,0;3000,2200);(0,100;1000,1200);(2000,1100;3000,2200);(4000,1100;5000,2200);(2000,3100;3000,4200);(4000,3100;5000,4200)"); + } + + i1.set_for_merged_input (true); + x = collect(i1, *g); + EXPECT_EQ (x, "[$1](0,0;3000,2200)/[$2](2000,3100;3000,4200)/[$2](4000,1100;5000,2200)/[$2](4000,3100;5000,4200)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + { + FlatPusher f; + i1.reset (); + i1.push (&f); + EXPECT_EQ (f.to_string (), "(0,0;3000,2200);(4000,1100;5000,2200);(2000,3100;3000,4200);(4000,3100;5000,4200)"); + } + + i1.set_for_merged_input (false); + x = collect(i1, *g); + EXPECT_EQ (x, "[$1](0,0;3000,2200)/[$2](0,100;1000,1200)/[$2](2000,1100;3000,2200)/[$2](2000,3100;3000,4200)/[$2](4000,1100;5000,2200)/[$2](4000,3100;5000,4200)"); + EXPECT_EQ (collect_with_copy(i1, *g), x); + + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), db::Trans (db::Vector (0, 2000)))); + + db::RecursiveShapeIterator i2 (*g, c0, 0); + + x = collect(i2, *g); + EXPECT_EQ (x, "[$1](0,0;3000,2200)/[$2](0,100;1000,1200)/[$2](2000,1100;3000,2200)/[$2](2000,3100;3000,4200)/[$2](4000,1100;5000,2200)/[$2](4000,3100;5000,4200)/[$2](0,2100;1000,3200)"); + EXPECT_EQ (collect_with_copy(i2, *g), x); + + { + FlatPusher f; + i2.reset (); + i2.push (&f); + EXPECT_EQ (f.to_string (), "(0,0;3000,2200);(0,100;1000,1200);(2000,1100;3000,2200);(4000,1100;5000,2200);(0,2100;1000,3200);(2000,3100;3000,4200);(4000,3100;5000,4200)"); + } + + i2.set_for_merged_input (true); + x = collect(i2, *g); + EXPECT_EQ (x, "[$1](0,0;3000,2200)/[$2](2000,3100;3000,4200)/[$2](4000,1100;5000,2200)/[$2](4000,3100;5000,4200)/[$2](0,2100;1000,3200)"); + EXPECT_EQ (collect_with_copy(i2, *g), x); + + { + FlatPusher f; + i2.reset (); + i2.push (&f); + EXPECT_EQ (f.to_string (), "(0,0;3000,2200);(4000,1100;5000,2200);(0,2100;1000,3200);(2000,3100;3000,4200);(4000,3100;5000,4200)"); + } +} TEST(13_ForMergedPerformance) { diff --git a/src/db/unit_tests/dbTextsTests.cc b/src/db/unit_tests/dbTextsTests.cc index 2fc3e652b..e7666647e 100644 --- a/src/db/unit_tests/dbTextsTests.cc +++ b/src/db/unit_tests/dbTextsTests.cc @@ -307,3 +307,22 @@ TEST(9_polygons) EXPECT_EQ (r.to_string (), "(9,19;9,21;11,21;11,19){17=>ABC};(-11,-21;-11,-19;-9,-19;-9,-21){17=>XZY}"); } +TEST(10_properties) +{ + db::PropertiesSet ps; + + ps.insert (tl::Variant ("id"), 1); + db::properties_id_type pid1 = db::properties_id (ps); + + db::Texts texts; + texts.insert (db::TextWithProperties (db::Text ("string", db::Trans ()), pid1)); + texts.insert (db::Text ("abc", db::Trans ())); + + EXPECT_EQ (texts.nth (0)->to_string (), "('abc',r0 0,0)"); + EXPECT_EQ (texts.nth (1)->to_string (), "('string',r0 0,0)"); + EXPECT_EQ (texts.nth (2) == 0, true); + + EXPECT_EQ (texts.nth_prop_id (0), db::properties_id_type (0)); + EXPECT_EQ (texts.nth_prop_id (1), pid1); +} + diff --git a/src/doc/doc/about/lvs_ref_global.xml b/src/doc/doc/about/lvs_ref_global.xml index ac633b4b3..d1121526c 100644 --- a/src/doc/doc/about/lvs_ref_global.xml +++ b/src/doc/doc/about/lvs_ref_global.xml @@ -82,6 +82,15 @@ See Netter#enable_parameter

See Netter#equivalent_pins for a description of that function.

+

"flag_missing_ports" - Checks if all top level ports are properly labelled

+ +

Usage:

+
    +
  • flag_missing_ports
  • +
+

+See Netter#flag_missing_ports for a description of that function. +

"ignore_parameter" - Specifies whether to ignore a parameter from a given device class for the compare

Usage:

diff --git a/src/doc/doc/about/lvs_ref_netter.xml b/src/doc/doc/about/lvs_ref_netter.xml index 463eebd69..be3e6e538 100644 --- a/src/doc/doc/about/lvs_ref_netter.xml +++ b/src/doc/doc/about/lvs_ref_netter.xml @@ -186,6 +186,27 @@ case pin names for SPICE netlists.

Use this method andwhere in the script before the compare call.

+

"flag_missing_ports" - Flags inconsistently labelled or missing ports in the current top circuit

+ +

Usage:

+
    +
  • flag_missing_ports
  • +
+

+This method must be called after "compare" was executed successfully and will +report errors if pins in the current top circuit's schematic are not labelled +correspondingly in the layout. This prevents swapping of port labels or +pads. +

+

+success = compare
+success && flag_missing_ports
+
+

+Note that in order to use this method, the top circuit from the schematic netlist +needs to have pins. This may not be always the case - for example, if the top +level circuit is not a subcircuit in a Spice netlist. +

"ignore_parameter" - Skip a specific parameter for a given device class name during device compare

Usage:

diff --git a/src/doc/doc/manual/lvs_compare.xml b/src/doc/doc/manual/lvs_compare.xml index f58319310..d4fe34697 100644 --- a/src/doc/doc/manual/lvs_compare.xml +++ b/src/doc/doc/manual/lvs_compare.xml @@ -296,6 +296,69 @@ tolerance("NMOS", "L", :absolute => 0.05, :relative => 0.01)
min_caps(1e-16)
+

Checking pin labels

+ +

+ LVS is basically name-agnostic, so except for resolving ambiguities, net names are + not considered. Topology matching has priority - if nets are not labelled + properly, LVS by default does not care. +

+ +

+ This may have adverse effects in the case of outbound connections - for example + pads. It's a fatal error to connect the chip pads incorrectly. To mitigate this + issue, the "flag_missing_ports" function is provided. +

+ +

+ You need to call this function after the compare step, i.e. +

+ +
compare
+flag_missing_ports
+ +

+ Or, if you want to quench pseudo errors, only in case of successful compare: +

+ +
success = compare
+success && flag_missing_ports
+ +

+ This function takes the schematic top circuit and investigates all + nets that are connected to a pin. It will check the name (label) of the + corresponding layout net and if names do not match, an error is written + into the log section of the LVS report. +

+ +

+ When you use this feature while working yourself bottom-up in the design, + it will make sure that all pins are properly labelled. If you use pins + in the top level circuit to describe the chip pads, this feature will make + sure that the correct nets are connected to the pads with the corresponding labels + on them. +

+ +

+ Note that it is possible to have SPICE netlists which do not have pins + at the top level circuit - e.g. if the top level circuit is not a SUBCKT. + In that case, the function will not report errors as there are not pin-carrying + nets. Only a warning is issues saying that no top level pins have been found. +

+ +

+ You can use +

+ +
schematic.make_top_level_pins
+ +

+ to create pins if none are provided. However, this method will turn every net into a pin + and force you to label every net in the top circuit then. + Hence, it is better to provide pins inside the schematic netlist. + Also note, that "make_top_level_pins" is implicitly included in "schematic.simplify". +

+

Compare and netlist hierarchy

diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index fa5cf402c..15c6356a4 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -1585,7 +1585,8 @@ module DRC self._context("report") do # finish what we got so far - _finish(false) + view = RBA::LayoutView::current + @def_output && @def_output.finish(false, view) @def_output = nil @def_output = _make_report(description, filename, cellname) diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index f88392542..c618af869 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1694,6 +1694,16 @@ TEST(93d_withAngle) run_test (_this, "93", true); } +TEST(94_texts_in_region_xor) +{ + run_test (_this, "94", false); +} + +TEST(94d_texts_in_region_xor) +{ + run_test (_this, "94", true); +} + TEST(100_edge_interaction_with_count) { run_test (_this, "100", false); diff --git a/src/edt/edt/edtInstPropertiesPage.cc b/src/edt/edt/edtInstPropertiesPage.cc index da06cb814..de27a4d90 100644 --- a/src/edt/edt/edtInstPropertiesPage.cc +++ b/src/edt/edt/edtInstPropertiesPage.cc @@ -925,7 +925,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) } void -InstPropertiesPage::apply () +InstPropertiesPage::apply (bool /*commit*/) { do_apply (true, false); } @@ -937,7 +937,7 @@ InstPropertiesPage::can_apply_to_all () const } void -InstPropertiesPage::apply_to_all (bool relative) +InstPropertiesPage::apply_to_all (bool relative, bool /*commit*/) { do_apply (false, relative); } diff --git a/src/edt/edt/edtInstPropertiesPage.h b/src/edt/edt/edtInstPropertiesPage.h index c33d7f3d6..f00a1a659 100644 --- a/src/edt/edt/edtInstPropertiesPage.h +++ b/src/edt/edt/edtInstPropertiesPage.h @@ -66,8 +66,8 @@ protected: edt::PCellParametersPage *mp_pcell_parameters; virtual bool readonly (); - virtual void apply (); - virtual void apply_to_all (bool relative); + virtual void apply (bool commit); + virtual void apply_to_all (bool relative, bool commit); virtual bool can_apply_to_all () const; void do_apply (bool current_only, bool relative); virtual ChangeApplicator *create_applicator (db::Cell &cell, const db::Instance &inst, double dbu); diff --git a/src/edt/edt/edtPCellParametersPage.cc b/src/edt/edt/edtPCellParametersPage.cc index 27ca1a261..8f27d312b 100644 --- a/src/edt/edt/edtPCellParametersPage.cc +++ b/src/edt/edt/edtPCellParametersPage.cc @@ -47,6 +47,31 @@ namespace edt { +static std::string variant_list_to_string (const tl::Variant &value) +{ + if (! value.is_list ()) { + tl::Variant v = tl::Variant::empty_list (); + v.push (value); + return v.to_parsable_string (); + } + + for (auto i = value.begin (); i != value.end (); ++i) { + if (! i->is_a_string () || std::string (i->to_string ()).find (",") != std::string::npos) { + return value.to_parsable_string (); + } + } + + // otherwise we can plainly combine the strings with "," + std::string res; + for (auto i = value.begin (); i != value.end (); ++i) { + if (i != value.begin ()) { + res += ","; + } + res += i->to_string (); + } + return res; +} + static void set_value (const db::PCellParameterDeclaration &p, QWidget *widget, const tl::Variant &value) { if (p.get_choices ().empty ()) { @@ -91,7 +116,7 @@ static void set_value (const db::PCellParameterDeclaration &p, QWidget *widget, QLineEdit *le = dynamic_cast (widget); if (le) { le->blockSignals (true); - le->setText (value.to_qstring ()); + le->setText (tl::to_qstring (variant_list_to_string (value))); le->blockSignals (false); } } @@ -905,8 +930,29 @@ PCellParametersPage::get_parameters_internal (db::ParameterStates &states, bool { QLineEdit *le = dynamic_cast (m_widgets [r]); if (le) { - std::vector values = tl::split (tl::to_string (le->text ()), ","); + + std::string s = tl::to_string (le->text ()); + + // try parsing a bracketed expression + tl::Extractor ex (s.c_str ()); + if (*ex.skip () == '(') { + tl::Variant v; + try { + ex.read (v); + ps.set_value (v); + break; + } catch (...) { + // ignore errors + } + } else if (ex.at_end ()) { + ps.set_value (tl::Variant::empty_list ()); + break; + } + + // otherwise: plain splitting at comma + std::vector values = tl::split (s, ","); ps.set_value (tl::Variant (values.begin (), values.end ())); + } } break; diff --git a/src/edt/edt/edtPropertiesPages.cc b/src/edt/edt/edtPropertiesPages.cc index 365ca1c63..26b1def30 100644 --- a/src/edt/edt/edtPropertiesPages.cc +++ b/src/edt/edt/edtPropertiesPages.cc @@ -215,7 +215,7 @@ ShapePropertiesPage::recompute_selection_ptrs (const std::vector gs = mp_service->handle_guiding_shape_changes (new_sel[index]); + std::pair gs = mp_service->handle_guiding_shape_changes (new_sel[index], commit); if (gs.first) { new_sel[index] = gs.second; @@ -350,9 +350,9 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) } void -ShapePropertiesPage::apply () +ShapePropertiesPage::apply (bool commit) { - do_apply (true, false); + do_apply (true, false, commit); } bool @@ -362,9 +362,9 @@ ShapePropertiesPage::can_apply_to_all () const } void -ShapePropertiesPage::apply_to_all (bool relative) +ShapePropertiesPage::apply_to_all (bool relative, bool commit) { - do_apply (false, relative); + do_apply (false, relative, commit); } void diff --git a/src/edt/edt/edtPropertiesPages.h b/src/edt/edt/edtPropertiesPages.h index 7d8d5b644..60d844f50 100644 --- a/src/edt/edt/edtPropertiesPages.h +++ b/src/edt/edt/edtPropertiesPages.h @@ -63,10 +63,10 @@ protected: private: virtual void update (); - virtual void apply (); - virtual void apply_to_all (bool relative); + virtual void apply (bool commit); + virtual void apply_to_all (bool relative, bool commit); virtual bool can_apply_to_all () const; - virtual void do_apply (bool current_only, bool relative); + virtual void do_apply (bool current_only, bool relative, bool commit); void recompute_selection_ptrs (const std::vector &new_sel); protected: diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index 75fbf5428..aeb4223d1 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -76,6 +76,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIter m_snap_to_objects (true), m_snap_objects_to_grid (true), m_top_level_sel (false), m_show_shapes_of_instances (true), m_max_shapes_of_instances (1000), + m_pcell_lazy_evaluation (0), m_hier_copy_mode (-1), m_indicate_secondary_selection (false), m_seq (0), @@ -391,6 +392,10 @@ Service::configure (const std::string &name, const std::string &value) tl::from_string (value, m_hier_copy_mode); service_configuration_changed (); + } else if (name == cfg_edit_pcell_lazy_eval_mode) { + + tl::from_string (value, m_pcell_lazy_evaluation); + } else { lay::EditorServiceBase::configure (name, value); } @@ -598,7 +603,7 @@ Service::end_move (const db::DPoint & /*p*/, lay::angle_constraint_type ac) transform (db::DCplxTrans (m_move_trans)); move_cancel (); // formally this functionality fits here // accept changes to guiding shapes - handle_guiding_shape_changes (); + handle_guiding_shape_changes (true); } m_alt_ac = lay::AC_Global; } @@ -846,7 +851,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector -Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const +Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj, bool commit) const { unsigned int cv_index = obj.cv_index (); lay::CellView cv = view ()->cellview (cv_index); @@ -1874,10 +1879,22 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const return std::make_pair (false, lay::ObjectInstPath ()); } - if (! layout->is_pcell_instance (obj.cell_index ()).first) { + auto pcell_decl = layout->pcell_declaration_for_pcell_variant (obj.cell_index ()); + if (! pcell_decl) { return std::make_pair (false, lay::ObjectInstPath ()); } + // Don't update unless we're committing or not in lazy PCell update mode + if (! commit) { + if (m_pcell_lazy_evaluation < 0) { + if (pcell_decl->wants_lazy_evaluation ()) { + return std::make_pair (false, lay::ObjectInstPath ()); + } + } else if (m_pcell_lazy_evaluation > 0) { + return std::make_pair (false, lay::ObjectInstPath ()); + } + } + db::cell_index_type top_cell = std::numeric_limits::max (); db::cell_index_type parent_cell = std::numeric_limits::max (); db::Instance parent_inst; @@ -1944,7 +1961,7 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const } bool -Service::handle_guiding_shape_changes () +Service::handle_guiding_shape_changes (bool commit) { EditableSelectionIterator s = begin_selection (); @@ -1953,7 +1970,7 @@ Service::handle_guiding_shape_changes () return false; } - std::pair gs = handle_guiding_shape_changes (*s); + std::pair gs = handle_guiding_shape_changes (*s, commit); if (gs.first) { // remove superfluous proxies diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index 7e2d34801..e782bbfb5 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -398,17 +398,19 @@ public: * * @return true, if PCells have been updated, indicating that our selection is no longer valid * + * @param commit If true, changes are "final" (and PCells are updated also in lazy evaluation mode) + * * This version assumes there is only one guiding shape selected and will update the selection. * It will also call layout.cleanup() if required. */ - bool handle_guiding_shape_changes (); + bool handle_guiding_shape_changes (bool commit); /** * @brief Handle changes in a specific guiding shape, i.e. create new PCell variants if required * * @return A pair of bool (indicating that the object path has changed) and the new guiding shape path */ - std::pair handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const; + std::pair handle_guiding_shape_changes (const lay::ObjectInstPath &obj, bool commit) const; /** * @brief Gets a value indicating whether a move operation is ongoing @@ -672,9 +674,12 @@ private: bool m_snap_to_objects; bool m_snap_objects_to_grid; db::DVector m_global_grid; + + // Other attributes bool m_top_level_sel; bool m_show_shapes_of_instances; unsigned int m_max_shapes_of_instances; + int m_pcell_lazy_evaluation; // Hierarchical copy mode (-1: ask, 0: shallow, 1: deep) int m_hier_copy_mode; diff --git a/src/gsi/gsi/gsiExpression.cc b/src/gsi/gsi/gsiExpression.cc index 4ac9fbc10..1727c2bc6 100644 --- a/src/gsi/gsi/gsiExpression.cc +++ b/src/gsi/gsi/gsiExpression.cc @@ -952,9 +952,11 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context const tl::Variant *arg = i >= int (args.size ()) ? get_kwarg (*a, kwargs) : &args[i]; if (! arg) { is_valid = a->spec ()->has_default (); - } else if (gsi::test_arg (*a, *arg, false /*strict*/)) { + } else if (gsi::test_arg (*a, *arg, false /*strict*/, false /*no object substitution*/)) { + sc += 100; + } else if (gsi::test_arg (*a, *arg, true /*loose*/, false /*no object substitution*/)) { ++sc; - } else if (test_arg (*a, *arg, true /*loose*/)) { + } else if (gsi::test_arg (*a, *arg, true /*loose*/, true /*with object substitution*/)) { // non-scoring match } else { is_valid = false; diff --git a/src/gsi/gsi/gsiVariantArgs.cc b/src/gsi/gsi/gsiVariantArgs.cc index 39dc0725e..5e1b4ffb7 100644 --- a/src/gsi/gsi/gsiVariantArgs.cc +++ b/src/gsi/gsi/gsiVariantArgs.cc @@ -46,12 +46,12 @@ inline void *get_object (tl::Variant &var) // ------------------------------------------------------------------- // Test if an argument can be converted to the given type -bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose); +bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose, bool object_substitution); template struct test_arg_func { - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType & /*atype*/, bool /*loose*/) + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType & /*atype*/, bool /*loose*/, bool /*object_substitution*/) { *ret = arg.can_convert_to (); } @@ -60,7 +60,16 @@ struct test_arg_func template <> struct test_arg_func { - void operator () (bool *ret, const tl::Variant & /*arg*/, const gsi::ArgType & /*atype*/, bool /*loose*/) + void operator () (bool *ret, const tl::Variant & /*arg*/, const gsi::ArgType & /*atype*/, bool /*loose*/, bool /*object_substitution*/) + { + *ret = true; + } +}; + +template <> +struct test_arg_func +{ + void operator () (bool *ret, const tl::Variant & /*arg*/, const gsi::ArgType & /*atype*/, bool /*loose*/, bool /*object_substitution*/) { *ret = true; } @@ -69,7 +78,7 @@ struct test_arg_func template <> struct test_arg_func { - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose, bool object_substitution) { // allow nil of pointers if ((atype.is_ptr () || atype.is_cptr ()) && arg.is_nil ()) { @@ -77,7 +86,7 @@ struct test_arg_func return; } - if (arg.is_list ()) { + if (object_substitution && arg.is_list ()) { // we may implicitly convert an array into a constructor call of a target object - // for now we only check whether the number of arguments is compatible with the array given. @@ -104,9 +113,9 @@ struct test_arg_func const tl::VariantUserClassBase *cls = arg.user_cls (); if (! cls) { *ret = false; - } else if (! cls->gsi_cls ()->is_derived_from (atype.cls ()) && (! loose || ! cls->gsi_cls ()->can_convert_to(atype.cls ()))) { - *ret = false; - } else if ((atype.is_ref () || atype.is_ptr ()) && cls->is_const ()) { + } else if (! (cls->gsi_cls () == atype.cls () || + (loose && (cls->gsi_cls ()->is_derived_from (atype.cls ()) || + (object_substitution && cls->gsi_cls ()->can_convert_to (atype.cls ())))))) { *ret = false; } else { *ret = true; @@ -117,7 +126,7 @@ struct test_arg_func template <> struct test_arg_func { - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose, bool /*object_substitution*/) { if (! arg.is_list ()) { *ret = false; @@ -129,7 +138,7 @@ struct test_arg_func *ret = true; for (tl::Variant::const_iterator v = arg.begin (); v != arg.end () && *ret; ++v) { - if (! test_arg (ainner, *v, loose)) { + if (! test_arg (ainner, *v, loose, true)) { *ret = false; } } @@ -139,7 +148,7 @@ struct test_arg_func template <> struct test_arg_func { - void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose) + void operator () (bool *ret, const tl::Variant &arg, const gsi::ArgType &atype, bool loose, bool /*object_substitution*/) { // Note: delegating that to the function avoids "injected class name used as template template expression" warning if (! arg.is_array ()) { @@ -152,16 +161,11 @@ struct test_arg_func const ArgType &ainner = *atype.inner (); const ArgType &ainner_k = *atype.inner_k (); - if (! arg.is_list ()) { - *ret = false; - return; - } - *ret = true; for (tl::Variant::const_array_iterator a = arg.begin_array (); a != arg.end_array () && *ret; ++a) { - if (! test_arg (ainner_k, a->first, loose)) { + if (! test_arg (ainner_k, a->first, loose, true)) { *ret = false; - } else if (! test_arg (ainner, a->second, loose)) { + } else if (! test_arg (ainner, a->second, loose, true)) { *ret = false; } } @@ -169,7 +173,7 @@ struct test_arg_func }; bool -test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose) +test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose, bool object_substitution) { // for const X * or X *, nil is an allowed value if ((atype.is_cptr () || atype.is_ptr ()) && arg.is_nil ()) { @@ -177,7 +181,7 @@ test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose) } bool ret = false; - gsi::do_on_type () (atype.type (), &ret, arg, atype, loose); + gsi::do_on_type () (atype.type (), &ret, arg, atype, loose, object_substitution); return ret; } diff --git a/src/gsi/gsi/gsiVariantArgs.h b/src/gsi/gsi/gsiVariantArgs.h index 75f312819..187132289 100644 --- a/src/gsi/gsi/gsiVariantArgs.h +++ b/src/gsi/gsi/gsiVariantArgs.h @@ -70,10 +70,11 @@ GSI_PUBLIC void pull_arg (gsi::SerialArgs &retlist, const gsi::ArgType &atype, t * @param atype The argument type * @param arg The value to pass to it * @param loose true for loose checking + * @param object_substitution true to substitute object arguments by lists (using constructor) or employing conversion constructors * * @return True, if the argument can be passed */ -GSI_PUBLIC bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose); +GSI_PUBLIC bool test_arg (const gsi::ArgType &atype, const tl::Variant &arg, bool loose, bool object_substitution); } diff --git a/src/gsi/unit_tests/gsiExpressionTests.cc b/src/gsi/unit_tests/gsiExpressionTests.cc index 0449850d5..a395a73d9 100644 --- a/src/gsi/unit_tests/gsiExpressionTests.cc +++ b/src/gsi/unit_tests/gsiExpressionTests.cc @@ -863,3 +863,38 @@ TEST(16) } } +// implicit conversions +TEST(17) +{ + tl::Eval e; + tl::Variant v; + + // smoke test + v = e.parse ("var rdb=ReportDatabase.new();" + "var cat=rdb.create_category('name');" + "var cell=rdb.create_cell('TOP');" + "var it=rdb.create_item(cell,cat);" + "var bwp=BoxWithProperties.new(Box.new(0,0,1,2), {1=>'value'});" + "it.add_value(bwp)").execute (); + + v = e.parse ("var rdb=ReportDatabase.new();" + "var cat=rdb.create_category('name');" + "var cell=rdb.create_cell('TOP');" + "var it=rdb.create_item(cell,cat);" + "var bwp=DBoxWithProperties.new(DBox.new(0,0,1,2), {1=>'value'});" + "it.add_value(bwp)").execute (); + + v = e.parse ("var rdb=ReportDatabase.new();" + "var cat=rdb.create_category('name');" + "var cell=rdb.create_cell('TOP');" + "var it=rdb.create_item(cell,cat);" + "var b=DBox.new(0,0,1,2);" + "it.add_value(b)").execute (); + + v = e.parse ("var rdb=ReportDatabase.new();" + "var cat=rdb.create_category('name');" + "var cell=rdb.create_cell('TOP');" + "var it=rdb.create_item(cell,cat);" + "it.add_value(17.5)").execute (); +} + diff --git a/src/gsiqt/qt6/QtXml/gsiDeclQDomNodeList.cc b/src/gsiqt/qt6/QtXml/gsiDeclQDomNodeList.cc index dca4a7fe5..9bae6e50d 100644 --- a/src/gsiqt/qt6/QtXml/gsiDeclQDomNodeList.cc +++ b/src/gsiqt/qt6/QtXml/gsiDeclQDomNodeList.cc @@ -35,6 +35,12 @@ // ----------------------------------------------------------------------- // class QDomNodeList + static bool QDomNodeList_operator_eq(const QDomNodeList *a, const QDomNodeList &b) { + return *a == b; + } + static bool QDomNodeList_operator_ne(const QDomNodeList *a, const QDomNodeList &b) { + return !(*a == b); + } // Constructor QDomNodeList::QDomNodeList() @@ -153,25 +159,6 @@ static void _call_f_length_c0 (const qt_gsi::GenericMethod * /*decl*/, void *cls } -// bool QDomNodeList::operator!=(const QDomNodeList &) - - -static void _init_f_operator_excl__eq__c2484 (qt_gsi::GenericMethod *decl) -{ - static gsi::ArgSpecBase argspec_0 ("arg1"); - decl->add_arg (argspec_0); - decl->set_return (); -} - -static void _call_f_operator_excl__eq__c2484 (const qt_gsi::GenericMethod * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs &ret) -{ - __SUPPRESS_UNUSED_WARNING(args); - tl::Heap heap; - const QDomNodeList &arg1 = gsi::arg_reader() (args, heap); - ret.write ((bool)((QDomNodeList *)cls)->operator!= (arg1)); -} - - // QDomNodeList &QDomNodeList::operator=(const QDomNodeList &) @@ -191,25 +178,6 @@ static void _call_f_operator_eq__2484 (const qt_gsi::GenericMethod * /*decl*/, v } -// bool QDomNodeList::operator==(const QDomNodeList &) - - -static void _init_f_operator_eq__eq__c2484 (qt_gsi::GenericMethod *decl) -{ - static gsi::ArgSpecBase argspec_0 ("arg1"); - decl->add_arg (argspec_0); - decl->set_return (); -} - -static void _call_f_operator_eq__eq__c2484 (const qt_gsi::GenericMethod * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs &ret) -{ - __SUPPRESS_UNUSED_WARNING(args); - tl::Heap heap; - const QDomNodeList &arg1 = gsi::arg_reader() (args, heap); - ret.write ((bool)((QDomNodeList *)cls)->operator== (arg1)); -} - - // int QDomNodeList::size() @@ -238,14 +206,15 @@ static gsi::Methods methods_QDomNodeList () { methods += new qt_gsi::GenericMethod ("isEmpty?", "@brief Method bool QDomNodeList::isEmpty()\n", true, &_init_f_isEmpty_c0, &_call_f_isEmpty_c0); methods += new qt_gsi::GenericMethod ("item", "@brief Method QDomNode QDomNodeList::item(int index)\n", true, &_init_f_item_c767, &_call_f_item_c767); methods += new qt_gsi::GenericMethod ("length", "@brief Method int QDomNodeList::length()\n", true, &_init_f_length_c0, &_call_f_length_c0); - methods += new qt_gsi::GenericMethod ("!=", "@brief Method bool QDomNodeList::operator!=(const QDomNodeList &)\n", true, &_init_f_operator_excl__eq__c2484, &_call_f_operator_excl__eq__c2484); methods += new qt_gsi::GenericMethod ("assign", "@brief Method QDomNodeList &QDomNodeList::operator=(const QDomNodeList &)\n", false, &_init_f_operator_eq__2484, &_call_f_operator_eq__2484); - methods += new qt_gsi::GenericMethod ("==", "@brief Method bool QDomNodeList::operator==(const QDomNodeList &)\n", true, &_init_f_operator_eq__eq__c2484, &_call_f_operator_eq__eq__c2484); methods += new qt_gsi::GenericMethod ("size", "@brief Method int QDomNodeList::size()\n", true, &_init_f_size_c0, &_call_f_size_c0); return methods; } gsi::Class decl_QDomNodeList ("QtXml", "QDomNodeList", + gsi::method_ext("==", &QDomNodeList_operator_eq, gsi::arg ("other"), "@brief Method bool QDomNodeList::operator==(const QDomNodeList &) const") + + gsi::method_ext("!=", &QDomNodeList_operator_ne, gsi::arg ("other"), "@brief Method bool QDomNodeList::operator!=(const QDomNodeList &) const") ++ methods_QDomNodeList (), "@qt\n@brief Binding of QDomNodeList"); diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index 4ff7a954b..056e7e5ce 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -784,7 +784,7 @@ PropertiesPage::reverse_color_order () } void -PropertiesPage::apply () +PropertiesPage::apply (bool /*commit*/) { bool has_error = false; @@ -915,7 +915,7 @@ PropertiesPage::browse () { BEGIN_PROTECTED - apply (); + apply (true); lay::FileDialog file_dialog (this, tl::to_string (QObject::tr ("Load Image File")), tl::to_string (QObject::tr ("All files (*)"))); @@ -941,7 +941,7 @@ PropertiesPage::save_pressed () { BEGIN_PROTECTED - apply (); + apply (true); lay::FileDialog file_dialog (this, tl::to_string (QObject::tr ("Save As KLayout Image File")), tl::to_string (QObject::tr ("KLayout image files (*.lyimg);;All files (*)"))); diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index b9ccedc33..e715f99cd 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -57,7 +57,7 @@ public: virtual void update (); virtual void leave (); virtual bool readonly (); - virtual void apply (); + virtual void apply (bool commit); void set_direct_image (img::Object *image); diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index 0dc06ae74..3b1c85589 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -71,7 +71,7 @@ public: BEGIN_PROTECTED properties_frame->set_direct_image (mp_image_object); - properties_frame->apply (); + properties_frame->apply (true); if (mp_image_object->is_empty ()) { throw tl::Exception (tl::to_string (tr ("No data loaded for that image"))); diff --git a/src/laybasic/laybasic/layProperties.h b/src/laybasic/laybasic/layProperties.h index ef4b8ec3c..09b16c2b9 100644 --- a/src/laybasic/laybasic/layProperties.h +++ b/src/laybasic/laybasic/layProperties.h @@ -154,8 +154,10 @@ public: * Apply any changes to the current objects. If nothing was * changed, the object may be left untouched. * The dialog will start a transaction on the manager object. + * + * @param commit Is true for the "final" changes (i.e. not during editing) */ - virtual void apply () + virtual void apply (bool /*commit*/) { // default implementation is empty. } @@ -174,8 +176,11 @@ public: * Apply any changes to the current object plus all other objects of the same kind. * If nothing was changed, the objects may be left untouched. * The dialog will start a transaction on the manager object. + * + * @param relative Is true if relative mode is selected + * @param commit Is true for the "final" changes (i.e. not during editing) */ - virtual void apply_to_all (bool /*relative*/) + virtual void apply_to_all (bool /*relative*/, bool /*commit*/) { // default implementation is empty. } diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 1b33e6455..eb99c9d4b 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -43,6 +43,7 @@ static const std::string cfg_grid_grid_color ("grid-grid-color"); static const std::string cfg_grid_style0 ("grid-style0"); static const std::string cfg_grid_style1 ("grid-style1"); static const std::string cfg_grid_style2 ("grid-style2"); +static const std::string cfg_grid_density ("grid-density"); static const std::string cfg_grid_visible ("grid-visible"); static const std::string cfg_grid_micron ("grid-micron"); static const std::string cfg_grid_show_ruler ("grid-show-ruler"); diff --git a/src/layui/layui/layPropertiesDialog.cc b/src/layui/layui/layPropertiesDialog.cc index b714742e8..edd85844b 100644 --- a/src/layui/layui/layPropertiesDialog.cc +++ b/src/layui/layui/layPropertiesDialog.cc @@ -197,7 +197,7 @@ PropertiesDialog::PropertiesDialog (QWidget * /*parent*/, db::Manager *manager, } for (size_t i = 0; i < mp_properties_pages.size (); ++i) { mp_stack->addWidget (mp_properties_pages [i]); - connect (mp_properties_pages [i], SIGNAL (edited ()), this, SLOT (apply ())); + connect (mp_properties_pages [i], SIGNAL (edited ()), this, SLOT (properties_edited ())); } // Necessary to maintain the page order for UI regression testing of 0.18 vs. 0.19 (because tl::Collection has changed to order) .. @@ -314,7 +314,7 @@ PropertiesDialog::current_index_changed (const QModelIndex &index, const QModelI db::Transaction t (mp_manager, tl::to_string (QObject::tr ("Apply changes")), m_transaction_id); - mp_properties_pages [m_index]->apply (); + mp_properties_pages [m_index]->apply (true); if (! t.is_empty ()) { m_transaction_id = t.id (); @@ -437,7 +437,7 @@ BEGIN_PROTECTED if (! mp_properties_pages [m_index]->readonly ()) { db::Transaction t (mp_manager, tl::to_string (QObject::tr ("Apply changes")), m_transaction_id); - mp_properties_pages [m_index]->apply (); + mp_properties_pages [m_index]->apply (true); if (! t.is_empty ()) { m_transaction_id = t.id (); } @@ -485,7 +485,7 @@ BEGIN_PROTECTED if (! mp_properties_pages [m_index]->readonly ()) { db::Transaction t (mp_manager, tl::to_string (QObject::tr ("Apply changes")), m_transaction_id); - mp_properties_pages [m_index]->apply (); + mp_properties_pages [m_index]->apply (true); if (! t.is_empty ()) { m_transaction_id = t.id (); } @@ -567,7 +567,7 @@ PropertiesDialog::any_prev () const } void -PropertiesDialog::apply () +PropertiesDialog::properties_edited () { BEGIN_PROTECTED @@ -580,9 +580,9 @@ BEGIN_PROTECTED try { if (mp_ui->apply_to_all_cbx->isChecked () && mp_properties_pages [m_index]->can_apply_to_all ()) { - mp_properties_pages [m_index]->apply_to_all (mp_ui->relative_cbx->isChecked ()); + mp_properties_pages [m_index]->apply_to_all (mp_ui->relative_cbx->isChecked (), false); } else { - mp_properties_pages [m_index]->apply (); + mp_properties_pages [m_index]->apply (false); } mp_properties_pages [m_index]->update (); @@ -632,7 +632,7 @@ BEGIN_PROTECTED db::Transaction t (mp_manager, tl::to_string (QObject::tr ("Apply changes")), m_transaction_id); - mp_properties_pages [m_index]->apply (); + mp_properties_pages [m_index]->apply (true); mp_properties_pages [m_index]->update (); if (! t.is_empty ()) { diff --git a/src/layui/layui/layPropertiesDialog.h b/src/layui/layui/layPropertiesDialog.h index 153002127..01e09db45 100644 --- a/src/layui/layui/layPropertiesDialog.h +++ b/src/layui/layui/layPropertiesDialog.h @@ -105,7 +105,7 @@ private: void update_controls (); public slots: - void apply (); + void properties_edited (); void next_pressed (); void prev_pressed (); void cancel_pressed (); diff --git a/src/layview/layview/GridNetConfigPage.ui b/src/layview/layview/GridNetConfigPage.ui index 34db49143..427129ba0 100644 --- a/src/layview/layview/GridNetConfigPage.ui +++ b/src/layview/layview/GridNetConfigPage.ui @@ -7,7 +7,7 @@ 0 0 483 - 341 + 361 @@ -59,6 +59,23 @@ 6 + + + + Far style + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Show ruler + + + @@ -108,7 +125,122 @@ - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + Grid + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + + Ruler + + + + + + + + + + + + + + Axis + + + + + + + + + + + + + + Close style + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Color (all) + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + Color @@ -118,14 +250,17 @@ - - - - Qt::Horizontal + + + + Style + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + @@ -169,30 +304,23 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + - + Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Qt::Horizontal + + + + Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -245,141 +373,37 @@ - - - - Far style - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Close style - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Color (all) - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - - - Grid - - - - - - - Show Ruler - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Style - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Color - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - - - - - Axis - - - - - - - Color - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Ruler - - - - + Qt::Horizontal + + + + 1 + + + + + + + Min. spacing + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + (Font height units) + + + diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index 2cc8c85a2..92fc3b6ab 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -41,6 +41,8 @@ namespace lay // ------------------------------------------------------------ // Helper functions to get and set the configuration +int default_density = 4; + static struct { lay::GridNet::GridStyle style; const char *string; @@ -79,6 +81,20 @@ GridNetStyleConverter::to_string (lay::GridNet::GridStyle style) return ""; } +void +GridNetDensityConverter::from_string (const std::string &value, int &density) +{ + density = default_density; // original default + tl::Extractor ex (value.c_str ()); + ex.try_read (density); +} + +std::string +GridNetDensityConverter::to_string (int density) +{ + return tl::to_string (density); +} + // ------------------------------------------------------------ // Implementation of the GridNetPluginDeclaration @@ -92,6 +108,7 @@ GridNetPluginDeclaration::get_options (std::vector < std::pair (cfg_grid_style0, GridNetStyleConverter ().to_string (lay::GridNet::Invisible))); options.push_back (std::pair (cfg_grid_style1, GridNetStyleConverter ().to_string (lay::GridNet::Dots))); options.push_back (std::pair (cfg_grid_style2, GridNetStyleConverter ().to_string (lay::GridNet::TenthDottedLines))); + options.push_back (std::pair (cfg_grid_density, "")); options.push_back (std::pair (cfg_grid_visible, tl::to_string (true))); options.push_back (std::pair (cfg_grid_show_ruler, tl::to_string (true))); // grid-micron is not configured here since some other entity is supposed to do this. @@ -122,7 +139,8 @@ GridNet::GridNet (LayoutViewBase *view) lay::Plugin (view), mp_view (view), m_visible (false), m_show_ruler (true), m_grid (1.0), - m_style0 (Invisible), m_style1 (Invisible), m_style2 (Invisible) + m_style0 (Invisible), m_style1 (Invisible), m_style2 (Invisible), + m_density (default_density) { // .. nothing yet .. } @@ -175,6 +193,12 @@ GridNet::configure (const std::string &name, const std::string &value) GridNetStyleConverter ().from_string (value, style); need_update = test_and_set (m_style2, style); + } else if (name == cfg_grid_density) { + + int density = 0; + GridNetDensityConverter ().from_string (value, density); + need_update = test_and_set (m_density, density); + } else if (name == cfg_grid_show_ruler) { bool sr = false; @@ -246,13 +270,14 @@ GridNet::render_bg (const lay::Viewport &vp, ViewObjectCanvas &canvas) // fw is the basic unit of the ruler geometry int fwr = lay::FixedFont::get_font (bmp_canvas->font_resolution ()).width (); + int threshold = std::min (1000, m_density * fwr); double dgrid = trans.ctrans (m_grid); GridStyle style = m_style1; // compute major grid and switch to secondary style if necessary int s = 0; - while (dgrid < fwr * 4) { + while (dgrid < threshold) { if (s == 0) { dgrid *= 2.0; } else if (s == 1) { @@ -279,56 +304,6 @@ GridNet::render_bg (const lay::Viewport &vp, ViewObjectCanvas &canvas) int nx = int (dbworld.width () / grid + eps) + 2; int ny = int (dbworld.height () / grid + eps) + 2; - if (m_show_ruler && dgrid < vp.width () * 0.2) { - - int rh = int (floor (0.5 + fwr * 0.8)); - int xoffset = int (floor (0.5 + fwr * 2.5)); - int yoffset = int (floor (0.5 + fwr * 2.5)); - - painter.fill_rect (db::Point (xoffset, vp.height () - yoffset - rh / 2), - db::Point (xoffset + int (floor (0.5 + dgrid)), vp.height () - yoffset + rh / 2), - ruler_color); - - painter.draw_rect (db::Point (xoffset + int (floor (0.5 + dgrid)), vp.height () - yoffset - rh / 2), - db::Point (xoffset + int (floor (0.5 + 2 * dgrid)), vp.height () - yoffset + rh / 2), - ruler_color); - - painter.draw_text (tl::sprintf ("%g \265m", grid * 2).c_str (), - db::Point (xoffset + int (floor (0.5 + trans.ctrans (2 * grid))), vp.height () - yoffset - rh / 2 - 2), - ruler_color, -1, 1); - - if (mp_view->global_trans ().fp_trans () != db::DFTrans ()) { - - // draw a small "F" indicating any global transformation - db::Point pts[] = { - db::Point (-4, -5), - db::Point (-4, 5), - db::Point (4, 5), - db::Point (4, 3), - db::Point (-2, 3), - db::Point (-2, 1), - db::Point (3, 1), - db::Point (3, -1), - db::Point (-2, -1), - db::Point (-2, -5), - db::Point (-4, -5) - }; - - db::Polygon poly; - poly.assign_hull (&pts[0], &pts[0] + (sizeof (pts) / sizeof (pts[0]))); - poly.transform (db::FTrans (mp_view->global_trans ().fp_trans ())); - - for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); !e.at_end (); ++e) { - db::Point p0 (xoffset + 2 * rh, vp.height () - yoffset - rh * 5); - db::Point p1 = p0 + db::Vector (int (floor (0.5 + (*e).p1 ().x () * 0.1 * rh * 4)), -int (floor (0.5 + (*e).p1 ().y () * 0.1 * rh * 4))); - db::Point p2 = p0 + db::Vector (int (floor (0.5 + (*e).p2 ().x () * 0.1 * rh * 4)), -int (floor (0.5 + (*e).p2 ().y () * 0.1 * rh * 4))); - painter.draw_line (p1, p2, ruler_color); - } - - } - - } - // draw grid if (style == Dots || style == TenthDottedLines || style == DottedLines || style == LightDottedLines) { @@ -549,6 +524,71 @@ GridNet::render_bg (const lay::Viewport &vp, ViewObjectCanvas &canvas) } + if (m_show_ruler && dgrid < vp.width () * 0.4) { + + int rh = int (floor (0.5 + fwr * 0.8)); + int xoffset = int (floor (0.5 + fwr * 2.5)); + int yoffset = int (floor (0.5 + fwr * 2.5)); + + painter.fill_rect (db::Point (xoffset, vp.height () - yoffset - rh / 2), + db::Point (xoffset + int (floor (0.5 + dgrid)), vp.height () - yoffset + rh / 2), + ruler_color); + + painter.draw_rect (db::Point (xoffset + int (floor (0.5 + dgrid)), vp.height () - yoffset - rh / 2), + db::Point (xoffset + int (floor (0.5 + 2 * dgrid)), vp.height () - yoffset + rh / 2), + ruler_color); + + double grid_value = grid * 2; + std::string fmt = "%g \265m"; + if (grid_value < 0.1 * (1 + db::epsilon)) { + grid_value *= 1000.0; + fmt = "%g nm"; + } else if (grid_value < 100.0 * (1 + db::epsilon)) { + fmt = "%g \265m"; + } else if (grid_value < 100000.0 * (1 + db::epsilon)) { + grid_value *= 1e-3; + fmt = "%g mm"; + } else { + grid_value *= 1e-6; + fmt = "%g m"; + } + + painter.draw_text (tl::sprintf (fmt, grid_value).c_str (), + db::Point (xoffset + int (floor (0.5 + trans.ctrans (2 * grid))), vp.height () - yoffset - rh / 2 - 2), + ruler_color, -1, 1); + + if (mp_view->global_trans ().fp_trans () != db::DFTrans ()) { + + // draw a small "F" indicating any global transformation + db::Point pts[] = { + db::Point (-4, -5), + db::Point (-4, 5), + db::Point (4, 5), + db::Point (4, 3), + db::Point (-2, 3), + db::Point (-2, 1), + db::Point (3, 1), + db::Point (3, -1), + db::Point (-2, -1), + db::Point (-2, -5), + db::Point (-4, -5) + }; + + db::Polygon poly; + poly.assign_hull (&pts[0], &pts[0] + (sizeof (pts) / sizeof (pts[0]))); + poly.transform (db::FTrans (mp_view->global_trans ().fp_trans ())); + + for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); !e.at_end (); ++e) { + db::Point p0 (xoffset + 2 * rh, vp.height () - yoffset - rh * 5); + db::Point p1 = p0 + db::Vector (int (floor (0.5 + (*e).p1 ().x () * 0.1 * rh * 4)), -int (floor (0.5 + (*e).p1 ().y () * 0.1 * rh * 4))); + db::Point p2 = p0 + db::Vector (int (floor (0.5 + (*e).p2 ().x () * 0.1 * rh * 4)), -int (floor (0.5 + (*e).p2 ().y () * 0.1 * rh * 4))); + painter.draw_line (p1, p2, ruler_color); + } + + } + + } + } } diff --git a/src/layview/layview/layGridNet.h b/src/layview/layview/layGridNet.h index 89b63c3f0..d75dcde37 100644 --- a/src/layview/layview/layGridNet.h +++ b/src/layview/layview/layGridNet.h @@ -87,6 +87,7 @@ private: GridStyle m_style0; GridStyle m_style1; GridStyle m_style2; + int m_density; }; class GridNetStyleConverter @@ -96,6 +97,13 @@ public: std::string to_string (lay::GridNet::GridStyle style); }; +class GridNetDensityConverter +{ +public: + void from_string (const std::string &value, int &density); + std::string to_string (int density); +}; + } #endif diff --git a/src/layview/layview/layGridNetConfigPage.cc b/src/layview/layview/layGridNetConfigPage.cc index 6736f72f1..900a31b53 100644 --- a/src/layview/layview/layGridNetConfigPage.cc +++ b/src/layview/layview/layGridNetConfigPage.cc @@ -94,6 +94,10 @@ GridNetConfigPage::setup (lay::Dispatcher *root) style = lay::GridNet::Invisible; root->config_get (cfg_grid_style2, style, GridNetStyleConverter ()); mp_ui->style2_cbx->setCurrentIndex (int (style)); + + int density = 0; + root->config_get (cfg_grid_density, density, GridNetDensityConverter ()); + mp_ui->grid_density_sb->setValue (density); } void @@ -108,6 +112,7 @@ GridNetConfigPage::commit (lay::Dispatcher *root) root->config_set (cfg_grid_style0, lay::GridNet::GridStyle (mp_ui->style0_cbx->currentIndex ()), GridNetStyleConverter ()); root->config_set (cfg_grid_style1, lay::GridNet::GridStyle (mp_ui->style1_cbx->currentIndex ()), GridNetStyleConverter ()); root->config_set (cfg_grid_style2, lay::GridNet::GridStyle (mp_ui->style2_cbx->currentIndex ()), GridNetStyleConverter ()); + root->config_set (cfg_grid_density, mp_ui->grid_density_sb->value (), GridNetDensityConverter ()); } } // namespace lay diff --git a/src/lvs/lvs/built-in-macros/_lvs_engine.rb b/src/lvs/lvs/built-in-macros/_lvs_engine.rb index 2465c2ca7..11959cf40 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_engine.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_engine.rb @@ -225,9 +225,16 @@ module LVS # @synopsis lvs_data # See \Netter#lvs_data for a description of that function. + # %LVS% + # @name flag_missing_ports + # @brief Checks if all top level ports are properly labelled + # @synopsis flag_missing_ports + # See \Netter#flag_missing_ports for a description of that function. + %w(schematic compare split_gates join_symmetric_nets tolerance ignore_parameter enable_parameter disable_parameter blank_circuit align same_nets same_nets! same_circuits same_device_classes equivalent_pins - min_caps max_res max_depth max_branch_complexity consider_net_names lvs_data no_lvs_hints).each do |f| + min_caps max_res max_depth max_branch_complexity consider_net_names lvs_data no_lvs_hints + flag_missing_ports).each do |f| eval <<"CODE" def #{f}(*args) _netter.#{f}(*args) diff --git a/src/lvs/lvs/built-in-macros/_lvs_netter.rb b/src/lvs/lvs/built-in-macros/_lvs_netter.rb index 4b3a44f7c..c4f8f5197 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_netter.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_netter.rb @@ -530,6 +530,33 @@ CODE @comparer_config << lambda { |comparer| comparer.with_log = false } end + # %LVS% + # @name flag_missing_ports + # @brief Flags inconsistently labelled or missing ports in the current top circuit + # @synopsis flag_missing_ports + # This method must be called after "compare" was executed successfully and will + # report errors if pins in the current top circuit's schematic are not labelled + # correspondingly in the layout. This prevents swapping of port labels or + # pads. + # + # @code + # success = compare + # success && flag_missing_ports + # @/code + # + # Note that in order to use this method, the top circuit from the schematic netlist + # needs to have pins. This may not be always the case - for example, if the top + # level circuit is not a subcircuit in a Spice netlist. + + def flag_missing_ports + + lvs_data.netlist || raise("Netlist not extracted yet") + lvs_data.xref || raise("Compare step was not executed yet") + + lvs_data.flag_missing_ports(lvs_data.netlist.top_circuit) + + end + # %LVS% # @name same_nets # @brief Establishes an equivalence between the nets diff --git a/src/lvs/unit_tests/lvsSimpleTests.cc b/src/lvs/unit_tests/lvsSimpleTests.cc index dbf89d211..69fc7e1b7 100644 --- a/src/lvs/unit_tests/lvsSimpleTests.cc +++ b/src/lvs/unit_tests/lvsSimpleTests.cc @@ -357,3 +357,8 @@ TEST(62_LayerNames) run_test (_this, "layer_names", "layer_names.gds", false, true, "TOP"); } +TEST(63_FlagMissingPorts) +{ + run_test (_this, "flag_missing_ports", "flag_missing_ports.gds", false, true, "TOP"); +} + diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc index 413db5084..dad91a563 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc @@ -33,8 +33,6 @@ namespace db { -// --------------------------------------------------------------- - // --------------------------------------------------------------- // GDS2ReaderBase diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc index ff229aeb9..d95144c75 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc @@ -38,7 +38,7 @@ namespace db // ----------------------------------------------------------------------------------- // Path resolution utility -std::string correct_path (const std::string &fn_in, const db::Layout &layout, const std::string &base_path) +std::vector correct_path (const std::string &fn_in, const db::Layout &layout, const std::string &base_path, bool glob) { const db::Technology *tech = layout.technology (); @@ -64,19 +64,28 @@ std::string correct_path (const std::string &fn_in, const db::Layout &layout, co if (tech && ! tech->base_path ().empty ()) { std::string new_fn = tl::combine_path (tech->base_path (), fn); if (tl::file_exists (new_fn)) { - return new_fn; + std::vector res; + res.push_back (new_fn); + return res; + } else if (glob) { + return tl::glob_expand (new_fn); } } if (! base_path.empty ()) { - return tl::combine_path (base_path, fn); - } else { - return fn; + fn = tl::combine_path (base_path, fn); } - } else { - return fn; } + + if (tl::file_exists (fn) || ! glob) { + std::vector res; + res.push_back (fn); + return res; + } else { + return tl::glob_expand (fn); + } + } // ----------------------------------------------------------------------------------- @@ -1059,7 +1068,7 @@ LEFDEFReaderState::read_map_file (const std::string &filename, db::Layout &layou std::map, std::vector > layer_map; for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { - read_single_map_file (correct_path (*p, layout, base_path), layer_map); + read_single_map_file (correct_path (*p, layout, base_path, false).front (), layer_map); } // build an explicit layer mapping now. diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h index 607410b6a..2a5736f0e 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.h @@ -52,7 +52,7 @@ struct MacroDesc; * @brief Correct a path relative to the stream and technology */ DB_PLUGIN_PUBLIC -std::string correct_path (const std::string &fn, const db::Layout &layout, const std::string &base_path); +std::vector correct_path (const std::string &fn, const db::Layout &layout, const std::string &base_path, bool glob); /** * @brief Convers a string to a MASKSHIFT index list diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc index 8d37fa283..06e42a0c7 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc @@ -141,11 +141,12 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti for (std::vector::const_iterator l = effective_options.begin_lef_files (); l != effective_options.end_lef_files (); ++l) { - std::string lp = correct_path (*l, layout, base_path); - - tl::InputStream lef_stream (lp); - tl::log << tl::to_string (tr ("Reading")) << " " << lp; - importer.read (lef_stream, layout, state); + auto paths = correct_path (*l, layout, base_path, true); + for (auto lp = paths.begin (); lp != paths.end (); ++lp) { + tl::InputStream lef_stream (*lp); + tl::log << tl::to_string (tr ("Reading")) << " " << *lp; + importer.read (lef_stream, layout, state); + } } @@ -164,14 +165,20 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti for (std::vector::const_iterator l = effective_options.begin_lef_files (); l != effective_options.end_lef_files (); ++l) { - std::string lp = correct_path (*l, layout, base_path); - lef_files_read.insert (tl::normalize_path (lp)); + auto paths = correct_path (*l, layout, base_path, true); + for (auto lp = paths.begin (); lp != paths.end (); ++lp) { - tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading LEF file: ")) + lp); + if (lef_files_read.insert (tl::normalize_path (*lp)).second) { - tl::InputStream lef_stream (lp); - tl::log << tl::to_string (tr ("Reading")) << " " << lp; - importer.read_lef (lef_stream, layout, state); + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading LEF file: ")) + *lp); + + tl::InputStream lef_stream (*lp); + tl::log << tl::to_string (tr ("Reading")) << " " << *lp; + importer.read_lef (lef_stream, layout, state); + + } + + } } @@ -223,22 +230,25 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti tl::shared_collection macro_layout_object_holder; for (std::vector::const_iterator l = effective_options.begin_macro_layout_files (); l != effective_options.end_macro_layout_files (); ++l) { - std::string lp = correct_path (*l, layout, base_path); + auto paths = correct_path (*l, layout, base_path, true); + for (auto lp = paths.begin (); lp != paths.end (); ++lp) { - tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading LEF macro layout file: ")) + lp); + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading LEF macro layout file: ")) + *lp); - tl::InputStream macro_layout_stream (lp); - tl::log << tl::to_string (tr ("Reading")) << " " << lp; - db::Layout *new_layout = new db::Layout (false); - macro_layout_object_holder.push_back (new_layout); - macro_layouts.push_back (new_layout); + tl::InputStream macro_layout_stream (*lp); + tl::log << tl::to_string (tr ("Reading")) << " " << *lp; + db::Layout *new_layout = new db::Layout (false); + macro_layout_object_holder.push_back (new_layout); + macro_layouts.push_back (new_layout); - db::Reader reader (macro_layout_stream); - reader.read (*new_layout, options); + db::Reader reader (macro_layout_stream); + reader.read (*new_layout, options); + + if (fabs (new_layout->dbu () / layout.dbu () - 1.0) > db::epsilon) { + importer.warn (tl::sprintf (tl::to_string (tr ("DBU of macro layout file '%s' does not match reader DBU (layout DBU is %.12g, reader DBU is set to %.12g)")), + *lp, new_layout->dbu (), layout.dbu ())); + } - if (fabs (new_layout->dbu () / layout.dbu () - 1.0) > db::epsilon) { - importer.warn (tl::sprintf (tl::to_string (tr ("DBU of macro layout file '%s' does not match reader DBU (layout DBU is %.12g, reader DBU is set to %.12g)")), - lp, new_layout->dbu (), layout.dbu ())); } } @@ -266,6 +276,14 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti } + // Warn about cells that could not be resolved + for (std::map::iterator f = foreign_cells.begin (); f != foreign_cells.end (); ++f) { + if (f->second != seen && layout.cell (f->second).is_ghost_cell ()) { + importer.warn (tl::sprintf (tl::to_string (tr ("Could not find a substitution layout for foreign cell '%s'")), + f->first)); + } + } + } state.finish (layout); diff --git a/src/plugins/streamers/maly/db_plugin/dbMALY.cc b/src/plugins/streamers/maly/db_plugin/dbMALY.cc new file mode 100644 index 000000000..372b91f2d --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/dbMALY.cc @@ -0,0 +1,183 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbMALY.h" +#include "dbMALYReader.h" +#include "dbStream.h" + +#include "tlClassRegistry.h" + +namespace db +{ + +// --------------------------------------------------------------- +// MALYDiagnostics implementation + +MALYDiagnostics::~MALYDiagnostics () +{ + // .. nothing yet .. +} + +// --------------------------------------------------------------- +// MALYData implementation + +std::string +MALYTitle::to_string () const +{ + std::string res; + res += "\"" + string + "\" " + transformation.to_string (); + res += tl::sprintf (" %g,%g,%g", width, height, pitch); + if (font == Standard) { + res += " [Standard]"; + } else if (font == Native) { + res += " [Native]"; + } + return res; +} + +std::string +MALYStructure::to_string () const +{ + std::string res; + res += path + "{" + topcell + "}"; + if (layer < 0) { + res += "(*)"; + } else { + res += tl::sprintf ("(%d)", layer); + } + + if (! mname.empty ()) { + res += " mname(" + mname + ")"; + } + if (! ename.empty ()) { + res += " ename(" + ename + ")"; + } + if (! dname.empty ()) { + res += " dname(" + dname + ")"; + } + + res += " "; + res += size.to_string (); + + res += " "; + res += transformation.to_string (); + + if (nx > 1 || ny > 1) { + res += tl::sprintf (" [%.12gx%d,%.12gx%d]", dx, nx, dy, ny); + } + + return res; +} + +std::string +MALYMask::to_string () const +{ + std::string res; + res += "Mask " + name + "\n"; + res += " Size " + tl::to_string (size_um); + + for (auto t = titles.begin (); t != titles.end (); ++t) { + res += "\n Title " + t->to_string (); + } + for (auto s = structures.begin (); s != structures.end (); ++s) { + res += "\n Ref " + s->to_string (); + } + return res; +} + +std::string +MALYData::to_string () const +{ + std::string res; + for (auto m = masks.begin (); m != masks.end (); ++m) { + if (m != masks.begin ()) { + res += "\n"; + } + res += m->to_string (); + } + return res; +} + +// --------------------------------------------------------------- +// MALY format declaration + +class MALYFormatDeclaration + : public db::StreamFormatDeclaration +{ +public: + MALYFormatDeclaration () + { + // .. nothing yet .. + } + + virtual std::string format_name () const { return "MALY"; } + virtual std::string format_desc () const { return "MALY jobdeck"; } + virtual std::string format_title () const { return "MALY (MALY jobdeck format)"; } + virtual std::string file_format () const { return "MALY jobdeck files (*.maly *.MALY *.mly *.MLY)"; } + + virtual bool detect (tl::InputStream &s) const + { + db::MALYReader reader (s); + return reader.test (); + } + + virtual ReaderBase *create_reader (tl::InputStream &s) const + { + return new db::MALYReader (s); + } + + virtual WriterBase *create_writer () const + { + return 0; + } + + virtual bool can_read () const + { + return true; + } + + virtual bool can_write () const + { + return false; + } + + virtual tl::XMLElementBase *xml_reader_options_element () const + { + return new db::ReaderOptionsXMLElement ("maly", + tl::make_member (&db::MALYReaderOptions::dbu, "dbu") + + tl::make_member (&db::MALYReaderOptions::layer_map, "layer-map") + + tl::make_member (&db::MALYReaderOptions::create_other_layers, "create-other-layers") + ); + } +}; + +// NOTE: Because MALY has such a high degree of syntactic freedom, the detection is somewhat +// fuzzy: do MALY at the very end of the detection chain +static tl::RegisteredClass reader_decl (new MALYFormatDeclaration (), 2300, "MALY"); + +// provide a symbol to force linking against +int force_link_MALY = 0; + +} + + diff --git a/src/plugins/streamers/maly/db_plugin/dbMALY.h b/src/plugins/streamers/maly/db_plugin/dbMALY.h new file mode 100644 index 000000000..32a5e07a7 --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/dbMALY.h @@ -0,0 +1,288 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbMALY +#define HDR_dbMALY + +#include "dbPoint.h" +#include "dbTrans.h" +#include "dbBox.h" +#include "dbPluginCommon.h" + +#include "tlException.h" +#include "tlInternational.h" +#include "tlString.h" +#include "tlAssert.h" + +#include +#include + +namespace db +{ + +/** + * @brief The diagnostics interface for reporting problems in the reader or writer + */ +class MALYDiagnostics +{ +public: + virtual ~MALYDiagnostics (); + + /** + * @brief Issue an error with positional information + */ + virtual void error (const std::string &txt) = 0; + + /** + * @brief Issue a warning with positional information + */ + virtual void warn (const std::string &txt, int warn_level) = 0; +}; + +/** + * @brief A class representing a title field on a mask + */ +class DB_PLUGIN_PUBLIC MALYTitle +{ +public: + /** + * @brief Default constructor + */ + MALYTitle () + : width (0.0), height (0.0), pitch (0.0), type (String), font (Standard) + { } + + /** + * @brief The type of the title + */ + enum Type + { + String = 0, // A user-defined string + Date = 1, // The date + Serial = 2 // A serial number + }; + + /** + * @brief The font to be used + */ + enum Font + { + FontNotSet = 0, // Undef + Standard = 1, // Standard font + Native = 2 // Native tool font + }; + + /** + * @brief The string for "String" type + */ + std::string string; + + /** + * @brief The transformation of the title + * + * The origin of the title is supposed to be in the center of + * the title field. + */ + db::DTrans transformation; + + /** + * @brief Optional font parameters: character width + */ + double width; + + /** + * @brief Optional font parameters: character height + */ + double height; + + /** + * @brief Optional font parameters: character pitch + */ + double pitch; + + /** + * @brief The type of the title + */ + Type type; + + /** + * @brief The font to be used + */ + Font font; + + /** + * @brief Returns a string representing the structure + */ + std::string to_string () const; +}; + +/** + * @brief A class representing a structure (pattern) on a mask + */ +class DB_PLUGIN_PUBLIC MALYStructure +{ +public: + /** + * @brief Default constructor + */ + MALYStructure () + : nx (1), ny (1), dx (0.0), dy (0.0), layer (-1) + { } + + /** + * @brief The (expanded) path of the pattern file + */ + std::string path; + + /** + * @brief The name of the top cell + * If empty, the topcell is determined automatically + */ + std::string topcell; + + /** + * @brief The pattern window in the original file + */ + db::DBox size; + + /** + * @brief The transformation needed to place the original file + */ + db::DCplxTrans transformation; + + /** + * @brief The number of placements in x direction + */ + int nx; + + /** + * @brief The number of placements in y direction + */ + int ny; + + /** + * @brief The placement pitch in x direction (if nx > 1) + */ + double dx; + + /** + * @brief The placement pitch in y direction (if ny > 1) + */ + double dy; + + /** + * @brief The design name + */ + std::string dname; + + /** + * @brief The name for the mask process + */ + std::string mname; + + /** + * @brief The name for the mask tool + */ + std::string ename; + + /** + * @brief The layer used from the OASIS file + * + * A value of -1 means "all". + */ + int layer; + + /** + * @brief Returns a string representing the structure + */ + std::string to_string () const; +}; + +/** + * @brief A class representing one mask + */ +class DB_PLUGIN_PUBLIC MALYMask +{ +public: + /** + * @brief Default constructor + */ + MALYMask () + : size_um (0.0) + { } + + /** + * @brief Size of the mask in micrometers + */ + double size_um; + + /** + * @brief Name of the mask + * + * This is also the name of the layer generated + */ + std::string name; + + /** + * @brief The list of structures + */ + std::list structures; + + /** + * @brief The list of titles + */ + std::list titles; + + /** + * @brief Returns a string representing the mask + */ + std::string to_string () const; +}; + +/** + * @brief A class representing the MALY file + */ +class DB_PLUGIN_PUBLIC MALYData +{ +public: + /** + * @brief Default constructor + */ + MALYData () + { } + + /** + * @brief The masks defined by the file + */ + std::list masks; + + /** + * @brief Returns a string representing the data set + */ + std::string to_string () const; +}; + +} + +#endif + diff --git a/src/plugins/streamers/maly/db_plugin/dbMALYFormat.h b/src/plugins/streamers/maly/db_plugin/dbMALYFormat.h new file mode 100644 index 000000000..e2e47b8cb --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/dbMALYFormat.h @@ -0,0 +1,162 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbMALYFormat +#define HDR_dbMALYFormat + +#include "dbSaveLayoutOptions.h" +#include "dbLoadLayoutOptions.h" +#include "dbPluginCommon.h" + +namespace db +{ + +/** + * @brief Structure that holds the MALY specific options for the reader + * NOTE: this structure is non-public linkage by intention. This way it's instantiated + * in all compile units and the shared object does not need to be linked. + */ +class DB_PLUGIN_PUBLIC MALYReaderOptions + : public FormatSpecificReaderOptions +{ +public: + /** + * @brief The constructor + */ + MALYReaderOptions () + : dbu (0.001), + create_other_layers (true) + { + // .. nothing yet .. + } + + /** + * @brief Specify the database unit to produce + * + * Specify the database unit which the resulting layout will receive. + */ + double dbu; + + /** + * @brief Specifies a layer mapping + * + * If a layer mapping is specified, only the given layers are read. + * Otherwise, all layers are read. + * Setting "create_other_layers" to true will make the reader + * create other layers for all layers not given in the layer map. + * Setting an empty layer map and create_other_layers to true effectively + * enables all layers for reading. + */ + db::LayerMap layer_map; + + /** + * @brief A flag indicating that a new layers shall be created + * + * If this flag is set to true, layers not listed in the layer map a created + * too. + */ + bool create_other_layers; + + /** + * @brief Implementation of FormatSpecificReaderOptions + */ + virtual FormatSpecificReaderOptions *clone () const + { + return new MALYReaderOptions (*this); + } + + /** + * @brief Implementation of FormatSpecificReaderOptions + */ + virtual const std::string &format_name () const + { + static const std::string n ("MALY"); + return n; + } +}; + +#if 0 // @@@ +/** + * @brief Structure that holds the MALY specific options for the Writer + * NOTE: this structure is non-public linkage by intention. This way it's instantiated + * in all compile units and the shared object does not need to be linked. + */ +class DB_PLUGIN_PUBLIC MALYWriterOptions + : public FormatSpecificWriterOptions +{ +public: + /** + * @brief The constructor + */ + MALYWriterOptions () + : lambda (0.0), write_timestamp (true) + { + // .. nothing yet .. + } + + /** + * @brief Specifies the lambda value for writing + * + * The lambda value is the basic scaling parameter. + * If this value is set to 0 or negative, the lambda value stored in the layout + * is used (meta data "lambda"). + */ + double lambda; + + /** + * @brief Specifies the technology value for writing Magic files + * + * If this value is set an empty string, the technology store in the layout's + * "technology" meta data is used. + */ + std::string tech; + + /** + * @brief A value indicating whether the real (true) or fake (false) timestamp is written + * + * A fake, static timestamp is useful for comparing files. + */ + bool write_timestamp; + + /** + * @brief Implementation of FormatSpecificWriterOptions + */ + virtual FormatSpecificWriterOptions *clone () const + { + return new MALYWriterOptions (*this); + } + + /** + * @brief Implementation of FormatSpecificWriterOptions + */ + virtual const std::string &format_name () const + { + static std::string n ("MALY"); + return n; + } +}; +#endif + +} + +#endif + diff --git a/src/plugins/streamers/maly/db_plugin/dbMALYReader.cc b/src/plugins/streamers/maly/db_plugin/dbMALYReader.cc new file mode 100644 index 000000000..a474874a7 --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/dbMALYReader.cc @@ -0,0 +1,1039 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbMALYReader.h" +#include "dbStream.h" +#include "dbObjectWithProperties.h" +#include "dbArray.h" +#include "dbStatic.h" +#include "dbShapeProcessor.h" +#include "dbTechnology.h" +#include "dbCellMapping.h" +#include "dbLayerMapping.h" +#include "dbGlyphs.h" + +#include "tlException.h" +#include "tlString.h" +#include "tlClassRegistry.h" +#include "tlFileUtils.h" +#include "tlUri.h" + +#include +#include + +namespace db +{ + +// --------------------------------------------------------------- +// MALYReader + +MALYReader::MALYReader (tl::InputStream &s) + : m_stream (s), + m_progress (tl::to_string (tr ("Reading MALY file")), 1000), + m_dbu (0.001), + m_last_record_line (0) +{ + m_progress.set_format (tl::to_string (tr ("%.0fk lines"))); + m_progress.set_format_unit (1000.0); + m_progress.set_unit (100000.0); +} + +MALYReader::~MALYReader () +{ + // .. nothing yet .. +} + +bool +MALYReader::test () +{ + try { + + tl::Extractor ex = read_record (); + return ex.test ("BEGIN") && ex.test ("MALY"); + + } catch (...) { + return false; + } +} + +const LayerMap & +MALYReader::read (db::Layout &layout) +{ + return read (layout, db::LoadLayoutOptions ()); +} + +const LayerMap & +MALYReader::read (db::Layout &layout, const db::LoadLayoutOptions &options) +{ + init (options); + + const db::MALYReaderOptions &specific_options = options.get_options (); + m_dbu = specific_options.dbu; + + set_layer_map (specific_options.layer_map); + set_create_layers (specific_options.create_other_layers); + set_keep_layer_names (true); + + prepare_layers (layout); + + MALYData data = read_maly_file (); + import_data (layout, data); + create_metadata (layout, data); + + finish_layers (layout); + return layer_map_out (); +} + +void +MALYReader::create_metadata (db::Layout &layout, const MALYData &data) +{ + tl::Variant boundary_per_mask = tl::Variant::empty_array (); + + for (auto m = data.masks.begin (); m != data.masks.end (); ++m) { + double ms = m->size_um; + db::DBox box (-0.5 * ms, -0.5 * ms, 0.5 * ms, 0.5 * ms); + boundary_per_mask.insert (m->name, box); + } + + layout.add_meta_info ("boundary_per_mask", MetaInfo (tl::to_string (tr ("Physical mask boundary per mask name")), boundary_per_mask)); +} + +void +MALYReader::import_data (db::Layout &layout, const MALYData &data) +{ + db::LayoutLocker locker (&layout); + + // create a new top cell + db::Cell &top_cell = layout.cell (layout.add_cell ("MALY_JOBDECK")); + + // count the number of files to read + size_t n = 0; + for (auto m = data.masks.begin (); m != data.masks.end (); ++m) { + n += m->structures.size (); + } + + tl::RelativeProgress progress (tl::to_string (tr ("Reading layouts")), n, size_t (1)); + + for (auto m = data.masks.begin (); m != data.masks.end (); ++m, ++progress) { + + db::Cell &mask_cell = layout.cell (layout.add_cell (("MASK_" + m->name).c_str ())); + top_cell.insert (db::CellInstArray (mask_cell.cell_index (), db::Trans ())); + + auto lp = open_layer (layout, m->name); + if (! lp.first) { + continue; + } + unsigned int target_layer = lp.second; + + for (auto s = m->structures.begin (); s != m->structures.end (); ++s) { + + db::LoadLayoutOptions options; + + tl::InputStream is (s->path); + db::Layout temp_layout; + db::Reader reader (is); + reader.read (temp_layout, options); + + // configure MEBES reader for compatibility with OASIS.Mask + try { + options.set_option_by_name ("mebes_produce_boundary", false); + options.set_option_by_name ("mebes_data_layer", s->layer); + options.set_option_by_name ("mebes_data_datatype", int (0)); + } catch (...) { + // ignore if there is no MEBES support + } + + db::cell_index_type source_cell; + + if (s->topcell.empty ()) { + + auto t = temp_layout.begin_top_down (); + if (t == temp_layout.end_top_down ()) { + throw tl::Exception (tl::to_string (tr ("Mask pattern file '%s' does not have a top cell")), s->path); + } + + source_cell = *t; + ++t; + if (t != temp_layout.end_top_down ()) { + throw tl::Exception (tl::to_string (tr ("Mask pattern file '%s' does not have a single top cell")), s->path); + } + + } else { + + auto cbm = temp_layout.cell_by_name (s->topcell.c_str ()); + if (! cbm.first) { + throw tl::Exception (tl::to_string (tr ("Mask pattern file '%s' does not have a cell named '%s' as required by mask '%s'")), s->path, s->topcell, m->name); + } + + source_cell = cbm.second; + + } + + int source_layer = temp_layout.get_layer_maybe (db::LayerProperties (s->layer, 0)); + if (source_layer >= 0) { + + // create a host cell for the pattern + + std::string cn = m->name; + if (s->topcell.empty ()) { + if (s->mname.empty ()) { + cn += ".PATTERN"; + } else { + cn += "." + s->mname; + } + } else { + cn += "." + s->topcell; + } + db::cell_index_type target_cell = layout.add_cell (cn.c_str ()); + + // create the pattern instance + + db::ICplxTrans trans = db::CplxTrans (layout.dbu ()).inverted () * s->transformation * db::CplxTrans (layout.dbu ()); + db::CellInstArray array; + if (s->nx > 1 || s->ny > 1) { + db::Coord idx = db::coord_traits::rounded (s->dx / layout.dbu ()); + db::Coord idy = db::coord_traits::rounded (s->dy / layout.dbu ()); + array = db::CellInstArray (target_cell, trans, trans.fp_trans () * db::Vector (idx, 0), trans.fp_trans () * db::Vector (0, idy), s->nx, s->ny); + } else { + array = db::CellInstArray (target_cell, trans); + } + mask_cell.insert (array); + + // move over the shapes from the pattern layout to the target layout + + db::CellMapping cm; + cm.create_single_mapping_full (layout, target_cell, temp_layout, source_cell); + + db::LayerMapping lm; + lm.map (source_layer, target_layer); + + layout.cell (target_cell).move_tree_shapes (temp_layout.cell (source_cell), cm, lm); + + } + + } + + // produce the titles + + for (auto t = m->titles.begin (); t != m->titles.end (); ++t) { + + const double one_mm = 1000.0; + + auto gen = db::TextGenerator::default_generator (); + double scale = std::min (t->width * one_mm / (gen->width () * gen->dbu ()), t->height * one_mm / (gen->height () * gen->dbu ())); + + auto &s = t->string; + int len = int (s.size ()); + db::DVector shift (-t->width * one_mm * len * 0.5, -t->height * one_mm * 0.5); + double char_spacing = t->width * one_mm - gen->width () * gen->dbu () * scale; + + db::Region text = gen->text_as_region (s, layout.dbu (), scale, false, 0.0, char_spacing, 0.0); + text.transform (db::Trans (db::CplxTrans (layout.dbu ()).inverted () * shift)); + text.transform (db::CplxTrans (layout.dbu ()).inverted () * db::DCplxTrans (t->transformation) * db::CplxTrans (layout.dbu ())); + + text.insert_into (&layout, mask_cell.cell_index (), target_layer); + + } + + } + +} + +void +MALYReader::unget_record () +{ + m_record_returned = m_record; +} + +tl::Extractor +MALYReader::read_record () +{ + if (! m_record_returned.empty ()) { + + m_record = m_record_returned; + m_record_returned.clear (); + + return tl::Extractor (m_record.c_str ()); + + } + + while (! m_stream.at_end ()) { + + m_last_record_line = m_stream.line_number (); + m_record = read_record_internal (); + + tl::Extractor ex (m_record.c_str ()); + if (ex.test ("+")) { + error (tl::to_string (tr ("'+' character past first column - did you mean to continue a line?"))); + } else if (! ex.at_end ()) { + return ex; + } + + } + + return tl::Extractor (); +} + +std::string +MALYReader::read_record_internal () +{ + std::string rec; + + while (! m_stream.at_end ()) { + + char c = m_stream.get_char (); + + // skip comments + if (c == '/') { + char cc = m_stream.peek_char (); + if (cc == '/') { + while (! m_stream.at_end () && (c = m_stream.get_char ()) != '\n') + ; + if (m_stream.at_end ()) { + break; + } + } else if (cc == '*') { + m_stream.get_char (); // eat leading "*" + while (! m_stream.at_end () && (m_stream.get_char () != '*' || m_stream.peek_char () != '/')) + ; + if (m_stream.at_end ()) { + m_last_record_line = m_stream.line_number (); + error (tl::to_string (tr ("/*...*/ comment not closed"))); + } + m_stream.get_char (); // eat trailing "/" + if (m_stream.at_end ()) { + break; + } + c = m_stream.get_char (); + } + } + + if (c == '\n') { + + if (m_stream.peek_char () == '+') { + + if (tl::Extractor (rec.c_str ()).at_end ()) { + m_last_record_line = m_stream.line_number (); + error (tl::to_string (tr ("'+' character at beginning of new record - did you mean to continue a line?"))); + } + + // continuation line + m_stream.get_char (); // eat "+" + if (m_stream.at_end ()) { + break; + } + + } else { + break; + } + + } else if (c == '"' || c == '\'') { + + rec += c; + + // skip quoted string + char quote = c; + while (! m_stream.at_end ()) { + c = m_stream.get_char (); + rec += c; + if (c == quote) { + quote = 0; + break; + } else if (c == '\\') { + if (m_stream.at_end ()) { + m_last_record_line = m_stream.line_number (); + error (tl::to_string (tr ("Unexpected end of file inside quoted string"))); + } + c = m_stream.get_char (); + rec += c; + } else if (c == '\n') { + m_last_record_line = m_stream.line_number (); + error (tl::to_string (tr ("Line break inside quoted string"))); + } + } + + if (quote) { + m_last_record_line = m_stream.line_number (); + error (tl::to_string (tr ("Unexpected end of file inside quotee string"))); + } + + } else { + rec += c; + } + + } + + return rec; +} + +MALYData +MALYReader::read_maly_file () +{ + MALYData data; + try { + do_read_maly_file (data); + } catch (tl::Exception &ex) { + error (ex.msg ()); + } + return data; +} + +void +MALYReader::extract_title_trans (tl::Extractor &ex, MALYReaderTitleSpec &spec) +{ + double x = 0.0, y = 0.0; + bool ymirror = false; + int rot = 0; + + ex.read (x); + ex.read (y); + + if (ex.test ("SIZE")) { + ex.read (spec.width); + ex.read (spec.height); + ex.read (spec.pitch); + } else { + spec.width = 1.0; + spec.height = 1.0; + spec.pitch = 1.0; + } + + if (ex.test ("MIRROR")) { + if (ex.test ("Y")) { + ymirror = true; + } else if (ex.test ("NONE")) { + ymirror = false; + } else { + error (tl::to_string (tr ("Expected 'Y' or 'NONE' for MIRROR spec"))); + } + } + + if (ex.test ("ROTATE")) { + unsigned int a = 0; + ex.read (a); + rot = (a / 90) % 4; + } + + spec.trans = db::DTrans (rot, false, db::DVector (x, y)) * db::DTrans (ymirror ? db::DFTrans::m90 : db::DFTrans::r0); +} + +MALYReader::MALYReaderParametersData::Base +MALYReader::string_to_base (const std::string &string) +{ + if (string == "ORIGIN") { + return MALYReaderParametersData::Origin; + } else if (string == "LOWERLEFT") { + return MALYReaderParametersData::LowerLeft; + } else if (string == "CENTER") { + return MALYReaderParametersData::Center; + } else { + throw tl::Exception (tl::to_string (tr ("Unknown base specification: ")) + string); + } +} + +bool +MALYReader::begin_section (tl::Extractor &ex, const std::string &name) +{ + tl::Extractor ex_saved = ex; + + if (ex.test ("BEGIN")) { + if (name.empty ()) { + m_sections.push_back (std::string ()); + ex.read_word (m_sections.back ()); + return true; + } else if (ex.test (name.c_str ())) { + m_sections.push_back (name); + return true; + } + } + + ex = ex_saved; + return false; +} + +bool +MALYReader::end_section (tl::Extractor &ex) +{ + tl_assert (! m_sections.empty ()); + if (ex.at_end ()) { + + error (tl::to_string (tr ("Unexpected end of file during section"))); + return false; + + } else if (ex.test ("END")) { + + ex.expect (m_sections.back ().c_str ()); + m_sections.pop_back (); + return true; + + } else { + + return false; + + } +} + +void +MALYReader::skip_section () +{ + while (true) { + tl::Extractor ex = read_record (); + if (begin_section (ex)) { + skip_section (); + } else if (end_section (ex)) { + break; + } + } +} + +void +MALYReader::read_parameter (MALYReaderParametersData &data) +{ + while (true) { + + tl::Extractor ex = read_record (); + + if (end_section (ex)) { + break; + } else if (ex.test ("MASKMIRROR")) { + + if (ex.test ("NONE")) { + data.maskmirror = false; + } else if (ex.test ("Y")) { + data.maskmirror = true; + } else { + error (tl::to_string (tr ("Expected value Y or NONE for MASKMIRROR"))); + } + + } else if (ex.test ("MASKSIZE")) { + + data.masksize = 0.0; + ex.read (data.masksize); + + } else if (ex.test ("FONT")) { + + if (ex.test ("STANDARD")) { + data.font = MALYTitle::Standard; + } else if (ex.test ("NATIVE")) { + data.font = MALYTitle::Native; + } else { + error (tl::to_string (tr ("Expected value STANDARD or NATIVE for FONT"))); + } + + } else if (ex.test ("BASE")) { + + std::string base; + ex.read_word (base); + data.base = string_to_base (base); + + } else if (ex.test ("ARYBASE")) { + + std::string base; + ex.read_word (base); + data.array_base = string_to_base (base); + + } else if (ex.test ("REFERENCE")) { + + ex.expect ("TOOL"); + + std::string para; + ex.read_word_or_quoted (para); + // TODO: what to do with "para" + + ex.expect_end (); + + } else if (ex.test ("ROOT")) { + + std::string format, path; + ex.read_word_or_quoted (format); + ex.read_word_or_quoted (path, ".\\/+-_"); + ex.expect_end (); + + data.roots.push_back (std::make_pair (format, path)); + + } else if (begin_section (ex)) { + warn (tl::to_string (tr ("Unknown section ignored"))); + skip_section (); + } else { + warn (tl::to_string (tr ("Unknown record ignored"))); + } + + } +} + +void +MALYReader::read_title (MALYReaderTitleData &data) +{ + while (true) { + + tl::Extractor ex = read_record (); + + if (end_section (ex)) { + break; + } else if (ex.test ("DATE")) { + + data.date_spec.given = true; + + if (ex.test ("OFF")) { + data.date_spec.enabled = false; + } else { + data.date_spec.enabled = true; + extract_title_trans (ex, data.date_spec); + ex.expect_end (); + } + + } else if (ex.test ("SERIAL")) { + + data.serial_spec.given = true; + + if (ex.test ("OFF")) { + data.serial_spec.enabled = false; + } else { + data.serial_spec.enabled = true; + extract_title_trans (ex, data.serial_spec); + ex.expect_end (); + } + + } else if (ex.test ("STRING")) { + + std::string text; + ex.read_word_or_quoted (text); + + data.string_titles.push_back (std::make_pair (text, MALYReaderTitleSpec ())); + data.string_titles.back ().second.enabled = true; + extract_title_trans (ex, data.string_titles.back ().second); + + ex.expect_end (); + + } else if (begin_section (ex)) { + warn (tl::to_string (tr ("Unknown section ignored"))); + skip_section (); + } else { + warn (tl::to_string (tr ("Unknown record ignored"))); + } + + } +} + +void +MALYReader::read_strgroup (MALYReaderStrGroupData &data) +{ + while (true) { + + bool is_sref = false; + + tl::Extractor ex = read_record (); + if (end_section (ex)) { + + break; + + } else if (ex.test ("PROPERTY")) { + + if (data.refs.empty ()) { + error (tl::to_string (tr ("PROPERTY entry without a preceeding SREF or AREF"))); + } + + while (! ex.at_end ()) { + if (ex.test ("DNAME")) { + ex.read_word_or_quoted (data.refs.back ().dname); + } else if (ex.test ("ENAME")) { + ex.read_word_or_quoted (data.refs.back ().ename); + } else if (ex.test ("MNAME")) { + ex.read_word_or_quoted (data.refs.back ().mname); + } else { + error (tl::to_string (tr ("Unknown PROPERTY item"))); + } + } + + } else if ((is_sref = ex.test ("SREF")) || ex.test ("AREF")) { + + data.refs.push_back (MALYReaderStrRefData ()); + MALYReaderStrRefData &ref = data.refs.back (); + + ex.read_word_or_quoted (ref.file); + ex.read_word_or_quoted (ref.name); + ex.read (ref.layer); + + if (ex.test ("ORG")) { + double x = 0.0, y = 0.0; + ex.read (x); + ex.read (y); + ref.org = db::DVector (x, y); + } + + if (ex.test ("SIZE")) { + double l = 0.0, b = 0.0, r = 0.0, t = 0.0; + ex.read (l); + ex.read (b); + ex.read (r); + ex.read (t); + ref.size = db::DBox (l, b, r, t); + } + + if (ex.test ("SCALE")) { + ex.read (ref.scale); + } + + if (! is_sref && ex.test ("ITERATION")) { + ex.read (ref.nx); + ex.read (ref.ny); + ex.read (ref.dx); + ex.read (ref.dy); + } + + ex.expect_end (); + + } else if (begin_section (ex)) { + warn (tl::to_string (tr ("Unknown section ignored"))); + skip_section (); + } else { + warn (tl::to_string (tr ("Unknown record ignored"))); + } + + } +} + +void +MALYReader::read_mask (MALYReaderMaskData &mask) +{ + while (true) { + + tl::Extractor ex = read_record (); + if (end_section (ex)) { + break; + } else if (begin_section (ex, "PARAMETER")) { + + ex.expect_end (); + read_parameter (mask.parameters); + + } else if (begin_section (ex, "TITLE")) { + + ex.expect_end (); + read_title (mask.title); + + } else if (begin_section (ex, "STRGROUP")) { + + mask.strgroups.push_back (MALYReaderStrGroupData ()); + + ex.read_word_or_quoted (mask.strgroups.back ().name); + ex.expect_end (); + + read_strgroup (mask.strgroups.back ()); + + } else if (begin_section (ex)) { + warn (tl::to_string (tr ("Unknown section ignored"))); + skip_section (); + } else { + warn (tl::to_string (tr ("Unknown record ignored"))); + } + + } +} + +bool +MALYReader::read_maskset (MALYData &data) +{ + tl::Extractor ex = read_record (); + + if (! begin_section (ex, "MASKSET")) { + unget_record (); + return false; + } + + MALYReaderMaskData cmask; + std::list masks; + + while (true) { + + ex = read_record (); + + if (end_section (ex)) { + + ex.expect_end (); + create_masks (cmask, masks, data); + return true; + + } else if (begin_section (ex, "MASK")) { + + masks.push_back (MALYReaderMaskData ()); + ex.read (masks.back ().name); + + ex.expect_end (); + read_mask (masks.back ()); + + } else if (begin_section (ex, "CMASK")) { + + ex.expect_end (); + read_mask (cmask); + + } else if (begin_section (ex)) { + warn (tl::to_string (tr ("Unknown section ignored"))); + skip_section (); + } else { + warn (tl::to_string (tr ("Unknown record ignored"))); + } + + } +} + +void +MALYReader::create_masks (const MALYReaderMaskData &cmask, const std::list &masks, MALYData &data) +{ + for (auto i = masks.begin (); i != masks.end (); ++i) { + + data.masks.push_back (MALYMask ()); + MALYMask &m = data.masks.back (); + + m.name = i->name; + + m.size_um = i->parameters.masksize * 25400.0; + if (m.size_um < db::epsilon) { + m.size_um = cmask.parameters.masksize * 25400.0; + } + if (m.size_um < db::epsilon) { + m.size_um = 7.0 * 25400.0; + warn (tl::to_string (tr ("No mask size given for - using default of 7 inch for mask: ")) + m.name); + } + + MALYTitle::Font font = i->parameters.font; + if (font == MALYTitle::FontNotSet) { + font = cmask.parameters.font; + } + if (font == MALYTitle::FontNotSet) { + font = MALYTitle::Standard; + } + + bool maskmirror = (i->parameters.maskmirror != cmask.parameters.maskmirror); + + const MALYReaderTitleSpec *date_spec = 0; + if (i->title.date_spec.given) { + date_spec = &i->title.date_spec; + } else if (cmask.title.date_spec.given) { + date_spec = &cmask.title.date_spec; + } + if (date_spec && date_spec->enabled) { + m.titles.push_back (create_title (MALYTitle::Date, *date_spec, font, maskmirror, std::string (""))); + } + + const MALYReaderTitleSpec *serial_spec = 0; + if (i->title.serial_spec.given) { + serial_spec = &i->title.serial_spec; + } else if (cmask.title.serial_spec.given) { + serial_spec = &cmask.title.serial_spec; + } + if (serial_spec && serial_spec->enabled) { + m.titles.push_back (create_title (MALYTitle::Serial, *serial_spec, font, maskmirror, std::string (""))); + } + + for (auto t = i->title.string_titles.begin (); t != i->title.string_titles.end (); ++t) { + m.titles.push_back (create_title (MALYTitle::String, t->second, font, maskmirror, t->first)); + } + for (auto t = cmask.title.string_titles.begin (); t != cmask.title.string_titles.end (); ++t) { + m.titles.push_back (create_title (MALYTitle::String, t->second, font, maskmirror, t->first)); + } + + MALYReaderParametersData::Base base = i->parameters.base; + if (base == MALYReaderParametersData::BaseNotSet) { + base = cmask.parameters.base; + } + if (base == MALYReaderParametersData::BaseNotSet) { + base = MALYReaderParametersData::Center; + warn (tl::to_string (tr ("No structure placement given - using 'center' for mask: ")) + m.name); + } + + MALYReaderParametersData::Base array_base = i->parameters.array_base; + if (array_base == MALYReaderParametersData::BaseNotSet) { + array_base = cmask.parameters.array_base; + } + if (array_base == MALYReaderParametersData::BaseNotSet) { + array_base = MALYReaderParametersData::Center; + warn (tl::to_string (tr ("No array structure placement given - using 'center' for mask: ")) + m.name); + } + + for (auto sg = cmask.strgroups.begin (); sg != cmask.strgroups.end (); ++sg) { + for (auto s = sg->refs.begin (); s != sg->refs.end (); ++s) { + m.structures.push_back (create_structure (i->parameters, cmask.parameters, *s, sg->name, base, array_base)); + } + } + for (auto sg = i->strgroups.begin (); sg != i->strgroups.end (); ++sg) { + for (auto s = sg->refs.begin (); s != sg->refs.end (); ++s) { + m.structures.push_back (create_structure (i->parameters, cmask.parameters, *s, sg->name, base, array_base)); + } + } + + } +} + +MALYTitle +MALYReader::create_title (MALYTitle::Type type, const MALYReaderTitleSpec &data, MALYTitle::Font font, bool maskmirror, const std::string &string) +{ + MALYTitle title; + + title.transformation = db::DTrans (maskmirror ? db::DFTrans::m90 : db::DFTrans::r0) * data.trans; + title.width = data.width; + title.height = data.height; + title.pitch = data.pitch; + title.type = type; + title.font = font; + title.string = string; + + return title; +} + +MALYStructure +MALYReader::create_structure (const MALYReaderParametersData &mparam, const MALYReaderParametersData &cparam, const MALYReaderStrRefData &data, const std::string & /*strgroup_name*/, MALYReaderParametersData::Base base, MALYReaderParametersData::Base array_base) +{ + MALYStructure str; + + str.size = data.size; + str.dname = data.dname; + str.ename = data.ename; + str.mname = data.mname; + str.topcell = data.name; + str.nx = std::max (1, data.nx); + str.ny = std::max (1, data.ny); + str.dx = data.dx; + str.dy = data.dy; + str.layer = data.layer; + + str.path = resolve_path (mparam, data.file); + if (str.path.empty ()) { + str.path = resolve_path (cparam, data.file); + } + if (str.path.empty ()) { + // try any fail later ... + str.path = data.file; + } + + MALYReaderParametersData::Base eff_base = (data.nx > 1 || data.ny > 1) ? array_base : base; + + db::DPoint rp; + switch (eff_base) { + case MALYReaderParametersData::LowerLeft: + rp = data.size.p1 (); + break; + case MALYReaderParametersData::Center: + default: + // NOTE: the center implies the whole array's center in case of an AREF + rp = (data.size + data.size.moved (db::DVector (str.dx * (str.nx - 1), str.dy * (str.ny - 1)))).center (); + break; + case MALYReaderParametersData::Origin: + break; + } + + db::DCplxTrans mirr (mparam.maskmirror != cparam.maskmirror ? db::DFTrans::m90 : db::DFTrans::r0); + str.transformation = mirr * db::DCplxTrans (data.scale, 0.0, false, data.org) * db::DCplxTrans (db::DPoint () - rp); + + return str; +} + +std::string +MALYReader::resolve_path (const MALYReaderParametersData ¶m, const std::string &path) +{ + if (tl::is_absolute (path)) { + + return path; + + } else { + + // NOTE: we don't differentiate by file type here. Each root is used in the + // same way to find the actual file. + // Relative paths are always resolved relative to the MALY file. + + for (auto r = param.roots.begin (); r != param.roots.end (); ++r) { + + std::string p = tl::combine_path (r->second, path); + if (! tl::is_absolute (p)) { + p = tl::combine_path (tl::dirname (m_stream.source ()), p); + } + + if (tl::file_exists (p)) { + return p; + } + + } + + } + + return std::string (); +} + +void +MALYReader::do_read_maly_file (MALYData &data) +{ + tl::Extractor ex = read_record (); + if (! begin_section (ex, "MALY")) { + error (tl::to_string (tr ("Header expected ('BEGIN MALY')"))); + } + + std::string version; + ex.read_word (version, "."); + // TODO: what to do with version string? + + ex.expect_end (); + + while (read_maskset (data)) + ; + + ex = read_record (); + if (! end_section (ex)) { + error (tl::to_string (tr ("Terminator expected ('END MALY')"))); + } + + ex = read_record (); + if (! ex.at_end ()) { + error (tl::to_string (tr ("Records found past end of file"))); + } +} + +void +MALYReader::error (const std::string &msg) +{ + throw MALYReaderException (msg, m_last_record_line, m_stream.source ()); +} + +void +MALYReader::warn (const std::string &msg, int wl) +{ + if (warn_level () < wl) { + return; + } + + if (first_warning ()) { + tl::warn << tl::sprintf (tl::to_string (tr ("In file %s:")), m_stream.source ()); + } + + int ws = compress_warning (msg); + if (ws < 0) { + tl::warn << msg + << tl::to_string (tr (" (line=")) << m_last_record_line + << tl::to_string (tr (", file=")) << m_stream.source () + << ")"; + } else if (ws == 0) { + tl::warn << tl::to_string (tr ("... further warnings of this kind are not shown")); + } +} + +} + diff --git a/src/plugins/streamers/maly/db_plugin/dbMALYReader.h b/src/plugins/streamers/maly/db_plugin/dbMALYReader.h new file mode 100644 index 000000000..ea4feabbb --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/dbMALYReader.h @@ -0,0 +1,258 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbMALYReader +#define HDR_dbMALYReader + +#include "dbPluginCommon.h" +#include "dbNamedLayerReader.h" +#include "dbLayout.h" +#include "dbMALY.h" +#include "dbMALYFormat.h" +#include "dbStreamLayers.h" +#include "dbPropertiesRepository.h" + +#include "tlException.h" +#include "tlInternational.h" +#include "tlProgress.h" +#include "tlString.h" +#include "tlStream.h" + +#include +#include + +namespace db +{ + +/** + * @brief Generic base class of MALY reader exceptions + */ +class DB_PLUGIN_PUBLIC MALYReaderException + : public ReaderException +{ +public: + MALYReaderException (const std::string &msg, size_t l, const std::string &file) + : ReaderException (tl::sprintf (tl::to_string (tr ("%s (line=%ld, file=%s)")), msg, l, file)) + { } +}; + +/** + * @brief The MALY format stream reader + */ +class DB_PLUGIN_PUBLIC MALYReader + : public NamedLayerReader, + public MALYDiagnostics +{ +public: + typedef std::vector property_value_list; + + /** + * @brief Construct a stream reader object + * + * @param s The stream delegate from which to read stream data from + */ + MALYReader (tl::InputStream &s); + + /** + * @brief Destructor + */ + ~MALYReader (); + + /** + * @brief Tests, if the stream is a valid MALY file + * + * This method can be used for the format detection + */ + bool test (); + + /** + * @brief The basic read method + * + * This method will read the stream data and translate this to + * insert calls into the layout object. This will not do much + * on the layout object beside inserting the objects. + * A set of options can be specified with the LoadLayoutOptions + * object. + * The returned map will contain all layers, the passed + * ones and the newly created ones. + * + * @param layout The layout object to write to + * @param map The LayerMap object + * @param create true, if new layers should be created + * @return The LayerMap object that tells where which layer was loaded + */ + virtual const LayerMap &read (db::Layout &layout, const LoadLayoutOptions &options); + + /** + * @brief The basic read method (without mapping) + * + * This method will read the stream data and translate this to + * insert calls into the layout object. This will not do much + * on the layout object beside inserting the objects. + * This version will read all input layers and return a map + * which tells which MALY layer has been read into which logical + * layer. + * + * @param layout The layout object to write to + * @return The LayerMap object + */ + virtual const LayerMap &read (db::Layout &layout); + + /** + * @brief Format + */ + virtual const char *format () const { return "MALY"; } + + /** + * @brief Issue an error with positional information + * + * Reimplements MALYDiagnostics + */ + virtual void error (const std::string &txt); + + /** + * @brief Issue a warning with positional information + * + * Reimplements MALYDiagnostics + */ + virtual void warn (const std::string &txt, int wl = 1); + + /** + * @brief Reads the MALY file into a MALYData structure + * + * This method is provided for test purposes mainly. + */ + MALYData read_maly_file (); + +private: + struct MALYReaderTitleSpec + { + MALYReaderTitleSpec () + : given (false), enabled (false), width (1.0), height (1.0), pitch (1.0) + { } + + bool given; + bool enabled; + db::DTrans trans; + double width, height, pitch; + }; + + struct MALYReaderTitleData + { + MALYReaderTitleData () + { } + + MALYReaderTitleSpec date_spec; + MALYReaderTitleSpec serial_spec; + std::list > string_titles; + }; + + struct MALYReaderParametersData + { + MALYReaderParametersData () + : base (BaseNotSet), array_base (BaseNotSet), masksize (0.0), maskmirror (false), font (MALYTitle::FontNotSet) + { } + + enum Base + { + BaseNotSet, + Origin, + Center, + LowerLeft + }; + + Base base; + Base array_base; + double masksize; + bool maskmirror; + MALYTitle::Font font; + std::list > roots; + }; + + struct MALYReaderStrRefData + { + MALYReaderStrRefData () + : layer (-1), scale (1.0), nx (1), ny (1), dx (0.0), dy (0.0) + { } + + std::string file; + std::string name; + std::string dname, ename, mname; + int layer; + db::DVector org; + db::DBox size; + double scale; + int nx, ny; + double dx, dy; + }; + + struct MALYReaderStrGroupData + { + std::string name; + std::list refs; + }; + + struct MALYReaderMaskData + { + std::string name; + MALYReaderParametersData parameters; + MALYReaderTitleData title; + std::list strgroups; + }; + + tl::TextInputStream m_stream; + tl::AbsoluteProgress m_progress; + double m_dbu; + unsigned int m_last_record_line; + std::string m_record; + std::string m_record_returned; + std::list m_sections; + + void import_data (db::Layout &layout, const MALYData &data); + void create_metadata (db::Layout &layout, const MALYData &data); + tl::Extractor read_record (); + void unget_record (); + std::string read_record_internal (); + void do_read_maly_file (MALYData &data); + bool read_maskset (MALYData &data); + void read_mask (MALYReaderMaskData &mask); + void read_title (MALYReaderTitleData &mask); + void read_parameter (MALYReaderParametersData &mask); + void read_strgroup (MALYReaderStrGroupData &mask); + db::DTrans extract_title_trans (tl::Extractor &ex); + void extract_title_trans (tl::Extractor &ex, MALYReaderTitleSpec &spec); + bool begin_section (tl::Extractor &ex, const std::string &name = std::string ()); + bool end_section (tl::Extractor &ex); + void skip_section (); + MALYTitle create_title (MALYTitle::Type type, const MALYReaderTitleSpec &data, MALYTitle::Font font, bool maskmirror, const std::string &string); + void create_masks (const MALYReaderMaskData &cmask, const std::list &masks, MALYData &data); + MALYStructure create_structure (const MALYReaderParametersData &mparam, const MALYReaderParametersData &cparam, const MALYReaderStrRefData &data, const std::string &strgroup_name, MALYReaderParametersData::Base base, MALYReaderParametersData::Base array_base); + std::string resolve_path (const MALYReaderParametersData ¶m, const std::string &path); + static MALYReaderParametersData::Base string_to_base (const std::string &string); +}; + +} + +#endif + diff --git a/src/plugins/streamers/maly/db_plugin/db_plugin.pro b/src/plugins/streamers/maly/db_plugin/db_plugin.pro new file mode 100644 index 000000000..60b947560 --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/db_plugin.pro @@ -0,0 +1,15 @@ + +TARGET = maly +DESTDIR = $$OUT_PWD/../../../../db_plugins + +include($$PWD/../../../db_plugin.pri) + +HEADERS = \ + dbMALY.h \ + dbMALYReader.h \ + dbMALYFormat.h \ + +SOURCES = \ + dbMALY.cc \ + dbMALYReader.cc \ + gsiDeclDbMALY.cc \ diff --git a/src/plugins/streamers/maly/db_plugin/gsiDeclDbMALY.cc b/src/plugins/streamers/maly/db_plugin/gsiDeclDbMALY.cc new file mode 100644 index 000000000..fa2a9cdcb --- /dev/null +++ b/src/plugins/streamers/maly/db_plugin/gsiDeclDbMALY.cc @@ -0,0 +1,146 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbMALY.h" +#include "dbMALYReader.h" +#include "dbLoadLayoutOptions.h" +#include "dbSaveLayoutOptions.h" +#include "gsiDecl.h" + +namespace gsi +{ + +// --------------------------------------------------------------- +// gsi Implementation of specific methods + +static void set_maly_dbu (db::LoadLayoutOptions *options, double dbu) +{ + options->get_options ().dbu = dbu; +} + +static double get_maly_dbu (const db::LoadLayoutOptions *options) +{ + return options->get_options ().dbu; +} + +static void set_layer_map (db::LoadLayoutOptions *options, const db::LayerMap &lm, bool f) +{ + options->get_options ().layer_map = lm; + options->get_options ().create_other_layers = f; +} + +static void set_layer_map1 (db::LoadLayoutOptions *options, const db::LayerMap &lm) +{ + options->get_options ().layer_map = lm; +} + +static db::LayerMap &get_layer_map (db::LoadLayoutOptions *options) +{ + return options->get_options ().layer_map; +} + +static void select_all_layers (db::LoadLayoutOptions *options) +{ + options->get_options ().layer_map = db::LayerMap (); + options->get_options ().create_other_layers = true; +} + +static bool create_other_layers (const db::LoadLayoutOptions *options) +{ + return options->get_options ().create_other_layers; +} + +static void set_create_other_layers (db::LoadLayoutOptions *options, bool l) +{ + options->get_options ().create_other_layers = l; +} + +// extend lay::LoadLayoutOptions with the MALY options +static +gsi::ClassExt maly_reader_options ( + gsi::method_ext ("maly_set_layer_map", &set_layer_map, gsi::arg ("map"), gsi::arg ("create_other_layers"), + "@brief Sets the layer map\n" + "This sets a layer mapping for the reader. The layer map allows selection and translation of the original layers, for example to assign layer/datatype numbers to the named layers.\n" + "@param map The layer map to set.\n" + "@param create_other_layers The flag indicating whether other layers will be created as well. Set to false to read only the layers in the layer map.\n" + "\n" + "Layer maps can also be used to map the named MALY mask layers to GDS layer/datatypes.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_layer_map=", &set_layer_map1, gsi::arg ("map"), + "@brief Sets the layer map\n" + "This sets a layer mapping for the reader. Unlike \\maly_set_layer_map, the 'create_other_layers' flag is not changed.\n" + "@param map The layer map to set.\n" + "\n" + "Layer maps can also be used to map the named MALY mask layers to GDS layer/datatypes.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_select_all_layers", &select_all_layers, + "@brief Selects all layers and disables the layer map\n" + "\n" + "This disables any layer map and enables reading of all layers.\n" + "New layers will be created when required.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_layer_map", &get_layer_map, + "@brief Gets the layer map\n" + "@return A reference to the layer map\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_create_other_layers?", &create_other_layers, + "@brief Gets a value indicating whether other layers shall be created\n" + "@return True, if other layers will be created.\n" + "This attribute acts together with a layer map (see \\maly_layer_map=). Layers not listed in this map are created as well when " + "\\maly_create_other_layers? is true. Otherwise they are ignored.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_create_other_layers=", &set_create_other_layers, gsi::arg ("create"), + "@brief Specifies whether other layers shall be created\n" + "@param create True, if other layers will be created.\n" + "See \\maly_create_other_layers? for a description of this attribute.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_dbu=", &set_maly_dbu, gsi::arg ("dbu"), + "@brief Specifies the database unit which the reader uses and produces\n" + "The database unit is the final resolution of the produced layout. This physical resolution is usually " + "defined by the layout system - GDS for example typically uses 1nm (maly_dbu=0.001).\n" + "All geometry in the MALY pattern files is brought to the database unit by scaling.\n" + "\n" + "This method has been added in version 0.30.2." + ) + + gsi::method_ext ("maly_dbu", &get_maly_dbu, + "@brief Specifies the database unit which the reader uses and produces\n" + "See \\maly_dbu= method for a description of this property.\n" + "\n" + "This method has been added in version 0.30.2." + ), + "" +); + +} + diff --git a/src/plugins/streamers/maly/lay_plugin/MALYReaderOptionPage.ui b/src/plugins/streamers/maly/lay_plugin/MALYReaderOptionPage.ui new file mode 100644 index 000000000..46ec332d2 --- /dev/null +++ b/src/plugins/streamers/maly/lay_plugin/MALYReaderOptionPage.ui @@ -0,0 +1,183 @@ + + + MALYReaderOptionPage + + + + 0 + 0 + 584 + 530 + + + + Form + + + + + + Input Options + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Micron + + + + + + + Database unit + + + + + + + This is the database unit of the resulting layout. Mask pattern with a different grid are adapted to this database unit through scaling. + + + true + + + + + + + + + + + 1 + 1 + + + + + + + Layer Subset And Layer Mapping + + + false + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + Read all layers (additionally to the ones in the mapping table) + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + + + + lay::LayerMappingWidget + QFrame +

layLayerMappingWidget.h
+ 1 + + enable_all_layers(bool) + + + + + dbu_le + read_all_cbx + + + + + layer_map + enable_all_layers(bool) + read_all_cbx + setChecked(bool) + + + 122 + 186 + + + 109 + 147 + + + + + diff --git a/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.cc b/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.cc new file mode 100644 index 000000000..c606f8a92 --- /dev/null +++ b/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.cc @@ -0,0 +1,110 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbMALY.h" +#include "dbMALYReader.h" +#include "dbLoadLayoutOptions.h" +#include "layMALYReaderPlugin.h" +#include "ui_MALYReaderOptionPage.h" +#include "gsiDecl.h" + +#include +#include + +namespace lay +{ + +// --------------------------------------------------------------- +// MALYReaderOptionPage definition and implementation + +MALYReaderOptionPage::MALYReaderOptionPage (QWidget *parent) + : StreamReaderOptionsPage (parent) +{ + mp_ui = new Ui::MALYReaderOptionPage (); + mp_ui->setupUi (this); +} + +MALYReaderOptionPage::~MALYReaderOptionPage () +{ + delete mp_ui; + mp_ui = 0; +} + +void +MALYReaderOptionPage::setup (const db::FormatSpecificReaderOptions *o, const db::Technology * /*tech*/) +{ + static const db::MALYReaderOptions default_options; + const db::MALYReaderOptions *options = dynamic_cast (o); + if (!options) { + options = &default_options; + } + + mp_ui->dbu_le->setText (tl::to_qstring (tl::to_string (options->dbu))); + mp_ui->layer_map->set_layer_map (options->layer_map); + mp_ui->read_all_cbx->setChecked (options->create_other_layers); +} + +void +MALYReaderOptionPage::commit (db::FormatSpecificReaderOptions *o, const db::Technology * /*tech*/) +{ + db::MALYReaderOptions *options = dynamic_cast (o); + if (options) { + + tl::from_string_ext (tl::to_string (mp_ui->dbu_le->text ()), options->dbu); + if (options->dbu > 1000.0 || options->dbu < 1e-9) { + throw tl::Exception (tl::to_string (QObject::tr ("Invalid value for database unit"))); + } + + options->layer_map = mp_ui->layer_map->get_layer_map (); + options->create_other_layers = mp_ui->read_all_cbx->isChecked (); + + } +} + +// --------------------------------------------------------------- +// MALYReaderPluginDeclaration definition and implementation + +class MALYReaderPluginDeclaration + : public StreamReaderPluginDeclaration +{ +public: + MALYReaderPluginDeclaration () + : StreamReaderPluginDeclaration (db::MALYReaderOptions ().format_name ()) + { + // .. nothing yet .. + } + + StreamReaderOptionsPage *format_specific_options_page (QWidget *parent) const + { + return new MALYReaderOptionPage (parent); + } + + db::FormatSpecificReaderOptions *create_specific_options () const + { + return new db::MALYReaderOptions (); + } +}; + +static tl::RegisteredClass plugin_decl (new lay::MALYReaderPluginDeclaration (), 10000, "MALYReader"); + +} + diff --git a/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.h b/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.h new file mode 100644 index 000000000..9de96c41a --- /dev/null +++ b/src/plugins/streamers/maly/lay_plugin/layMALYReaderPlugin.h @@ -0,0 +1,59 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_layMALYReaderPlugin_h +#define HDR_layMALYReaderPlugin_h + +#include "layStream.h" +#include + +namespace Ui +{ + class MALYReaderOptionPage; +} + +namespace lay +{ + +class MALYReaderOptionPage + : public StreamReaderOptionsPage +{ +Q_OBJECT + +public: + MALYReaderOptionPage (QWidget *parent); + ~MALYReaderOptionPage (); + + void setup (const db::FormatSpecificReaderOptions *options, const db::Technology *tech); + void commit (db::FormatSpecificReaderOptions *options, const db::Technology *tech); + +private: + Ui::MALYReaderOptionPage *mp_ui; +}; + +} + +#endif + + diff --git a/src/plugins/streamers/maly/lay_plugin/lay_plugin.pro b/src/plugins/streamers/maly/lay_plugin/lay_plugin.pro new file mode 100644 index 000000000..0677a2552 --- /dev/null +++ b/src/plugins/streamers/maly/lay_plugin/lay_plugin.pro @@ -0,0 +1,22 @@ + +TARGET = maly_ui +DESTDIR = $$OUT_PWD/../../../../lay_plugins + +include($$PWD/../../../lay_plugin.pri) + +INCLUDEPATH += $$PWD/../db_plugin +DEPENDPATH += $$PWD/../db_plugin +LIBS += -L$$DESTDIR/../db_plugins -lmaly + +!isEmpty(RPATH) { + QMAKE_RPATHDIR += $$RPATH/db_plugins +} + +HEADERS = \ + layMALYReaderPlugin.h \ + +SOURCES = \ + layMALYReaderPlugin.cc \ + +FORMS = \ + MALYReaderOptionPage.ui \ diff --git a/src/plugins/streamers/maly/maly.pro b/src/plugins/streamers/maly/maly.pro new file mode 100644 index 000000000..0a2501ae9 --- /dev/null +++ b/src/plugins/streamers/maly/maly.pro @@ -0,0 +1,10 @@ + +TEMPLATE = subdirs + +SUBDIRS = db_plugin unit_tests +unit_tests.depends += db_plugin + +!equals(HAVE_QT, "0") { + SUBDIRS += lay_plugin + lay_plugin.depends += db_plugin +} diff --git a/src/plugins/streamers/maly/unit_tests/dbMALYReaderTests.cc b/src/plugins/streamers/maly/unit_tests/dbMALYReaderTests.cc new file mode 100644 index 000000000..95a6ff447 --- /dev/null +++ b/src/plugins/streamers/maly/unit_tests/dbMALYReaderTests.cc @@ -0,0 +1,144 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbMALYReader.h" +#include "dbLayoutDiff.h" +#include "dbWriter.h" +#include "dbTestSupport.h" +#include "tlUnitTest.h" + +#include + +static void run_test (tl::TestBase *_this, const std::string &base, const char *file, const char *file_au, const char *map = 0, double dbu = 0.001) +{ + db::MALYReaderOptions *opt = new db::MALYReaderOptions(); + opt->dbu = dbu; + + db::LayerMap lm; + if (map) { + unsigned int ln = 0; + tl::Extractor ex (map); + while (! ex.at_end ()) { + std::string n; + int l; + ex.read_word_or_quoted (n); + ex.test (":"); + ex.read (l); + ex.test (","); + lm.map (n, ln++, db::LayerProperties (l, 0)); + } + opt->layer_map = lm; + opt->create_other_layers = true; + } + + db::LoadLayoutOptions options; + options.set_options (opt); + + db::Manager m (false); + db::Layout layout (&m); + + { + std::string fn (base); + fn += "/maly/"; + fn += file; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (layout, options); + } + + std::string fn_au (base); + fn_au += "/maly/"; + fn_au += file_au; + + db::compare_layouts (_this, layout, fn_au, db::WriteOAS); +} + +TEST(1_Basic) +{ + std::string fn (tl::testdata ()); + fn += "/maly/MALY_test1.maly"; + + tl::InputStream stream (fn); + db::MALYReader reader (stream); + + db::MALYData data = reader.read_maly_file (); + + EXPECT_EQ (data.to_string (), + "Mask A\n" + " Size 127000\n" + " Title \"\" m90 0,-50 1,1,1 [Standard]\n" + " Title \"MaskA1\" m90 50,50 1,1,1 [Standard]\n" + " Title \"WITH \"QUOTES\"\" r270 -50,0 1,1,1 [Standard]\n" + " Ref A1.oas{CHIP_A}(1) (0,0;10,10) m90 *1 20,0\n" + " Ref A2.oas{CHIP_A}(2) ename(e001) dname(d001) (0,0;50,50) m90 *0.8 20,0 [2x5,1x2]\n" + " Ref B3.oas{CHIP_A}(2) (0,0;12,12) m90 *1 20,0" + ) +} + +static std::string run_test_with_error (tl::TestBase * /*_this*/, const std::string &file) +{ + std::string fn (tl::testdata ()); + fn += "/maly/"; + fn += file; + + tl::InputStream stream (fn); + db::MALYReader reader (stream); + + try { + reader.read_maly_file (); + tl_assert (false); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + return ex.msg (); + } +} + +TEST(2_Errors) +{ + EXPECT_EQ (run_test_with_error (_this, "MALY_test2a.maly").find ("Line break inside quoted string (line=17,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2b.maly").find ("/*...*/ comment not closed (line=43,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2c.maly").find ("Expected value STANDARD or NATIVE for FONT (line=7,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2d.maly").find ("Unknown base specification: NOVALIDBASE (line=8,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2e.maly").find ("Expected end of text here: NOVALIDKEY .. (line=15,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2f.maly").find ("Expected 'Y' or 'NONE' for MIRROR spec (line=15,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2g.maly").find ("Expected end of text here: UNEXPECTED (line=20,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2h.maly").find ("Expected value Y or NONE for MASKMIRROR (line=23,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2i.maly").find ("Expected end of text here: UNEXPECTED (line=29,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2j.maly").find ("Expected end of text here: NOVALIDKEY .. (line=30,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2k.maly").find ("Expected a real number here: SCALE 0.80 .. (line=31,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2l.maly").find ("Expected 'PARAMETER' here: CMASK (line=19,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2m.maly").find ("Expected 'CMASK' here: TITLE (line=18,"), size_t (0)); + EXPECT_EQ (run_test_with_error (_this, "MALY_test2n.maly").find ("Header expected ('BEGIN MALY') (line=2, "), size_t (0)); +} + +TEST(10_BasicLayout) +{ + run_test (_this, tl::testdata (), "MALY_test10.maly", "maly_test10_au.oas"); + run_test (_this, tl::testdata (), "MALY_test10.maly", "maly_test10_lm_au.oas", "A: 10, B: 11, C: 12, D: 13"); +} + +TEST(11_Titles) +{ + run_test (_this, tl::testdata (), "MALY_test11.maly", "maly_test11_au.oas"); +} + diff --git a/src/plugins/streamers/maly/unit_tests/unit_tests.pro b/src/plugins/streamers/maly/unit_tests/unit_tests.pro new file mode 100644 index 000000000..a9d89c591 --- /dev/null +++ b/src/plugins/streamers/maly/unit_tests/unit_tests.pro @@ -0,0 +1,19 @@ + +DESTDIR_UT = $$OUT_PWD/../../../.. + +TARGET = maly_tests + +include($$PWD/../../../../lib_ut.pri) + +SOURCES = \ + dbMALYReaderTests.cc + +INCLUDEPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common +DEPENDPATH += $$LAY_INC $$TL_INC $$DB_INC $$GSI_INC $$PWD/../db_plugin $$PWD/../../../common + +LIBS += -L$$DESTDIR_UT -lklayout_db -lklayout_tl -lklayout_gsi + +PLUGINPATH = $$OUT_PWD/../../../../db_plugins +QMAKE_RPATHDIR += $$PLUGINPATH + +LIBS += -L$$PLUGINPATH -lmaly diff --git a/src/plugins/streamers/streamers.pro b/src/plugins/streamers/streamers.pro index 5d7bbf3ad..8bfcea845 100644 --- a/src/plugins/streamers/streamers.pro +++ b/src/plugins/streamers/streamers.pro @@ -6,4 +6,3 @@ SUBDIR_LIST = $$files($$PWD/*) SUBDIR_LIST -= $$PWD/streamers.pro SUBDIRS = $$SUBDIR_LIST - diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc index b3b0a0e31..7a29a7ff0 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc @@ -661,15 +661,15 @@ D25ViewWidget::enter (const db::RecursiveShapeIterator *iter, double zstart, dou void D25ViewWidget::entry (const db::Region &data, double dbu, double zstart, double zstop) { - // try to establish a default color from the region's origin if required - const db::RecursiveShapeIterator *iter = 0; const db::OriginalLayerRegion *original = dynamic_cast (data.delegate ()); if (original) { - iter = original->iter (); + // try to establish a default color from the region's origin if required + auto it = original->begin_iter (); + enter (&it.first, zstart, zstop); + } else { + enter (0, zstart, zstop); } - enter (iter, zstart, zstop); - tl::AbsoluteProgress progress (tl::to_string (tr ("Rendering ..."))); render_region (progress, *m_layers.back ().vertex_chunk, *m_layers.back ().normals_chunk, *m_layers.back ().line_chunk, data, dbu, db::CplxTrans (dbu).inverted () * m_bbox, zstart, zstop); } @@ -677,15 +677,15 @@ D25ViewWidget::entry (const db::Region &data, double dbu, double zstart, double void D25ViewWidget::entry (const db::Edges &data, double dbu, double zstart, double zstop) { - // try to establish a default color from the region's origin if required - const db::RecursiveShapeIterator *iter = 0; const db::OriginalLayerEdges *original = dynamic_cast (data.delegate ()); if (original) { - iter = original->iter (); + // try to establish a default color from the region's origin if required + auto it = original->begin_iter (); + enter (&it.first, zstart, zstop); + } else { + enter (0, zstart, zstop); } - enter (iter, zstart, zstop); - tl::AbsoluteProgress progress (tl::to_string (tr ("Rendering ..."))); render_edges (progress, *m_layers.back ().vertex_chunk, *m_layers.back ().normals_chunk, *m_layers.back ().line_chunk, data, dbu, db::CplxTrans (dbu).inverted () * m_bbox, zstart, zstop); } @@ -693,15 +693,15 @@ D25ViewWidget::entry (const db::Edges &data, double dbu, double zstart, double z void D25ViewWidget::entry (const db::EdgePairs &data, double dbu, double zstart, double zstop) { - // try to establish a default color from the region's origin if required - const db::RecursiveShapeIterator *iter = 0; const db::OriginalLayerEdgePairs *original = dynamic_cast (data.delegate ()); if (original) { - iter = original->iter (); + // try to establish a default color from the region's origin if required + auto it = original->begin_iter (); + enter (&it.first, zstart, zstop); + } else { + enter (0, zstart, zstop); } - enter (iter, zstart, zstop); - tl::AbsoluteProgress progress (tl::to_string (tr ("Rendering ..."))); render_edge_pairs (progress, *m_layers.back ().vertex_chunk, *m_layers.back ().normals_chunk, *m_layers.back ().line_chunk, data, dbu, db::CplxTrans (dbu).inverted () * m_bbox, zstart, zstop); } diff --git a/src/pya/pya/pyaCallables.cc b/src/pya/pya/pyaCallables.cc index f9fd2c75f..27f5fa790 100644 --- a/src/pya/pya/pyaCallables.cc +++ b/src/pya/pya/pyaCallables.cc @@ -49,7 +49,7 @@ pya_object_deallocate (PyObject *self) // may trigger a GC (https://github.com/KLayout/klayout/issues/1054). // According to the comments this may be turned into a release mode assertion, so // we better work around it. - ++self->ob_refcnt; + Py_IncRef(self); // Mute Python warnings in debug case PyObject_GC_UnTrack (self); @@ -413,6 +413,8 @@ match_method (int mid, PyObject *self, PyObject *args, PyObject *kwargs, bool st if (! arg) { is_valid = a->spec ()->has_default (); } else if (test_arg (*a, arg.get (), false /*strict*/, false /*object substitution*/)) { + sc += 100; + } else if (test_arg (*a, arg.get (), true /*loose*/, false /*object substitution*/)) { ++sc; } else if (test_arg (*a, arg.get (), true /*loose*/, true /*object substitution*/)) { // non-scoring match diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index 44afc5917..d7db77eee 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -440,7 +440,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo obj = clsact->create_from_adapted (obj); } - // we wil own the new object + // we will own the new object pass_obj = true; } @@ -488,6 +488,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo PYAObjectBase *new_object = PYAObjectBase::from_pyobject_unsafe (new_pyobject); new (new_object) PYAObjectBase (clsact, new_pyobject); new_object->set (obj, pass_obj, is_const, can_destroy); + return new_pyobject; } diff --git a/src/pya/pya/pyaMarshal.cc b/src/pya/pya/pyaMarshal.cc index 7a50c85e7..0326f2e18 100644 --- a/src/pya/pya/pyaMarshal.cc +++ b/src/pya/pya/pyaMarshal.cc @@ -1232,7 +1232,7 @@ struct test_arg_func return; } - if (! (cls_decl == acls || (loose && (cls_decl->is_derived_from (atype.cls ()) || cls_decl->can_convert_to (atype.cls ()))))) { + if (! (cls_decl == acls || (loose && (cls_decl->is_derived_from (atype.cls ()) || (object_substitution && cls_decl->can_convert_to (atype.cls ())))))) { *ret = false; return; } diff --git a/src/pya/pya/pyaObject.cc b/src/pya/pya/pyaObject.cc index 63f1e8517..1805b68b8 100644 --- a/src/pya/pya/pyaObject.cc +++ b/src/pya/pya/pyaObject.cc @@ -238,9 +238,16 @@ PYAObjectBase::object_destroyed () detach (); - // NOTE: this may delete "this"! - if (!prev_owner) { - Py_DECREF (py_object ()); + if (! prev_owner) { + const gsi::ClassBase *cls = cls_decl (); + if (cls && cls->is_managed ()) { + // If the object was owned on C++ side before, we need to decrement the + // reference count to reflect the fact, that there no longer is an external + // owner. + // NOTE: this may delete "this", hence we return + Py_DECREF (py_object ()); + return; + } } } @@ -249,31 +256,44 @@ PYAObjectBase::object_destroyed () void PYAObjectBase::release () { + // "release" means to release ownership of the C++ object on the C++ side. + // In other words: to transfer ownership to the script side. Specifically to + // transfer it to the Python domain. + // If the object is managed we first reset the ownership of all other clients // and then make us the owner const gsi::ClassBase *cls = cls_decl (); if (cls && cls->is_managed ()) { void *o = obj (); if (o) { + // NOTE: "keep" means "move ownership of the C++ object to C++". In other words, + // release ownership of the C++ object on script side. cls->gsi_object (o)->keep (); + if (! m_owned) { + // We have to *decrement* the reference count as now there is no other entity + // holding a reference to this Python object. + // NOTE: this may delete "this", hence we return + m_owned = true; + Py_DECREF (py_object ()); + return; + } } } - // NOTE: this is fairly dangerous - if (!m_owned) { - m_owned = true; - // NOTE: this may delete "this"! TODO: this should not happen. Can we assert that somehow? - Py_DECREF (py_object ()); - } + m_owned = true; } void PYAObjectBase::keep_internal () { if (m_owned) { + // "keep" means to transfer ownership of the C++ object to C++ side, while + // "m_owned" refers to ownership on the Python side. So if we perform this + // transfer, we need to reflect the fact that there is another entity holding + // a reference. Py_INCREF (py_object ()); - m_owned = false; } + m_owned = false; } void @@ -284,9 +304,11 @@ PYAObjectBase::keep () void *o = obj (); if (o) { if (cls->is_managed ()) { + // dispatch the keep notification - this will call "keep_internal" through the + // event handler (StatusChangedListener) cls->gsi_object (o)->keep (); } else { - keep_internal (); + m_owned = false; } } } @@ -341,16 +363,18 @@ PYAObjectBase::set (void *obj, bool owned, bool const_ref, bool can_destroy) if (cls->is_managed ()) { gsi::ObjectBase *gsi_object = cls->gsi_object (m_obj); - // Consider the case of "keep inside constructor" if (gsi_object->already_kept ()) { - keep_internal (); + // Consider the case of "keep inside constructor" + m_owned = false; + } + if (! m_owned) { + // "m_owned = false" means ownership of the C++ object is on C++ side, + // and not on script side. In that case, we need to increment the + // reference count to reflect the fact that there is an external owner. + Py_INCREF (py_object ()); } gsi_object->status_changed_event ().add (mp_listener, &StatusChangedListener::object_status_changed); } - - if (!m_owned) { - Py_INCREF (py_object ()); - } } // TODO: a static (singleton) instance is not thread-safe @@ -587,7 +611,7 @@ PYAObjectBase::obj () throw tl::Exception (tl::to_string (tr ("Object has been destroyed already"))); } else { // delayed creation of a detached C++ object .. - set(cls_decl ()->create (), true, false, true); + set (cls_decl ()->create (), true, false, true); } } diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi index 989021861..703445e19 100644 --- a/src/pymod/distutils_src/klayout/dbcore.pyi +++ b/src/pymod/distutils_src/klayout/dbcore.pyi @@ -11620,7 +11620,7 @@ class DEdgePair: ... ... -class DEdgePairWithProperties(EdgePair): +class DEdgePairWithProperties(DEdgePair): r""" @brief A DEdgePair object with properties attached. This class represents a combination of a DEdgePair object an user properties. User properties are stored in form of a properties ID. Convenience methods are provided to manipulate or retrieve user properties directly. @@ -11685,7 +11685,7 @@ class DEdgePairWithProperties(EdgePair): @brief Returns a string representing the polygon """ ... - def _assign(self, other: EdgePair) -> None: + def _assign(self, other: DEdgePair) -> None: r""" @brief Assigns another object to self """ @@ -15645,8 +15645,7 @@ class DText: Setter: @brief Sets the horizontal alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ size: float r""" @@ -15682,8 +15681,7 @@ class DText: Setter: @brief Sets the vertical alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ x: float r""" @@ -25368,13 +25366,15 @@ class EdgePairs(ShapeCollection): @brief Creates a copy of self """ ... - def __getitem__(self, n: int) -> EdgePair: + def __getitem__(self, n: int) -> Any: r""" @brief Returns the nth edge pair This method returns nil if the index is out of range. It is available for flat edge pairs only - i.e. those for which \has_valid_edge_pairs? is true. Use \flatten to explicitly flatten an edge pair collection. The \each iterator is the more general approach to access the edge pairs. + + Since version 0.30.1, this method returns a \EdgePairWithProperties object. """ ... def __iadd__(self, other: EdgePairs) -> EdgePairs: @@ -29131,7 +29131,7 @@ class Edges(ShapeCollection): @brief Creates a copy of self """ ... - def __getitem__(self, n: int) -> Edge: + def __getitem__(self, n: int) -> Any: r""" @brief Returns the nth edge of the collection @@ -29139,6 +29139,8 @@ class Edges(ShapeCollection): This method returns the raw edge (not merged edges, even if merged semantics is enabled). The \each iterator is the more general approach to access the edges. + + Since version 0.30.1, this method returns an \EdgeWithProperties object. """ ... def __iadd__(self, other: Edges) -> Edges: @@ -34943,11 +34945,11 @@ class Instance: Starting with version 0.25 the displacement is of vector type. Setter: - @brief Sets the displacement vector for the 'a' axis in micrometer units + @brief Sets the displacement vector for the 'a' axis - Like \a= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally. + If the instance was not an array instance before it is made one. - This method has been introduced in version 0.25. + This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type. """ b: Vector r""" @@ -35490,7 +35492,7 @@ class Instance: r""" @brief Gets the layout this instance is contained in - This method has been introduced in version 0.22. + This const version of the method has been introduced in version 0.25. """ ... @overload @@ -35498,7 +35500,7 @@ class Instance: r""" @brief Gets the layout this instance is contained in - This const version of the method has been introduced in version 0.25. + This method has been introduced in version 0.22. """ ... def pcell_declaration(self) -> PCellDeclaration_Native: @@ -47741,17 +47743,17 @@ class Netlist: @overload def circuit_by_cell_index(self, cell_index: int) -> Circuit: r""" - @brief Gets the circuit object for a given cell index (const version). + @brief Gets the circuit object for a given cell index. If the cell index is not valid or no circuit is registered with this index, nil is returned. - - This constness variant has been introduced in version 0.26.8. """ ... @overload def circuit_by_cell_index(self, cell_index: int) -> Circuit: r""" - @brief Gets the circuit object for a given cell index. + @brief Gets the circuit object for a given cell index (const version). If the cell index is not valid or no circuit is registered with this index, nil is returned. + + This constness variant has been introduced in version 0.26.8. """ ... @overload @@ -47785,7 +47787,6 @@ class Netlist: @brief Gets the circuit objects for a given name filter (const version). The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern. - This constness variant has been introduced in version 0.26.8. """ ... @@ -58443,7 +58444,7 @@ class Region(ShapeCollection): @brief Creates a copy of self """ ... - def __getitem__(self, n: int) -> Polygon: + def __getitem__(self, n: int) -> Any: r""" @brief Returns the nth polygon of the region @@ -58451,6 +58452,8 @@ class Region(ShapeCollection): This method returns the raw polygon (not merged polygons, even if merged semantics is enabled). The \each iterator is the more general approach to access the polygons. + + Since version 0.30.1, this method returns a \PolygonWithProperties object. """ ... def __iadd__(self, other: Region) -> Region: @@ -63229,11 +63232,10 @@ class Shape: Starting with version 0.23, this method returns nil, if the shape does not represent an edge. Setter: - @brief Replaces the shape by the given edge - This method replaces the shape by the given edge. This method can only be called for editable layouts. It does not change the user properties of the shape. - Calling this method will invalidate any iterators. It should not be called inside a loop iterating over shapes. + @brief Replaces the shape by the given edge (in micrometer units) + This method replaces the shape by the given edge, like \edge= with a \Edge argument does. This version translates the edge from micrometer units to database units internally. - This method has been introduced in version 0.22. + This method has been introduced in version 0.25. """ edge_pair: Any r""" @@ -70138,13 +70140,15 @@ class Texts(ShapeCollection): @brief Creates a copy of self """ ... - def __getitem__(self, n: int) -> Text: + def __getitem__(self, n: int) -> Any: r""" @brief Returns the nth text This method returns nil if the index is out of range. It is available for flat texts only - i.e. those for which \has_valid_texts? is true. Use \flatten to explicitly flatten an text collection. The \each iterator is the more general approach to access the texts. + + Since version 0.30.1, this method returns a \TextWithProperties object. """ ... def __iadd__(self, other: Texts) -> Texts: diff --git a/src/pymod/distutils_src/klayout/rdbcore.pyi b/src/pymod/distutils_src/klayout/rdbcore.pyi index 237cf2d15..73e6d9688 100644 --- a/src/pymod/distutils_src/klayout/rdbcore.pyi +++ b/src/pymod/distutils_src/klayout/rdbcore.pyi @@ -655,6 +655,14 @@ class RdbItem: """ ... @overload + def add_value(self, value: db.DText) -> None: + r""" + @brief Adds a text object to the values of this item + @param value The text to add. + This method has been introduced in version 0.30.1 to support text objects with properties. + """ + ... + @overload def add_value(self, value: float) -> None: r""" @brief Adds a numeric value to the values of this item diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index 7979bc6cc..77faa3d88 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -539,6 +539,8 @@ private: if (arg == Qundef) { is_valid = a->spec ()->has_default (); } else if (test_arg (*a, arg, false /*strict*/, false /*with object substitution*/)) { + sc += 100; + } else if (test_arg (*a, arg, true /*loose*/, false /*with object substitution*/)) { ++sc; } else if (test_arg (*a, arg, true /*loose*/, true /*with object substitution*/)) { // non-scoring match diff --git a/src/rba/rba/rbaMarshal.cc b/src/rba/rba/rbaMarshal.cc index 29cf797c5..ba5f3e3e7 100644 --- a/src/rba/rba/rbaMarshal.cc +++ b/src/rba/rba/rbaMarshal.cc @@ -1217,7 +1217,7 @@ struct test_arg_func // in loose mode (second pass) try to match the types via implicit constructors, // in strict mode (first pass) require direct type match - if (p->cls_decl () == atype.cls () || (loose && (p->cls_decl ()->is_derived_from (atype.cls ()) || p->cls_decl ()->can_convert_to (atype.cls ())))) { + if (p->cls_decl () == atype.cls () || (loose && (p->cls_decl ()->is_derived_from (atype.cls ()) || (object_substitution && p->cls_decl ()->can_convert_to (atype.cls ()))))) { // type matches: check constness if ((atype.is_ref () || atype.is_ptr ()) && p->const_ref ()) { *ret = false; diff --git a/src/rdb/rdb/gsiDeclRdb.cc b/src/rdb/rdb/gsiDeclRdb.cc index 10307e96f..d2db8c0d1 100644 --- a/src/rdb/rdb/gsiDeclRdb.cc +++ b/src/rdb/rdb/gsiDeclRdb.cc @@ -999,6 +999,11 @@ Class decl_RdbItem ("rdb", "RdbItem", "@param value The box to add.\n" "This method has been introduced in version 0.25 as a convenience method." ) + + gsi::method_ext ("add_value", &add_value_t, gsi::arg ("value"), + "@brief Adds a text object to the values of this item\n" + "@param value The text to add.\n" + "This method has been introduced in version 0.30.1 to support text objects with properties." + ) + gsi::method_ext ("add_value", &add_value_t, gsi::arg ("value"), "@brief Adds an edge object to the values of this item\n" "@param value The edge to add.\n" diff --git a/src/rdb/rdb/rdbUtils.cc b/src/rdb/rdb/rdbUtils.cc index 6d0181162..4692b35bd 100644 --- a/src/rdb/rdb/rdbUtils.cc +++ b/src/rdb/rdb/rdbUtils.cc @@ -146,7 +146,7 @@ public: m_cell_stack.pop_back (); } - virtual new_inst_mode new_inst (const db::RecursiveShapeIterator * /*iter*/, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/) + virtual new_inst_mode new_inst (const db::RecursiveShapeIterator * /*iter*/, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/, bool /*skip_shapes*/) { db::cell_index_type ci = inst.object ().cell_index (); if (m_id_to_cell.find (ci) != m_id_to_cell.end ()) { diff --git a/src/tl/tl/tlFileUtils.cc b/src/tl/tl/tlFileUtils.cc index 7debc3591..1735d853f 100644 --- a/src/tl/tl/tlFileUtils.cc +++ b/src/tl/tl/tlFileUtils.cc @@ -25,6 +25,7 @@ #include "tlLog.h" #include "tlInternational.h" #include "tlEnv.h" +#include "tlGlobPattern.h" #include #include @@ -430,6 +431,63 @@ std::vector dir_entries (const std::string &s, bool with_files, boo return ee; } +static void glob_partial (const std::string &where, std::vector::const_iterator pfrom, std::vector::const_iterator pto, std::vector &res) +{ + if (pfrom == pto) { + if (! is_dir (where)) { + res.push_back (where); + } + return; + } + + auto p = where + *pfrom; + if (file_exists (p)) { + glob_partial (p, pfrom + 1, pto, res); + return; + } + + if (tl::trimmed_part (*pfrom) == "**") { + if (pfrom + 1 == pto) { + // a glob pattern can't be "**" without anything after that + return; + } + auto subdirs = dir_entries (where, false, true, true); + for (auto s = subdirs.begin (); s != subdirs.end (); ++s) { + glob_partial (combine_path (where, *s), pfrom, pto, res); + } + ++pfrom; + } + +#if defined(_WIN32) + if (where.empty ()) { + // On Windows, we cannot iterate the drives + std::string root = *pfrom; + ++pfrom; + glob_partial (root, pfrom, pto, res); + return; + } +#endif + + tl::GlobPattern glob (tl::trimmed_part (*pfrom)); + ++pfrom; + auto entries = dir_entries (where, true, true, true); + for (auto e = entries.begin (); e != entries.end (); ++e) { + if (glob.match (*e)) { + glob_partial (combine_path (where, *e), pfrom, pto, res); + } + } +} + +std::vector glob_expand (const std::string &path) +{ + auto apath = absolute_file_path (path); + auto parts = split_path (apath); + + std::vector res; + glob_partial (std::string (), parts.begin (), parts.end (), res); + return res; +} + bool mkdir (const std::string &path) { #if defined(_WIN32) diff --git a/src/tl/tl/tlFileUtils.h b/src/tl/tl/tlFileUtils.h index 3456eb6da..4401ac41b 100644 --- a/src/tl/tl/tlFileUtils.h +++ b/src/tl/tl/tlFileUtils.h @@ -138,6 +138,14 @@ bool TL_PUBLIC is_dir (const std::string &s); */ std::vector TL_PUBLIC dir_entries (const std::string &s, bool with_files = true, bool with_dirs = true, bool without_dotfiles = false); +/** + * @brief Expands a glob pattern into a set of files + * + * This version supports "**" for recursive directory expansion. + * Apart from that the features of tl::GlobPattern are supported. + */ +std::vector TL_PUBLIC glob_expand (const std::string &path); + /** * @brief Rename the given file */ diff --git a/src/tl/tl/tlUri.cc b/src/tl/tl/tlUri.cc index d0a6aade3..8032a2cc2 100644 --- a/src/tl/tl/tlUri.cc +++ b/src/tl/tl/tlUri.cc @@ -41,7 +41,7 @@ static char hex2int (char c) return c - '0'; } else if (c >= 'A' && c <= 'F') { return (c - 'A') + 10; - } else if (c >= 'a' || c <= 'f') { + } else if (c >= 'a' && c <= 'f') { return (c - 'a') + 10; } else { return 0; diff --git a/src/tl/unit_tests/tlFileUtilsTests.cc b/src/tl/unit_tests/tlFileUtilsTests.cc index c12776608..2c5e210cc 100644 --- a/src/tl/unit_tests/tlFileUtilsTests.cc +++ b/src/tl/unit_tests/tlFileUtilsTests.cc @@ -995,3 +995,94 @@ TEST (24) EXPECT_EQ (tl::file_exists (p), false); } +// glob_expand +TEST (25) +{ + tl::TemporaryDirectory tmpdir ("tl_tests"); + auto p = tmpdir.path (); + + auto ad = tl::combine_path (p, "a"); + tl::mkpath (ad); + auto aad = tl::combine_path (ad, "a"); + tl::mkpath (aad); + auto aaad = tl::combine_path (aad, "a"); + tl::mkpath (aaad); + auto bd = tl::combine_path (p, "b"); + tl::mkpath (bd); + + { + std::ofstream os (tl::combine_path (ad, "test.txt")); + os << "A test"; + os.close (); + } + + { + std::ofstream os (tl::combine_path (aad, "test.txt")); + os << "A test"; + os.close (); + } + + { + std::ofstream os (tl::combine_path (aaad, "test.txt")); + os << "A test"; + os.close (); + } + + { + std::ofstream os (tl::combine_path (aaad, "test2.txt")); + os << "A test"; + os.close (); + } + + { + std::ofstream os (tl::combine_path (bd, "test.txt")); + os << "A test"; + os.close (); + } + + { + std::ofstream os (tl::combine_path (p, "test2.txt")); + os << "A test"; + os.close (); + } + + std::vector au; + + auto res = tl::glob_expand (tl::combine_path (p, "*.txt")); + au.push_back (tl::combine_path (p, "test2.txt")); + + std::sort (res.begin (), res.end ()); + std::sort (au.begin (), au.end ()); + EXPECT_EQ (tl::join (res, "\n"), tl::join (au, "\n")); + + res = tl::glob_expand (tl::combine_path (tl::combine_path (p, "**"), "*.txt")); + au.clear (); + au.push_back (tl::combine_path (p, "test2.txt")); + au.push_back (tl::combine_path (ad, "test.txt")); + au.push_back (tl::combine_path (aad, "test.txt")); + au.push_back (tl::combine_path (aaad, "test.txt")); + au.push_back (tl::combine_path (aaad, "test2.txt")); + au.push_back (tl::combine_path (bd, "test.txt")); + + std::sort (res.begin (), res.end ()); + std::sort (au.begin (), au.end ()); + EXPECT_EQ (tl::join (res, "\n"), tl::join (au, "\n")); + + res = tl::glob_expand (tl::combine_path (tl::combine_path (p, "**"), "*2.txt")); + au.clear (); + au.push_back (tl::combine_path (p, "test2.txt")); + au.push_back (tl::combine_path (aaad, "test2.txt")); + + std::sort (res.begin (), res.end ()); + std::sort (au.begin (), au.end ()); + EXPECT_EQ (tl::join (res, "\n"), tl::join (au, "\n")); + + res = tl::glob_expand (tl::combine_path (tl::combine_path (tl::combine_path (p, "**"), "a"), "*2.txt")); + au.clear (); + au.push_back (tl::combine_path (aaad, "test2.txt")); + + std::sort (res.begin (), res.end ()); + std::sort (au.begin (), au.end ()); + EXPECT_EQ (tl::join (res, "\n"), tl::join (au, "\n")); +} + diff --git a/testdata/bd/strmxor_au7d.oas b/testdata/bd/strmxor_au7d.oas new file mode 100644 index 000000000..9d64f3f38 Binary files /dev/null and b/testdata/bd/strmxor_au7d.oas differ diff --git a/testdata/bd/strmxor_covered1.gds b/testdata/bd/strmxor_covered1.gds new file mode 100644 index 000000000..f3d6eb6c4 Binary files /dev/null and b/testdata/bd/strmxor_covered1.gds differ diff --git a/testdata/bd/strmxor_covered2.gds b/testdata/bd/strmxor_covered2.gds new file mode 100644 index 000000000..da6141b50 Binary files /dev/null and b/testdata/bd/strmxor_covered2.gds differ diff --git a/testdata/buddies/buddies.rb b/testdata/buddies/buddies.rb index bf7afb096..7e309ac27 100644 --- a/testdata/buddies/buddies.rb +++ b/testdata/buddies/buddies.rb @@ -113,7 +113,7 @@ Warning: Layer 2/0 is not present in first layout, but in second Result summary (layers without differences are not shown): Layer Output Differences (shape count) - ------------------------------------------------------- + ---------------------------------------------------------------- 1/0 - (no such layer in second layout) 2/0 - (no such layer in first layout) diff --git a/testdata/drc/drcSimpleTests_94.drc b/testdata/drc/drcSimpleTests_94.drc new file mode 100644 index 000000000..5809b64af --- /dev/null +++ b/testdata/drc/drcSimpleTests_94.drc @@ -0,0 +1,26 @@ + +source $drc_test_source +target $drc_test_target + +if $drc_test_deep + deep +end + +l1 = input(1, 0) +l2 = input(2, 0) + +l1.output(1, 0) +l2.output(2, 0) + +x = l1 ^ l2 + +# we detect point-like polygons here by explicitly +# iterating. The enlargement makes sure, we have something +# to write to the output layout. +boxes = x.data.each.collect do |p| + RBA::Box::new(p.bbox.enlarged(10, 10)) +end +x.data = RBA::Region::new(boxes) + +x.output(10, 0) + diff --git a/testdata/drc/drcSimpleTests_94.gds b/testdata/drc/drcSimpleTests_94.gds new file mode 100644 index 000000000..7e3a7cd52 Binary files /dev/null and b/testdata/drc/drcSimpleTests_94.gds differ diff --git a/testdata/drc/drcSimpleTests_au94.gds b/testdata/drc/drcSimpleTests_au94.gds new file mode 100644 index 000000000..eeddc48bf Binary files /dev/null and b/testdata/drc/drcSimpleTests_au94.gds differ diff --git a/testdata/drc/drcSimpleTests_au94d.gds b/testdata/drc/drcSimpleTests_au94d.gds new file mode 100644 index 000000000..bb39fbbcc Binary files /dev/null and b/testdata/drc/drcSimpleTests_au94d.gds differ diff --git a/testdata/lvs/flag_missing_ports.cir b/testdata/lvs/flag_missing_ports.cir new file mode 100644 index 000000000..9a86f30d6 --- /dev/null +++ b/testdata/lvs/flag_missing_ports.cir @@ -0,0 +1,23 @@ +* Extracted by KLayout + +* cell ND2X1 +* pin VSS +* pin VDD +* pin b +* pin X +* pin SUBSTRATE +.SUBCKT ND2X1 1 3 5 6 7 +* net 1 VSS +* net 3 VDD +* net 5 b +* net 6 X +* net 7 SUBSTRATE +* device instance $1 r0 *1 0.85,5.8 PMOS +M$1 1 6 2 4 PMOS L=0.25U W=1.5U AS=0.6375P AD=0.3375P PS=3.85U PD=1.95U +* device instance $2 r0 *1 1.55,5.8 PMOS +M$2 2 5 1 4 PMOS L=0.25U W=1.5U AS=0.3375P AD=0.6375P PS=1.95U PD=3.85U +* device instance $3 r0 *1 0.85,2.135 NMOS +M$3 8 6 3 7 NMOS L=0.25U W=0.95U AS=0.40375P AD=0.21375P PS=2.75U PD=1.4U +* device instance $4 r0 *1 1.55,2.135 NMOS +M$4 2 5 8 7 NMOS L=0.25U W=0.95U AS=0.21375P AD=0.40375P PS=1.4U PD=2.75U +.ENDS ND2X1 diff --git a/testdata/lvs/flag_missing_ports.gds b/testdata/lvs/flag_missing_ports.gds new file mode 100644 index 000000000..9311b21cc Binary files /dev/null and b/testdata/lvs/flag_missing_ports.gds differ diff --git a/testdata/lvs/flag_missing_ports.lvs b/testdata/lvs/flag_missing_ports.lvs new file mode 100644 index 000000000..4c35142a3 --- /dev/null +++ b/testdata/lvs/flag_missing_ports.lvs @@ -0,0 +1,76 @@ + +source($lvs_test_source, "ND2X1") + +report_lvs($lvs_test_target_lvsdb, true) + +target_netlist($lvs_test_target_cir, write_spice, "Extracted by KLayout") + +schematic("flag_missing_ports.sch") + +deep + +# Drawing layers + +nwell = input(1, 0) +active = input(2, 0) +pplus = input(3, 0) +nplus = input(4, 0) +poly = input(5, 0) +contact = input(8, 0) +metal1 = input(9, 0) +via1 = input(10, 0) +metal2 = input(11, 0) + +# Bulk layer for terminal provisioning + +bulk = polygon_layer + +# Computed layers + +active_in_nwell = active & nwell +pactive = active_in_nwell & pplus +pgate = pactive & poly +psd = pactive - pgate +ntie = active_in_nwell & nplus + +active_outside_nwell = active - nwell +nactive = active_outside_nwell & nplus +ngate = nactive & poly +nsd = nactive - ngate +ptie = active_outside_nwell & pplus + +# Device extraction + +# PMOS transistor device extraction +extract_devices(mos4("PMOS"), { "SD" => psd, "G" => pgate, "W" => nwell, + "tS" => psd, "tD" => psd, "tG" => poly, "tW" => nwell }) + +# NMOS transistor device extraction +extract_devices(mos4("NMOS"), { "SD" => nsd, "G" => ngate, "W" => bulk, + "tS" => nsd, "tD" => nsd, "tG" => poly, "tW" => bulk }) + +# Define connectivity for netlist extraction + +# Inter-layer +connect(psd, contact) +connect(nsd, contact) +connect(poly, contact) +connect(ntie, contact) +connect(nwell, ntie) +connect(ptie, contact) +connect(contact, metal1) +connect(metal1, via1) +connect(via1, metal2) + +# Global +connect_global(bulk, "SUBSTRATE") +connect_global(ptie, "SUBSTRATE") + +# Compare section + +netlist.simplify + +compare + +flag_missing_ports + diff --git a/testdata/lvs/flag_missing_ports.lvsdb b/testdata/lvs/flag_missing_ports.lvsdb new file mode 100644 index 000000000..34ba019af --- /dev/null +++ b/testdata/lvs/flag_missing_ports.lvsdb @@ -0,0 +1,362 @@ +#%lvsdb-klayout + +# Layout +layout( + top(ND2X1) + unit(0.001) + + # Layer section + # This section lists the mask layers (drawing or derived) and their connections. + + # Mask layers + layer(l3 '1/0') + layer(l4 '5/0') + layer(l8 '8/0') + layer(l11 '9/0') + layer(l12) + layer(l13) + layer(l7) + layer(l2) + layer(l9) + layer(l6) + layer(l10) + + # Mask layer connectivity + connect(l3 l3 l9) + connect(l4 l4 l8) + connect(l8 l4 l8 l11 l2 l9 l6 l10) + connect(l11 l8 l11 l12) + connect(l12 l11 l12 l13) + connect(l13 l12 l13) + connect(l7 l7) + connect(l2 l8 l2) + connect(l9 l3 l8 l9) + connect(l6 l8 l6) + connect(l10 l8 l10) + + # Global nets and connectivity + global(l7 SUBSTRATE) + global(l10 SUBSTRATE) + + # Device class section + class(PMOS MOS4) + class(NMOS MOS4) + + # Device abstracts section + # Device abstracts list the pin shapes of the devices. + device(D$PMOS PMOS + terminal(S + rect(l2 (-550 -750) (425 1500)) + ) + terminal(G + rect(l4 (-125 -750) (250 1500)) + ) + terminal(D + rect(l2 (125 -750) (450 1500)) + ) + terminal(B + rect(l3 (-125 -750) (250 1500)) + ) + ) + device(D$PMOS$1 PMOS + terminal(S + rect(l2 (-575 -750) (450 1500)) + ) + terminal(G + rect(l4 (-125 -750) (250 1500)) + ) + terminal(D + rect(l2 (125 -750) (425 1500)) + ) + terminal(B + rect(l3 (-125 -750) (250 1500)) + ) + ) + device(D$NMOS NMOS + terminal(S + rect(l6 (-550 -475) (425 950)) + ) + terminal(G + rect(l4 (-125 -475) (250 950)) + ) + terminal(D + rect(l6 (125 -475) (450 950)) + ) + terminal(B + rect(l7 (-125 -475) (250 950)) + ) + ) + device(D$NMOS$1 NMOS + terminal(S + rect(l6 (-575 -475) (450 950)) + ) + terminal(G + rect(l4 (-125 -475) (250 950)) + ) + terminal(D + rect(l6 (125 -475) (425 950)) + ) + terminal(B + rect(l7 (-125 -475) (250 950)) + ) + ) + + # Circuit section + # Circuits are the hierarchical building blocks of the netlist. + circuit(ND2X1 + + # Circuit boundary + rect((-100 400) (2600 7600)) + + # Nets with their geometries + net(1 name(VSS) + rect(l8 (1110 5160) (180 180)) + rect(l8 (-180 920) (180 180)) + rect(l8 (-180 -730) (180 180)) + rect(l11 (-240 -790) (300 1700)) + rect(l11 (-1350 0) (2400 800)) + rect(l11 (-1150 -400) (0 0)) + rect(l2 (-275 -2150) (425 1500)) + rect(l2 (-400 -1500) (425 1500)) + ) + net(2 + rect(l8 (1810 1770) (180 180)) + rect(l8 (-180 370) (180 180)) + rect(l8 (-1580 3760) (180 180)) + rect(l8 (-180 -730) (180 180)) + rect(l8 (-180 -730) (180 180)) + rect(l8 (1220 920) (180 180)) + rect(l8 (-180 -1280) (180 180)) + rect(l8 (-180 370) (180 180)) + polygon(l11 (-240 -4180) (0 1390) (490 0) (0 -300) (-190 0) (0 -1090)) + rect(l11 (-110 1390) (300 1400)) + polygon(l11 (-1890 0) (0 600) (300 0) (0 -300) (1590 0) (0 -300)) + rect(l11 (-1890 600) (300 1400)) + rect(l11 (1100 -1700) (300 300)) + rect(l11 (-300 0) (300 1400)) + rect(l2 (-1750 -1450) (425 1500)) + rect(l2 (950 -1500) (425 1500)) + rect(l6 (-425 -4890) (425 950)) + ) + net(3 name(VDD) + rect(l8 (410 1770) (180 180)) + rect(l8 (-180 370) (180 180)) + rect(l11 (-240 -1300) (300 1360)) + rect(l11 (-650 -2160) (2400 800)) + rect(l11 (-1150 -400) (0 0)) + rect(l6 (-950 860) (425 950)) + ) + net(4 + rect(l3 (-100 4500) (2600 3500)) + ) + net(5 name(b) + rect(l4 (1425 2860) (250 1940)) + rect(l4 (-345 -950) (300 300)) + rect(l4 (-205 650) (250 2000)) + rect(l4 (-250 -2000) (250 2000)) + rect(l4 (-250 -5390) (250 1450)) + rect(l8 (-285 1050) (180 180)) + rect(l11 (-70 -90) (0 0)) + rect(l11 (-170 -150) (300 300)) + ) + net(6 name(X) + rect(l4 (725 2860) (250 1940)) + rect(l4 (-325 -1850) (300 300)) + rect(l4 (-225 1550) (250 2000)) + rect(l4 (-250 -2000) (250 2000)) + rect(l4 (-250 -5390) (250 1450)) + rect(l8 (-265 150) (180 180)) + rect(l11 (-90 -90) (0 0)) + rect(l11 (-150 -150) (300 300)) + ) + net(7 name(SUBSTRATE)) + net(8 + rect(l6 (975 1660) (425 950)) + rect(l6 (-400 -950) (425 950)) + ) + + # Outgoing pins and their connections to nets + pin(1 name(VSS)) + pin(3 name(VDD)) + pin(5 name(b)) + pin(6 name(X)) + pin(7 name(SUBSTRATE)) + + # Devices and their connections + device(1 D$PMOS + location(850 5800) + param(L 0.25) + param(W 1.5) + param(AS 0.6375) + param(AD 0.3375) + param(PS 3.85) + param(PD 1.95) + terminal(S 2) + terminal(G 6) + terminal(D 1) + terminal(B 4) + ) + device(2 D$PMOS$1 + location(1550 5800) + param(L 0.25) + param(W 1.5) + param(AS 0.3375) + param(AD 0.6375) + param(PS 1.95) + param(PD 3.85) + terminal(S 1) + terminal(G 5) + terminal(D 2) + terminal(B 4) + ) + device(3 D$NMOS + location(850 2135) + param(L 0.25) + param(W 0.95) + param(AS 0.40375) + param(AD 0.21375) + param(PS 2.75) + param(PD 1.4) + terminal(S 3) + terminal(G 6) + terminal(D 8) + terminal(B 7) + ) + device(4 D$NMOS$1 + location(1550 2135) + param(L 0.25) + param(W 0.95) + param(AS 0.21375) + param(AD 0.40375) + param(PS 1.4) + param(PD 2.75) + terminal(S 8) + terminal(G 5) + terminal(D 2) + terminal(B 7) + ) + + ) +) + +# Reference netlist +reference( + + # Device class section + class(PMOS MOS4) + class(NMOS MOS4) + + # Circuit section + # Circuits are the hierarchical building blocks of the netlist. + circuit(ND2X1 + + # Nets + net(1 name(VDD)) + net(2 name(OUT)) + net(3 name(VSS)) + net(4 name(NWELL)) + net(5 name(B)) + net(6 name(A)) + net(7 name(BULK)) + net(8 name('1')) + + # Outgoing pins and their connections to nets + pin(1 name(VDD)) + pin(2 name(OUT)) + pin(3 name(VSS)) + pin(4 name(NWELL)) + pin(5 name(B)) + pin(6 name(A)) + pin(7 name(BULK)) + + # Devices and their connections + device(1 PMOS + name($1) + param(L 0.25) + param(W 1.5) + param(AS 0) + param(AD 0) + param(PS 0) + param(PD 0) + terminal(S 1) + terminal(G 6) + terminal(D 2) + terminal(B 4) + ) + device(2 PMOS + name($2) + param(L 0.25) + param(W 1.5) + param(AS 0) + param(AD 0) + param(PS 0) + param(PD 0) + terminal(S 1) + terminal(G 5) + terminal(D 2) + terminal(B 4) + ) + device(3 NMOS + name($3) + param(L 0.25) + param(W 0.95) + param(AS 0) + param(AD 0) + param(PS 0) + param(PD 0) + terminal(S 3) + terminal(G 6) + terminal(D 8) + terminal(B 7) + ) + device(4 NMOS + name($4) + param(L 0.25) + param(W 0.95) + param(AS 0) + param(AD 0) + param(PS 0) + param(PD 0) + terminal(S 8) + terminal(G 5) + terminal(D 2) + terminal(B 7) + ) + + ) +) + +# Cross reference +xref( + circuit(ND2X1 ND2X1 match + log( + entry(error description('Port mismatch \'$4\' vs. \'NWELL\'')) + entry(error description('Port mismatch \'$2\' vs. \'OUT\'')) + entry(error description('Port mismatch \'SUBSTRATE\' vs. \'BULK\'')) + entry(error description('Port mismatch \'VDD\' vs. \'VSS\'')) + entry(error description('Port mismatch \'VSS\' vs. \'VDD\'')) + entry(error description('Port mismatch \'X\' vs. \'A\'')) + ) + xref( + net(8 8 match) + net(4 4 match) + net(2 2 match) + net(7 7 match) + net(3 3 match) + net(1 1 match) + net(6 6 match) + net(5 5 match) + pin(() 3 match) + pin(() 1 match) + pin(4 6 match) + pin(1 2 match) + pin(0 0 match) + pin(3 5 match) + pin(2 4 match) + device(3 3 match) + device(4 4 match) + device(1 1 match) + device(2 2 match) + ) + ) +) diff --git a/testdata/lvs/flag_missing_ports.sch b/testdata/lvs/flag_missing_ports.sch new file mode 100644 index 000000000..f0f917e0a --- /dev/null +++ b/testdata/lvs/flag_missing_ports.sch @@ -0,0 +1,7 @@ + +.SUBCKT ND2X1 VDD OUT VSS NWELL B A BULK +M$1 OUT A VDD NWELL PMOS L=0.25U W=1.5U +M$2 OUT B VDD NWELL PMOS L=0.25U W=1.5U +M$3 1 A VSS BULK NMOS L=0.25U W=0.95U +M$4 OUT B 1 BULK NMOS L=0.25U W=0.95U +.ENDS ND2X1 diff --git a/testdata/maly/MALY_test1.maly b/testdata/maly/MALY_test1.maly new file mode 100644 index 000000000..92512ae14 --- /dev/null +++ b/testdata/maly/MALY_test1.maly @@ -0,0 +1,60 @@ + +// A comment + +/* +A multi-line comment +BEGIN MALY 1.1 -- not seend +*/ + +BEGIN MALY 1.1 // ignored + BEGIN MASKSET + BEGIN CMASK + AN UNKNOWN MASK RECORD + BEGIN NONSENSE + SOMETHING INSIDE A NONSENSE RECORD + BEGIN MORE + SOMETHING INSIDE ANOTHER NONSENSE RECORD + END MORE + END NONSENSE + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE "/home/NATIVE" + REFERENCE TOOL a.para + AN UNKNOWN PARAMETER + END PARAMETER + BEGIN TITLE + AN UNKNOWN TITLE RECORD + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "WITH \"QUOTES\"" 50.0 0 ++SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 ++// with a continuation line + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + +// A comment at the end diff --git a/testdata/maly/MALY_test10.maly b/testdata/maly/MALY_test10.maly new file mode 100644 index 000000000..0e70b1ee2 --- /dev/null +++ b/testdata/maly/MALY_test10.maly @@ -0,0 +1,62 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 7 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK test10_oas + MASKMIRROR NONE + END PARAMETER + END CMASK + BEGIN MASK A + BEGIN STRGROUP G1 + SREF pat.oas TOP 1 ORG -2000.0 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 0.8 + AREF pat.oas TOP 2 ORG 1000 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 1.0 ITERATION 2 5 1500 2000 + END STRGROUP + BEGIN STRGROUP G2 + SREF pat.oas TOP 3 ORG -3000.0 2000 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 2.0 + END STRGROUP + END MASK + BEGIN MASK B + BEGIN PARAMETER + MASKMIRROR Y + END PARAMETER + BEGIN STRGROUP G1 + SREF pat.oas TOP 1 ORG -2000.0 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 0.8 + AREF pat.oas TOP 2 ORG 1000 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 1.0 ITERATION 2 5 1500 2000 + END STRGROUP + BEGIN STRGROUP G2 + SREF pat.oas TOP 3 ORG -3000.0 2000 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 2.0 + END STRGROUP + END MASK + BEGIN MASK C + BEGIN PARAMETER + ARYBASE LOWERLEFT + BASE LOWERLEFT + END PARAMETER + BEGIN STRGROUP G1 + SREF pat.oas TOP 1 ORG -2000.0 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 0.8 + AREF pat.oas TOP 2 ORG 1000 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 1.0 ITERATION 2 5 1500 2000 + END STRGROUP + BEGIN STRGROUP G2 + SREF pat.oas TOP 3 ORG -3000.0 2000 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 2.0 + END STRGROUP + END MASK + BEGIN MASK D + BEGIN PARAMETER + ARYBASE CENTER + BASE CENTER + END PARAMETER + BEGIN STRGROUP G1 + SREF pat.oas TOP 1 ORG -2000.0 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 0.8 + AREF pat.oas TOP 2 ORG 1000 0 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 1.0 ITERATION 2 5 1500 2000 + END STRGROUP + BEGIN STRGROUP G2 + SREF pat.oas TOP 3 ORG -3000.0 2000 SIZE -250.0 -250.0 1000.0 1000.0 SCALE 2.0 + END STRGROUP + END MASK + END MASKSET +END MALY diff --git a/testdata/maly/MALY_test11.maly b/testdata/maly/MALY_test11.maly new file mode 100644 index 000000000..4bfc631f1 --- /dev/null +++ b/testdata/maly/MALY_test11.maly @@ -0,0 +1,38 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 7 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK test10_oas + MASKMIRROR NONE + END PARAMETER + BEGIN TITLE + DATE -50000 -5000 MIRROR Y ROTATE 90 + SERIAL -50000 -10000 + STRING "A STRING TITLE UPSIDE DOWN" 0 -50000 MIRROR Y ROTATE 180 + STRING "A STRING TITLE" 0 -51500 + END TITLE + END CMASK + BEGIN MASK A + END MASK + BEGIN MASK B + BEGIN PARAMETER + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + SERIAL OFF + STRING "A STRING TITLE FOR MASK B" 5000 -53000 + END TITLE + END MASK + BEGIN MASK C + BEGIN TITLE + DATE OFF + STRING "A STRING TITLE FOR MASK C" 0 -55000 SIZE 1.5 2.0 3.0 + END TITLE + END MASK + END MASKSET +END MALY diff --git a/testdata/maly/MALY_test2a.maly b/testdata/maly/MALY_test2a.maly new file mode 100644 index 000000000..53f459c17 --- /dev/null +++ b/testdata/maly/MALY_test2a.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST\" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2b.maly b/testdata/maly/MALY_test2b.maly new file mode 100644 index 000000000..19d98c940 --- /dev/null +++ b/testdata/maly/MALY_test2b.maly @@ -0,0 +1,42 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + /* not terminated + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2c.maly b/testdata/maly/MALY_test2c.maly new file mode 100644 index 000000000..671061bef --- /dev/null +++ b/testdata/maly/MALY_test2c.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT NOVALIDFONT // wrong keyword + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2d.maly b/testdata/maly/MALY_test2d.maly new file mode 100644 index 000000000..ef775d98a --- /dev/null +++ b/testdata/maly/MALY_test2d.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE NOVALIDBASE // not a valid keyword + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2e.maly b/testdata/maly/MALY_test2e.maly new file mode 100644 index 000000000..212ed0556 --- /dev/null +++ b/testdata/maly/MALY_test2e.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 NOVALIDKEYWORD + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2f.maly b/testdata/maly/MALY_test2f.maly new file mode 100644 index 000000000..1fc9256b5 --- /dev/null +++ b/testdata/maly/MALY_test2f.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR NOVALIDSPEC ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2g.maly b/testdata/maly/MALY_test2g.maly new file mode 100644 index 000000000..041baf271 --- /dev/null +++ b/testdata/maly/MALY_test2g.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A UNEXPECTED + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2h.maly b/testdata/maly/MALY_test2h.maly new file mode 100644 index 000000000..24b8c96a9 --- /dev/null +++ b/testdata/maly/MALY_test2h.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR NOVALIDKEYWORD + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2i.maly b/testdata/maly/MALY_test2i.maly new file mode 100644 index 000000000..5c02ca50e --- /dev/null +++ b/testdata/maly/MALY_test2i.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 UNEXPECTED + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2j.maly b/testdata/maly/MALY_test2j.maly new file mode 100644 index 000000000..7d561956e --- /dev/null +++ b/testdata/maly/MALY_test2j.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 NOVALIDKEYWORD + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2k.maly b/testdata/maly/MALY_test2k.maly new file mode 100644 index 000000000..170e50cab --- /dev/null +++ b/testdata/maly/MALY_test2k.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 // missing argument ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2l.maly b/testdata/maly/MALY_test2l.maly new file mode 100644 index 000000000..1b3e40065 --- /dev/null +++ b/testdata/maly/MALY_test2l.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + // missing closing section END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2m.maly b/testdata/maly/MALY_test2m.maly new file mode 100644 index 000000000..5a9530e3d --- /dev/null +++ b/testdata/maly/MALY_test2m.maly @@ -0,0 +1,41 @@ + +BEGIN MALY 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + // missing opening BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/MALY_test2n.maly b/testdata/maly/MALY_test2n.maly new file mode 100644 index 000000000..b80eafdad --- /dev/null +++ b/testdata/maly/MALY_test2n.maly @@ -0,0 +1,41 @@ + +BEGIN WRONG 1.1 + BEGIN MASKSET + BEGIN CMASK + BEGIN PARAMETER + MASKSIZE 5 + FONT STANDARD + BASE ORIGIN + ARYBASE ORIGIN + ROOT OASIS.MASK /home/MASK + ROOT NATIVE /home/NATIVE + REFERENCE TOOL a.para + END PARAMETER + BEGIN TITLE + DATE 50.0 -50.0 MIRROR Y ROTATE 180 + SERIAL 0 -50.0 + STRING "TEST" 50.0 0 SIZE 1.0 1.0 1.0 MIRROR Y ROTATE 90 + END TITLE + END CMASK + BEGIN MASK A + BEGIN PARAMETER + ROOT OASIS.MASK /home/mask1 + MASKMIRROR Y + END PARAMETER + BEGIN TITLE + DATE OFF + STRING MaskA1 -50.0 50.00 + END TITLE + BEGIN STRGROUP G1 + SREF A1.oas CHIP_A 1 ORG -20.0 0 SIZE 0.0 0.0 10.0 10.0 + AREF A2.oas CHIP_A 2 ORG -20.0 0 SIZE 0.0 0.0 50.0 50.0 ++ SCALE 0.800 ITERATION 5 2 2.0 1.0 + PROPERTY DNAME d001 ENAME e001 + END STRGROUP + BEGIN STRGROUP G2 + SREF B3.oas CHIP_A 2 ORG -20.0 0.0 SIZE 0.0 0.0 12.0 12.0 + END STRGROUP + END MASK + END MASKSET +END MALY + diff --git a/testdata/maly/maly_test10_au.oas b/testdata/maly/maly_test10_au.oas new file mode 100644 index 000000000..89f1533a8 Binary files /dev/null and b/testdata/maly/maly_test10_au.oas differ diff --git a/testdata/maly/maly_test10_lm_au.oas b/testdata/maly/maly_test10_lm_au.oas new file mode 100644 index 000000000..5d495f5b1 Binary files /dev/null and b/testdata/maly/maly_test10_lm_au.oas differ diff --git a/testdata/maly/maly_test11_au.oas b/testdata/maly/maly_test11_au.oas new file mode 100644 index 000000000..cb76b91c4 Binary files /dev/null and b/testdata/maly/maly_test11_au.oas differ diff --git a/testdata/maly/test10_oas/pat.oas b/testdata/maly/test10_oas/pat.oas new file mode 100644 index 000000000..2818a3a18 Binary files /dev/null and b/testdata/maly/test10_oas/pat.oas differ diff --git a/testdata/python/dbLayoutTest.py b/testdata/python/dbLayoutTest.py index 1179f898b..bae65e913 100644 --- a/testdata/python/dbLayoutTest.py +++ b/testdata/python/dbLayoutTest.py @@ -684,7 +684,7 @@ class DBLayoutTest(unittest.TestCase): ly = pya.Layout(True) pv = [ [ 17, "a" ], [ "b", [ 1, 5, 7 ] ] ] pid = ly.properties_id( pv ) - # does not work? @@@ + # does not work? # pv = { 17: "a", "b": [ 1, 5, 7 ] } # pid2 = ly.properties_id( pv ) # self.assertEqual( pid, pid2 ) diff --git a/testdata/python/dbShapesTest.py b/testdata/python/dbShapesTest.py index d7e65f11a..09fa12fce 100644 --- a/testdata/python/dbShapesTest.py +++ b/testdata/python/dbShapesTest.py @@ -24,7 +24,7 @@ import os class DBShapesTest(unittest.TestCase): # Shape objects as hashes - def test_12(self): + def test_1(self): s = pya.Shapes() s1 = s.insert(pya.Box(1, 2, 3, 4)) @@ -50,6 +50,18 @@ class DBShapesTest(unittest.TestCase): self.assertEqual(h[s2], 2) self.assertEqual(h[s3], 3) + # Issue #2012 (reference count) + def test_2(self): + + ly = pya.Layout() + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + top.shapes(l1).insert(pya.Box(0, 0, 100, 200)) + + shapes = top.shapes(l1) + self.assertEqual(sys.getrefcount(shapes), 2) + + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(DBShapesTest) diff --git a/testdata/python/rdbTest.py b/testdata/python/rdbTest.py index dfa2194d6..a7c34925f 100644 --- a/testdata/python/rdbTest.py +++ b/testdata/python/rdbTest.py @@ -972,6 +972,78 @@ class RDB_TestClass(unittest.TestCase): _cat_same = None self.assertEqual(_subcat.rdb_id(), _subcat_same.rdb_id()) + def test_15(self): + + p = pya.DPolygon(pya.DBox(0.5, 1, 2, 3)) + pwp = pya.DPolygonWithProperties(p, { 1: "value" }) + e = pya.DEdge(pya.DPoint(0, 0), pya.DPoint(1, 2)) + ewp = pya.DEdgeWithProperties(e, { 1: "value" }) + ep = pya.DEdgePair(e, e.moved(10, 10)) + epwp = pya.DEdgePairWithProperties(ep, { 1: "value" }) + t = pya.DText("text", pya.DTrans.R0) + twp = pya.DTextWithProperties(t, { 1: "value" }) + b = pya.DBox(0, 0, 1, 2) + bwp = pya.DBoxWithProperties(b, { 1: "value" }) + + ip = pya.Polygon(pya.Box(0, 1, 2, 3)) + ipwp = pya.PolygonWithProperties(ip, { 1: "value" }) + ie = pya.Edge(pya.Point(0, 0), pya.Point(1, 2)) + iewp = pya.EdgeWithProperties(ie, { 1: "value" }) + iep = pya.EdgePair(ie, ie.moved(10, 10)) + iepwp = pya.EdgePairWithProperties(iep, { 1: "value" }) + it = pya.Text("text", pya.Trans.R0) + itwp = pya.TextWithProperties(it, { 1: "value" }) + ib = pya.Box(0, 0, 1, 2) + ibwp = pya.BoxWithProperties(ib, { 1: "value" }) + + rdb = pya.ReportDatabase() + + cat = rdb.create_category("name") + cell = rdb.create_cell("TOP") + item = rdb.create_item(cell, cat) + + item.add_value(p) + item.add_value(pwp) + item.add_value(b) + item.add_value(bwp) + item.add_value(t) + item.add_value(twp) + item.add_value(e) + item.add_value(ewp) + item.add_value(ep) + item.add_value(epwp) + + item.add_value(ip) + item.add_value(ipwp) + item.add_value(ib) + item.add_value(ibwp) + item.add_value(it) + item.add_value(itwp) + item.add_value(ie) + item.add_value(iewp) + item.add_value(iep) + item.add_value(iepwp) + + item.add_value("string") + item.add_value(17.5) + + values = [ str(v) for v in item.each_value() ] + + self.assertEqual(values, [ + 'polygon: (0.5,1;0.5,3;2,3;2,1)', 'polygon: (0.5,1;0.5,3;2,3;2,1)', + 'box: (0,0;1,2)', 'box: (0,0;1,2)', + "label: ('text',r0 0,0)", "label: ('text',r0 0,0)", + 'edge: (0,0;1,2)', 'edge: (0,0;1,2)', + 'edge-pair: (0,0;1,2)/(10,10;11,12)', 'edge-pair: (0,0;1,2)/(10,10;11,12)', + 'polygon: (0,1;0,3;2,3;2,1)', 'polygon: (0,1;0,3;2,3;2,1)', + 'box: (0,0;1,2)', 'box: (0,0;1,2)', + "label: ('text',r0 0,0)", "label: ('text',r0 0,0)", + 'edge: (0,0;1,2)', 'edge: (0,0;1,2)', + 'edge-pair: (0,0;1,2)/(10,10;11,12)', 'edge-pair: (0,0;1,2)/(10,10;11,12)', + 'text: string', + 'float: 17.5' + ]) + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(RDB_TestClass) diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index d67bc33c6..ac157ff53 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -111,7 +111,7 @@ class DBEdgePairs_TestClass < TestBase assert_equal(csort(r.edges.to_s), csort("(0,0;0,100);(-10,0;-20,50)")) assert_equal(r.is_empty?, false) assert_equal(r.size, 1) - assert_equal(r[0].to_s, "(0,0;0,100)/(-10,0;-20,50)") + assert_equal(r[0].to_s, "(0,0;0,100)/(-10,0;-20,50) props={}") assert_equal(r[1].to_s, "") assert_equal(r.bbox.to_s, "(-20,0;0,100)") @@ -221,7 +221,7 @@ class DBEdgePairs_TestClass < TestBase r.flatten assert_equal(r.has_valid_edge_pairs?, true) - assert_equal(r[1].to_s, "(0,101;2,103)/(10,111;12,113)") + assert_equal(r[1].to_s, "(0,101;2,103)/(10,111;12,113) props={}") assert_equal(r[100].inspect, "nil") assert_equal(r.bbox.to_s, "(0,1;212,113)") @@ -622,6 +622,7 @@ class DBEdgePairs_TestClass < TestBase r = RBA::EdgePairs::new([ RBA::EdgePairWithProperties::new(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500)), { 1 => "one" }) ]) assert_equal(r.to_s, "(0,0;100,100)/(200,300;200,500){1=>one}") + assert_equal(r[0].to_s, "(0,0;100,100)/(200,300;200,500) props={1=>one}") r = RBA::EdgePairs::new([]) assert_equal(r.to_s, "") diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 086a0eb9c..7a8047b23 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -235,7 +235,7 @@ class DBEdges_TestClass < TestBase assert_equal(r.is_empty?, false) assert_equal(r.count, 12) assert_equal(r.hier_count, 12) - assert_equal(r[1].to_s, "(-10,20;10,20)") + assert_equal(r[1].to_s, "(-10,20;10,20) props={}") assert_equal(r[100].to_s, "") assert_equal(r.bbox.to_s, "(-10,-20;210,120)") assert_equal(r.is_merged?, false) @@ -252,11 +252,11 @@ class DBEdges_TestClass < TestBase r.flatten assert_equal(r.has_valid_edges?, true) - assert_equal(r[1].to_s, "(-10,80;10,120)") + assert_equal(r[1].to_s, "(-10,80;10,120) props={}") assert_equal(r[100].to_s, "") assert_equal(r.bbox.to_s, "(-10,-20;210,120)") assert_equal(r.is_merged?, false) - + r = RBA::Edges::new(ly.begin_shapes(c1.cell_index, l1), RBA::ICplxTrans::new(10, 20), true) assert_equal(r.to_s(30), "(0,0;0,40);(0,40;20,40);(20,40;20,0);(20,0;0,0);(0,100;0,140);(0,140;20,140);(20,140;20,100);(20,100;0,100);(200,100;200,140);(200,140;220,140);(220,140;220,100);(220,100;200,100)") assert_equal(r.is_empty?, false) @@ -970,6 +970,7 @@ class DBEdges_TestClass < TestBase r = RBA::Edges::new([ RBA::EdgeWithProperties::new(RBA::Edge::new(0, 0, 100, 100), { 1 => "one" }) ]) assert_equal(r.to_s, "(0,0;100,100){1=>one}") + assert_equal(r[0].to_s, "(0,0;100,100) props={1=>one}") r = RBA::Edges::new([]) assert_equal(r.to_s, "") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 5f8588f8b..985dd9406 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -226,10 +226,16 @@ class DBRegion_TestClass < TestBase r.flatten assert_equal(r.has_valid_polygons?, true) - assert_equal(r[1].to_s, "(-10,80;-10,120;10,120;10,80)") + assert_equal(r[1].to_s, "(-10,80;-10,120;10,120;10,80) props={}") assert_equal(r[4].to_s, "") assert_equal(r.bbox.to_s, "(-10,-20;210,120)") assert_equal(r.is_merged?, false) + + r = RBA::Region::new + r.insert(RBA::PolygonWithProperties::new(RBA::Box::new(0, 0, 10, 20), { 1 => 'value' })) + r.insert(RBA::Box::new(1, 2, 11, 22)) + assert_equal(r[0].to_s, "(1,2;1,22;11,22;11,2) props={}") + assert_equal(r[1].to_s, "(0,0;0,20;10,20;10,0) props={1=>value}") r = RBA::Region::new(ly.begin_shapes(c1.cell_index, l2), "*") assert_equal(csort(r.to_s), csort("(-11,-21;-11,-19;-9,-19;-9,-21);(9,19;9,21;11,21;11,19);(-11,79;-11,81;-9,81;-9,79);(9,119;9,121;11,121;11,119);(189,79;189,81;191,81;191,79);(209,119;209,121;211,121;211,119)")) diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 145379068..4f481ef3f 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -103,7 +103,7 @@ class DBTexts_TestClass < TestBase assert_equal(r.is_empty?, false) assert_equal(r.count, 1) assert_equal(r.hier_count, 1) - assert_equal(r[0].to_s, "('uvw',r0 110,210)") + assert_equal(r[0].to_s, "('uvw',r0 110,210) props={}") assert_equal(r[1].to_s, "") assert_equal(r.bbox.to_s, "(110,210;110,210)") @@ -223,9 +223,13 @@ class DBTexts_TestClass < TestBase r.flatten assert_equal(r.has_valid_texts?, true) - assert_equal(r[1].to_s, "('abc',r0 100,-100)") + assert_equal(r[1].to_s, "('abc',r0 100,-100) props={}") assert_equal(r[100].inspect, "nil") assert_equal(r.bbox.to_s, "(100,-200;300,-100)") + + r = RBA::Texts::new + r.insert(RBA::TextWithProperties::new(RBA::Text::new("string", RBA::Trans::new), { 1 => "value" })) + assert_equal(r[0].to_s, "('string',r0 0,0) props={1=>value}") dss = RBA::DeepShapeStore::new r = RBA::Texts::new(ly.begin_shapes(c1.cell_index, l1), dss) diff --git a/testdata/ruby/rdbTest.rb b/testdata/ruby/rdbTest.rb index 392e0cfa4..91ecb9bf2 100644 --- a/testdata/ruby/rdbTest.rb +++ b/testdata/ruby/rdbTest.rb @@ -1131,6 +1131,80 @@ class RDB_TestClass < TestBase end + def test_15 + + p = RBA::DPolygon::new(RBA::DBox::new(0.5, 1, 2, 3)) + pwp = RBA::DPolygonWithProperties::new(p, { 1 => "value" }) + e = RBA::DEdge::new(RBA::DPoint::new(0, 0), RBA::DPoint::new(1, 2)) + ewp = RBA::DEdgeWithProperties::new(e, { 1 => "value" }) + ep = RBA::DEdgePair::new(e, e.moved(10, 10)) + epwp = RBA::DEdgePairWithProperties::new(ep, { 1 => "value" }) + t = RBA::DText::new("text", RBA::DTrans::R0) + twp = RBA::DTextWithProperties::new(t, { 1 => "value" }) + b = RBA::DBox::new(0, 0, 1, 2) + bwp = RBA::DBoxWithProperties::new(b, { 1 => "value" }) + + ip = RBA::Polygon::new(RBA::Box::new(0, 1, 2, 3)) + ipwp = RBA::PolygonWithProperties::new(ip, { 1 => "value" }) + ie = RBA::Edge::new(RBA::Point::new(0, 0), RBA::Point::new(1, 2)) + iewp = RBA::EdgeWithProperties::new(ie, { 1 => "value" }) + iep = RBA::EdgePair::new(ie, ie.moved(10, 10)) + iepwp = RBA::EdgePairWithProperties::new(iep, { 1 => "value" }) + it = RBA::Text::new("text", RBA::Trans::R0) + itwp = RBA::TextWithProperties::new(it, { 1 => "value" }) + ib = RBA::Box::new(0, 0, 1, 2) + ibwp = RBA::BoxWithProperties::new(ib, { 1 => "value" }) + + rdb = RBA::ReportDatabase::new + + cat = rdb.create_category("name") + cell = rdb.create_cell("TOP") + item = rdb.create_item(cell, cat) + + item.add_value(p) + item.add_value(pwp) + item.add_value(b) + item.add_value(bwp) + item.add_value(t) + item.add_value(twp) + item.add_value(e) + item.add_value(ewp) + item.add_value(ep) + item.add_value(epwp) + + item.add_value(ip) + item.add_value(ipwp) + item.add_value(ib) + item.add_value(ibwp) + item.add_value(it) + item.add_value(itwp) + item.add_value(ie) + item.add_value(iewp) + item.add_value(iep) + item.add_value(iepwp) + + item.add_value("string") + item.add_value(17.5) + + values = item.each_value.collect(&:to_s) + + assert_equal(values, [ + 'polygon: (0.5,1;0.5,3;2,3;2,1)', 'polygon: (0.5,1;0.5,3;2,3;2,1)', + 'box: (0,0;1,2)', 'box: (0,0;1,2)', + "label: ('text',r0 0,0)", "label: ('text',r0 0,0)", + 'edge: (0,0;1,2)', 'edge: (0,0;1,2)', + 'edge-pair: (0,0;1,2)/(10,10;11,12)', 'edge-pair: (0,0;1,2)/(10,10;11,12)', + 'polygon: (0,1;0,3;2,3;2,1)', 'polygon: (0,1;0,3;2,3;2,1)', + 'box: (0,0;1,2)', 'box: (0,0;1,2)', + "label: ('text',r0 0,0)", "label: ('text',r0 0,0)", + 'edge: (0,0;1,2)', 'edge: (0,0;1,2)', + 'edge-pair: (0,0;1,2)/(10,10;11,12)', 'edge-pair: (0,0;1,2)/(10,10;11,12)', + 'text: string', + 'float: 17.5' + ]) + + end + end load("test_epilogue.rb") diff --git a/version.sh b/version.sh index 737a9f907..3b9bf8033 100644 --- a/version.sh +++ b/version.sh @@ -2,10 +2,10 @@ # This script is sourced to define the main version parameters # The main version -KLAYOUT_VERSION="0.30.0" +KLAYOUT_VERSION="0.30.1" # The version used for PyPI (don't use variables here!) -KLAYOUT_PYPI_VERSION="0.30.0" +KLAYOUT_PYPI_VERSION="0.30.1" # The build date KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d")