klayout/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc

685 lines
18 KiB
C++

/*
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 "dbMAGReader.h"
#include "dbStream.h"
#include "dbObjectWithProperties.h"
#include "dbArray.h"
#include "dbStatic.h"
#include "dbShapeProcessor.h"
#include "dbTechnology.h"
#include "tlException.h"
#include "tlString.h"
#include "tlClassRegistry.h"
#include "tlFileUtils.h"
#include "tlUri.h"
#include <cctype>
#include <string>
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), mp_klayout_tech (0)
{
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)
{
init (options);
prepare_layers (layout);
mp_klayout_tech = layout.technology ();
const db::MAGReaderOptions &specific_options = options.get_options<db::MAGReaderOptions> ();
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;
set_layer_map (specific_options.layer_map);
set_create_layers (specific_options.create_other_layers);
set_keep_layer_names (specific_options.keep_layer_names);
tl::URI source_uri (m_stream.source ());
std::string top_cellname = cell_name_from_path (source_uri.path ());
db::cell_index_type top_cell;
if (layout.has_cell (top_cellname.c_str ())) {
top_cell = layout.cell_by_name (top_cellname.c_str ()).second;
} else {
top_cell = layout.add_cell (top_cellname.c_str ());
}
check_dbu (m_dbu);
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 ();
prepare_layers (layout);
{
tl::SelfTimer timer (tl::verbosity () >= 11, "Reading MAGIC file tree");
// This is the seed
do_read (layout, top_cell, m_stream);
while (! m_cells_to_read.empty ()) {
std::pair<std::string, std::pair<std::string, db::cell_index_type> > 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_out ();
}
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, int wl)
{
if (warn_level () < wl) {
return;
}
if (first_warning ()) {
tl::warn << tl::sprintf (tl::to_string (tr ("In file %s:")), mp_current_stream->source ());
}
int ws = compress_warning (msg);
if (ws < 0) {
tl::warn << msg
<< tl::to_string (tr (" (line=")) << mp_current_stream->line_number ()
<< tl::to_string (tr (", file=")) << mp_current_stream->source ()
<< ")";
} else if (ws == 0) {
tl::warn << tl::to_string (tr ("... further warnings of this kind are not shown"));
}
}
db::cell_index_type
MAGReader::cell_from_path (const std::string &path, db::Layout &layout)
{
std::string cellname = tl::filename (path);
std::map<std::string, db::cell_index_type>::const_iterator c = m_cells_read.find (cellname);
if (c != m_cells_read.end ()) {
return c->second;
}
// NOTE: this can lead to cell variants if a cell is present with different library paths ... (L500_CHAR_p)
db::cell_index_type ci;
if (layout.has_cell (cellname.c_str ())) {
// NOTE: this reuses an existing cell and will add(!) the layout to the latter. This
// enables "incremental read" like for GDS files.
ci = layout.cell_by_name (cellname.c_str ()).second;
} else {
ci = layout.add_cell (cell_name_from_path (path).c_str ());
}
m_cells_read.insert (std::make_pair (cellname, ci));
std::string cell_file;
if (! resolve_path (path, layout, 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 (cellname, std::make_pair (cell_file, ci)));
}
return ci;
}
std::string
MAGReader::cell_name_from_path (const std::string &path)
{
std::string file = tl::filename (path);
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_abstract_path ();
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, const db::Layout & /*layout*/, std::string &real_path)
{
tl::Eval expr;
// the variables supported for evaluation are
// "tech_name": the name of the KLayout technology this file is loaded for (this may be the Magic technology name)
// "tech_dir": the path to KLayout's technology folder for "tech_name" or the default technology's folder path
// "magic_tech": the technology name from the Magic file currently read
if (mp_klayout_tech) {
expr.set_var ("tech_dir", mp_klayout_tech->base_path ());
expr.set_var ("tech_name", mp_klayout_tech->name ());
} else {
expr.set_var ("tech_dir", std::string ("."));
expr.set_var ("tech_name", std::string ());
}
expr.set_var ("magic_tech", 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<std::string>::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, tl::to_string (tr ("File read: ")) + m_stream.source ());
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 ("lambda", db::MetaInfo ("lambda value (tech scaling)", 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 ("magic_technology", db::MetaInfo (tl::to_string (tr ("MAGIC technology string")), m_tech));
// propose this is the KLayout technology unless a good one is given
if (! mp_klayout_tech) {
layout.add_meta_info ("technology", db::MetaInfo (tl::to_string (tr ("Technology name")), m_tech));
}
}
ex.expect_end ();
} else if (ex.test ("timestamp")) {
size_t ts = 0;
ex.read (ts);
if (&m_stream == &stream) {
// initial file - store timestamp
layout.add_meta_info ("magic_timestamp", db::MetaInfo ("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 if (lname == "checkpaint") {
// ignore "checkpaint" internal layer
in_labels = false;
valid_layer = false;
} else {
in_labels = false;
std::pair<bool, unsigned int> 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 ("tri")) {
if (in_labels) {
error (tl::to_string (tr ("'rect' statement inside labels section")));
} else if (valid_layer) {
read_tri (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")));
} else {
sp.disable_progress ();
}
sp.set_base_verbosity (40);
std::vector<db::Text> saved_texts;
for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) {
unsigned int li = (unsigned int) (*l).first;
db::Shapes &shapes = cell.shapes (li);
// save texts before merge
saved_texts.clear ();
for (db::Shapes::shape_iterator t = shapes.begin (db::ShapeIterator::Texts); ! t.at_end (); ++t) {
saved_texts.push_back (db::Text ());
t->text (saved_texts.back ());
}
sp.merge (layout, cell, li, shapes, false);
// re-insert the texts
for (std::vector<db::Text>::const_iterator t = saved_texts.begin (); t != saved_texts.end (); ++t) {
shapes.insert (*t);
}
}
}
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_tri (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);
bool s = false, e = false;
while (! ex.at_end ()) {
if (ex.test ("s")) {
s = true;
} else if (ex.test ("e")) {
e = true;
} else {
break;
}
}
ex.expect_end ();
std::vector<db::Point> pts;
if (s && e) {
pts.push_back (db::Point (l, b));
pts.push_back (db::Point (r, t));
pts.push_back (db::Point (r, b));
} else if (s) {
pts.push_back (db::Point (l, b));
pts.push_back (db::Point (l, t));
pts.push_back (db::Point (r, b));
} else if (e) {
pts.push_back (db::Point (r, b));
pts.push_back (db::Point (l, t));
pts.push_back (db::Point (r, t));
} else {
pts.push_back (db::Point (l, b));
pts.push_back (db::Point (l, t));
pts.push_back (db::Point (r, t));
}
db::SimplePolygon poly;
poly.assign_hull (pts.begin (), pts.end ());
layout.cell (cell_index).shapes (layer).insert ((poly * 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);
// skip sticky flag (optional)
ex.test ("s");
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 (ex.get (), db::DTrans ());
double x = 0.5 * (l + r);
double y = 0.5 * (b + t);
if (pos == 2 || pos == 3 || pos == 4) {
text.halign (db::HAlignRight);
x = r;
} else if (pos == 6 || pos == 7 || pos == 8) {
text.halign (db::HAlignLeft);
x = l;
} else {
text.halign (db::HAlignCenter);
}
if (pos == 1 || pos == 2 || pos == 8) {
text.valign (db::VAlignTop);
y = t;
} else if (pos == 4 || pos == 5 || pos == 6) {
text.valign (db::VAlignBottom);
y = b;
} else {
text.valign (db::VAlignCenter);
}
text.move (db::DVector (x, y));
if (true || lname != "space") { // really? "space"? ignore it?
std::pair<bool, unsigned int> 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<std::string, std::string>::const_iterator lp = m_use_lib_paths.find (filename);
if (lp != m_use_lib_paths.end ()) {
lib_path = lp->second;
}
} else {
// give precedence 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 (0, ysep) * 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
a = trans * a;
b = trans * b;
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));
}
}
}