diff --git a/src/buddies/src/bd/bd.pro b/src/buddies/src/bd/bd.pro index 25076cbf2..46a9d36fd 100644 --- a/src/buddies/src/bd/bd.pro +++ b/src/buddies/src/bd/bd.pro @@ -21,6 +21,7 @@ SOURCES = \ strm2gdstxt.cc \ strm2txt.cc \ strmcmp.cc \ + strmxor.cc \ HEADERS = \ bdCommon.h \ diff --git a/src/buddies/src/bd/strmcmp.cc b/src/buddies/src/bd/strmcmp.cc index 2bf4ca800..75106588e 100644 --- a/src/buddies/src/bd/strmcmp.cc +++ b/src/buddies/src/bd/strmcmp.cc @@ -72,7 +72,7 @@ BD_PUBLIC int strmcmp (int argc, char *argv[]) ) << tl::arg ("-s|--silent", &silent, "Enables silent mode", "In silent mode, no differences are printed, but the exit code indicates whether " - "the layout are the same (0) or differences exist (> 0)." + "the layouts are the same (0) or differences exist (> 0)." ) << tl::arg ("#!--with-text-orientation", &no_text_orientation, "Compares orientations for texts", "With this option, text orientation is compared too. The position of the " diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc new file mode 100644 index 000000000..b8d3a6e7e --- /dev/null +++ b/src/buddies/src/bd/strmxor.cc @@ -0,0 +1,290 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 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 "bdReaderOptions.h" +#include "dbLayout.h" +#include "dbTilingProcessor.h" +#include "dbReader.h" +#include "dbWriter.h" +#include "dbSaveLayoutOptions.h" +#include "tlCommandLineParser.h" + +BD_PUBLIC int strmxor (int argc, char *argv[]) +{ + bd::GenericReaderOptions generic_reader_options_a; + generic_reader_options_a.set_prefix ("a"); + generic_reader_options_a.set_long_prefix ("a-"); + generic_reader_options_a.set_group_prefix ("Input A"); + + bd::GenericReaderOptions generic_reader_options_b; + generic_reader_options_b.set_prefix ("b"); + generic_reader_options_b.set_long_prefix ("b-"); + generic_reader_options_b.set_group_prefix ("Input B"); + + std::string infile_a, infile_b, output; + std::string top_a, top_b; + bool dont_summarize_missing_layers = false; + bool silent = false; + std::vector tolerances; + int tolerance_bump = 10000; + int threads = 1; + double tile_size = 0.0; + + tl::CommandLineOptions cmd; + generic_reader_options_a.add_options (cmd); + generic_reader_options_b.add_options (cmd); + + cmd << tl::arg ("input_a", &infile_a, "The first input file (any format, may be gzip compressed)") + << tl::arg ("input_b", &infile_b, "The second input file (any format, may be gzip compressed)") + << tl::arg ("?output", &output, "The output file to which the XOR differences are written", + "This argument is optional. If not given, the exit status along indicates whether the layouts " + "are identical or not." + ) + << tl::arg ("-ta|--top-a=name", &top_a, "Specifies the cell to take as top cell from the first layout", + "Use this option to take a specific cell as the top cell from the first layout. All " + "cells not called directly or indirectly from this cell are ignored. If you use this option, " + "--top-b must be specified too and can be different from the first layout's top cell." + ) + << tl::arg ("-tb|--top-b=name", &top_b, "Specifies the cell to take as top cell from the second layout", + "See --top-a for details." + ) + << tl::arg ("-s|--silent", &silent, "Enables silent mode", + "In silent mode, no summary is printed, but the exit code indicates whether " + "the layouts are the same (0) or differences exist (> 0)." + ) + << tl::arg ("-l|--layer-details", &dont_summarize_missing_layers, "Output details about differences for missing layers", + "With this option, missing layers are treated as \"empty\" and the whole layer of the other " + "layout is output. Without this option, a message is printed for missing layers instead." + ) + << tl::arg ("-t|--tolerance=values", &tolerances, "Specifies tolerances for the geometry compare", + "This option can take multiple tolerance values. The values are given in micrometer units and " + "are separated by a comma. If a tolerance is given, XOR differences are " + "only reported when they are larger than the tolerance value. Tolerance values must be given in " + "ascending order." + ) + << tl::arg ("-n|--threads=threads", &threads, "Specifies the number of threads to use", + "If given, multiple threads are used for the XOR computation. This way, multiple cores can " + "be utilized." + ) + << tl::arg ("-p|--tiles=size", &tile_size, "Specifies tiling mode", + "In tiling mode, the layout is divided into tiles of the given size. Each tile is computed " + "individually. Multiple tiles can be processed in parallel on multiple cores." + ) + << tl::arg ("-b|--layer-bump=offset", &tolerance_bump, "Specifies the layer number offset to add for every tolerance", + "This value is the number added to the original layer number to form a layer set for each tolerance " + "value. If this value is set to 1000, the first tolerance value will produce XOR results on the " + "original layers. A second tolerance value will produce XOR results on the original layers + 1000. " + "A third tolerance value will produce XOR results on the original layers + 2000." + ) + ; + + cmd.brief ("This program will compare two layout files with a geometrical XOR operation"); + + cmd.parse (argc, argv); + + if (top_a.empty () != top_b.empty ()) { + throw tl::Exception ("Both -ta|--top-a and -tb|--top-b top cells must be given"); + } + + if (tolerances.empty ()) { + tolerances.push_back (0.0); + } else { + for (std::vector::const_iterator t = tolerances.begin () + 1; t != tolerances.end (); ++t) { + if (*(t - 1) > *t - db::epsilon) { + throw tl::Exception ("Tolerance values (-t|--tolerances) must be given in ascending order"); + } + } + } + + db::Layout layout_a; + db::Layout layout_b; + + { + db::LoadLayoutOptions load_options; + generic_reader_options_a.configure (load_options); + + tl::InputStream stream (infile_a); + db::Reader reader (stream); + reader.read (layout_a, load_options); + } + + { + db::LoadLayoutOptions load_options; + generic_reader_options_b.configure (load_options); + + tl::InputStream stream (infile_b); + db::Reader reader (stream); + reader.read (layout_b, load_options); + } + + if (top_a.empty ()) { + + db::Layout::top_down_const_iterator t; + + t = layout_a.begin_top_down (); + if (t != layout_a.end_top_cells ()) { + top_a = layout_a.cell_name (*t); + ++t; + if (t != layout_a.end_top_cells ()) { + throw tl::Exception ("Top cell of first layout is not unique and cannot be determined automatically"); + } + } + + t = layout_b.begin_top_down (); + if (t != layout_b.end_top_cells ()) { + top_b = layout_b.cell_name (*t); + ++t; + if (t != layout_b.end_top_cells ()) { + throw tl::Exception ("Top cell of second layout is not unique and cannot be determined automatically"); + } + } + + } + + std::pair index_a = layout_a.cell_by_name (top_a.c_str ()); + std::pair index_b = layout_b.cell_by_name (top_b.c_str ()); + + if (! index_a.first) { + throw tl::Exception ("'" + top_a + "' is not a valid cell name in first layout"); + } + if (! index_b.first) { + throw tl::Exception ("'" + top_b + "' is not a valid cell name in second layout"); + } + + std::map > l2l_map; + + for (db::Layout::layer_iterator l = layout_a.begin_layers (); l != layout_a.end_layers (); ++l) { + l2l_map.insert (std::make_pair (*(*l).second, std::make_pair (-1, -1))).first->second.first = (*l).first; + } + for (db::Layout::layer_iterator l = layout_b.begin_layers (); l != layout_b.end_layers (); ++l) { + l2l_map.insert (std::make_pair (*(*l).second, std::make_pair (-1, -1))).first->second.second = (*l).first; + } + + bool result = true; + + db::TilingProcessor proc; + proc.set_dbu (std::min (layout_a.dbu (), layout_b.dbu ())); + proc.set_threads (std::max (1, threads)); + if (tile_size > db::epsilon) { + proc.tile_size (tile_size, tile_size); + } + proc.tile_border (tolerances.back () * 2.0, tolerances.back () * 2.0); + + db::Layout output_layout; + output_layout.dbu (proc.dbu ()); + + db::cell_index_type output_top = output_layout.add_cell ("XOR"); + + std::vector output_layers; + + int index = 1; + + for (std::map >::const_iterator ll = l2l_map.begin (); ll != l2l_map.end (); ++ll) { + + if (ll->second.first < 0 && ! dont_summarize_missing_layers) { + + tl::warn << "Layer " << ll->first.to_string () << " is not present in first layout, but in second"; + result = false; + + } else if (ll->second.second < 0 && ! dont_summarize_missing_layers) { + + tl::warn << "Layer " << ll->first.to_string () << " is not present in second layout, but in first"; + result = false; + + } else { + + std::string in_a = "a" + tl::to_string (index); + std::string in_b = "b" + tl::to_string (index); + + if (ll->second.first < 0) { + proc.input (in_a, db::RecursiveShapeIterator ()); + } else { + proc.input (in_a, db::RecursiveShapeIterator (layout_a, layout_a.cell (index_a.second), ll->second.first)); + } + + if (ll->second.second < 0) { + proc.input (in_b, db::RecursiveShapeIterator ()); + } else { + proc.input (in_b, db::RecursiveShapeIterator (layout_b, layout_b.cell (index_b.second), ll->second.second)); + } + + std::string expr = "var x=" + in_a + "^" + in_b + "; "; + + int tol_index = 1; + for (std::vector::const_iterator t = tolerances.begin (); t != tolerances.end (); ++t) { + + std::string out = "o" + tl::to_string (index) + "_" + tl::to_string (tol_index); + + db::LayerProperties lp = ll->first; + if (lp.layer >= 0) { + lp.layer += (tol_index - 1) * tolerance_bump; + } + + unsigned int output_layer = output_layout.insert_layer (lp); + output_layers.push_back (output_layer); + + // @@@ TODO: silent mode + proc.output (out, output_layout, output_top, output_layer); + + if (*t > db::epsilon) { + expr += "x=x.sized(-int(" + tl::to_string (*t) + "/_dbu)/2).sized(int(" + tl::to_string (*t) + "/_dbu)/2); "; + } + expr += "_output(" + out + ",x); "; + + } + + proc.queue (expr); + + } + + ++index; + + } + + // Runs the processor + + proc.execute ("Running XOR"); + + // Write the output layout + + if (! output.empty()) { + + db::SaveLayoutOptions save_options; + save_options.set_format_from_filename (output); + + tl::OutputStream stream (output); + db::Writer writer (save_options); + writer.write (output_layout, stream); + + } + + // Determine the output status based on the output top cell's emptyness + + for (std::vector::const_iterator l = output_layers.begin (); l != output_layers.end () && result; ++l) { + result = output_layout.cell (output_top).bbox (*l).empty (); + } + + // @@@ TODO: print a nice summary unless "silent" is set + + return result ? 0 : 1; +} + diff --git a/src/buddies/src/strmxor/strmxor.cc b/src/buddies/src/strmxor/strmxor.cc deleted file mode 100644 index 622e46fc6..000000000 --- a/src/buddies/src/strmxor/strmxor.cc +++ /dev/null @@ -1,236 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2017 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 "bdInit.h" -#include "dbLayout.h" -#include "dbReader.h" -#include "dbWriter.h" -#include "dbShapeProcessor.h" -#include "tlString.h" - -void -syntax () -{ - printf ("Syntax: strmxor [-u ] [-topa ] [-topb ] [-oasis|-oas] [-gds2|-gds] []\n"); -} - -int -main (int argc, char *argv []) -{ - std::string topcell_a; - std::string topcell_b; - std::string infile_a; - std::string infile_b; - std::string outfile; - double undersize = 0.0; - bool format_set = false; - std::string format; - - int ret = 0; - - try { - - for (int i = 1; i < argc; ++i) { - std::string o (argv[i]); - if (o == "-u") { - if (i < argc - 1) { - ++i; - tl::from_string (argv[i], undersize); - } - } else if (o == "-topa") { - if (i < argc - 1) { - ++i; - topcell_a = argv[i]; - } - } else if (o == "-topb") { - if (i < argc - 1) { - ++i; - topcell_b = argv[i]; - } - } else if (o == "-oasis" || o == "-oas") { - format_set = true; - format = "OASIS"; - } else if (o == "-gds2" || o == "-gds") { - format_set = true; - format = "GDS2"; - } else if (o == "-h" || o == "-help" || o == "--help") { - syntax (); - return 0; - } else if (argv[i][0] == '-') { - throw tl::Exception("Unknown option: %s - use '-h' for help", (const char *) argv[i]); - } else if (infile_a.empty ()) { - infile_a = argv[i]; - } else if (infile_b.empty ()) { - infile_b = argv[i]; - } else if (outfile.empty ()) { - outfile = argv[i]; - } else { - throw tl::Exception("Superfluous argument: %s - use '-h' for help", (const char *) argv[i]); - } - } - - if (infile_a.empty () || infile_b.empty ()) { - throw tl::Exception("Both input files must be specified"); - } - - db::Manager m; - db::Layout layout_a (&m); - db::Layout layout_b (&m); - - { - tl::InputStream stream (infile_a); - db::Reader reader (stream); - reader.read (layout_a); - } - - { - tl::InputStream stream (infile_b); - db::Reader reader (stream); - reader.read (layout_b); - } - - db::cell_index_type top_a; - if (topcell_a.empty ()) { - db::Layout::top_down_iterator t = layout_a.begin_top_down (); - if (t == layout_a.end_top_cells ()) { - throw tl::Exception ("Layout A (%s) does not have a top cell", infile_a); - } - top_a = *t++; - if (t != layout_a.end_top_cells ()) { - throw tl::Exception ("Layout A (%s) has multiple top cells", infile_a); - } - } else { - std::pair cn = layout_a.cell_by_name (topcell_a.c_str ()); - if (! cn.first) { - throw tl::Exception ("Layout A (%s) does not have a topcell called '%s'", infile_a, topcell_a); - } - top_a = cn.second; - } - - db::cell_index_type top_b; - if (topcell_b.empty ()) { - db::Layout::top_down_iterator t = layout_b.begin_top_down (); - if (t == layout_b.end_top_cells ()) { - throw tl::Exception ("Layout B (%s) does not have a top cell", infile_b); - } - top_b = *t++; - if (t != layout_b.end_top_cells ()) { - throw tl::Exception ("Layout B (%s) has multiple top cells", infile_b); - } - } else { - std::pair cn = layout_b.cell_by_name (topcell_b.c_str ()); - if (! cn.first) { - throw tl::Exception ("Layout B (%s) does not have a topcell called '%s'", infile_b, topcell_b); - } - top_b = cn.second; - } - - if (fabs (layout_a.dbu () - layout_b.dbu ()) > 1e-6) { - throw tl::Exception("Input file database units differ (A:%g vs. B:%g)", layout_a.dbu (), layout_b.dbu ()); - } - - std::map > all_layers; - for (unsigned int i = 0; i < layout_a.layers (); ++i) { - if (layout_a.is_valid_layer (i)) { - all_layers.insert (std::make_pair(layout_a.get_properties (i), std::make_pair(-1, -1))).first->second.first = int (i); - } - } - for (unsigned int i = 0; i < layout_b.layers (); ++i) { - if (layout_b.is_valid_layer (i)) { - all_layers.insert (std::make_pair(layout_b.get_properties (i), std::make_pair(-1, -1))).first->second.second = int (i); - } - } - - db::Layout output; - output.dbu (layout_a.dbu ()); - db::cell_index_type top_id = output.add_cell (layout_a.cell_name (top_a)); - - db::Coord us = db::coord_traits::rounded (undersize / layout_a.dbu ()); - - db::ShapeProcessor sp; - - size_t ndiff = 0; - - for (std::map >::const_iterator l = all_layers.begin (); l != all_layers.end (); ++l) { - - int layer_id = output.insert_layer (l->first); - - if (l->second.first >= 0 && l->second.second >= 0) { - - sp.boolean (layout_a, layout_a.cell (top_a), l->second.first, layout_b, layout_b.cell (top_b), l->second.second, - output.cell (top_id).shapes (layer_id), db::BooleanOp::Xor, true /*recursive*/); - - sp.size (output, output.cell (top_id), layer_id, output.cell (top_id).shapes (layer_id), -us, (unsigned int) 2, true /*recursive*/); - - } else if (l->second.first >= 0) { - - sp.size (layout_a, layout_a.cell (top_a), l->second.first, output.cell (top_id).shapes (layer_id), -us, (unsigned int) 2, true /*recursive*/); - - } else if (l->second.second >= 0) { - - sp.size (layout_b, layout_b.cell (top_b), l->second.second, output.cell (top_id).shapes (layer_id), -us, (unsigned int) 2, true /*recursive*/); - - } - - size_t n = output.cell (top_id).shapes (layer_id).size (); - // if (n > 0) { - ndiff += n; - tl::info << " " << l->first.to_string () << ": " << n; - // } - - } - - if (ndiff > 0) { - tl::info << "----------------------------------------------------"; - tl::info << " Total differences: " << ndiff; - ret = 1; - } - - if (! outfile.empty ()) { - - db::SaveLayoutOptions options; - options.set_format_from_filename (outfile); - if (format_set) { - options.set_format (format); - } - - db::Writer writer (options); - tl::OutputStream file (outfile, tl::OutputStream::OM_Auto); - writer.write (output, file); - - } - - } catch (std::exception &ex) { - tl::error << ex.what (); - return 1; - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - return 1; - } catch (...) { - tl::error << "unspecific error"; - return 1; - } - - return ret; -} - - diff --git a/src/buddies/src/strmxor/strmxor.pro b/src/buddies/src/strmxor/strmxor.pro index 0896bd247..e04ca961a 100644 --- a/src/buddies/src/strmxor/strmxor.pro +++ b/src/buddies/src/strmxor/strmxor.pro @@ -1,13 +1,2 @@ -include($$PWD/../../../klayout.pri) - -TEMPLATE = app - -TARGET = strmxor -DESTDIR = $$OUT_PWD/../../.. - -SOURCES = strmxor.cc - -INCLUDEPATH += ../bd ../../../tl ../../../db ../../../gsi -DEPENDPATH += ../bd ../../../tl ../../../db ../../../gsi -LIBS += -L$$DESTDIR -lklayout_bd -lklayout_tl -lklayout_db -lklayout_gsi +include($$PWD/../buddy_app.pri) diff --git a/src/buddies/unit_tests/bdStrmcmpTests.cc b/src/buddies/unit_tests/bdStrmcmpTests.cc index 389b6eaaf..9616fff81 100644 --- a/src/buddies/unit_tests/bdStrmcmpTests.cc +++ b/src/buddies/unit_tests/bdStrmcmpTests.cc @@ -533,3 +533,57 @@ TEST(8B) "Cell TRANS in a is renamed to SNART in b\n" ); } + +TEST(9A) +{ + CaptureChannel cap; + + tl::warn.add (&cap, false); + tl::info.add (&cap, false); + tl::error.add (&cap, false); + + std::string input_a = ut::testsrc (); + input_a += "/testdata/bd/strmcmp_in.gds"; + + std::string input_b = ut::testsrc (); + input_b += "/testdata/bd/strmcmp_ref9.gds"; + + char *argv[] = { "x", const_cast (input_a.c_str ()), const_cast (input_b.c_str ()) }; + + EXPECT_EQ (strmcmp (sizeof (argv) / sizeof (argv[0]), argv), 1); + + EXPECT_EQ (cap.captured_text (), + "Layer 8/1 is not present in layout b, but in a\n" + "Layouts differ\n" + ); +} + +TEST(9B) +{ + CaptureChannel cap; + + tl::warn.add (&cap, false); + tl::info.add (&cap, false); + tl::error.add (&cap, false); + + std::string input_a = ut::testsrc (); + input_a += "/testdata/bd/strmcmp_in.gds"; + + std::string input_b = ut::testsrc (); + input_b += "/testdata/bd/strmcmp_ref9.gds"; + + char *argv[] = { "x", "-l", const_cast (input_a.c_str ()), const_cast (input_b.c_str ()) }; + + EXPECT_EQ (strmcmp (sizeof (argv) / sizeof (argv[0]), argv), 1); + + EXPECT_EQ (cap.captured_text (), + "Texts differ for layer 8/1 in cell RINGO\n" + "Not in b but in a:\n" + " ('FB',r0 0,1800)\n" + " ('OSC',r0 24560,1800)\n" + " ('VDD',r0 0,2800)\n" + " ('VSS',r0 0,0)\n" + "Not in a but in b:\n" + "Layouts differ\n" + ); +} diff --git a/testdata/bd/strmcmp_ref9.gds b/testdata/bd/strmcmp_ref9.gds new file mode 100644 index 000000000..b44e2a2ab Binary files /dev/null and b/testdata/bd/strmcmp_ref9.gds differ