gds_to_svg: moved magic- and Inkscape-related methods to separate modules
Signed-off-by: Grzegorz Latosinski <glatosinski@antmicro.com>
This commit is contained in:
parent
873c89fd13
commit
007ecca8f4
|
@ -17,209 +17,143 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
Module for generating cell layouts for given technology files and GDS files.
|
||||||
|
|
||||||
|
Creates cell layout SVG files from GDS cell files using `magic` tool.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
FATAL_ERROR = re.compile('((Error parsing)|(No such file or directory)|(couldn\'t be read))') # noqa: E501
|
sys.path.insert(0, os.path.abspath(__file__ + '../../../'))
|
||||||
READING_REGEX = re.compile('Reading "([^"]*)".')
|
|
||||||
|
|
||||||
debug = True
|
from skywater_pdk.tools import magic, draw # noqa: E402
|
||||||
superdebug = True
|
|
||||||
|
|
||||||
|
|
||||||
def _magic_tcl_header(ofile, gdsfile):
|
def convert_gds_to_svg(
|
||||||
|
input_gds,
|
||||||
|
input_techfile,
|
||||||
|
output_svg=None,
|
||||||
|
tmp_tcl=None,
|
||||||
|
keep_temporary_files=False) -> int:
|
||||||
"""
|
"""
|
||||||
Adds a header to TCL file.
|
Converts GDS file to SVG cell layout diagram.
|
||||||
|
|
||||||
|
Generates TCL script for drawing a cell layout in `magic` tool and creates
|
||||||
|
a SVG file with the diagram.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
ofile: output file stream
|
input_gds : str
|
||||||
gdsfile: path to GDS file
|
Path to input GDS file
|
||||||
"""
|
input_techfile : str
|
||||||
print('#!/bin/env wish', file=ofile)
|
Path to input technology definition file (.tech)
|
||||||
print('drc off', file=ofile)
|
output_svg : str
|
||||||
print('scalegrid 1 2', file=ofile)
|
Path to output SVG file
|
||||||
print('cif istyle vendorimport', file=ofile)
|
keep_temporary_files : bool
|
||||||
print('gds readonly true', file=ofile)
|
Determines if intermediate TCL script should be kept
|
||||||
print('gds rescale false', file=ofile)
|
|
||||||
print('tech unlock *', file=ofile)
|
|
||||||
print('cif warning default', file=ofile)
|
|
||||||
print('set VDD VPWR', file=ofile)
|
|
||||||
print('set GND VGND', file=ofile)
|
|
||||||
print('set SUB SUBS', file=ofile)
|
|
||||||
print('gds read ' + gdsfile, file=ofile)
|
|
||||||
|
|
||||||
|
Returns
|
||||||
def run_magic(destdir, tcl_path, input_techfile, d="null"):
|
-------
|
||||||
"""
|
int : 0 if finished successfully, error code from `magic` otherwise
|
||||||
Runs magic to generate layout files.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
destdir: destination directory
|
|
||||||
tcl_path: path to input TCL file
|
|
||||||
input_techfile: path to the technology file
|
|
||||||
d: Workstation type, can be NULL, X11, OGL or XWIND
|
|
||||||
"""
|
|
||||||
cmd = [
|
|
||||||
'magic',
|
|
||||||
'-nowrapper',
|
|
||||||
'-d'+d,
|
|
||||||
'-noconsole',
|
|
||||||
'-T', input_techfile,
|
|
||||||
os.path.abspath(tcl_path)
|
|
||||||
]
|
|
||||||
with open(tcl_path.replace(".tcl", ".sh"), "w") as f:
|
|
||||||
f.write("#!/bin/sh\n")
|
|
||||||
f.write(" ".join(cmd))
|
|
||||||
mproc = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
stdin=subprocess.DEVNULL,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
cwd=destdir,
|
|
||||||
universal_newlines=True)
|
|
||||||
assert mproc.stdout
|
|
||||||
max_cellname_width = 0
|
|
||||||
output_by_cells = [('', [])]
|
|
||||||
fatal_errors = []
|
|
||||||
for line in mproc.stdout.splitlines():
|
|
||||||
cifwarn = ('CIF file read warning: Input off lambda grid by 1/2; ' +
|
|
||||||
'snapped to grid')
|
|
||||||
if line.startswith(cifwarn):
|
|
||||||
continue
|
|
||||||
m = FATAL_ERROR.match(line)
|
|
||||||
if m:
|
|
||||||
fatal_errors.append(line)
|
|
||||||
m = READING_REGEX.match(line)
|
|
||||||
if m:
|
|
||||||
cell_name = m.group(1)
|
|
||||||
max_cellname_width = max(max_cellname_width, len(cell_name))
|
|
||||||
output_by_cells.append((cell_name, []))
|
|
||||||
output_by_cells[-1][-1].append(line)
|
|
||||||
for cell, lines in output_by_cells:
|
|
||||||
prefix = "magic " + cell.ljust(max_cellname_width) + ':'
|
|
||||||
for line in lines:
|
|
||||||
is_error = 'rror' in line
|
|
||||||
if superdebug or (debug and is_error):
|
|
||||||
print(prefix, line)
|
|
||||||
assert not mproc.stderr, mproc.stderr
|
|
||||||
if mproc.returncode != 0 or fatal_errors:
|
|
||||||
if fatal_errors:
|
|
||||||
msg = ['ERROR: Magic had fatal errors in output:'] + fatal_errors
|
|
||||||
else:
|
|
||||||
msg = ['ERROR: Magic exited with status ' + str(mproc.returncode)]
|
|
||||||
msg.append("")
|
|
||||||
msg.append(" ".join(cmd))
|
|
||||||
msg.append('='*75)
|
|
||||||
msg.append(mproc.stdout)
|
|
||||||
msg.append('='*75)
|
|
||||||
msg.append(destdir)
|
|
||||||
msg.append(tcl_path)
|
|
||||||
msg.append('-'*75)
|
|
||||||
msg.append(msg[0])
|
|
||||||
raise SystemError('\n'.join(msg))
|
|
||||||
return output_by_cells
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_svg(input_gds, input_techfile, output=None):
|
|
||||||
"""
|
|
||||||
Converts GDS file to a SVG layout image.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
input_gds: path to input GDS file
|
|
||||||
input_techfile: path to the technology file
|
|
||||||
output: optional path to the final SVG file
|
|
||||||
"""
|
"""
|
||||||
input_gds = os.path.abspath(input_gds)
|
input_gds = os.path.abspath(input_gds)
|
||||||
input_techfile = os.path.abspath(input_techfile)
|
if output_svg:
|
||||||
destdir, gdsfile = os.path.split(input_gds)
|
output_svg = os.path.abspath(output_svg)
|
||||||
basename, ext = os.path.splitext(gdsfile)
|
destdir, _ = os.path.split(output_svg)
|
||||||
if output:
|
|
||||||
output_svg = output
|
|
||||||
else:
|
else:
|
||||||
output_svg = os.path.join(destdir, "{}.svg".format(basename))
|
destdir, name = os.path.split(input_gds)
|
||||||
assert not os.path.exists(output_svg), output_svg + " already exists!?"
|
output_svg = os.path.join(destdir, f'{name}.svg')
|
||||||
tcl_path = os.path.join(destdir, "{}.gds2svg.tcl".format(basename))
|
input_techfile = os.path.abspath(input_techfile)
|
||||||
with open(tcl_path, 'w') as ofile:
|
|
||||||
_magic_tcl_header(ofile, input_gds)
|
workdir, _ = os.path.split(input_techfile)
|
||||||
ofile.write("load " + basename + "\n")
|
|
||||||
ofile.write("box 0 0 0 0\n")
|
if output_svg:
|
||||||
ofile.write("select top cell\n")
|
filename, _ = os.path.splitext(output_svg)
|
||||||
ofile.write("expand\n")
|
if not tmp_tcl:
|
||||||
ofile.write("view\n")
|
tmp_tcl = f'{filename}.tcl'
|
||||||
ofile.write("select clear\n")
|
try:
|
||||||
ofile.write("box position -1000 -1000\n")
|
tmp_tcl, output_svg = magic.create_tcl_plot_script_for_gds(
|
||||||
ofile.write("plot svg " + basename + ".tmp1.svg\n")
|
input_gds,
|
||||||
ofile.write("quit -noprompt\n")
|
tmp_tcl,
|
||||||
run_magic(destdir, tcl_path, input_techfile, " XR")
|
output_svg)
|
||||||
tmp1_svg = os.path.join(destdir, "{}.tmp1.svg".format(basename))
|
magic.run_magic(
|
||||||
tmp2_svg = os.path.join(destdir, "{}.tmp2.svg".format(basename))
|
tmp_tcl,
|
||||||
assert os.path.exists(tmp1_svg), tmp1_svg + " doesn't exist!?"
|
input_techfile,
|
||||||
os.unlink(tcl_path)
|
workdir,
|
||||||
for i in range(0, 3):
|
display_workstation='XR')
|
||||||
# Remove the background
|
|
||||||
with open(tmp1_svg) as f:
|
if not keep_temporary_files:
|
||||||
data = f.read()
|
if os.path.exists(tmp_tcl):
|
||||||
data = re.sub(
|
os.unlink(tmp_tcl)
|
||||||
'<rect[^>]* style="[^"]*fill-opacity:1;[^"]*"/>',
|
assert os.path.exists(output_svg), f'Magic did not create {output_svg}'
|
||||||
'',
|
except magic.MagicError as err:
|
||||||
data
|
if not keep_temporary_files:
|
||||||
)
|
if os.path.exists(tmp_tcl):
|
||||||
with open(tmp2_svg, 'w') as f:
|
os.unlink(tmp_tcl)
|
||||||
f.write(data)
|
print(err)
|
||||||
# Use inkscape to crop
|
return err.errorcode
|
||||||
retcode = run_inkscape([
|
except Exception:
|
||||||
|
if not keep_temporary_files:
|
||||||
|
if os.path.exists(tmp_tcl):
|
||||||
|
os.unlink(tmp_tcl)
|
||||||
|
raise
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_gds_diagram(input_svg, output_svg) -> int:
|
||||||
|
"""
|
||||||
|
Crops and cleans up GDS diagram.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_svg : str
|
||||||
|
Input SVG file with cell layout
|
||||||
|
output_svg : str
|
||||||
|
Output SVG file with cleaned cell layout
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int : 0 if successful, error code from Inkscape otherwise
|
||||||
|
"""
|
||||||
|
with open(input_svg, 'r') as f:
|
||||||
|
data = f.read()
|
||||||
|
data = re.sub(
|
||||||
|
'<rect[^>]* style="[^"]*fill-opacity:1;[^"]*"/>',
|
||||||
|
'',
|
||||||
|
data
|
||||||
|
)
|
||||||
|
with open(output_svg, 'w') as f:
|
||||||
|
f.write(data)
|
||||||
|
result = draw.run_inkscape([
|
||||||
"--verb=FitCanvasToDrawing",
|
"--verb=FitCanvasToDrawing",
|
||||||
"--verb=FileSave",
|
"--verb=FileSave",
|
||||||
"--verb=FileClose",
|
"--verb=FileClose",
|
||||||
"--verb=FileQuit",
|
"--verb=FileQuit",
|
||||||
tmp2_svg])
|
output_svg
|
||||||
if retcode == 0:
|
],
|
||||||
break
|
3)
|
||||||
for i in range(0, 3):
|
if result[-1] != 0:
|
||||||
# Convert back to plain SVG
|
return result[-1]
|
||||||
retcode = run_inkscape([
|
|
||||||
"--export-plain-svg=%s" % (tmp2_svg),
|
|
||||||
"--export-background-opacity=1.0",
|
|
||||||
tmp2_svg])
|
|
||||||
if retcode == 0:
|
|
||||||
break
|
|
||||||
os.unlink(tmp1_svg)
|
|
||||||
# Move into the correct location
|
|
||||||
os.rename(tmp2_svg, output_svg)
|
|
||||||
print("Created", output_svg)
|
|
||||||
|
|
||||||
|
result = draw.run_inkscape([
|
||||||
def run_inkscape(args):
|
f'--export-plain-svg={output_svg}',
|
||||||
"""
|
'--existsport-background-opacity=1.0',
|
||||||
Run Inkscape with given arguments.
|
output_svg
|
||||||
|
],
|
||||||
Parameters
|
3)
|
||||||
----------
|
return result[-1]
|
||||||
args: List of arguments to be passed to Inkscape
|
|
||||||
"""
|
|
||||||
p = subprocess.Popen(["inkscape"] + args)
|
|
||||||
try:
|
|
||||||
p.wait(timeout=60)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print("ERROR: Inkscape timed out! Sending SIGTERM")
|
|
||||||
p.terminate()
|
|
||||||
try:
|
|
||||||
p.wait(timeout=60)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print("ERROR: Inkscape timed out! Sending SIGKILL")
|
|
||||||
p.kill()
|
|
||||||
p.wait()
|
|
||||||
return p.returncode
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
parser = argparse.ArgumentParser(argv[0])
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=argv[0],
|
||||||
|
description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'input_gds',
|
'input_gds',
|
||||||
help="Path to the input .gds file"
|
help="Path to the input .gds file"
|
||||||
|
@ -232,9 +166,42 @@ def main(argv):
|
||||||
'--output-svg',
|
'--output-svg',
|
||||||
help='Path to the output .svg file'
|
help='Path to the output .svg file'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--output-tcl',
|
||||||
|
help='Path to temporary TCL file'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--keep-temporary-files',
|
||||||
|
help='Keep the temporary files in the end',
|
||||||
|
action='store_true'
|
||||||
|
)
|
||||||
args = parser.parse_args(argv[1:])
|
args = parser.parse_args(argv[1:])
|
||||||
convert_to_svg(args.input_gds, args.input_tech, args.output_svg)
|
|
||||||
return 0
|
if args.output_svg:
|
||||||
|
filename, _ = os.path.splitext(args.output_svg)
|
||||||
|
tmp_svg = f'{filename}.tmp.svg'
|
||||||
|
else:
|
||||||
|
filename, _ = os.path.splitext(args.input_gds)
|
||||||
|
tmp_svg = f'{filename}.tmp.svg'
|
||||||
|
args.output_svg = f'{filename}.svg'
|
||||||
|
|
||||||
|
result = convert_gds_to_svg(
|
||||||
|
args.input_gds,
|
||||||
|
args.input_tech,
|
||||||
|
tmp_svg,
|
||||||
|
args.output_tcl,
|
||||||
|
args.keep_temporary_files
|
||||||
|
)
|
||||||
|
|
||||||
|
if result != 0:
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = cleanup_gds_diagram(tmp_svg, args.output_svg)
|
||||||
|
|
||||||
|
if not args.keep_temporary_files:
|
||||||
|
os.unlink(tmp_svg)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
Creates cell layouts for cells with GDS files in the skywater-pdk libraries.
|
||||||
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
@ -24,11 +28,15 @@ import contextlib
|
||||||
import traceback
|
import traceback
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
from gds_to_svg import convert_to_svg
|
from gds_to_svg import convert_gds_to_svg
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
parser = argparse.ArgumentParser(prog=argv[0])
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=argv[0],
|
||||||
|
description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'libraries_dir',
|
'libraries_dir',
|
||||||
help='Path to the libraries directory of skywater-pdk',
|
help='Path to the libraries directory of skywater-pdk',
|
||||||
|
@ -92,7 +100,7 @@ def main(argv):
|
||||||
print('Run the script with --create-dirs')
|
print('Run the script with --create-dirs')
|
||||||
return errno.ENOENT
|
return errno.ENOENT
|
||||||
outfile = outdir / gdsfile.with_suffix('.svg').name
|
outfile = outdir / gdsfile.with_suffix('.svg').name
|
||||||
convert_to_svg(gdsfile, args.tech_file, outfile)
|
convert_gds_to_svg(gdsfile, args.tech_file, outfile)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(
|
print(
|
||||||
f'Failed to generate cell layout for {str(gdsfile)}',
|
f'Failed to generate cell layout for {str(gdsfile)}',
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2020 SkyWater PDK Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
A module for altering diagrams and image files using Inkscape and other tools.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def run_inkscape(args, retries=1, inkscape_executable='inkscape') -> int:
|
||||||
|
"""
|
||||||
|
Runs Inkscape for given arguments.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
args : List[str]
|
||||||
|
List of arguments to provide to Inkscape
|
||||||
|
retries : int
|
||||||
|
Number of tries to run Inkscape with given arguments
|
||||||
|
|
||||||
|
Returns
|
||||||
|
Union[List[int], int] : error codes for Inkscape runs
|
||||||
|
"""
|
||||||
|
returncodes = []
|
||||||
|
for i in range(retries):
|
||||||
|
p = subprocess.Popen([inkscape_executable] + args)
|
||||||
|
try:
|
||||||
|
p.wait(timeout=60)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("ERROR: Inkscape timed out! Sending SIGTERM")
|
||||||
|
p.terminate()
|
||||||
|
try:
|
||||||
|
p.wait(timeout=60)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("ERROR: Inkscape timed out! Sending SIGKILL")
|
||||||
|
p.kill()
|
||||||
|
p.wait()
|
||||||
|
if retries == 1:
|
||||||
|
return p.returncode
|
||||||
|
returncodes.append(p.returncode)
|
||||||
|
if p.returncode == 0:
|
||||||
|
break
|
||||||
|
return returncodes
|
|
@ -0,0 +1,191 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2020 SkyWater PDK Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
A module containing various functions related to `magic` tool
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
FATAL_ERROR = re.compile('((Error parsing)|(No such file or directory)|(couldn\'t be read))') # noqa: E501
|
||||||
|
|
||||||
|
|
||||||
|
class MagicError(Exception):
|
||||||
|
"""
|
||||||
|
Raised when there are errors in magic tool execution.
|
||||||
|
"""
|
||||||
|
def __init__(self, message, errorcode):
|
||||||
|
self.errorcode = errorcode
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
def add_magic_tcl_header(ofile, gdsfile):
|
||||||
|
"""
|
||||||
|
Adds a header to a TCL file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ofile : TextIOWrapper
|
||||||
|
output file stream
|
||||||
|
gdsfile : str
|
||||||
|
path to GDS file
|
||||||
|
"""
|
||||||
|
ofile.write('#!/bin/env wish\n')
|
||||||
|
ofile.write('drc off\n')
|
||||||
|
ofile.write('scalegrid 1 2\n')
|
||||||
|
ofile.write('cif istyle vendorimport\n')
|
||||||
|
ofile.write('gds readonly true\n')
|
||||||
|
ofile.write('gds rescale false\n')
|
||||||
|
ofile.write('tech unlock *\n')
|
||||||
|
ofile.write('cif warning default\n')
|
||||||
|
ofile.write('set VDD VPWR\n')
|
||||||
|
ofile.write('set GND VGND\n')
|
||||||
|
ofile.write('set SUB SUBS\n')
|
||||||
|
ofile.write(f'gds read {gdsfile}\n')
|
||||||
|
|
||||||
|
|
||||||
|
def create_tcl_plot_script_for_gds(
|
||||||
|
input_gds,
|
||||||
|
output_tcl=None,
|
||||||
|
output_svg=None) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Creates TCL script for creating cell layout image from GDS file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
input_gds : str
|
||||||
|
Path to GDS file
|
||||||
|
output_tcl : str
|
||||||
|
Path to created TCL file
|
||||||
|
output_svg : str
|
||||||
|
Path where the SVG file created by output_tcl script should be located
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple(str, str) : paths to TCL and SVG files (can be used if autogenerated)
|
||||||
|
"""
|
||||||
|
input_gds = os.path.abspath(input_gds)
|
||||||
|
|
||||||
|
destdir, gdsfile = os.path.split(input_gds)
|
||||||
|
basename, ext = os.path.splitext(gdsfile)
|
||||||
|
|
||||||
|
if not output_tcl:
|
||||||
|
output_tcl = os.path.join(destdir, f'{basename}.gds2svg.tcl')
|
||||||
|
|
||||||
|
if not output_svg:
|
||||||
|
output_svg = os.path.join(destdir, f'{basename}.tmp.svg')
|
||||||
|
|
||||||
|
with open(output_tcl, 'w') as ofile:
|
||||||
|
add_magic_tcl_header(ofile, input_gds)
|
||||||
|
ofile.write(f"load {basename}\n")
|
||||||
|
ofile.write("box 0 0 0 0\n")
|
||||||
|
ofile.write("select top cell\n")
|
||||||
|
ofile.write("expand\n")
|
||||||
|
ofile.write("view\n")
|
||||||
|
ofile.write("select clear\n")
|
||||||
|
ofile.write("box position -1000 -1000\n")
|
||||||
|
ofile.write(f"plot svg {output_svg}\n")
|
||||||
|
ofile.write("quit -noprompt\n")
|
||||||
|
|
||||||
|
return output_tcl, output_svg
|
||||||
|
|
||||||
|
|
||||||
|
def run_magic(
|
||||||
|
tcl_file,
|
||||||
|
technology_file,
|
||||||
|
workdir=None,
|
||||||
|
display_workstation='NULL',
|
||||||
|
debug=False,
|
||||||
|
magic_executable='magic') -> int:
|
||||||
|
"""
|
||||||
|
Generates layout files for a given TCL file and technology file.
|
||||||
|
|
||||||
|
Uses `magic` tool for generating the layout.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tcl_file : str
|
||||||
|
path to input TCL file
|
||||||
|
technology_file : str
|
||||||
|
path to the technology file
|
||||||
|
workdir : str
|
||||||
|
path to the working directory for `magic`
|
||||||
|
display_workstation : str
|
||||||
|
graphics interface, can be NULL, X11 or OpenGL
|
||||||
|
debug : bool
|
||||||
|
True if all output from `magic` tool should be displayed
|
||||||
|
magic_executable : str
|
||||||
|
Path to `magic` executable
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int: return code for `magic` tool
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
AssertionError
|
||||||
|
Raised when display_workstation is not NULL, X11 or OpenGL
|
||||||
|
MagicError
|
||||||
|
Raised when `magic` tool failed to run for given files
|
||||||
|
"""
|
||||||
|
assert display_workstation in ['NULL', 'X11', 'OpenGL', 'XR']
|
||||||
|
cmd = [
|
||||||
|
magic_executable,
|
||||||
|
'-nowrapper',
|
||||||
|
'-noconsole',
|
||||||
|
f'-d{display_workstation}',
|
||||||
|
f'-T{os.path.abspath(technology_file)}',
|
||||||
|
'-D' if debug else '',
|
||||||
|
os.path.abspath(tcl_file)
|
||||||
|
]
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
cwd=workdir,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
errors_present = False
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
m = FATAL_ERROR.match(line)
|
||||||
|
if m:
|
||||||
|
errors_present = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if result.returncode != 0 or errors_present:
|
||||||
|
msg = ['ERROR: There were fatal errors in magic.']
|
||||||
|
msg += result.stdout.splitlines()
|
||||||
|
msg += [f'ERROR: Magic exited with status {result.returncode}']
|
||||||
|
msg.append("")
|
||||||
|
msg.append(" ".join(cmd))
|
||||||
|
msg.append('='*75)
|
||||||
|
msg.append(result.stdout)
|
||||||
|
msg.append('='*75)
|
||||||
|
msg.append(tcl_file)
|
||||||
|
msg.append('-'*75)
|
||||||
|
msg.append(msg[0])
|
||||||
|
raise MagicError('\n'.join(msg), result.returncode)
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
print(result.stdout)
|
Loading…
Reference in New Issue