/* KLayout Layout Viewer Copyright (C) 2006-2019 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 "dbMAGReader.h" #include "dbStream.h" #include "dbObjectWithProperties.h" #include "dbArray.h" #include "dbStatic.h" #include "dbShapeProcessor.h" #include "tlException.h" #include "tlString.h" #include "tlClassRegistry.h" #include "tlFileUtils.h" #include "tlUri.h" #include #include namespace db { // --------------------------------------------------------------- // MAGReader MAGReader::MAGReader (tl::InputStream &s) : m_stream (s), m_progress (tl::to_string (tr ("Reading MAG file")), 1000), m_lambda (1.0), m_dbu (0.001), m_merge (true) { m_progress.set_format (tl::to_string (tr ("%.0fk lines"))); m_progress.set_format_unit (1000.0); m_progress.set_unit (100000.0); mp_current_stream = 0; } MAGReader::~MAGReader () { // .. nothing yet .. } const LayerMap & MAGReader::read (db::Layout &layout) { return read (layout, db::LoadLayoutOptions ()); } const LayerMap & MAGReader::read (db::Layout &layout, const db::LoadLayoutOptions &options) { prepare_layers (); const db::MAGReaderOptions &specific_options = options.get_options (); m_lambda = specific_options.lambda; m_dbu = specific_options.dbu; m_lib_paths = specific_options.lib_paths; m_merge = specific_options.merge; mp_current_stream = 0; db::LayerMap lm = specific_options.layer_map; lm.prepare (layout); set_layer_map (lm); set_create_layers (specific_options.create_other_layers); set_keep_layer_names (specific_options.keep_layer_names); db::cell_index_type top_cell = layout.add_cell (cell_name_from_path (m_stream.source ()).c_str ()); layout.dbu (m_dbu); m_cells_to_read.clear (); m_cells_read.clear (); m_use_lib_paths.clear (); m_dbu_trans_inv = db::CplxTrans (m_dbu).inverted (); m_tech.clear (); { tl::SelfTimer timer (tl::verbosity () >= 21, "Reading MAGIC file tree"); // This is the seed do_read (layout, top_cell, m_stream); while (! m_cells_to_read.empty ()) { std::pair > next = *m_cells_to_read.begin (); m_cells_to_read.erase (m_cells_to_read.begin ()); tl::InputStream stream (next.second.first); tl::TextInputStream text_stream (stream); do_read (layout, next.second.second, text_stream); } } finish_layers (layout); return layer_map (); } void MAGReader::error (const std::string &msg) { throw MAGReaderException (msg, mp_current_stream->line_number (), mp_current_stream->source ()); } void MAGReader::warn (const std::string &msg) { // TODO: compress tl::warn << msg << tl::to_string (tr (" (line=")) << mp_current_stream->line_number () << tl::to_string (tr (", file=")) << mp_current_stream->source () << ")"; } db::cell_index_type MAGReader::cell_from_path (const std::string &path, db::Layout &layout) { std::map::const_iterator c = m_cells_read.find (path); if (c != m_cells_read.end ()) { return c->second; } // @@@ this can lead to cell variants if a cell is present with different library paths ... (L500_CHAR_p) db::cell_index_type ci = layout.add_cell (cell_name_from_path (path).c_str ()); m_cells_read.insert (std::make_pair (path, ci)); std::string cell_file; if (! resolve_path (path, cell_file)) { // skip with a warning if the file can't be opened (TODO: better to raise an error?) tl::warn << tl::to_string (tr ("Unable to find a layout file for cell - skipping this cell: ")) << path; layout.cell (ci).set_ghost_cell (true); } else { m_cells_to_read.insert (std::make_pair (path, std::make_pair (cell_file, ci))); } return ci; } std::string MAGReader::cell_name_from_path (const std::string &path) { std::string file = tl::split (path, "/").back (); return tl::split (file, ".").front (); } static bool find_and_normalize_file (const tl::URI &uri, std::string &path) { // TODO: sync with plugin definition static const char *extensions[] = { ".mag", ".mag.gz", ".MAG", ".MAG.gz" }; for (size_t e = 0; e < sizeof (extensions) / sizeof (extensions [0]); ++e) { if (uri.scheme ().empty () || uri.scheme () == "file") { std::string fp = uri.path () + extensions[e]; if (tl::verbosity () >= 30) { tl::log << tl::to_string (tr ("Trying layout file: ")) << fp; } if (tl::file_exists (fp)) { path = fp; return true; } } else { // TODO: this is not quite efficient, but the only thing we can do for now tl::URI uri_with_ext = uri; uri_with_ext.set_path (uri_with_ext.path () + extensions[e]); std::string us = uri_with_ext.to_string (); if (tl::verbosity () >= 30) { tl::log << tl::to_string (tr ("Trying layout URI: ")) << us; } try { tl::InputStream is (us); if (is.get (1)) { path = us; return true; } } catch (...) { // .. nothing yet .. } } } return false; } bool MAGReader::resolve_path (const std::string &path, std::string &real_path) { tl::Eval expr; /* @@@ expr.set_var ("tech_dir", m_default_base_path); expr.set_var ("tech_file", m_lyt_file); expr.set_var ("tech_name", m_lyt_file); */ expr.set_var ("tech_info", m_tech); tl::URI path_uri (path); // absolute URIs are kept - we just try to figure out the suffix if (tl::is_absolute (path_uri.path ())) { return find_and_normalize_file (path_uri, real_path); } tl::URI source_uri (mp_current_stream->source ()); source_uri.set_path (tl::dirname (source_uri.path ())); // first attempt: try relative to source if (find_and_normalize_file (source_uri.resolved (tl::URI (path)), real_path)) { return true; } // then try relative to library paths for (std::vector::const_iterator lp = m_lib_paths.begin (); lp != m_lib_paths.end (); ++lp) { std::string lib_path = expr.interpolate (*lp); if (find_and_normalize_file (source_uri.resolved (tl::URI (lib_path).resolved (tl::URI (path))), real_path)) { return true; } } return false; } void MAGReader::do_read (db::Layout &layout, db::cell_index_type cell_index, tl::TextInputStream &stream) { try { mp_current_stream = &stream; do_read_part (layout, cell_index, stream); if (m_merge) { do_merge_part (layout, cell_index); } } catch (tl::Exception &ex) { error (ex.msg ()); } } void MAGReader::do_read_part (db::Layout &layout, db::cell_index_type cell_index, tl::TextInputStream &stream) { tl::SelfTimer timer (tl::verbosity () >= 31, "File read"); if (tl::verbosity () >= 30) { tl::log << "Reading layout file: " << stream.source (); } std::string l = stream.get_line (); if (l != "magic") { error (tl::to_string (tr ("Could not find 'magic' header line - is this a MAGIC file?"))); } layout.add_meta_info (db::MetaInfo ("magic_lambda", "MAGIC lambda value", tl::to_string (m_lambda))); bool valid_layer = false; unsigned int current_layer = 0; bool in_labels = false; while (! stream.at_end ()) { std::string l = stream.get_line (); tl::Extractor ex (l.c_str ()); if (ex.at_end () || ex.test ("#")) { // skip empty lines and comments continue; } else if (ex.test ("tech")) { ex.read_word_or_quoted (m_tech); if (&m_stream == &stream) { // initial file - store technology layout.add_meta_info (db::MetaInfo ("magic_tech", "MAGIC technology string", m_tech)); } ex.expect_end (); } else if (ex.test ("timestamp")) { size_t ts = 0; ex.read (ts); if (&m_stream == &stream) { // initial file - store technology layout.add_meta_info (db::MetaInfo ("magic_timestamp", "MAGIC main file timestamp", tl::to_string (ts))); } ex.expect_end (); } else if (ex.test ("<<")) { std::string lname; ex.read_word_or_quoted (lname); if (lname == "end") { in_labels = false; valid_layer = false; } else if (lname == "labels") { in_labels = true; } else { in_labels = false; std::pair ll = open_layer (layout, lname); valid_layer = ll.first; current_layer = ll.second; } ex.expect (">>"); ex.expect_end (); } else if (ex.test ("rect")) { if (in_labels) { error (tl::to_string (tr ("'rect' statement inside labels section"))); } else if (valid_layer) { read_rect (ex, layout, cell_index, current_layer); } } else if (ex.test ("rlabel")) { if (! in_labels) { error (tl::to_string (tr ("'rlabel' statement outside labels section"))); } else { read_rlabel (ex, layout, cell_index); } } else if (ex.test ("use")) { read_cell_instance (ex, stream, layout, cell_index); } } } void MAGReader::do_merge_part (Layout &layout, cell_index_type cell_index) { tl::SelfTimer timer (tl::verbosity () >= 31, "Merge step"); db::Cell &cell = layout.cell (cell_index); db::ShapeProcessor sp; if (tl::verbosity () >= 40) { sp.enable_progress (tl::to_string (tr ("Merging shapes for MAG reader"))); } for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) { unsigned int li = (unsigned int) (*l).first; sp.merge (layout, cell, li, cell.shapes (li), false); } } void MAGReader::read_rect (tl::Extractor &ex, Layout &layout, cell_index_type cell_index, unsigned int layer) { double l, b, r, t; ex.read (l); ex.read (b); ex.read (r); ex.read (t); ex.expect_end (); db::DBox box (l, b, r, t); layout.cell (cell_index).shapes (layer).insert ((box * m_lambda).transformed (m_dbu_trans_inv)); } void MAGReader::read_rlabel (tl::Extractor &ex, Layout &layout, cell_index_type cell_index) { std::string lname; ex.read (lname); double l, b, r, t; ex.read (l); ex.read (b); ex.read (r); ex.read (t); int pos = 0; ex.read (pos); ex.skip (); db::DText text (db::DTrans (), *ex); double x = 0.5 * (l + r); double y = 0.5 * (b + t); if (pos == 2 || pos == 3 || pos == 4) { x = r; } else if (pos == 6 || pos == 7 || pos == 8) { x = l; } if (pos == 1 || pos == 2 || pos == 8) { y = t; } else if (pos == 4 || pos == 5 || pos == 6) { y = b; } text.move (db::DVector (x, y)); if (lname != "space") { // really? "space"? std::pair ll = open_layer (layout, lname); if (ll.first) { layout.cell (cell_index).shapes (ll.second).insert ((text * m_lambda).transformed (m_dbu_trans_inv)); } } } void MAGReader::read_cell_instance (tl::Extractor &ex, tl::TextInputStream &stream, Layout &layout, cell_index_type cell_index) { const char *include_chars_in_files = "$_,.-$+#:;[]()<>|/\\"; std::string filename, use_id, lib_path; ex.read_word_or_quoted (filename, include_chars_in_files); if (! ex.at_end ()) { ex.read_word_or_quoted (use_id); } if (! ex.at_end ()) { ex.read_word_or_quoted (lib_path, include_chars_in_files); } if (lib_path.empty ()) { std::map::const_iterator lp = m_use_lib_paths.find (filename); if (lp != m_use_lib_paths.end ()) { lib_path = lp->second; } } else { // give precendence to lib_path filename = tl::filename (filename); // save for next use m_use_lib_paths.insert (std::make_pair (filename, lib_path)); } if (! lib_path.empty ()) { // NOTE: we don't use the system separator because it looks like MAG files use "/". filename = lib_path + "/" + filename; } // read more lines until box db::DVector a, b, p; unsigned long na = 1, nb = 1; db::DCplxTrans trans; while (! stream.at_end ()) { std::string l = stream.get_line (); tl::Extractor ex2 (l.c_str ()); if (ex2.at_end () || ex2.test ("#")) { continue; } else if (ex2.test ("array")) { int xlo = 0, xhi = 0, ylo = 0, yhi = 0; double xsep = 0.0, ysep = 0.0; ex2.read (xlo); ex2.read (xhi); ex2.read (xsep); ex2.read (ylo); ex2.read (yhi); ex2.read (ysep); na = (unsigned long) std::max (0, xhi - xlo + 1); a = db::DVector (xsep, 0) * m_lambda; nb = (unsigned long) std::max (0, yhi - ylo + 1); b = db::DVector (ysep, 0) * m_lambda; } else if (ex2.test ("timestamp")) { // ignored } else if (ex2.test ("transform")) { double m11 = 0.0, m12 = 0.0, m21 = 0.0, m22 = 0.0; double dx = 0.0, dy = 0.0; ex2.read (m11); ex2.read (m12); ex2.read (dx); ex2.read (m21); ex2.read (m22); ex2.read (dy); trans = db::DCplxTrans (db::Matrix2d (m11, m12, m21, m22), db::DVector (dx, dy) * m_lambda); } else if (ex2.test ("box")) { // ignored break; } } // create the instance db::cell_index_type ci = cell_from_path (filename, layout); db::ICplxTrans itrans = m_dbu_trans_inv * trans * db::CplxTrans (m_dbu); if (na == 1 && nb == 1) { layout.cell (cell_index).insert (db::CellInstArray (db::CellInst (ci), itrans)); } else { layout.cell (cell_index).insert (db::CellInstArray (db::CellInst (ci), itrans, m_dbu_trans_inv * a, m_dbu_trans_inv * b, na, nb)); } } }