#!/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 import re 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 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") 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]): 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. 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': try: write_netlistsvg(cellpath, define_data) except ChildProcessError: print ("Netlistsvg error, retrying with json patch...") write_netlistsvg(cellpath, define_data, json_patching=True) 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())