Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding GDS to SVG converter #249

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 file:requirements.txt
Empty file.
Empty file.
120 changes: 120 additions & 0 deletions scripts/python-skywater-pdk/skywater_pdk/cell/generate/layout.py
Original file line number Diff line number Diff line change
@@ -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))
208 changes: 208 additions & 0 deletions scripts/python-skywater-pdk/skywater_pdk/gds_to_svg.py
Original file line number Diff line number Diff line change
@@ -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))
Empty file.