Merge d9f25ae20d
into 49d3c73c2c
This commit is contained in:
commit
d70c82580d
|
@ -17,9 +17,13 @@ name: skywater-pdk-scripts
|
|||
channels:
|
||||
- symbiflow
|
||||
- defaults
|
||||
- LiteX-Hub
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- python=3.8
|
||||
- pip
|
||||
- magic
|
||||
|
||||
# Packages installed from PyPI
|
||||
- pip:
|
||||
- -r requirements.txt
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
Creates cell layouts for cells with GDS files in the skywater-pdk libraries.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import contextlib
|
||||
import traceback
|
||||
import errno
|
||||
|
||||
sys.path.insert(0, os.path.abspath(__file__ + '/../../../../'))
|
||||
|
||||
from skywater_pdk.gds_to_svg import convert_gds_to_svg # noqa: E402
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=argv[0],
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'libraries_dir',
|
||||
help='Path to the libraries directory of skywater-pdk',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'tech_file',
|
||||
help='Path to the .tech file',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'output_dir',
|
||||
help='Path to the output directory',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'--libname',
|
||||
help='Library name to generate cell layouts for from GDS files',
|
||||
type=str
|
||||
)
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
help='Version to generate cell layouts for from GDS files',
|
||||
type=str
|
||||
)
|
||||
parser.add_argument(
|
||||
'--create-dirs',
|
||||
help='Create directories for output when not present',
|
||||
action='store_true'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--failed-inputs',
|
||||
help='Path to GDS files for which Magic failed to generate SVG files',
|
||||
type=Path
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
gdsfiles = list(args.libraries_dir.rglob('*.gds'))
|
||||
|
||||
nc = contextlib.nullcontext()
|
||||
|
||||
with open(args.failed_inputs, 'w') if args.failed_inputs else nc as err:
|
||||
for gdsfile in gdsfiles:
|
||||
outdir = (args.output_dir /
|
||||
gdsfile.parent.resolve()
|
||||
.relative_to(args.libraries_dir.resolve()))
|
||||
library = outdir.relative_to(args.output_dir).parts[0]
|
||||
ver = outdir.relative_to(args.output_dir).parts[1]
|
||||
if args.libname and args.libname != library:
|
||||
continue
|
||||
if args.version and args.version != ver:
|
||||
continue
|
||||
print(f'===> {str(gdsfile)}')
|
||||
try:
|
||||
if not outdir.exists():
|
||||
if args.create_dirs:
|
||||
outdir.mkdir(parents=True)
|
||||
else:
|
||||
print(f'The output directory {str(outdir)} is missing')
|
||||
print('Run the script with --create-dirs')
|
||||
return errno.ENOENT
|
||||
outfile = outdir / gdsfile.with_suffix('.svg').name
|
||||
convert_gds_to_svg(gdsfile, args.tech_file, outfile)
|
||||
except Exception:
|
||||
print(
|
||||
f'Failed to generate cell layout for {str(gdsfile)}',
|
||||
file=sys.stderr
|
||||
)
|
||||
traceback.print_exc()
|
||||
err.write(f'{gdsfile}\n')
|
||||
|
||||
print('Finished generating cell layouts')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
|
@ -0,0 +1,208 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
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 os
|
||||
import re
|
||||
import argparse
|
||||
|
||||
sys.path.insert(0, os.path.abspath(__file__ + '/../../'))
|
||||
|
||||
from skywater_pdk.tools import magic, draw # noqa: E402
|
||||
|
||||
|
||||
def convert_gds_to_svg(
|
||||
input_gds,
|
||||
input_techfile,
|
||||
output_svg=None,
|
||||
tmp_tcl=None,
|
||||
keep_temporary_files=False) -> int:
|
||||
"""
|
||||
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
|
||||
----------
|
||||
input_gds : str
|
||||
Path to input GDS file
|
||||
input_techfile : str
|
||||
Path to input technology definition file (.tech)
|
||||
output_svg : str
|
||||
Path to output SVG file
|
||||
keep_temporary_files : bool
|
||||
Determines if intermediate TCL script should be kept
|
||||
|
||||
Returns
|
||||
-------
|
||||
int : 0 if finished successfully, error code from `magic` otherwise
|
||||
"""
|
||||
input_gds = os.path.abspath(input_gds)
|
||||
if output_svg:
|
||||
output_svg = os.path.abspath(output_svg)
|
||||
destdir, _ = os.path.split(output_svg)
|
||||
else:
|
||||
destdir, name = os.path.split(input_gds)
|
||||
output_svg = os.path.join(destdir, f'{name}.svg')
|
||||
input_techfile = os.path.abspath(input_techfile)
|
||||
|
||||
workdir, _ = os.path.split(input_techfile)
|
||||
|
||||
if output_svg:
|
||||
filename, _ = os.path.splitext(output_svg)
|
||||
if not tmp_tcl:
|
||||
tmp_tcl = f'{filename}.tcl'
|
||||
try:
|
||||
tmp_tcl, output_svg = magic.create_tcl_plot_script_for_gds(
|
||||
input_gds,
|
||||
tmp_tcl,
|
||||
output_svg)
|
||||
magic.run_magic(
|
||||
tmp_tcl,
|
||||
input_techfile,
|
||||
workdir,
|
||||
display_workstation='XR')
|
||||
|
||||
if not keep_temporary_files:
|
||||
if os.path.exists(tmp_tcl):
|
||||
os.unlink(tmp_tcl)
|
||||
assert os.path.exists(output_svg), f'Magic did not create {output_svg}'
|
||||
except magic.MagicError as err:
|
||||
if not keep_temporary_files:
|
||||
if os.path.exists(tmp_tcl):
|
||||
os.unlink(tmp_tcl)
|
||||
print(err)
|
||||
return err.errorcode
|
||||
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=FileSave",
|
||||
"--verb=FileClose",
|
||||
"--verb=FileQuit",
|
||||
output_svg
|
||||
],
|
||||
3)
|
||||
if result[-1] != 0:
|
||||
return result[-1]
|
||||
|
||||
result = draw.run_inkscape([
|
||||
f'--export-plain-svg={output_svg}',
|
||||
'--existsport-background-opacity=1.0',
|
||||
output_svg
|
||||
],
|
||||
3)
|
||||
return result[-1]
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=argv[0],
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'input_gds',
|
||||
help="Path to the input .gds file"
|
||||
)
|
||||
parser.add_argument(
|
||||
'input_tech',
|
||||
help="Path to the input .tech file"
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output-svg',
|
||||
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:])
|
||||
|
||||
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__':
|
||||
sys.exit(main(sys.argv))
|
|
@ -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