diff --git a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc index afacdec00..74b844ac4 100644 --- a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc +++ b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc @@ -25,13 +25,159 @@ #include "dbPolygonGenerators.h" #include "tlStream.h" #include "tlUtils.h" +#include "tlUniqueName.h" #include #include +#include namespace db { +// --------------------------------------------------------------------------------- +// CIFWriter utilities + +namespace +{ + +struct CellNameValidator +{ + CellNameValidator () { } + + bool is_valid (const std::string &name) + { + for (const char *c = name.c_str (); *c; ++c) { + if (! (isdigit (*c) || isupper (*c) || islower (*c) || *c == '$' || *c == '_' || *c == ':')) { + return false; + } + } + return true; + } + + std::string create_valid (const std::string &name) + { + std::string res; + res.reserve (name.size ()); + for (const char *c = name.c_str (); *c; ++c) { + if (isdigit (*c) || isupper (*c) || islower (*c) || *c == '$' || *c == '_' || *c == ':') { + res += *c; + } + } + if (res.empty ()) { + res = "C"; + } + return res; + } + + const char *separator () + { + return "$"; + } +}; + +struct LayerNameValidator +{ + LayerNameValidator () { } + + bool is_valid (const std::string &name) + { + for (const char *c = name.c_str (); *c; ++c) { + if (! (isdigit (*c) || isupper (*c) || *c == '_')) { + return false; + } + } + return true; + } + + std::string create_valid (const std::string &name) + { + std::string res; + res.reserve (name.size ()); + for (const char *c = name.c_str (); *c; ++c) { + char cu = toupper (*c); + if (isdigit (cu) || isalpha (cu) || *c == '_') { + res += cu; + } + } + if (res.empty ()) { + res = "C"; + } + return res; + } + + const char *separator () + { + return "N"; + } +}; + +} + +/** + * @brief Gets the CIF name for a given layer + */ +std::string cif_layer_name (const db::LayerProperties &lp) +{ + if (lp.is_named ()) { + return lp.name; + } else if (lp.is_null ()) { + return std::string (); + } else if (lp.datatype <= 0) { + return std::string ("L") + tl::to_string (lp.layer); + } else { + return std::string ("L") + tl::to_string (lp.layer) + "D" + tl::to_string (lp.datatype); + } +} + +// --------------------------------------------------------------------------------- +// CIFValidNameGenerator implementation + +template +CIFValidNameGenerator::CIFValidNameGenerator () { } + +template +template +void +CIFValidNameGenerator::insert (ID id, const std::string &name, Validator validator) +{ + if (m_existing_names.find (name) == m_existing_names.end () && validator.is_valid (name)) { + m_valid_names.insert (std::make_pair (id, name)); + m_existing_names.insert (name); + } else { + m_pending_names.insert (std::make_pair (id, name)); + } +} + +template +template +const std::string & +CIFValidNameGenerator::valid_name_for_id (ID id, Validator validator) +{ + typename std::map::const_iterator i = m_valid_names.find (id); + if (i != m_valid_names.end ()) { + return i->second; + } + + typename std::map::iterator j = m_pending_names.find (id); + if (j != m_pending_names.end ()) { + std::string valid_name = tl::unique_name (validator.create_valid (j->second), m_existing_names, validator.separator ()); + m_pending_names.erase (j); + m_valid_names.insert (std::make_pair (id, valid_name)); + return *m_existing_names.insert (valid_name).first; + } + + tl_assert (false); +} + +template +void +CIFValidNameGenerator::clear () +{ + m_existing_names.clear (); + m_valid_names.clear (); + m_pending_names.clear (); +} + // --------------------------------------------------------------------------------- // CIFWriter implementation @@ -79,6 +225,9 @@ CIFWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLa m_options = options.get_options (); mp_stream = &stream; + m_layer_names.clear (); + m_cell_names.clear (); + // compute the scale factor to get to the 10 nm basic database unit of CIF double tl_scale = options.scale_factor () * layout.dbu () / 0.01; @@ -121,6 +270,16 @@ CIFWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLa std::map db_to_cif_index_map; std::set called_cells; + // register layers for generating valid names + for (std::vector >::const_iterator layer = layers.begin (); layer != layers.end (); ++layer) { + m_layer_names.insert (layer->first, cif_layer_name (layer->second), LayerNameValidator ()); + } + + // register cells for generating valid cell names + for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { + m_cell_names.insert (*cell, layout.cell_name (*cell), CellNameValidator ()); + } + // body for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { @@ -135,7 +294,7 @@ CIFWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLa double sf = 1.0; *this << "DS " << cell_index << " " << tl_scale_denom << " " << tl_scale_divider << ";" << m_endl; - *this << "9 " << tl::to_word_or_quoted_string (layout.cell_name (*cell)) << ";" << m_endl; + *this << "9 " << m_cell_names.valid_name_for_id (*cell, CellNameValidator ()) << ";" << m_endl; // instances for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { @@ -200,7 +359,7 @@ CIFWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLa for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { m_needs_emit = true; - m_layer = l->second; + m_layer = l->first; write_texts (layout, cref, l->first, sf); write_polygons (layout, cref, l->first, sf); @@ -245,7 +404,7 @@ CIFWriter::emit_layer() { if (m_needs_emit) { m_needs_emit = false; - *this << "L " << tl::to_word_or_quoted_string (tl::to_upper_case (m_layer.name), "0123456789_.$") << ";" << m_endl; + *this << "L " << m_layer_names.valid_name_for_id (m_layer, LayerNameValidator ()) << ";" << m_endl; } } diff --git a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.h b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.h index bda5bf2be..b59f59b63 100644 --- a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.h +++ b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.h @@ -32,6 +32,9 @@ #include "dbSaveLayoutOptions.h" #include "tlProgress.h" +#include +#include + namespace tl { class OutputStream; @@ -43,6 +46,25 @@ namespace db class Layout; class SaveLayoutOptions; +/** + * @brief A class generating valid names + */ +template +class DB_PLUGIN_PUBLIC_TEMPLATE CIFValidNameGenerator +{ +public: + CIFValidNameGenerator (); + + template void insert (ID id, const std::string &name, Validator validator); + template const std::string &valid_name_for_id (ID id, Validator validator); + void clear (); + +public: + std::map m_valid_names; + std::map m_pending_names; + std::set m_existing_names; +}; + /** * @brief A CIF writer abstraction */ @@ -67,9 +89,11 @@ private: CIFWriterOptions m_options; tl::AbsoluteProgress m_progress; endl_tag m_endl; - db::LayerProperties m_layer; + unsigned int m_layer; bool m_needs_emit; - + CIFValidNameGenerator m_layer_names; + CIFValidNameGenerator m_cell_names; + CIFWriter &operator<<(const char *s); CIFWriter &operator<<(const std::string &s); CIFWriter &operator<<(endl_tag); diff --git a/src/plugins/streamers/cif/unit_tests/dbCIFReader.cc b/src/plugins/streamers/cif/unit_tests/dbCIFReader.cc index 69f548e56..ed10d9190 100644 --- a/src/plugins/streamers/cif/unit_tests/dbCIFReader.cc +++ b/src/plugins/streamers/cif/unit_tests/dbCIFReader.cc @@ -87,7 +87,6 @@ static void run_test (tl::TestBase *_this, const std::string &base, const char * } // normalize the layout by writing to CIF and reading from .. - { tl::OutputStream stream (tmp_cif_file); @@ -133,6 +132,110 @@ static void run_test (tl::TestBase *_this, const std::string &base, const char * } } +static void run_test2 (tl::TestBase *_this, const std::string &base, db::Layout &layout, const char *file_au, const char *file_au_cif, const char *map = 0, double dbu = 0.001, bool dummy_calls = false, bool blank_sep = false) +{ + db::CIFReaderOptions *opt = new db::CIFReaderOptions(); + opt->dbu = dbu; + + db::LayerMap lm; + if (map) { + unsigned int ln = 0; + tl::Extractor ex (map); + while (! ex.at_end ()) { + lm.add_expr (ex, ln++); + ex.test (","); + } + opt->layer_map = lm; + opt->create_other_layers = true; + } + + db::LoadLayoutOptions options; + options.set_options (opt); + + db::Manager m (false); + db::Layout layout2 (&m), layout2_cif (&m), layout_au (&m), layout_au_cif (&m); + + // generate a "unique" name ... + unsigned int hash = 0; + for (const char *cp = file_au; *cp; ++cp) { + hash = (hash << 4) ^ (hash >> 4) ^ ((unsigned int) *cp); + } + + // normalize the layout by writing to GDS and reading from .. + + std::string tmp_gds_file = _this->tmp_file (tl::sprintf ("tmp_%x.gds", hash)); + std::string tmp_cif_file = _this->tmp_file (tl::sprintf ("tmp_%x.cif", hash)); + + { + tl::OutputStream stream (tmp_gds_file); + db::SaveLayoutOptions options; + options.set_format ("GDS2"); + db::Writer writer (options); + writer.write (layout, stream); + } + + { + tl::InputStream stream (tmp_gds_file); + db::Reader reader (stream); + reader.read (layout2); + } + + // normalize the layout by writing to CIF and reading from .. + + { + tl::OutputStream stream (tmp_cif_file); + + db::CIFWriterOptions *opt = new db::CIFWriterOptions(); + opt->dummy_calls = dummy_calls; + opt->blank_separator = blank_sep; + + db::CIFWriter writer; + db::SaveLayoutOptions options; + options.set_options (opt); + writer.write (layout, stream, options); + } + + { + tl::InputStream stream (tmp_cif_file); + + db::CIFReaderOptions *opt = new db::CIFReaderOptions(); + opt->dbu = dbu; + db::LoadLayoutOptions reread_options; + reread_options.set_options (opt); + + db::Reader reader (stream); + reader.read (layout2_cif, reread_options); + } + + { + std::string fn (base); + fn += "/cif/"; + fn += file_au; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (layout_au); + } + + { + std::string fn (base); + fn += "/cif/"; + fn += file_au_cif; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (layout_au_cif); + } + + bool equal = db::compare_layouts (layout2, layout_au, db::layout_diff::f_boxes_as_polygons | db::layout_diff::f_verbose | db::layout_diff::f_flatten_array_insts, 1); + if (! equal) { + _this->raise (tl::sprintf ("Compare failed after reading - see %s vs %s\n", tmp_gds_file, file_au)); + } + + equal = db::compare_layouts (layout2_cif, layout_au_cif, db::layout_diff::f_boxes_as_polygons | db::layout_diff::f_verbose | db::layout_diff::f_flatten_array_insts, 1); + if (! equal) { + _this->raise (tl::sprintf ("Compare failed after writing - see %s vs %s\n", tmp_cif_file, file_au_cif)); + } +} + TEST(1a) { run_test (_this, tl::testdata_private (), "t1.cif.gz", "t1a_au.gds.gz"); @@ -206,3 +309,26 @@ TEST(rot_instances2) { run_test (_this, tl::testdata (), "issue_578.cif", "issue_578_au.gds"); } + +// issue #972 +TEST(bad_names) +{ + db::Layout ly; + + db::cell_index_type ci = ly.add_cell ("(bad_cell,a b/c)"); + db::Cell &cell = ly.cell (ci); + + unsigned int l1 = ly.insert_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.insert_layer (db::LayerProperties (1, 5)); + unsigned int l3 = ly.insert_layer (db::LayerProperties ("a b c")); + unsigned int l4 = ly.insert_layer (db::LayerProperties ("(a b c)")); + unsigned int l5 = ly.insert_layer (db::LayerProperties ("a,b/c")); + + cell.shapes (l1).insert (db::Box (0, 0, 10, 10)); + cell.shapes (l2).insert (db::Box (0, 0, 20, 20)); + cell.shapes (l3).insert (db::Box (0, 0, 30, 30)); + cell.shapes (l4).insert (db::Box (0, 0, 40, 40)); + cell.shapes (l5).insert (db::Box (0, 0, 50, 50)); + + run_test2 (_this, tl::testdata (), ly, "issue_972_au.gds", "issue_972_au.cif"); +} diff --git a/testdata/cif/issue_972_au.cif b/testdata/cif/issue_972_au.cif new file mode 100644 index 000000000..9db788b65 --- /dev/null +++ b/testdata/cif/issue_972_au.cif @@ -0,0 +1,15 @@ +(CIF file written 2022-02-12 00:19:54 by KLayout); +DS 1 1 10; +9 bad_cellabc; +L L1D0; +B 10 10 5,5; +L L1D5; +B 20 20 10,10; +L ABC; +B 30 30 15,15; +L ABCN1; +B 40 40 20,20; +L ABCN2; +B 50 50 25,25; +DF; +E diff --git a/testdata/cif/issue_972_au.gds b/testdata/cif/issue_972_au.gds new file mode 100644 index 000000000..1aa839432 Binary files /dev/null and b/testdata/cif/issue_972_au.gds differ