From 3a9fef6ed1b3e9add5878e5bd1e6922d73f5baae Mon Sep 17 00:00:00 2001 From: Wojciech Gryncewicz Date: Fri, 13 Nov 2020 18:07:00 +0100 Subject: [PATCH 1/4] Added netlistsvg wrapper/generator --- .../skywater_pdk/netlistsvg-generate.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py diff --git a/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py b/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py new file mode 100755 index 000000000..1b0cd929d --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 The SkyWater PDK Authors. +# +# Use of this source code is governed by the Apache 2.0 +# license that can be found in the LICENSE file or at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + + +import csv +import json +import os +import sys + + +# prerequisities: netlistsvg +# input: paths to cell dirs, containing Yosys netlist +# output: generates [cell_prefix].schematic.svg +# example usage 1: ./netlistsvg-generate.py \ +# ../../../libraries/sky130_fd_sc_ms/latest/cells/a2111o \ +# ../../../libraries/sky130_fd_sc_ms/latest/cells/a2111oi +# example usage 2: ./netlistsvg-generate.py ALLLIBS + +def outfile(cellpath, define_data, ftype='', extra='', exists=False): + fname = define_data['name'].lower().replace('$', '_') + if ftype: + ftype = '.'+ftype + outpath = os.path.join(cellpath, f'{define_data["file_prefix"]}{extra}{ftype}.svg') + if exists is None: + pass + elif not exists: + #assert not os.path.exists(outpath), "Refusing to overwrite existing file:"+outpath + print("Creating", outpath) + elif exists: + assert os.path.exists(outpath), "Missing required:"+outpath + return outpath + + +def write_netlistsvg(cellpath, define_data): + netlist_json = os.path.join(cellpath, define_data['file_prefix']+'.json') + if not os.path.exists(netlist_json): + print("No " + define_data['file_prefix'] + ".json in", cellpath) + assert os.path.exists(netlist_json), netlist_json + outpath = outfile(cellpath, define_data, 'schematic') + oscmd = 'netlistsvg ' + netlist_json + ' -o ' + outpath + r = os.system(oscmd)>>8 + assert r == 0 + return r + + +def process(cellpath): + print() + print(cellpath) + define_json = os.path.join(cellpath, 'definition.json') + if not os.path.exists(define_json): + print("No definition.json in", cellpath) + assert os.path.exists(define_json), define_json + define_data = json.load(open(define_json)) + + if define_data['type'] == 'cell': + write_netlistsvg(cellpath, define_data) + + return + + +def main(args): + if len(args) and args[0] == 'ALLLIBS': + scrpath = os.path.dirname(os.path.realpath(__file__)) + relpath = '/../../../libraries/*/latest/cells/*' + #relpath = '/../../../libraries/*ms/latest/cells/x*' # DBG: limited + args = os.popen('ls -d ' + scrpath + relpath).read().strip().split('\n') + errors = 0 + for a in args: + try: + process(os.path.realpath(a)) + except: + errors +=1 + print (f'\n{len(args)} files processed, {errors} errors.') + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) + From 17c37e15cc3330e011a4e937364b51be8ff4c3b6 Mon Sep 17 00:00:00 2001 From: Wojciech Gryncewicz Date: Wed, 18 Nov 2020 19:42:07 +0100 Subject: [PATCH 2/4] Moved to argparse, pathlib, subprocess Signed-off-by: Wojciech Gryncewicz --- .../skywater_pdk/netlistsvg-generate.py | 99 ++++++++++++++----- 1 file changed, 74 insertions(+), 25 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py b/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py index 1b0cd929d..370a8f120 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py +++ b/scripts/python-skywater-pdk/skywater_pdk/netlistsvg-generate.py @@ -14,17 +14,25 @@ import json import os import sys +import argparse +import pathlib +import glob +import subprocess +def outfile(cellpath, define_data, ftype='', extra='', exists=False): + ''' Determines output file path and name. -# prerequisities: netlistsvg -# input: paths to cell dirs, containing Yosys netlist -# output: generates [cell_prefix].schematic.svg -# example usage 1: ./netlistsvg-generate.py \ -# ../../../libraries/sky130_fd_sc_ms/latest/cells/a2111o \ -# ../../../libraries/sky130_fd_sc_ms/latest/cells/a2111oi -# example usage 2: ./netlistsvg-generate.py ALLLIBS + Args: + cellpath - path to a cell [str of pathlib.Path] + define_data - cell definition data [dic] + ftype - file type suffix [str] + extra - extra suffix [str] + exist - optional check if file exists [bool or None] + + Returns: + outpath - output file namepath [str] + ''' -def outfile(cellpath, define_data, ftype='', extra='', exists=False): fname = define_data['name'].lower().replace('$', '_') if ftype: ftype = '.'+ftype @@ -40,18 +48,29 @@ def outfile(cellpath, define_data, ftype='', extra='', exists=False): def write_netlistsvg(cellpath, define_data): + ''' Generates netlistsvg for a given cell. + + Args: + cellpath - path to a cell [str of pathlib.Path] + define_data - cell definition data [dic] + ''' + netlist_json = os.path.join(cellpath, define_data['file_prefix']+'.json') if not os.path.exists(netlist_json): - print("No " + define_data['file_prefix'] + ".json in", cellpath) + print("No netlist in", cellpath) assert os.path.exists(netlist_json), netlist_json outpath = outfile(cellpath, define_data, 'schematic') - oscmd = 'netlistsvg ' + netlist_json + ' -o ' + outpath - r = os.system(oscmd)>>8 - assert r == 0 - return r - + if subprocess.call(['netlistsvg', netlist_json, '-o', outpath]): + raise ChildProcessError("netlistsvg execution failed") def process(cellpath): + ''' Processes cell indicated by path. + Opens cell definiton and calls further processing + + Args: + cellpath - path to a cell [str of pathlib.Path] + ''' + print() print(cellpath) define_json = os.path.join(cellpath, 'definition.json') @@ -66,20 +85,50 @@ def process(cellpath): return -def main(args): - if len(args) and args[0] == 'ALLLIBS': - scrpath = os.path.dirname(os.path.realpath(__file__)) - relpath = '/../../../libraries/*/latest/cells/*' - #relpath = '/../../../libraries/*ms/latest/cells/x*' # DBG: limited - args = os.popen('ls -d ' + scrpath + relpath).read().strip().split('\n') +def main(): + ''' Generates netlistsvg schematic from cell netlist.''' + + prereq_txt = 'prerequisities:\n netlistsvg' + output_txt = 'output:\n generates [cell_prefix].schematic.svg' + allcellpath = '../../../libraries/*/latest/cells/*' + + parser = argparse.ArgumentParser( + description = main.__doc__, + epilog = prereq_txt +'\n\n'+ output_txt, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + "--all_libs", + help="process all cells in "+allcellpath, + action="store_true") + parser.add_argument( + "cell_dir", + help="path to the cell directory", + type=pathlib.Path, + nargs="*") + + args = parser.parse_args() + + if args.all_libs: + path = pathlib.Path(allcellpath).expanduser() + parts = path.parts[1:] if path.is_absolute() else path.parts + paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts))) + args.cell_dir = list(paths) + + cell_dirs = [d.resolve() for d in args.cell_dir if d.is_dir()] + errors = 0 - for a in args: + for d in cell_dirs: try: - process(os.path.realpath(a)) - except: + process(d) + except KeyboardInterrupt: + sys.exit(1) + except (AssertionError, FileNotFoundError, ChildProcessError) as ex: + print (f'Error: {type(ex).__name__}') + print (f'{ex.args}') errors +=1 - print (f'\n{len(args)} files processed, {errors} errors.') + print (f'\n{len(cell_dirs)} files processed, {errors} errors.') + return 0 if errors else 1 if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) + sys.exit(main()) From da5a537f69e991f91ad443aa5c982c5e27e75683 Mon Sep 17 00:00:00 2001 From: Wojciech Gryncewicz Date: Wed, 25 Nov 2020 15:54:29 +0100 Subject: [PATCH 3/4] Netlistsvg wrapper moved and renamed Signed-off-by: Wojciech Gryncewicz --- .../skywater_pdk/cell/generate/netlist.py | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100755 scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py diff --git a/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py b/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py new file mode 100755 index 000000000..370a8f120 --- /dev/null +++ b/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright 2020 The SkyWater PDK Authors. +# +# Use of this source code is governed by the Apache 2.0 +# license that can be found in the LICENSE file or at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + + +import csv +import json +import os +import sys +import argparse +import pathlib +import glob +import subprocess + +def outfile(cellpath, define_data, ftype='', extra='', exists=False): + ''' Determines output file path and name. + + Args: + cellpath - path to a cell [str of pathlib.Path] + define_data - cell definition data [dic] + ftype - file type suffix [str] + extra - extra suffix [str] + exist - optional check if file exists [bool or None] + + Returns: + outpath - output file namepath [str] + ''' + + fname = define_data['name'].lower().replace('$', '_') + if ftype: + ftype = '.'+ftype + outpath = os.path.join(cellpath, f'{define_data["file_prefix"]}{extra}{ftype}.svg') + if exists is None: + pass + elif not exists: + #assert not os.path.exists(outpath), "Refusing to overwrite existing file:"+outpath + print("Creating", outpath) + elif exists: + assert os.path.exists(outpath), "Missing required:"+outpath + return outpath + + +def write_netlistsvg(cellpath, define_data): + ''' Generates netlistsvg for a given cell. + + Args: + cellpath - path to a cell [str of pathlib.Path] + define_data - cell definition data [dic] + ''' + + netlist_json = os.path.join(cellpath, define_data['file_prefix']+'.json') + if not os.path.exists(netlist_json): + print("No netlist in", cellpath) + assert os.path.exists(netlist_json), netlist_json + outpath = outfile(cellpath, define_data, 'schematic') + if subprocess.call(['netlistsvg', netlist_json, '-o', outpath]): + raise ChildProcessError("netlistsvg execution failed") + +def process(cellpath): + ''' Processes cell indicated by path. + Opens cell definiton and calls further processing + + Args: + cellpath - path to a cell [str of pathlib.Path] + ''' + + print() + print(cellpath) + define_json = os.path.join(cellpath, 'definition.json') + if not os.path.exists(define_json): + print("No definition.json in", cellpath) + assert os.path.exists(define_json), define_json + define_data = json.load(open(define_json)) + + if define_data['type'] == 'cell': + write_netlistsvg(cellpath, define_data) + + return + + +def main(): + ''' Generates netlistsvg schematic from cell netlist.''' + + prereq_txt = 'prerequisities:\n netlistsvg' + output_txt = 'output:\n generates [cell_prefix].schematic.svg' + allcellpath = '../../../libraries/*/latest/cells/*' + + parser = argparse.ArgumentParser( + description = main.__doc__, + epilog = prereq_txt +'\n\n'+ output_txt, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + "--all_libs", + help="process all cells in "+allcellpath, + action="store_true") + parser.add_argument( + "cell_dir", + help="path to the cell directory", + type=pathlib.Path, + nargs="*") + + args = parser.parse_args() + + if args.all_libs: + path = pathlib.Path(allcellpath).expanduser() + parts = path.parts[1:] if path.is_absolute() else path.parts + paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts))) + args.cell_dir = list(paths) + + cell_dirs = [d.resolve() for d in args.cell_dir if d.is_dir()] + + errors = 0 + for d in cell_dirs: + try: + process(d) + except KeyboardInterrupt: + sys.exit(1) + except (AssertionError, FileNotFoundError, ChildProcessError) as ex: + print (f'Error: {type(ex).__name__}') + print (f'{ex.args}') + errors +=1 + print (f'\n{len(cell_dirs)} files processed, {errors} errors.') + return 0 if errors else 1 + +if __name__ == "__main__": + sys.exit(main()) + From 35187a347912df5248fbd5b493db4d5f5634c9d1 Mon Sep 17 00:00:00 2001 From: Wojciech Gryncewicz Date: Tue, 29 Dec 2020 21:18:12 +0100 Subject: [PATCH 4/4] Added json patching option for minor netlistsvg incompatibilities Signed-off-by: Wojciech Gryncewicz --- .../skywater_pdk/cell/generate/netlist.py | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py b/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py index 370a8f120..49ff72ebe 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py +++ b/scripts/python-skywater-pdk/skywater_pdk/cell/generate/netlist.py @@ -18,6 +18,7 @@ import pathlib import glob import subprocess +import re def outfile(cellpath, define_data, ftype='', extra='', exists=False): ''' Determines output file path and name. @@ -46,22 +47,65 @@ def outfile(cellpath, define_data, ftype='', extra='', exists=False): assert os.path.exists(outpath), "Missing required:"+outpath return outpath +def patch_netlist_json (infile, outfile): + ''' Patches yosys generated netlist json + to workaround netlistsvg compability issues. + Currently covered: + - convert unsupported pin direction 'inout' to 'output' + - convert unsupported High-Z connection ("z") to none ("x") -def write_netlistsvg(cellpath, define_data): + Args: + infile - input file [str of pathlib.Path] + outfile - patched file [str of pathlib.Path] + ''' + + block_header = '\s*"\w*"\:\s*\{' #eg. "connections": { + unsupported_dir = '\s*"direction":\s*"inout"' #eg. "direction": "input" + unsupported_con = '\s*"\w*":\s*\[\s*"z"\s*\]' #eg. "A": [ "z" ] + json = '' + current_block = None + + with open(str(infile),'r') as f: + for line in f: + if re.match (block_header, line): + current_block = line.partition(':')[0].strip(' "\n') + if re.match (unsupported_dir, line): + line = re.sub ( ':\s*"inout"', ': "output"', line) + if current_block =='connections' and re.match (unsupported_con, line): + line = re.sub ( '\[\s*"z"\s*\]', '[ "x" ]' , line, flags=re.IGNORECASE) + json +=line + with open(str(outfile),'w') as f: + f.write(json) + + +def write_netlistsvg(cellpath, define_data, json_patching = False): ''' Generates netlistsvg for a given cell. Args: cellpath - path to a cell [str of pathlib.Path] define_data - cell definition data [dic] + json_patching - filter netlist for potential netlistsvg compablity issues ''' netlist_json = os.path.join(cellpath, define_data['file_prefix']+'.json') if not os.path.exists(netlist_json): print("No netlist in", cellpath) assert os.path.exists(netlist_json), netlist_json + + if json_patching: #correct netlistsvg parsing issues + netlist_json_fix = os.path.join(cellpath, define_data['file_prefix']+'.patched.json') + patch_netlist_json (netlist_json, netlist_json_fix) + netlist_json = netlist_json_fix + outpath = outfile(cellpath, define_data, 'schematic') if subprocess.call(['netlistsvg', netlist_json, '-o', outpath]): - raise ChildProcessError("netlistsvg execution failed") + if json_patching: + os.remove(netlist_json_fix) + raise ChildProcessError("netlistsvg execution failed") + + if json_patching: + pass + os.remove(netlist_json_fix) def process(cellpath): ''' Processes cell indicated by path. @@ -80,7 +124,11 @@ def process(cellpath): define_data = json.load(open(define_json)) if define_data['type'] == 'cell': - write_netlistsvg(cellpath, define_data) + try: + write_netlistsvg(cellpath, define_data) + except ChildProcessError: + print ("Netlistsvg error, retrying with json patch...") + write_netlistsvg(cellpath, define_data, json_patching=True) return