blob: 85fc69074f69a64f1be5f830bebf1328c8e70c1b [file] [log] [blame]
#!/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)