blob: 7eb4d0a4b5ad8538fbe6056e6726314250572a4e [file] [log] [blame]
#!/usr/bin/env python3
# Script to read all files in a directory of SPECTRE-compatible device model
# files, and convert them to a form that is compatible with ngspice.
import csv
import glob
import json
import os
import pprint
import re
import sys
import textwrap
from collections import defaultdict
WIDTH=200
def usage():
print('spectre_to_spice.py <path_to_spectre> <path_to_spice>')
print(' [-nocmt] [-debug] [-ignore=file1,file2,...]')
# Check if a parameter value is a valid number (real, float, integer)
# or is some kind of expression.
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
# Parse a parameter line. If "inparam" is true, then this is a continuation
# line of an existing parameter statement. If "insub" is not true, then the
# paramters are global parameters (not part of a subcircuit).
#
# If inside a subcircuit, remove the keyword "parameters". If outside,
# change it to ".param"
def parse_param_line(line, inparam, insub, iscall, ispassed):
# Regexp patterns
parm1rex = re.compile('[ \t]*parameters[ \t]*(.*)')
parm2rex = re.compile('[ \t]*params:[ \t]*(.*)')
parm3rex = re.compile('[ \t]*\+[ \t]*(.*)')
parm4rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
parm5rex = re.compile('[ \t]*([^= \t]+)[ \t]*(.*)')
parm6rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([\'{][^\'}]+[\'}])[ \t]*(.*)')
rtok = re.compile('([^ \t\n]+)[ \t]*(.*)')
fmtline = []
if iscall:
rest = line
elif inparam:
pmatch = parm3rex.match(line)
if pmatch:
fmtline.append('+')
rest = pmatch.group(1)
else:
return '', ispassed
else:
pmatch = parm1rex.match(line)
if pmatch:
if insub:
fmtline.append('+')
else:
fmtline.append('.param')
rest = pmatch.group(1)
else:
pmatch = parm2rex.match(line)
if pmatch:
if insub:
fmtline.append('+')
else:
return '', ispassed
rest = pmatch.group(1)
else:
return '', ispassed
while rest != '':
if iscall:
# It is hard to believe this is legal syntax, but there are
# instances of "x * y" with spaces in the expression and no
# quotes or braces delimiting the expression.
rest = re.sub('[ \t]*\*[ \t]*', '*', rest)
pmatch = parm4rex.match(rest)
if pmatch:
if ispassed:
# End of passed parameters. Break line and generate ".param"
ispassed = False
fmtline.append('\n.param ')
# If expression is already in single quotes or braces, then catch
# everything inside the delimiters, including any spaces.
if pmatch.group(2).startswith("'") or pmatch.group(2).startswith('{'):
pmatchx = parm6rex.match(rest)
if pmatchx:
pmatch = pmatchx
fmtline.append(pmatch.group(1))
fmtline.append('=')
value = pmatch.group(2)
rest = pmatch.group(3)
# Watch for spaces in expressions (have they no rules??!)
# as indicated by something after a space not being an
# alphabetical character (parameter name) or '$' (comment)
needmore = False
while rest != '':
rmatch = rtok.match(rest)
if rmatch:
expch = rmatch.group(1)[0]
if expch == '$':
break
elif expch.isalpha() and not needmore:
break
else:
needmore = False
value += rmatch.group(1)
rest = rmatch.group(2)
if any((c in '+-*/({^~!') for c in rmatch.group(1)[-1]):
needmore = True
if rest != '' and any((c in '+-*/(){}^~!') for c in rest[0]):
needmore = True
else:
break
if is_number(value):
fmtline.append(value)
elif value.strip().startswith("'"):
fmtline.append(value)
else:
# It is not possible to know if a spectre expression continues
# on another line without some kind of look-ahead, but check
# if the parameter ends in an operator.
lastc = value.strip()[-1]
if any((c in '*+-/,(') for c in lastc):
fmtline.append('{' + value)
else:
fmtline.append('{' + value + '}')
# These parameter sub-expressions are related to monte carlo
# simulation and are incompatible with ngspice. So put them
# in an in-line comment. Avoid double-commenting things that
# were already in-line comments.
if rest != '':
nmatch = parm4rex.match(rest)
if not nmatch:
if rest.lstrip().startswith('$ '):
fmtline.append(rest)
elif rest.strip() != '':
fmtline.append(' $ ' + rest.replace(' ', '').replace('\t', ''))
rest = ''
else:
# Match to a CDL subckt parameter that does not have an '=' and so
# assumes that the parameter is always passed, and therefore must
# be part of the .subckt line. A parameter without a value is not
# legal SPICE, so supply a default value of 1.
pmatch = parm5rex.match(rest)
if pmatch:
# NOTE: Something that is not a parameter name should be
# extended from the previous line. Note that this parsing is
# not rigorous and is possible to break. . .
if any((c in '+-*/(){}^~!') for c in pmatch.group(1).strip()):
fmtline.append(rest)
if not any((c in '*+-/,(') for c in rest.strip()[-1]):
fmtline.append('}')
rest = ''
else:
# Parameter name must have a value. Set default 1
fmtline.append(pmatch.group(1) + '=1')
ispassed = True
rest = pmatch.group(2)
else:
break
finalline = ' '.join(fmtline)
# ngspice does not understand round(), so replace it with the equivalent
# floor() expression.
finalline = re.sub('round\\(', 'floor(0.5+', finalline)
# use of "no" and "yes" as parameter values is not allowed in ngspice.
finalline = re.sub('sw_et[ \t]*=[ \t]*{no}', 'sw_et=0', finalline)
finalline = re.sub('sw_et[ \t]*=[ \t]*{yes}', 'sw_et=1', finalline)
finalline = re.sub('isnoisy[ \t]*=[ \t]*{no}', 'isnoisy=0', finalline)
finalline = re.sub('isnoisy[ \t]*=[ \t]*{yes}', 'isnoisy=1', finalline)
# Use of "m" in parameters is forbidden. Specifically look for "{N*m}".
# e.g., replace "mult = {2*m}" with "mult = 2". Note that this usage
# is most likely an error in the source.
finalline = re.sub('\{([0-9]+)\*[mM]\}', r'\1', finalline)
return finalline, ispassed
def get_param_names(line):
# Find parameter names in a ".param" line and return a list of them.
# This is used to check if a bare word parameter name is passed to
# a capacitor or resistor device in the position of a value but
# without delimiters, so that it cannot be distinguished from a
# model name. There are only a few instances of this, so this
# routine is not rigorously checking all parameters, just entries
# on lines with ".param".
parmrex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
rest = line
paramnames = []
while rest != '':
pmatch = parmrex.match(rest)
if pmatch:
paramnames.append(pmatch.group(1))
rest = pmatch.group(3)
else:
break
return paramnames
def convert_file(in_file, out_file, nocmt, ignore='', debug=False):
#if in_file.endswith('monte.cor') or in_file.endswith('models.all'):
# print("Skipping", in_file)
# return
if in_file in ignore:
print('Skipping ', in_file)
return
print("Starting to convert", in_file)
# Regexp patterns
setsoarex = re.compile('[ \t]*\.setsoa[ \t]+', re.IGNORECASE)
statrex = re.compile('[ \t]*statistics[ \t]*\{(.*)')
simrex = re.compile('[ \t]*simulator[ \t]+([^= \t]+)[ \t]*=[ \t]*(.+)')
insubrex = re.compile('[ \t]*inline[ \t]+subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)')
cdlsubrex = re.compile('\.?subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)')
endsubrex = re.compile('[ \t]*ends[ \t]*(.*)')
endonlysubrex = re.compile('[ \t]*ends[ \t]*')
modelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+\{(.*)')
cdlmodelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)')
binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)')
shincrex = re.compile('\.inc[ \t]+')
isexprrex = re.compile('[^0-9a-zA-Z_]')
paramrex = re.compile('\.param[ \t]+(.*)')
stdsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+(.*)')
stdmodelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)')
stdendsubrex = re.compile('\.ends[ \t]+(.+)')
stdendonlysubrex = re.compile('\.ends[ \t]*')
# Devices (resistor, capacitor, subcircuit as resistor or capacitor)
caprex = re.compile('c([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*capacitor[ \t]*(.*)', re.IGNORECASE)
resrex = re.compile('r([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*resistor[ \t]*(.*)', re.IGNORECASE)
cdlrex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
stddevrex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
stddev2rex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t\'{]+[\'{][^\'}]+[\'}])[ \t]*(.*)', re.IGNORECASE)
stddev3rex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]+(.*)', re.IGNORECASE)
in_dir = os.path.dirname(in_file)
with open(in_file, 'r') as ifile:
idata = ifile.read()
in_s8xfile = '../../s8x/Models/'+os.path.basename(in_file)
def include(m):
incfile = m.group('file')
print(" Includes", incfile)
if incfile == in_s8xfile or incfile.endswith('sonos_tteol.cor'):
s8xpath = os.path.abspath(os.path.join(in_dir, incfile))
print(" Pulling", s8xpath, "({}) into".format(incfile), in_file)
return open(s8xpath).read()
else:
return m.group(0)
idata = RE_TRAILING_WS.sub('', idata)
idata = RE_INCLUDE.sub(include, idata)
if nocmt:
speclines = rewrite_comments(out_file, idata)
else:
speclines = idata.splitlines()
insub = False
inparam = False
inmodel = False
inpinlist = False
isspectre = False
ispassed = False
spicelines = []
calllines = []
modellines = []
paramnames = []
savematch = None
blockskip = 0
subname = ''
subnames = []
modname = ''
modtype = ''
# For future reference: A much better approach to this is to first contatenate
# all continuation lines by replacing the "+" with "\n", which can be treated
# as whitespace in any regexp. This makes parsing continuation lines much
# simpler, as there is no need to do any "look-ahead" to check what's on the
# next line(s). At the end, all "\n" in strings can be replaced by "\n+",
# which preserves the original formatting of the continuation lines.
for line in speclines:
# Item 1a. C++-style // comments get replaced with * comment character
if line.strip().startswith('//'):
# Replace the leading "//" with SPICE-style comment "*".
if modellines != []:
modellines.append(line.strip().replace('//', '*', 1))
elif calllines != []:
calllines.append(line.strip().replace('//', '*', 1))
else:
spicelines.append(line.strip().replace('//', '*', 1))
continue
# Item 1b. In-line C++-style // comments get replaced with $ comment character
elif ' //' in line:
line = line.replace(' //', ' $ ', 1)
elif '//' in line:
line = line.replace('//', ' $ ', 1)
elif '\t//' in line:
line = line.replace('\t//', '\t$ ', 1)
# Item 2. Handle SPICE-style comment lines
if line.strip().startswith('*'):
if modellines != []:
modellines.append(line.strip())
elif calllines != []:
calllines.append(line.strip())
else:
spicelines.append(line.strip())
continue
# Item 4. Flag continuation lines
if line.strip().startswith('+'):
contline = True
else:
contline = False
if line.strip() != '':
if inparam:
inparam = False
if inpinlist:
inpinlist = False
# Item 3. Handle blank lines like comment lines
if line.strip() == '':
if modellines != []:
modellines.append(line.strip())
elif calllines != []:
calllines.append(line.strip())
else:
spicelines.append(line.strip())
continue
# Item 5. Count through { ... } blocks that are not SPICE syntax
if blockskip > 0:
# Warning: Assumes one brace per line, may or may not be true
if '{' in line:
blockskip = blockskip + 1
elif '}' in line:
blockskip = blockskip - 1
if blockskip == 0:
spicelines.append('* ' + line)
continue
if blockskip > 0:
spicelines.append('* ' + line)
continue
# Item 6. Handle continuation lines
if contline:
if inparam:
# Continue handling parameters
fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed)
if fmtline != '':
if modellines != []:
modellines.append(fmtline)
elif calllines != []:
calllines.append(fmtline)
else:
spicelines.append(fmtline)
continue
# Item 7. Regexp matching
# Catch "simulator lang="
smatch = simrex.match(line)
if smatch:
if smatch.group(1) == 'lang':
if smatch.group(2) == 'spice':
isspectre = False
elif smatch.group(2) == 'spectre':
isspectre = True
continue
# If inside a subcircuit, remove "parameters". If outside,
# change it to ".param"
fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed)
if fmtline != '':
inparam = True
spicelines.append(fmtline)
continue
# ".setsoa" not handled by ngspice (comment it out)
smatch = setsoarex.match(line)
if smatch:
spicelines.append('* ' + line)
continue
# statistics---not sure if it is always outside an inline subcircuit
smatch = statrex.match(line)
if smatch:
if '}' not in smatch.group(1):
blockskip = 1
spicelines.append('* ' + line)
continue
# model---not sure if it is always inside an inline subcircuit
iscdl = False
if isspectre:
mmatch = modelrex.match(line)
if not mmatch:
mmatch = cdlmodelrex.match(line)
if mmatch:
iscdl = True
else:
mmatch = stdmodelrex.match(line)
if mmatch:
modname = mmatch.group(1)
modtype = mmatch.group(2)
if isspectre and '}' in mmatch.group(1):
savematch = mmatch
inmodel = 1
# Continue to "if inmodel == 1" block below
else:
fmtline, ispassed = parse_param_line(mmatch.group(3), True, False, True, ispassed)
if isspectre and (modtype == 'resistor' or modtype == 'r2'):
modtype = 'r'
modellines.append('.model ' + modname + ' ' + modtype + ' ' + fmtline)
if fmtline != '':
inparam = True
inmodel = 2
continue
if not insub:
# Things to parse if not in a subcircuit
imatch = insubrex.match(line) if isspectre else None
if not imatch:
# Check for spectre format subckt or CDL format .subckt lines
imatch = cdlsubrex.match(line)
if not imatch:
if not isspectre:
# Check for standard SPICE format .subckt lines
imatch = stdsubrex.match(line)
if imatch:
# If a model block is pending, then dump it
if modellines != []:
for line in modellines:
spicelines.append(line)
modellines = []
inmodel = False
insub = True
ispassed = True
subname = imatch.group(1)
subnames.append(subname)
if isspectre:
devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
else:
devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
# If there is no close-parenthesis then we should expect it on
# a continuation line
inpinlist = True if ')' not in line else False
# Remove parentheses groups from subcircuit arguments
spicelines.append('.subckt ' + ' ' + subname + ' ' + imatch.group(2))
continue
else:
# Things to parse when inside of an "inline subckt" block
if inpinlist:
# Watch for pin list continuation line.
if isspectre:
if ')' in line:
inpinlist = False
pinlist = line.replace(')', '')
spicelines.append(pinlist)
else:
spicelines.append(line)
continue
else:
if isspectre:
ematch = endsubrex.match(line)
if not ematch:
ematch = endonlysubrex.match(line)
else:
ematch = stdendsubrex.match(line)
if not ematch:
ematch = stdendonlysubrex.match(line)
if ematch:
try:
endname = ematch.group(1).strip()
except:
pass
else:
if endname != subname and endname != '':
print('Error: "ends" name does not match "subckt" name!')
print('"ends" name = ' + endname)
print('"subckt" name = ' + subname)
if len(calllines) > 0:
line = calllines[0]
if modtype.startswith('bsim'):
line = 'M' + line
elif modtype.startswith('nmos'):
line = 'M' + line
elif modtype.startswith('pmos'):
line = 'M' + line
elif modtype.startswith('res'):
line = 'R' + line
elif modtype.startswith('cap'):
line = 'C' + line
elif modtype.startswith('pnp'):
line = 'Q' + line
elif modtype.startswith('npn'):
line = 'Q' + line
elif modtype.startswith('d'):
line = 'D' + line
spicelines.append(line)
for line in calllines[1:]:
spicelines.append(line)
calllines = []
# Last check: Do any model types confict with the way they
# are called within the subcircuit? Spectre makes it very
# hard to know what type of device is being instantiated. . .
for modelline in modellines:
mmatch = stdmodelrex.match(modelline)
if mmatch:
modelname = mmatch.group(1).lower().split('.')[0]
modeltype = mmatch.group(2).lower()
newspicelines = []
for line in spicelines:
cmatch = stddev3rex.match(line)
if cmatch:
devtype = cmatch.group(1).lower()
if modelname in cmatch.group(3):
if devtype == 'x':
if modeltype == 'pnp' or modeltype == 'npn':
line = 'q' + line[1:]
elif modeltype == 'c' or modeltype == 'r':
line = modeltype + line[1:]
elif modeltype == 'd':
line = modeltype + line[1:]
elif modeltype == 'nmos' or modeltype == 'pmos':
line = 'm' + line[1:]
newspicelines.append(line)
spicelines = newspicelines
# Now add any in-circuit models
spicelines.append('')
for line in modellines:
spicelines.append(line)
modellines = []
# Complete the subcircuit definition
spicelines.append('.ends ' + subname)
insub = False
inmodel = False
subname = ''
paramnames = []
continue
# Check for close of model
if isspectre and inmodel:
if '}' in line:
inmodel = False
continue
# Check for devices R and C.
dmatch = caprex.match(line)
if dmatch:
fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
if fmtline != '':
inparam = True
spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
continue
else:
spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
continue
dmatch = resrex.match(line)
if dmatch:
fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
if fmtline != '':
inparam = True
spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
continue
else:
spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
continue
cmatch = cdlrex.match(line)
if not cmatch:
if not isspectre or 'capacitor' in line or 'resistor' in line:
cmatch = stddevrex.match(line)
if cmatch:
ispassed = False
devtype = cmatch.group(1)
devmodel = cmatch.group(4)
# Handle spectreisms. . .
if devmodel == 'capacitor':
devtype = 'c'
devmodel = ''
elif devmodel == 'resistor':
devtype = 'r'
devmodel = ''
elif devtype.lower() == 'n' or devtype.lower() == 'p':
# May be specific to SkyWater models, or is it a spectreism?
devtype = 'x' + devtype
# If a capacitor or resistor value is a parameter or expression,
# it must be enclosed in single quotes. Otherwise, if the named
# device model is a subcircuit, then the devtype must be "x".
elif devtype.lower() == 'c' or devtype.lower() == 'r':
if devmodel in subnames:
devtype = 'x' + devtype
else:
devvalue = devmodel.split('=')
if len(devvalue) > 1:
if "'" in devvalue[1] or "{" in devvalue[1]:
# Re-parse this catching everything in delimiters,
# including spaces.
cmatch2 = stddev2rex.match(line)
if cmatch2:
cmatch = cmatch2
devtype = cmatch.group(1)
devmodel = cmatch.group(4)
devvalue = devmodel.split('=')
if isexprrex.search(devvalue[1]):
if devvalue[1].strip("'") == devvalue[1]:
devmodel = devvalue[0] + "='" + devvalue[1] + "'"
else:
if devmodel in paramnames or isexprrex.search(devmodel):
if devmodel.strip("'") == devmodel:
devmodel = "'" + devmodel + "'"
fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed)
if fmtline != '':
inparam = True
# "perim" in diodes is not permitted by ngspice
if devtype == 'd':
fmtline = re.sub('[ \t]+perim[ \t]*=[ \t]*[^ \t]+', '', fmtline)
spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + fmtline)
continue
else:
spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + cmatch.group(5))
continue
else:
# SkyWater-specific handling for model nvhv and pvhv, which
# call the _base subcircuit as type 'm'.
cmatch = stddev3rex.match(line)
if cmatch:
devtype = cmatch.group(1)
if devtype.lower() == 'm':
devname = cmatch.group(2)
if devname == 'ain1':
line = 'x' + line
# Check for a .param line and collect parameter names
pmatch = paramrex.match(line)
if pmatch:
paramnames.extend(get_param_names(pmatch.group(1)))
# Check for a line that begins with the subcircuit name
dmatch = devrex.match(line)
if dmatch:
fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
if fmtline != '':
inparam = True
calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
continue
else:
calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
continue
if inmodel == 1 or inmodel == 2:
# This line should have the model bin, if there is one, and a type.
if inmodel == 1:
bmatch = binrex.match(savematch.group(3))
savematch = None
else:
bmatch = binrex.match(line)
if bmatch:
bin = bmatch.group(1)
type = bmatch.group(2)
if type == 'n':
convtype = 'nmos'
elif type == 'p':
convtype = 'pmos'
else:
convtype = type
# If there is a binned model then it replaces any original
# model line that was saved.
if modellines[-1].startswith('.model'):
modellines = modellines[0:-1]
modellines.append('')
modellines.append('.model ' + modname + '.' + bin + ' ' + convtype)
continue
else:
fmtline, ispassed = parse_param_line(line, True, True, False, ispassed)
if fmtline != '':
modellines.append(fmtline)
continue
# Copy line as-is
spicelines.append(line)
# Make sure any pending model lines are taken care of
if modellines != []:
for line in modellines:
spicelines.append(line)
modellines = []
inmodel = False
output = '\n'.join(spicelines)
output = cleanup_spice_data(output)
if not output.strip():
print("Skipping empty file:", outfile)
return
# Output the result to out_file.
print("Writing", out_file)
assert not os.path.exists(out_file), out_file
with open(out_file, 'w') as ofile:
ofile.write(output)
RE_MULTI_NEWLINE = re.compile('\\n+')
RE_BSIM4 = re.compile('^NEW BSIM4 Parameters\\(([^)]*)\\)$', flags=re.I|re.M)
RE_DUP_LINES = re.compile('^(?P<l>.+)$\\n*(?P=l)', flags=re.M)
def strip_comment(cmt):
"""
>>> strip_comment([' // File created for mismatch simulations.'])
'File created for mismatch simulations.'
>>> strip_comment(['//', '// DC IV MOS PARAMETERS', '//'])
'DC IV MOS PARAMETERS'
>>> strip_comment(['// ****', '//', '// ROUT PARAMETERS', '//'])
'ROUT PARAMETERS'
>>> strip_comment([
... '** models for resistance and capacitances **',
... '** this is lrhc corner **',
... '*********************************************',
... '**********************************************',
... '** tolerances of different layers **',
... '**********************************************',
... ])
'models for resistance and capacitances\\n this is lrhc corner\\n tolerances of different layers'
>>> strip_comment(['// **PARAMETERS FOR ASYMMETRIC AND BIAS-DEPENDENT RDS MODEL******'])
'PARAMETERS FOR ASYMMETRIC AND BIAS-DEPENDENT RDS MODEL'
>>> strip_comment(['// NEW BSIM4 Parameters(Model Selectors)'])
'BSIM4 - Model Selectors'
"""
if not cmt:
return ''
cmt = list(cmt)
for i, c in enumerate(cmt):
cmt[i] = c.lstrip()
# Remove the comment character
if cmt[0].startswith('*'):
p = '*'
elif cmt[0].startswith('//'):
p = '//'
elif cmt[0].startswith('$'):
p = '$'
else:
raise ValueError('Unknown starting comment: '+repr(cmt[0]))
for i, c in enumerate(cmt):
assert c.startswith(p), repr((p, i, c))+'\n'+pprint.pformat(cmt, width=100)
if c.endswith(p):
cmt[i] = c[len(p):-len(p)]
else:
cmt[i] = c[len(p):]
# Remove boxes around stuff
# This should also cover full lines of a single character like '*****' or '#####'
for i, c in enumerate(cmt):
if len(c) <= 2:
continue
c = c.strip()
while c and c[0] == c[-1] and not c[0].isalnum() and c[0] != ' ':
c = c[1:-1]
cmt[i] = c
for i, c in enumerate(cmt):
cmt[i] = c.strip('*')
# Find the longest prefix
if len(cmt) > 1:
p = os.path.commonprefix(cmt)
for i, c in enumerate(cmt):
cmt[i] = c[len(p):].rstrip()
cmt = '\n'.join(cmt)
cmt = textwrap.dedent(cmt).strip()
cmt = RE_MULTI_NEWLINE.sub('\n', cmt)
# Fix a spelling mistake...
cmt = cmt.replace('PARAMTERS', 'PARAMETERS')
# Strip out the BSIM4 parameters comment
cmt = RE_BSIM4.sub('BSIM4 - \\1', cmt)
return cmt
RE_BIN = re.compile('([^,]+),\\s+Bin\\s+([^,]+),(.*)')
RE_PARAM = re.compile('[\w\s+.()/-]*((PARAMETERS)|(SELECTORS))[\w\s+.()/-]*', flags=re.I)
RE_PARAM_END = re.compile('PARAMETERS$', flags=re.I)
RE_FIX_CASE = re.compile('\\b([A-Z][A-Z][A-Z][A-Z][A-Z]*)\\b')
RE_UNITS = re.compile('Units:\\w*(.*)', flags=re.I)
# for L>=4 and W<=0.75
RE_L_AND_W = re.compile('((L[\s<>]=)|(W[\s<>]=))')
# 'High Density 3-terminal Vertical Parallel Plate Capacitor (for S8P ONLY)\nFixed layout is here:\n$depot/icm/proj/s8rf2/dev/opus/s8rf2/s8rf2_xcmvpp_hd5_atlas_fingercap2_l5'
# '3-terminal Vertical Parallel Plate Capacitor /w LI-M5 Shield (for S8P ONLY)\nThis is the ~100fF fixed capacitor model\nFixed layout is here:\ndepot/icm/proj/s8rf2/dev/opus/s8rf2/s8rf2_xcmvpp11p5x11p7_m3_lim5shield/layout'
RE_CAP = re.compile('(?P<desc1>.*)\\(for S8P ONLY\\)(?P<desc2>.*)Fixed layout is here:(?P<oldp>.*?)/(?P<p>[^/]*)((?:/layout)|$)', flags=re.DOTALL|re.I)
UNIT_MAPPING = {
'1/c': '1/coulomb',
'1/c^2': '1/coulomb^2',
'1/oc': '1/celsius',
'1/volt': 'volt',
'a': 'amper',
'a/m': 'amper/meter',
'a/m^2': 'amper/meter^2',
'angstrom': 'angstrom',
'c': 'coulomb',
'ev': 'electron-volt',
'ev/c': 'electron-volt/coulomb',
'ev/k': 'electron-volt/kelvin',
'farads/m': 'farad/meter',
'farads/m^2': 'farad/meter^2',
'f/m': 'farad/meter',
'f/m^2': 'farad/meter^2',
'm': 'meter',
'meter': 'meter',
'f/um': 'farad/micrometer',
'f/um^2': 'farad/micrometer^2',
'v': 'volt',
'v/c': 'volt/coulomb',
# Long replacements
'ohms (ohms/m^2 if area defined in netlist)': 'ohm (ohm/meter^2 if area defined)',
'temerature in oc passed from eldo *.temp statements': 'celsius',
}
def process_special_comments(cmt):
"""
>>> process_special_comments(strip_comment(['// NEW BSIM4 Parameters(Mobility Parameters)']))
('BSIM4 - Mobility Parameters', 'param', ['BSIM4 - Mobility'])
>>> process_special_comments('nlowvt_rf_base_m4_b, Bin 008, W = 5.05, L = 0.25')
('\\nnlowvt_rf_base_m4_b, Bin 008, W = 5.05, L = 0.25\\n------------------------------------------------', 'bin', [['nlowvt_rf_base_m4_b', 'Bin 008', 'W = 5.05', 'L = 0.25']])
>>> process_special_comments('Parameters TO Make Model Into Cadflow')
(False, None, [])
>>> process_special_comments('DC IV MOS PARAMETERS')
('DC IV MOS Parameters', 'param', ['DC IV MOS'])
>>> process_special_comments('DC IV MOS parameters')
('DC IV MOS parameters', 'param', ['DC IV MOS'])
>>> process_special_comments('GATE INDUCED DRAIN LEAKAGE MODEL PARAMETERS')
('Gate Induced Drain Leakage Model Parameters', 'param', ['Gate Induced Drain Leakage Model'])
>>> process_special_comments('P+ Poly Preres Parameters')
('P+ Poly Preres Parameters', 'param', ['P+ Poly Preres'])
>>> process_special_comments('Units: F/um')
('Units: farad/micrometer', 'units', ['farad/micrometer'])
>>> process_special_comments(' F/um ')
('Units: farad/micrometer', 'units', ['farad/micrometer'])
>>> process_special_comments('All devices')
('All devices', None, [])
>>> process_special_comments('Not used')
('Not used', None, [])
>>> process_special_comments('All W with L=0.5um')
('All W with L=0.5um', None, [])
>>> process_special_comments('for L>=4 and W<=0.75')
('for L>=4 and W<=0.75', None, [])
>>> process_special_comments('Default Corner File created by discrete_corners\\nNumber of BINS: 1')
('Number of bins: 1', None, [])
>>> process_special_comments('4-terminal Vertical Parallel Plate Capacitor /w LI-M4 fingers and M5 Shield (for S8P ONLY)\\nFixed layout is here:\\ndepot/icm/proj/s8rf2/dev/opus/s8rf2/s8rf2_xcmvpp11p5x11p7_m5shield')
('4-terminal Vertical Parallel Plate Capacitor /w LI-M4 fingers and M5 Shield\\nLayout: s8rf2_xcmvpp11p5x11p7_m5shield', None, [])
>>> process_special_comments("3-terminal Vertical Parallel Plate Capacitor /w LI-M5 Shield (for S8P ONLY)\\nThis is the ~100fF fixed capacitor model\\nFixed layout is here:\\ndepot/icm/proj/s8rf2/dev/opus/s8rf2/s8rf2_xcmvpp11p5x11p7_m3_lim5shield/layout")
('3-terminal Vertical Parallel Plate Capacitor /w LI-M5 Shield\\nThis is the ~100fF fixed capacitor model\\nLayout: s8rf2_xcmvpp11p5x11p7_m3_lim5shield', None, [])
>>> process_special_comments("Include files in ../../../s8x/ directory\\nP+ Poly Preres Corner Parameters")
('P+ Poly Preres Corner Parameters', 'param', ['P+ Poly Preres Corner'])
>>> pprint.pprint(process_special_comments("nhvnativeesd w/l's to match MRGA and pm3 file\\nnhvnativeesd, Bin 011, W = 10.0, L = 2.0\\nnhvnativeesd, Bin 012, W = 10.0, L = 4.0\\nnhvnativeesd, Bin 013, W = 10.0, L = 0.9\\n"))
('\\n'
"nhvnativeesd w/l's to match MRGA and pm3 file\\n"
'nhvnativeesd, Bin 011, W = 10.0, L = 2.0\\n'
'nhvnativeesd, Bin 012, W = 10.0, L = 4.0\\n'
'nhvnativeesd, Bin 013, W = 10.0, L = 0.9\\n'
'---------------------------------------------',
'bin',
[["nhvnativeesd w/l's to match MRGA and pm3 file"],
['nhvnativeesd', 'Bin 011', 'W = 10.0', 'L = 2.0'],
['nhvnativeesd', 'Bin 012', 'W = 10.0', 'L = 4.0'],
['nhvnativeesd', 'Bin 013', 'W = 10.0', 'L = 0.9']])
#>>> process_special_comments('DIODE DC IV PARAMTERS\\n\\nNEW BSIM4 Parameters(DIODE DC IV parameters)')
#('* Diode DC IV Parameters', 'param', 'Diode DC IV Parameters')
Returns:
new - Comment value to put in output file, empty string if the comment is discarded.
str - comment type
[d] - comment data
"""
if 'rights' in cmt.lower():
return '', None, []
# Strip duplicate stuff
cmt = RE_DUP_LINES.sub(lambda m: m.group('l'), cmt)
m = RE_BIN.match(cmt)
if m:
a = []
b = []
for l in cmt.splitlines():
l = l.strip()
if not l:
continue
if ',' not in l:
a.append(l)
b.append([l])
else:
d = [v.strip() for v in l.split(',')]
a.append(', '.join(d))
b.append(d)
return '\n'+'\n'.join(a)+'\n'+'-'*max(len(nc) for nc in a), 'bin', b
m = RE_UNITS.match(cmt)
if m:
unit = m.group(1)
unit = unit.strip().lower()
unit_new = UNIT_MAPPING.get(unit, None)
if not unit_new:
assert unit in UNIT_MAPPING.values(), (unit, unit_new)
unit_new = new
return 'Units: ' + unit_new, 'units', [unit_new]
if 'L' in cmt and 'W' in cmt:
m = RE_L_AND_W.search(cmt)
if m:
return cmt, None, []
uc = cmt.strip().lower()
if uc in UNIT_MAPPING:
unit_new = UNIT_MAPPING[uc]
return 'Units: ' + unit_new, 'units', [unit_new]
if '\n' not in uc:
if 'all' in uc:
return cmt, None, []
if 'not used' in uc:
return cmt, None, []
if 'number of bins:' in uc:
before, after = uc.split('number of bins:')
return 'Number of bins: '+after.strip(), None, []
if '(for s8p only)\n' in uc:
m = RE_CAP.match(' '.join(cmt.splitlines()))
assert m, cmt
new_cmt = m.group('desc1').strip()
assert new_cmt, (m, m.groups())
if m.group('desc2').strip():
new_cmt += '\n'+m.group('desc2').strip()
if m.group('p'):
new_cmt += '\nLayout: '+m.group('p')
return new_cmt, None, []
m = RE_PARAM.match(cmt)
if m and 'cadflow' not in cmt.lower():
if '\n' in cmt:
cmt = '\n'.join(s for s in cmt.splitlines() if 'parameters' in s.lower())
cmt = RE_FIX_CASE.sub(lambda m: m.group(0).title(), cmt)
ocmt = cmt
if RE_PARAM_END.search(ocmt):
ocmt = ocmt[:-len(' Parameters')]
return cmt, 'param', [ocmt]
return False, None, []
def convert_comments(idata):
# Replace all C style // comments with '*' or '$'
cmt_start = None
for i, l in enumerate(idata):
if '//' in l:
if l.strip().startswith('//'):
assert '//.' not in l, repr(l)
idata[i] = '*' + l.lstrip('/')
else:
before, after = l.split('//', 1)
idata[i] = before + '$' + after.lstrip('/')
#assert '/*' not in l, idata
assert '*/' not in idata[i], idata[i]+'\n'+'\n'.join(idata)
def get_last_comment(comments):
i = len(comments)
while i > 0:
if comments[i-1].startswith('...'):
break
i -= 1
return comments[i:]
EOF_MARKER = 'EOF\0'
def rewrite_comments(out_file, idata):
idata = list(idata.splitlines())
idata.append(EOF_MARKER)
convert_comments(idata)
speclines = ['* SKY130 Spice File.']
comments_raw = []
comments_nonspecial = []
comments_special = defaultdict(list)
comments_special_debug = defaultdict(list)
for l in idata:
if l.strip().startswith('*'):
if not l.startswith('*.'):
comments_raw.append(l)
continue
if not l.strip():
continue
if comments_raw and not comments_raw[-1].startswith('...'):
cmt_last = get_last_comment(comments_raw)
comments_raw.append('...')
cmt_clean = strip_comment(cmt_last)
cmt_new, cmt_type, cmt_value = process_special_comments(cmt_clean)
if cmt_new:
cmt_new = textwrap.indent(cmt_new, '* ', lambda line: True)
print('Replacing comment:', repr('\n'.join(cmt_last)), 'with', repr(cmt_new))
speclines.append(cmt_new)
if cmt_type:
comments_special[cmt_type].extend(cmt_value)
comments_special_debug[cmt_type].append((cmt_last, cmt_clean, (cmt_new, cmt_type, cmt_value)))
elif not cmt_new and cmt_clean:
comments_nonspecial.append(cmt_clean)
if '$' in l:
l, cmt = l.split('$', 1)
comments_raw.append('... $ ' + cmt)
cmt_clean = cmt.strip()
cmt_new, cmt_type, cmt_value = process_special_comments(cmt_clean)
if cmt_new:
l = "{}$ {}".format(l, cmt_new)
if cmt_type:
comments_special[cmt_type].append(cmt_value)
comments_special_debug[cmt_type].append((cmt, cmt_clean, (cmt_new, cmt_type, cmt_value)))
elif not cmt_new and cmt_clean:
comments_nonspecial.append('... $ ' + cmt_clean)
speclines.append(l)
assert speclines[-1] == EOF_MARKER, pprint.pformat(speclines, width=WIDTH)
speclines.pop(-1)
assert not comments_raw or comments_raw[-1].startswith('...'), pprint.pformat(comments_raw)
if comments_nonspecial:
print('Non-special comments:')
print('-'*10)
pprint.pprint(comments_nonspecial, width=WIDTH)
print('-'*10)
cmt_file = out_file+'.stripped.comments'
print("Writing stripped comments to:", cmt_file)
assert not os.path.exists(cmt_file), cmt_file
with open(cmt_file, 'w') as f:
for c in comments_nonspecial:
f.write(c)
f.write('\n')
f.write('---\n')
if comments_special:
comments_special = dict(comments_special)
print("Special comments:")
print('-'*10)
pprint.pprint(comments_special, width=WIDTH)
print('-'*10)
if 'bin' in comments_special:
def clean(s, a='='):
s = s.split(a)[-1]
s = s.strip()
s = s.lstrip('0')
if '.' in s:
s = s.rstrip('0')
if len(s) == 0:
s = '0'
elif s[0] == '.':
s = '0'+s
elif s[-1] == '.':
s = s+'0'
return s
bin_csv = out_file+'.bins.csv'
with open(bin_csv, 'w', newline='') as f:
c = csv.writer(f)
c.writerow(['device','bin','W','L'])
for r in comments_special['bin']:
assert len(r) in (1, 4), r
if len(r) == 1:
c.writerow(r)
else:
d, b, w, l = r
b = clean(b, ' ')
w = clean(w)
l = clean(l)
c.writerow([d, b, w, l])
del comments_special['bin']
for k in comments_special:
sfile = out_file+'.'+k+'.json'
with open(sfile, 'w') as f:
json.dump(comments_special[k], f, sort_keys=True, indent=' ')
if len(comments_raw) > 1:
cmt_file = out_file+'.raw.comments'
print("Writing comments to:", cmt_file)
assert not os.path.exists(cmt_file), cmt_file
with open(cmt_file, 'w') as f:
for c in comments_raw:
f.write(c)
f.write('\n')
return speclines
# * <comment>
RE_CMT_FULL_LINE = re.compile('\\n(\\*[^\\.]*[^\\n]*\\n)+')
# XXXX $ <comment>
RE_CMT_END_LINE = re.compile('\\$.*?$', flags=re.MULTILINE)
RE_LINE_PLUS_START = re.compile('^\\+([ \\t]*)', flags=re.MULTILINE)
RE_LINE_EQUALS = re.compile('[ \\t]+=[ \\t]+')
RE_CMT_INCLUDE_IG = re.compile('^\\*\\*Include files in.*\\n', flags=re.MULTILINE)
RE_EXTRA_CMT = re.compile('^(\\*\\*\\*\\n)+', flags=re.MULTILINE)
RE_BIG_CMT = re.compile('^\\*\\*\\*(.*)$', flags=re.MULTILINE)
RE_SMALL_CMT = re.compile('\\n(\\*[ \\t]*\\n)+', flags=re.MULTILINE)
RE_MULTI_NEWLINE = re.compile('\\n+')
RE_INCLUDE = re.compile('^\\.inc(lude)?[ \\t]+["\']?(?P<file>[^"\'\\n\\r]+)["\']?$', flags=re.MULTILINE|re.IGNORECASE)
RE_INCLUDE_CMT = re.compile('^\\*[ \\t]*\\.inc(lude)?[ \\t]+["\']?(?P<file>[^"\'\\n\\r]+)["\']?$', flags=re.MULTILINE|re.IGNORECASE)
RE_TRAILING_WS = re.compile('[ \\t]+$', flags=re.MULTILINE)
RE_INT = re.compile('= [ \\t]*(-?[0-9]+)[ \\t]*$', flags=re.MULTILINE)
RE_FIX_SCIENTIFIC = re.compile('= [ \\t]*(-?)([0-9.]+)[eE]([+\\-]?)([0-9.]+)[ \\t]*$', flags=re.MULTILINE)
RE_CPAR_REMOVE = re.compile("^.param ((cpar_area)|(cpar_perim)) = '[^']*'\n", flags=re.MULTILINE)
def cleanup_comment(m):
return '*** ' + ' '.join(m.group(1).strip().split())
def fix_scientific(m):
"""
>>> RE_FIX_SCIENTIFIC.sub(fix_scientific, '= 0.0')
'= 0.0'
>>> RE_FIX_SCIENTIFIC.sub(fix_scientific, '= 0e0')
'= 0.0e+0'
>>> RE_FIX_SCIENTIFIC.sub(fix_scientific, '= -2.4222865e-005')
'= -2.4222865e-5'
>>> RE_FIX_SCIENTIFIC.sub(fix_scientific, '= 1e+10')
'= 1.0e+10'
>>> RE_FIX_SCIENTIFIC.sub(fix_scientific, '= 1e00010')
'= 1.0e+10'
"""
neg = m.group(1)
before = m.group(2)
sign = m.group(3)
after = m.group(4)
while len(before) > 1 and before[0] == '0':
before = before[1:]
while len(after) > 1 and after[0] == '0':
after = after[1:]
if not sign:
sign = '+'
if '.' not in before:
before += '.0'
return '= {}{}e{}{}'.format(neg, before, sign, after)
def cleanup_spice_data(data):
"""
>>> cleanup_spice_data('''
... **Include files in ../../../s8x/ directory
... .inc "../../s8x/Models/ss.cor"
... ''')
'.include "../../s8x/Models/ss.cor"\\n'
>>> cleanup_spice_data('''
... .inc "../../s8x/Models/sonos_ttteol.cor"
... ''')
'.include "../../s8x/Models/sonos_ttteol.cor"\\n'
>>> cleanup_spice_data('''
... .inc "models.a"
... ''')
'.include "models.a"\\n'
>>> cleanup_spice_data('''
... .include models.a
... ''')
'.include "models.a"\\n'
>>> cleanup_spice_data('''
... .include 'models.a'
... ''')
'.include "models.a"\\n'
>>> print(cleanup_spice_data('''
... * RF MOS PARAMETERS
... .inc "nshort_rf_base_b_fs.cor"
... .inc "nlowvt_rf_base_b_fs.cor"
... ''').strip())
* RF MOS PARAMETERS
.include "nshort_rf_base_b_fs.cor"
.include "nlowvt_rf_base_b_fs.cor"
>>> print(cleanup_spice_data('''
... + dvt1w = 1147200.0
... + vbm = -3
... + rdswmin = 0
... + rdw = 1
... + rdwmin = 0
... + rsw = 0
... + rswmin = 0
... ''').strip())
+ dvt1w = 1147200.0
+ vbm = -3.0
+ rdswmin = 0.0
+ rdw = 1.0
+ rdwmin = 0.0
+ rsw = 0.0
+ rswmin = 0.0
>>> print(cleanup_spice_data('''
... + nsd = 1.0e+020
... + alpha1 = 1.0e-10
... + pdiblcb = -2.4222865e-005
... + alpha1 = 1e-010
... ''').strip())
+ nsd = 1.0e+20
+ alpha1 = 1.0e-10
+ pdiblcb = -2.4222865e-5
+ alpha1 = 1.0e-10
>>> print(cleanup_spice_data('''
... + nsd = 1.0e+020
... + lint = {1.1932e-008+nshort_lint_diff}
... + alpha1 = 1e-010
... ''').strip())
+ nsd = 1.0e+20
+ lint = {1.1932e-008+nshort_lint_diff}
+ alpha1 = 1.0e-10
>>> print(cleanup_spice_data('''
... .param vc1 = -25.0e-6
... .param vc2 = 90.0e-6
... .param carea = 'camimc*(wc)*(lc)'
... .param cperim = 'cpmimc*((wc)+(lc))*2'
... .param czero = 'carea + cperim' dev/gauss='0.01*2.8*(carea + cperim)/sqrt(wc*lc*mf)'
... .param cpar_area = 'mcm4f_ca_w_7_680_s_1_240*(wc)*(lc)*m*1e-12'
... .param cpar_perim = 'mcm4f_cf_w_7_680_s_1_240*((wc)+(lc))*m*2*1e-6'
... c1 c0 a 'czero*(1+vc1*(v(c0)-v(c1))+vc2*(v(c0)-v(c1))*(v(c0)-v(c1)))' tc1=0 tc2=0
... rs1 a b1 'r1' tc1=tc1rm4 tc2=tc2rm4
... rs2 b1 c1 'r2' tc1=tc1rvia4 tc2=tc2rvia4
... .ends sky130_fd_pr__cap_mim_m3_2
... ''').strip())
.param vc1 = -25.0e-6
.param vc2 = 90.0e-6
.param carea = 'camimc*(wc)*(lc)'
.param cperim = 'cpmimc*((wc)+(lc))*2'
.param czero = 'carea + cperim' dev/gauss='0.01*2.8*(carea + cperim)/sqrt(wc*lc*mf)'
c1 c0 a 'czero*(1+vc1*(v(c0)-v(c1))+vc2*(v(c0)-v(c1))*(v(c0)-v(c1)))' tc1=0 tc2=0
rs1 a b1 'r1' tc1=tc1rm4 tc2=tc2rm4
rs2 b1 c1 'r2' tc1=tc1rvia4 tc2=tc2rvia4
.ends sky130_fd_pr__cap_mim_m3_2
"""
# "../../s8x/Models/ss.cor"
data = RE_TRAILING_WS.sub('', data)
data = RE_LINE_PLUS_START.sub('+ ', data)
data = RE_LINE_EQUALS.sub(' = ', data)
data = RE_CMT_INCLUDE_IG.sub('', data)
data = RE_EXTRA_CMT.sub('', data)
data = RE_BIG_CMT.sub(cleanup_comment, data)
data = RE_MULTI_NEWLINE.sub('\n', data)
data = RE_INCLUDE.sub('.include "\\g<file>"', data)
data = RE_INCLUDE_CMT.sub('*.include "\\g<file>"', data)
data = RE_INT.sub('= \\1.0', data)
data = RE_FIX_SCIENTIFIC.sub(fix_scientific, data)
data = RE_CPAR_REMOVE.sub('', data)
iinc = data.find('.inc ')
assert iinc == -1, (iinc, data[iinc-100:iinc+100])
data = data.strip()
if data and data[-1] != '\n':
data += '\n'
return data
if __name__ == '__main__':
import doctest
fails, _ = doctest.testmod()
if fails != 0:
sys.exit("Some test failed")
if len(sys.argv) == 1:
print("No options given to spectre_to_spice.py.")
usage()
sys.exit(0)
optionlist = []
arguments = []
for option in sys.argv[1:]:
if option.find('-', 0) == 0:
optionlist.append(option)
else:
arguments.append(option)
if len(arguments) != 2:
print("Wrong number of arguments given to convert_spectre.py.")
usage()
sys.exit(0)
debug = '-debug' in optionlist
nocmt = '-nocmt' in optionlist
try:
ignore = next(item for item in optionlist if item.split('=')[0] == '-ignore').split('=')[1].split(',')
except:
ignore = ''
specpath = os.path.abspath(arguments[0])
spicepath = os.path.abspath(arguments[1])
do_one_file = False
if not os.path.exists(specpath):
print('No such source directory ' + specpath)
sys.exit(1)
if os.path.isfile(specpath):
do_one_file = True
if do_one_file:
if arguments[1].endswith('/'):
if not os.path.exists(spicepath):
print("Creating:", spicepath)
os.makedirs(spicepath)
else:
assert os.path.isdir(spicepath), "Not directory? " + spicepath
c = os.path.commonprefix([spicepath, specpath])
spicepath = os.path.join(spicepath, specpath[len(c):].replace('/', '_'))
if os.path.exists(spicepath):
print('Error: File ' + spicepath + ' exists.')
sys.exit(1)
convert_file(specpath, spicepath, nocmt, ignore, debug)
else:
if not os.path.exists(spicepath):
os.makedirs(spicepath)
specfilelist = glob.glob(specpath + '/*')
for filename in specfilelist:
if filename.endswith('readme'):
continue
if filename.endswith('.tmp'):
continue
if filename.endswith('.comments'):
continue
fileext = os.path.splitext(filename)[1]
# Ignore verilog or verilog-A files that might be in a model directory
if fileext == '.v' or fileext == '.va':
continue
# .scs files are purely spectre and meaningless to SPICE, so ignore them.
if fileext == '.scs':
continue
froot = os.path.split(filename)[1]
print()
convert_file(filename, spicepath + '/' + froot, nocmt, ignore, debug)
print()
exit(0)