| #!/usr/bin/env python3 |
| # Copyright 2020 The 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. |
| |
| |
| import csv |
| from pathlib import Path |
| import argparse |
| from collections import defaultdict |
| import shlex |
| import re |
| from termcolor import colored |
| import sys |
| from prettytable import PrettyTable |
| import json |
| import clean_spice_files |
| |
| copyright = [ |
| '* Copyright 2020 The 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.', |
| '' |
| ] |
| |
| sourcetodests = defaultdict(list) |
| |
| RE_SUBCKT = re.compile(r'\.subckt\s+(?P<subcktname>[^ ]+)\s?(?P<nodenames>.*)') |
| # RE_MODEL = re.compile(r'^[\.]*model\s+(?P<modelname>[^ ]+)\s+(?P<modeltype>[^ ]+)') |
| |
| # RE_MODEL_END = re.compile(r'^[\s\n]*ends(?P<end>.*)(\n|$)') |
| # RE_SUBCKT_END = re.compile(r'^.ends') |
| RE_GROUP = re.compile(r'[*\s]*?(?P<group>[^*\n]+?)[*\s]*(\n|$)') |
| RE_MODE_LINE = re.compile(r'(?P<modeid>\d+)\s*:\s*type=\s*(?P<modetype>.*)(\n|$)') |
| RE_INLINE_SUBCKT_MODEL = re.compile(r'^(?P<modelname>[^ ]+)\s*\((?P<nodenames>.*)\)\s*(?P<subcktname>[^ ]+)\s*(?P<parameters>.*)($|\n)') |
| |
| RE_MODEL = re.compile(r'[\.]?model') |
| |
| def order_parameters(paramname): |
| priority = { |
| 'subckt$subckt': 0, |
| 'subckt$inlinesubckt': 1, |
| 'subckt$nodes': 2, |
| 'model$model': 3, |
| 'model$type': 4, |
| 'mode$mode': 5, |
| 'mode$modetype': 6, |
| 'model$lmin': 7, |
| 'model$lmax': 8, |
| 'model$wmin': 9, |
| 'model$wmax': 10, |
| 'model$level': 11, |
| 'model$tnom': 12, |
| 'model$version': 13, |
| 'model$tox': 14, |
| 'model$toxm': 15, |
| 'mode$lmin': 7, |
| 'mode$lmax': 8, |
| 'mode$wmin': 9, |
| 'mode$wmax': 10, |
| 'mode$level': 11, |
| 'mode$tnom': 12, |
| 'mode$version': 13, |
| 'mode$tox': 14, |
| 'mode$toxm': 15, |
| } |
| if paramname in priority: |
| return (priority[paramname], paramname) |
| return (1000, paramname) |
| |
| |
| def csv_to_pm3(parameters, csventry): |
| # we have following orders |
| # inlinesubckt nodes model type |
| # inlinesubckt nodes model type mode modetype |
| # model type |
| # subckt nodes |
| # subckt nodes model type |
| result = [] |
| if parameters[0] == 'model$model': |
| assert all(['model$' in parameter for parameter in parameters]), parameters |
| for entry in csventry: |
| assert 'N/A' not in entry[:2], entry |
| result.append(f'.model {entry[0]} {entry[1]}') |
| for parameter, value in zip(parameters[2:], entry[2:]): |
| if value != 'N/A': |
| result.append(f'+{parameter.split("$")[1]}= {value}') |
| elif 'mode$mode' in parameters: |
| lastsubcktdef = None |
| lastmodel = None |
| firstsubcktdef = True |
| firstmodel = True |
| for entry in csventry: |
| assert 'N/A' not in entry[:4], entry |
| if entry[0] != lastsubcktdef: |
| if not firstsubcktdef: |
| result.append(f'ends {lastsubcktdef}') |
| lastsubcktdef = entry[0] |
| firstsubcktdef = False |
| if 'subckt$inlinesubckt' in parameters: |
| result.append(f'inline subckt {entry[0]} ({" ".join(entry[1].split(","))})') |
| else: |
| result.append(f'.subckt {entry[0]} {" ".join(entry[1].split(","))}') |
| for parameter, value in zip(parameters[2:], entry[2:]): |
| if value != 'N/A' and parameter.split("$")[0] == 'subckt': |
| result.append(f'+{parameter.split("$")[1]}= {value}') |
| if 'mode$mode' in set(parameters): |
| if entry[2] != lastmodel: |
| if not firstmodel: |
| result.append('}') |
| firstmodel = False |
| result.append(f'model {entry[2]} {entry[3]} {{') |
| lastmodel = entry[2] |
| result.append(f'{entry[4]}: type={entry[5]}') |
| for parameter, value in zip(parameters[6:], entry[6:]): |
| if value != 'N/A' and parameter.split("$")[0] == 'mode': |
| result.append(f'+{parameter.split("$")[1]}= {value}') |
| if not firstmodel: |
| result.append('}') |
| if not firstsubcktdef: |
| result.append(f'ends {lastsubcktdef}') |
| elif parameters[0] in ['subckt$subckt', 'subckt$inlinesubckt']: |
| lastsubckt = None |
| firstsubckt = True |
| assert not any(['mode$' in parameter for parameter in parameters]), entry |
| for entry in csventry: |
| assert 'N/A' not in entry[:2], entry |
| if lastsubckt != entry[0]: |
| if not firstsubckt: |
| result.append('.ends') |
| firstsubckt = False |
| if parameters[0] == 'subckt$subckt': |
| result.append(f'.subckt {entry[0]} {" ".join(entry[1].split(","))}') |
| else: |
| result.append(f'inline subckt {entry[0]} ({" ".join(entry[1].split(","))})') |
| lastsubckt = entry[0] |
| startpoint = 2 |
| for parameter, value in zip(parameters[startpoint:], entry[startpoint:]): |
| if value != 'N/A' and parameter.split('$')[0] == 'subckt': |
| result.append(f'+{parameter.split("$")[1]}= {value}') |
| if parameters[2] == 'model$model' and 'N/A' not in entry[2:4]: |
| assert 'N/A' not in entry[2:4], (parameters, entry) |
| if parameters[0] == 'subckt$subckt': |
| result.append(f'.model {entry[2]} {entry[3]}') |
| else: |
| result.append(f'{entry[2]} ({" ".join(entry[1].split(","))})') |
| startpoint = 4 |
| for parameter, value in zip(parameters[startpoint:], entry[startpoint:]): |
| if value != 'N/A' and parameter.split('$')[0] == 'model': |
| result.append(f'+{parameter.split("$")[1]}= {value}') |
| if not firstsubckt: |
| result.append('.ends') |
| # elif parameters[0] == 'subckt$inlinesubckt': |
| result = clean_spice_files.basic_cleaning(result) |
| result = clean_spice_files.makefixedwidthcolumn(result) |
| return result |
| |
| def pm3_to_csv(lines): |
| result = [] |
| params = {} |
| params['subckt'] = {} |
| params['model'] = {} |
| params['mode'] = {} |
| scopetype = None |
| leftovers = [] |
| parameterbag = {} |
| def produce_line(): |
| finparams = {} |
| finparams.update(params['subckt']) |
| finparams.update(params['model']) |
| finparams.update(params['mode']) |
| if len(finparams) > 0: |
| result.append(finparams) |
| scopetype = None |
| for line in lines: |
| try: |
| if not line.strip(): |
| continue |
| if line.strip().startswith('*'): |
| continue |
| line = re.sub(r'\$.*$', '', line) |
| origline = line |
| line = line.strip() |
| elements = shlex.split(line, posix=False) |
| m = RE_MODE_LINE.match(line) |
| if m: |
| if scopetype == 'mode': |
| produce_line() |
| params['mode'] = {} |
| params['mode']['mode$mode'] = m.group('modeid') |
| params['mode']['mode$modetype'] = m.group('modetype') |
| scopetype = 'mode' |
| continue |
| ind = 0 |
| if line.startswith('parameters'): |
| l = re.sub('\s+', ' ', line) |
| prs = l.split(' ')[1:] |
| assert len(prs) % 2 == 0 |
| ind = 0 |
| while ind < len(prs): |
| parameterbag[prs[ind].split('=')[0]] = prs[ind+1] |
| ind += 2 |
| continue |
| def parse_parameters(): |
| if scopetype is None: |
| return |
| ind = 0 |
| prevkey = None |
| while ind < len(elements): |
| if not elements[ind].strip(): |
| ind += 1 |
| elif elements[ind].endswith('='): |
| if scopetype is None: |
| print(colored(line, 'magenta'), file=sys.stderr) |
| ind += 2 |
| continue |
| key = elements[ind][:-1] |
| val = elements[ind+1] |
| for paramnam, paramval in parameterbag.items(): |
| val = val.replace(paramnam, paramval) |
| params[scopetype][f'{scopetype}${key}'] = val |
| prevkey = key |
| ind += 2 |
| if ind + 1 < len(elements) and 'dev/gauss' in elements[ind]: |
| val = elements[ind+1] |
| for paramnam, paramval in parameterbag.items(): |
| val = val.replace(paramnam, paramval) |
| params[scopetype][f'{scopetype}${key}'] = ' '.join([params[scopetype][f'{scopetype}${key}'], elements[ind], val]) |
| ind += 2 |
| elif '=' in elements[ind]: |
| res = elements[ind].split('=') |
| val = res[1] |
| for paramnam, paramval in parameterbag.items(): |
| val = val.replace(paramnam, paramval) |
| params[scopetype][f'{scopetype}${res[0]}'] = val |
| prevkey = res[0] |
| ind += 1 |
| elif prevkey is not None: |
| val = elements[ind] |
| for paramnam, paramval in parameterbag.items(): |
| val = val.replace(paramnam, paramval) |
| params[scopetype][f'{scopetype}${prevkey}'] += elements[ind] |
| ind += 1 |
| else: |
| print(colored(line, 'cyan'), file=sys.stderr) |
| raise Exception(line) |
| if elements[0] == '.subckt': |
| produce_line() |
| params['subckt'] = {} |
| params['model'] = {} |
| params['mode'] = {} |
| params['subckt']['subckt$subckt'] = elements[1] |
| nodelist = [] |
| ind = 2 |
| for element in elements[2:]: |
| if '=' in element: |
| break |
| element = element.replace('(', '') |
| element = element.replace(')', '') |
| nodelist.append(element) |
| ind += 1 |
| params['subckt']['subckt$nodes'] = ','.join(nodelist) |
| scopetype = 'subckt' |
| elements = elements[ind:] |
| parse_parameters() |
| elif RE_MODEL.match(elements[0]): |
| if scopetype != 'subckt': |
| produce_line() |
| params['model'] = {} |
| params['mode'] = {} |
| params['model']['model$model'] = elements[1] |
| params['model']['model$type'] = elements[2] |
| scopetype = 'model' |
| if len(elements) >=4 and elements[3] == '{': |
| continue |
| elements = elements[3:] |
| parse_parameters() |
| elif elements[0].startswith('+'): |
| elements[0] = elements[0][1:] |
| parse_parameters() |
| elif elements[0] == 'parameters': |
| elements = elements[1:] |
| parse_parameters() |
| else: |
| m = RE_INLINE_SUBCKT_MODEL.match(line) |
| if m: |
| if scopetype is not None: |
| produce_line() |
| params['subckt'] = {} |
| params['model'] = {} |
| params['mode'] = {} |
| scopetype = 'model' |
| params['subckt']['subckt$inlinesubckt'] = m.group('subcktname') |
| params['subckt']['subckt$nodes'] = ','.join(m.group('nodenames').split(' ')) |
| params['model']['model$model'] = m.group('modelname') |
| params['model']['model$type'] = params['subckt']['subckt$inlinesubckt'] |
| elements = m.group('parameters').split() |
| parse_parameters() |
| elif ('subckt$subckt' in params['subckt'] and params['subckt']['subckt$subckt'] in elements) or ('model$model' in params['model'] and params['model']['model$model'].split('.')[0] in elements): |
| typend = params['subckt']['subckt$subckt'] if params['subckt']['subckt$subckt'] in elements else params['model']['model$model'].split('.')[0] |
| typ = params['subckt']['subckt$subckt'] if params['subckt']['subckt$subckt'] in elements else params['model']['model$type'] |
| if scopetype != 'subckt': |
| produce_line() |
| params['model'] = {} |
| params['mode'] = {} |
| params['model']['model$model'] = elements[0] |
| scopetype = 'model' |
| ind = 1 |
| nodes = [] |
| for el in elements[1:]: |
| if el == typend: |
| break |
| nodes.append(el) |
| ind += 1 |
| params['subckt']['subckt$nodes'] = ','.join(nodes) |
| params['model']['model$type'] = elements[ind] |
| elements = elements[ind+1:] |
| parse_parameters() |
| else: |
| if 'ends' not in line.strip() and not line.strip().startswith('inline subckt'): |
| if scopetype is None or not line.strip() in ['{', '}']: |
| leftovers.append(origline.rstrip()) |
| print(colored(line, 'yellow'), file=sys.stderr) |
| except: |
| print(colored(line, 'red'), file=sys.stderr) |
| raise |
| produce_line() |
| parameters = set() |
| for entry in result: |
| parameters.update(entry.keys()) |
| |
| parameters = sorted(parameters, key=order_parameters) |
| |
| csvcontent = [parameters] |
| |
| for entry in result: |
| row = [] |
| for param in parameters: |
| if param in entry: |
| row.append(entry[param]) |
| else: |
| row.append('N/A') |
| csvcontent.append(row) |
| |
| return csvcontent, leftovers |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "input_dir", |
| help="The path to the directory containing skywater-pdk", |
| type=Path) |
| parser.add_argument( |
| "--sourcetodests", |
| help="Mapping from source files to destination files", |
| type=Path) |
| parser.add_argument( |
| "--dry-run", |
| help="Do not perform actions on filesystem, only print", |
| action="store_true") |
| |
| args = parser.parse_args() |
| |
| files = sorted(args.input_dir.rglob('*.pm3')) |
| |
| allfilesnum = len(files) |
| |
| parameters = [] |
| # file model parameter value |
| filesdata = defaultdict(lambda: defaultdict(dict)) |
| |
| for num, filename in enumerate(files): |
| with open(filename, 'r') as data: |
| print(f'[{num:05d}/{allfilesnum:05d}]: {filename}') |
| print(f'[{num:05d}/{allfilesnum:05d}]: {filename}', file=sys.stderr) |
| # parameters across many entries |
| lines = [] |
| for line in data.readlines(): |
| lines.append(line) |
| |
| result, leftovers = pm3_to_csv(lines) |
| if len(result) > 1: |
| with open(filename.with_suffix('.csv'), 'w', newline='') as csvfile: |
| writer = csv.writer(csvfile, delimiter=';') |
| writer.writerows(result) |
| sourcetodests[str(filename)].append(str(Path(filename).with_suffix('.csv'))) |
| |
| with open(filename.with_suffix('.table'), 'w') as tablefile: |
| t = PrettyTable(result[0]) |
| t.align = 'r' |
| t.border = False |
| for line in result[1:]: |
| t.add_row(line) |
| tablefile.write(str(t)) |
| sourcetodests[str(filename)].append(str(Path(filename).with_suffix('.table'))) |
| # print(colored('\n'.join(csv_to_pm3(result[0], result[1:])), 'green')) |
| binnedspice = csv_to_pm3(result[0], result[1:]) |
| csv2bsimname = filename.with_suffix(f'.csv2bsim{filename.suffix}') |
| with open(csv2bsimname, 'w') as csv2bsimfile: |
| csv2bsimfile.write('\n'.join(copyright + binnedspice)) |
| with open(filename.with_suffix(f'.csvwrap{filename.suffix}'), 'w') as csvwrapfile: |
| leftovers.append(f'.include "{Path(csv2bsimname).name}"') |
| csvwrapfile.write('\n'.join(copyright + leftovers)) |
| with open(args.sourcetodests, 'w') as srctodst: |
| json.dump(sourcetodests, srctodst, indent=2) |