This commit is contained in:
Grzegorz Latosiński 2023-02-06 11:11:28 -03:00 committed by GitHub
commit d70c82580d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 583 additions and 0 deletions

View File

@ -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

View File

@ -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))

View File

@ -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))

View File

@ -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

View File

@ -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)