Merge branch 'master' of 192.168.0.7:/home/tim/gitsrc/open_pdks/
diff --git a/VERSION b/VERSION
index 8b54409..9495615 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.28
+1.0.31
diff --git a/common/find_all_devices.py b/common/find_all_devices.py
new file mode 100755
index 0000000..71c27c5
--- /dev/null
+++ b/common/find_all_devices.py
@@ -0,0 +1,559 @@
+#!/usr/bin/env python3
+#-----------------------------------------------------------
+#
+# Find all devices which have subcircuit definitions in the path
+# skywater-pdk/libraries/sky130_fd_pr/VERSION/cells/. List all of
+# these devices. Then find all paths from the directory models/
+# that will read the subcircuit definition through a hierarchical
+# series of includes.
+#-----------------------------------------------------------
+
+import re
+import os
+import sys
+
+#-----------------------------------------------------------
+# Find all models in the model directory path, recursively
+#-----------------------------------------------------------
+
+def addmodels(modelpath):
+ modelfmts = os.listdir(modelpath)
+ files_to_parse = []
+ for modelfmt in modelfmts:
+ if os.path.isdir(modelpath + '/' + modelfmt):
+ files_to_parse.extend(addmodels(modelpath + '/' + modelfmt))
+ else:
+ fmtext = os.path.splitext(modelfmt)[1]
+ if fmtext == '.spice':
+ files_to_parse.append(modelpath + '/' + modelfmt)
+
+ return files_to_parse
+
+#-----------------------------------------------------------
+# Find the device name in a SPICE "X" line
+#-----------------------------------------------------------
+
+def get_device_name(line):
+ # The instance name has already been parsed out of the line, and
+ # all continuation lines have been added, so this routine finds
+ # the last keyword that is not a parameter (i.e., does not contain
+ # '=').
+
+ tokens = line.split()
+ for token in tokens:
+ if '=' in token:
+ break
+ else:
+ devname = token
+
+ return devname
+
+#-----------------------------------------------------------
+# Pick the file from the list that is appropriate for the
+# choice of either FEOL or BEOL corner. Mostly ad-hoc rules
+# based on the known file names.
+#-----------------------------------------------------------
+
+def choose_preferred(inclist, feol, beol, notop, debug):
+
+ try:
+ # The top-level file 'sky130.lib.spice' is always preferred
+ incfile = next(item for item in inclist if os.path.split(item)[1] == 'sky130.lib.spice')
+ except:
+ # (1) Sort list by the length of the root name of the file
+ inclist = sorted(inclist, key=lambda x: len(os.path.splitext(os.path.split(x)[1])[0]))
+
+ # (2) Sort list by depth of directory hierarchy
+ inclist = sorted(inclist, key=lambda x: len(x.split('/')))
+
+ for incfile in inclist:
+ incname = os.path.split(incfile)[1]
+ if debug:
+ print(' choose preferred, checking: "' + incname + '"')
+
+ elif incname == 'custom.spice':
+ # Ignore "custom.spice" (example file, unused)
+ continue
+ elif notop and incname.startswith('correl'):
+ # Ignore "correl1.spice", etc., if "-notop" option chosen
+ continue
+
+ elif feol in incname:
+ break
+ elif 't' in beol and 'typical' in incname:
+ break
+ elif 'h' in beol and 'high' in incname:
+ break;
+ elif 'l' in beol and 'low' in incname:
+ break;
+
+ if debug:
+ incname = os.path.split(incfile)[1]
+ print(' choose_preferred: chose ' + incfile + ' (' + incname + ')')
+ return incfile
+
+#---------------------------------------------------------------------
+# Sort files with the subcircuit by relevance. Files are considered
+# in the order ".model.spice", ".pm3.spice", and ".spice".
+#---------------------------------------------------------------------
+
+def preferred_order(subfiles, feol):
+ ordfiles = []
+ feolstr = '__' + feol
+
+ # Sort by length first, so shorter ones, e.g., without "leak" or
+ # "discrete", end up at the front of the list.
+ ordlist = sorted(subfiles, key=len)
+
+ for file in ordlist[:]:
+ if file.endswith('.corner.spice') and feolstr in file:
+ ordfiles.append(file)
+ ordlist.remove(file)
+
+ for file in ordlist[:]:
+ if file.endswith('.model.spice'):
+ ordfiles.append(file)
+ ordlist.remove(file)
+
+ for file in ordlist[:]:
+ if file.endswith('.pm3.spice') and feolstr in file:
+ ordfiles.append(file)
+ ordlist.remove(file)
+
+ for file in ordlist[:]:
+ if file.endswith('.pm3.spice'):
+ ordfiles.append(file)
+ ordlist.remove(file)
+
+ ordfiles.extend(ordlist)
+ return ordfiles
+
+#-----------------------------------------------------------
+# Find the appropriate file to include to handle this device
+#-----------------------------------------------------------
+
+def check_device(subfiles, includedict, modfilesdict, feol, beol, notop, debug):
+ # subfiles = list of files that define this subcircuit.
+ # includedict = files that include the dictionary keyword file
+ # modfilesdict = files that are included by the dictionary keyword file
+ # feol = FEOL corner (fs, tt, ss, etc.) for transistors, diodes
+ # beol = BEOL corner (hl, tt, ll, etc.) for capacitors, resistors, inductors
+
+ ordfiles = preferred_order(subfiles, feol)
+ if debug:
+ print('')
+ print('Check_device: Search order:')
+ for ordfile in ordfiles:
+ print(' ' + os.path.split(ordfile)[1])
+
+ # Find the proper include file to include this device. Attempt on all
+ # entries in ordfiles, and stop at the first one that returns a result.
+ # Assume that all files have unique names and point to the proper
+ # location, so that is is only necessary to look at the last path
+ # component.
+
+ if debug:
+ print('\nClimb hierarchy of includes to the top:')
+
+ for ordfile in ordfiles:
+ ordname = os.path.split(ordfile)[1]
+ if debug:
+ print(' (1) Search for "' + ordname + '"')
+ try:
+ inclist = includedict[ordname][1:]
+ except:
+ if debug:
+ print(' No include file found for "' + ordfile + '"')
+ print(' Sample entry:')
+ for key in includedict:
+ print(' ' + key + ': "' + str(includedict[key][1:]) + '"')
+ break
+ continue
+ else:
+ if debug:
+ print(' Starting list = ')
+ for item in inclist:
+ print(' ' + item)
+
+ while True:
+ incfile = choose_preferred(inclist, feol, beol, notop, debug)
+ incname = os.path.split(incfile)[1]
+ if debug:
+ print(' (2) Search for "' + incname + '"')
+ try:
+ inclist = includedict[incname][1:]
+ except:
+ break
+ else:
+ if debug:
+ print(' Continuing list = ')
+ for item in inclist:
+ print(' ' + item)
+
+ if debug:
+ print('Final top level include file is: "' + incfile + '"')
+ return incfile
+
+ # Should only happen if subfiles is empty list
+ return None
+
+#-----------------------------------------------------------
+# Find all cells and all models
+#-----------------------------------------------------------
+
+def find_everything(pathtop):
+ cellspath = pathtop + '/cells'
+ modelspath = pathtop + '/models'
+
+ allcells = os.listdir(cellspath)
+
+ subcktrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+', re.IGNORECASE)
+ includerex = re.compile('\.include[ \t]+([^ \t]+)', re.IGNORECASE)
+
+ filesdict = {}
+ subcktdict = {}
+ includedict = {}
+ modfilesdict = {}
+
+ for cellfile in allcells:
+ cellpath = cellspath + '/' + cellfile
+ cellfmts = os.listdir(cellpath)
+ files_to_parse = []
+ for cellfmt in cellfmts:
+ fmtext = os.path.splitext(cellfmt)[1]
+ if fmtext == '.spice':
+ files_to_parse.append(cellpath + '/' + cellfmt)
+
+ for file in files_to_parse:
+ with open(file, 'r') as ifile:
+ spicelines = ifile.read().splitlines()
+ for line in spicelines:
+ smatch = subcktrex.match(line)
+ if smatch:
+ subname = smatch.group(1)
+ try:
+ subcktdict[subname].append(file)
+ except:
+ subcktdict[subname] = [file]
+ filetail = os.path.split(file)[1]
+ try:
+ filesdict[filetail].append(subname)
+ except:
+ filesdict[filetail] = [subname]
+
+ files_to_parse = addmodels(modelspath)
+ files_to_parse.extend(addmodels(cellspath))
+
+ for file in files_to_parse:
+ # NOTE: Avoid problems with sonos directories using
+ # "tt.spice", which causes the include chain recursive
+ # loop to fail to exit. This is a one-off exception
+ # (hack alert)
+ if '_of_life' in file:
+ continue
+
+ with open(file, 'r') as ifile:
+ spicelines = ifile.read().splitlines()
+ for line in spicelines:
+ imatch = includerex.match(line)
+ if imatch:
+ incname = imatch.group(1).strip('"')
+ inckey = os.path.split(incname)[1]
+
+ try:
+ inclist = includedict[inckey]
+ except:
+ includedict[inckey] = [incname, file]
+ else:
+ if file not in inclist[1:]:
+ includedict[inckey].append(file)
+ filetail = os.path.split(file)[1]
+ try:
+ modlist = modfilesdict[filetail]
+ except:
+ modfilesdict[filetail] = [incname]
+ else:
+ if incname not in modlist:
+ modfilesdict[filetail].append(incname)
+
+ return filesdict, subcktdict, includedict, modfilesdict
+
+#-----------------------------------------------------------
+# Main application
+#-----------------------------------------------------------
+
+def do_find_all_devices(pathtop, sourcefile, cellname=None, feol='tt', beol='tt', doall=False, notop=False, debug=False):
+
+ (filesdict, subcktdict, includedict, modfilesdict) = find_everything(pathtop)
+
+ if sourcefile:
+ # Parse the source file and find all 'X' records, and collect a list
+ # of all primitive devices used in the file by cross-checking against
+ # the dictionary of subcircuits.
+
+ devrex = re.compile('x([^ \t]+)[ \t]+(.*)', re.IGNORECASE)
+ incfiles = []
+
+ with open(sourcefile, 'r') as ifile:
+ spicelines = ifile.read().splitlines()
+
+ if debug:
+ print('Netlist file first line is "' + spicelines[0] + '"')
+
+ isdev = False
+ for line in spicelines:
+ if line.startswith('*'):
+ continue
+ if line.strip() == '':
+ continue
+ elif line.startswith('+'):
+ if isdev:
+ rest += line[1:]
+ elif isdev:
+ devname = get_device_name(rest)
+ try:
+ subfiles = subcktdict[devname]
+ except:
+ pass
+ else:
+ incfile = check_device(subfiles, includedict, modfilesdict, feol, beol, notop, debug)
+ if not incfile:
+ incfile = preferred_order(subfiles, feol)[0]
+
+ if incfile:
+ if debug:
+ print('Device ' + devname + ': Include ' + incfile)
+ if incfile not in incfiles:
+ incfiles.append(incfile)
+ else:
+ print('Something went dreadfully wrong with device "' + devname + '"')
+
+ isdev = False
+
+ smatch = devrex.match(line)
+ if smatch:
+ instname = smatch.group(1)
+ rest = smatch.group(2)
+ isdev = True
+ elif isdev:
+ devname = get_device_name(rest)
+ try:
+ subfiles = subcktdict[devname]
+ except:
+ pass
+ else:
+ incfile = check_device(subfiles, includedict, modfilesdict, feol, beol, notop, debug)
+ if not incfile:
+ incfile = preferred_order(subfiles, feol)[0]
+
+ if incfile:
+ if debug:
+ print('Device "' + devname + '": Include "' + incfile + '"')
+ if incfile not in incfiles:
+ incfiles.append(incfile)
+ else:
+ print('Something went dreadfully wrong with device "' + devname + '"')
+ isdev = False
+
+ # Return the .include lines needed
+ return incfiles
+
+ elif cellname:
+ # Diagnostic: Given a cell name on the command line (with -cell=<name>),
+ # Run check_device() on the cell and report.
+ try:
+ subfiles = subcktdict[cellname]
+ except:
+ print('No cell "' + cellname + '" was found in the PDK files.')
+ sys.exit(1)
+
+ incfile = check_device(subfiles, includedict, modfilesdict, feol, beol, notop, debug)
+ if debug:
+ print('')
+ print('Report:')
+ print('')
+ print('Cell = "' + cellname + '"')
+ print('')
+
+ bestfilepath = preferred_order(subfiles, feol)[0]
+ if bestfilepath.startswith(pathtop):
+ bestfile = bestfilepath[len(pathtop) + 1:]
+ print('Subcircuit defined in (from ' + pathtop + '/): "' + bestfile + '"')
+
+ if debug:
+ print('')
+ print('Top level include: ')
+
+ if incfile:
+ return [incfile]
+ else:
+ return [bestfilepath]
+
+ elif doall:
+ allincludes = []
+ for cellname in subcktdict:
+
+ # Diagnostic: Given a cell name on the command line (with -cell=<name>),
+ # Run check_device() on the cell and report.
+ try:
+ subfiles = subcktdict[cellname]
+ except:
+ print('No cell "' + cellname + '" was found in the PDK files.')
+ continue
+
+ incfile = check_device(subfiles, includedict, modfilesdict, feol, beol, notop, debug)
+ print('Cell = "' + cellname + '"')
+ bestfilepath = preferred_order(subfiles, feol)[0]
+ if bestfilepath.startswith(pathtop):
+ bestfile = bestfilepath[len(pathtop) + 1:]
+ print(' Subcircuit: "' + os.path.split(bestfile)[1] + '"')
+ print(' Include: ', end='')
+ if incfile:
+ if incfile not in allincludes:
+ allincludes.append(incfile)
+ print('"' + incfile + '"')
+ else:
+ if bestfilepath not in allincludes:
+ allincludes.append(bestfilepath)
+ print('"' + bestfilepath + '"')
+
+ print('')
+ print('Summary: All files to include:\n')
+ return allincludes
+
+ else:
+ # No source file given, so just dump the lists of subcircuits, models,
+ # and files into four different output files.
+
+ nsubs = 0
+ with open('sublist.txt', 'w') as ofile:
+ for key in subcktdict:
+ nsubs += 1
+ value = subcktdict[key]
+ print(key + ': ' + ', '.join(value), file=ofile)
+
+ nfiles = 0
+ with open('filelist.txt', 'w') as ofile:
+ for key in filesdict:
+ nfiles += 1
+ value = filesdict[key]
+ print(key + ': ' + ', '.join(value), file=ofile)
+
+ with open('inclist.txt', 'w') as ofile:
+ for key in includedict:
+ value = includedict[key]
+ print(key + '(' + value[0] + '): ' + ', '.join(value[1:]), file=ofile)
+
+ with open('modfilelist.txt', 'w') as ofile:
+ for key in modfilesdict:
+ value = modfilesdict[key]
+ print(key + ': ' + ', '.join(value), file=ofile)
+
+ print('Found ' + str(nsubs) + ' subcircuit definitions in ' + str(nfiles) + ' files.')
+ return []
+
+#-----------------------------------------------------------
+# Command-line entry point
+#-----------------------------------------------------------
+
+if __name__ == "__main__":
+
+ optionlist = []
+ arguments = []
+
+ for option in sys.argv[1:]:
+ if option.find('-', 0) == 0:
+ optionlist.append(option)
+ else:
+ arguments.append(option)
+
+ # Defaults: Set up for the most recent PDK version.
+ version = 'v0.20.1'
+ pathtop = '../../libraries/sky130_fd_pr/' + version
+
+ # Default FEOL corner is "tt", and default BEOL corner is "tt"
+ feol = 'tt'
+ beol = 'tt'
+ cellname = None
+ debug = False
+ doall = False
+ notop = False
+
+ # Override defaults from options
+
+ for option in optionlist:
+ if option.startswith('-version'):
+ try:
+ version = option.split('=')[1]
+ except:
+ print('Option usage: -version=<versionstring>')
+ sys.exit(1)
+ elif option.startswith('-corner') or option.startswith('-feol'):
+ try:
+ feol = option.split('=')[1]
+ except:
+ print('Option usage: -feol=<corner_name>')
+ sys.exit(1)
+ elif option.startswith('-beol'):
+ try:
+ beol = option.split('=')[1]
+ except:
+ print('Option usage: -beol=<corner_name>')
+ sys.exit(1)
+ elif option.startswith('-cell'):
+ try:
+ cellname = option.split('=')[1]
+ except:
+ print('Option usage: -cell=<cell_name>')
+ sys.exit(1)
+ elif option == '-notop':
+ notop = True
+ elif option == '-all':
+ doall = True
+ elif option == '-debug':
+ debug = True
+
+ # Parse "-pdkpath" after the others because it is dependent on any option
+ # "-version" passed on the command line.
+
+ for option in optionlist:
+ if option.startswith('-pdkpath'):
+ try:
+ pathroot = option.split('=')[1]
+ except:
+ print('Option usage: -pdkpath=<pathname>')
+ sys.exit(1)
+ if not os.path.isdir(pathroot):
+ print('Cannot find PDK directory ' + pathroot)
+ sys.exit(1)
+ pathtop = pathroot + '/libraries/sky130_fd_pr/' + version
+ if not os.path.isdir(pathtop):
+ print('Cannot find primitive device directory ' + pathtop)
+ sys.exit(1)
+
+ # To be done: Make this a useful routine that can insert one or more
+ # .include statements into a SPICE netlist. Should take any number of
+ # files on the arguments line and modify the files in place.
+
+ if len(arguments) > 0:
+ sourcefile = arguments[0]
+ if not os.path.isfile(sourcefile):
+ print('Cannot read SPICE source file ' + sourcefile)
+ sys.exit(1)
+ else:
+ sourcefile = None
+
+ if not os.path.isdir(pathtop):
+ print('Cannot find PDK path top level directory ' + pathtop)
+ sys.exit(1)
+ elif debug:
+ print('\nFinding everything in ' + pathtop + '.')
+
+ incfiles = do_find_all_devices(pathtop, sourcefile, cellname, feol, beol, doall, notop, debug)
+ for incfile in incfiles:
+ if incfile.endswith('.lib.spice'):
+ print('.lib ' + incfile + ' ' + feol)
+ else:
+ print('.include "' + incfile + '"')
+
+ sys.exit(0)
diff --git a/common/spectre_to_spice.py b/common/spectre_to_spice.py
index a89cdd4..fdbfe31 100755
--- a/common/spectre_to_spice.py
+++ b/common/spectre_to_spice.py
@@ -35,6 +35,7 @@
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 = []
@@ -68,6 +69,13 @@
return '', ispassed
while rest != '':
+ if iscall:
+ # It is hard to believe that this is legal even in spectre.
+ # Parameter expression given with no braces or quotes around
+ # the expression. Fix the expression by removing the spaces
+ # around '*'.
+ rest = re.sub('[ \t]*\*[ \t]*', '*', rest)
+
pmatch = parm4rex.match(rest)
if pmatch:
if ispassed:
@@ -75,6 +83,13 @@
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)
@@ -136,6 +151,28 @@
return ' '.join(fmtline), 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
+
+# Run the spectre-to-ngspice conversion
+
def convert_file(in_file, out_file):
# Regexp patterns
@@ -149,16 +186,22 @@
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]+(.*)')
+ 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]*([crdlmqx])([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \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)
+
with open(in_file, 'r') as ifile:
try:
@@ -176,9 +219,11 @@
spicelines = []
calllines = []
modellines = []
+ paramnames = []
savematch = None
blockskip = 0
subname = ''
+ subnames = []
modname = ''
modtype = ''
@@ -312,7 +357,7 @@
# 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'):
+ if isspectre and (modtype == 'resistor'):
modtype = 'r'
modellines.append('.model ' + modname + ' ' + modtype + ' ' + fmtline)
if fmtline != '':
@@ -345,6 +390,7 @@
insub = True
ispassed = True
subname = imatch.group(1)
+ subnames.append(subname)
if isspectre:
devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
else:
@@ -388,7 +434,7 @@
else:
if endname != subname and endname != '':
print('Error: "ends" name does not match "subckt" name!')
- print('"ends" name = ' + ematch.group(1).strip())
+ print('"ends" name = ' + endname)
print('"subckt" name = ' + subname)
if len(calllines) > 0:
line = calllines[0]
@@ -410,11 +456,36 @@
line = 'D' + line
spicelines.append(line)
- # Will need more handling here for other component types. . .
+ for line in calllines[1:]:
+ spicelines.append(line)
+ calllines = []
- 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('')
@@ -428,6 +499,7 @@
insub = False
inmodel = False
subname = ''
+ paramnames = []
continue
# Check for close of model
@@ -460,6 +532,10 @@
continue
cmatch = cdlrex.match(line)
+ if not cmatch:
+ if not isspectre:
+ cmatch = stddevrex.match(line)
+
if cmatch:
ispassed = False
devtype = cmatch.group(1)
@@ -478,6 +554,39 @@
# necessary to find the model and discover that the
# model is a resistor and not a subcircuit.
devtype = 'r'
+ elif devtype.lower() == 'n' or devtype.lower() == 'p':
+ # May be specific to SkyWater models, or is it a spectreism?
+ # NOTE: There is a check, below, to revisit this assignment
+ # and ensure that it matches the model type.
+ 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 != '':
@@ -488,6 +597,11 @@
spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + cmatch.group(5))
continue
+ # 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)
diff --git a/common/split_one_spice.py b/common/split_one_spice.py
index c454c8d..86a448a 100755
--- a/common/split_one_spice.py
+++ b/common/split_one_spice.py
@@ -294,10 +294,13 @@
# All comment lines that are surrounded by lines marked -3 should
# also be marked -3. This keeps comments that are completely inside
# blocks that are only in the common file out of the individual files.
+ # ignore "* statistics" and "* mismatch" lines.
lineno = 0
for line in inplines[1:]:
lineno += 1
+ if line.startswith('*') and ('statistics' in line or 'mismatch' in line):
+ continue
if linedest[lineno] == -1 and linedest[lineno - 1] == -3:
testline = lineno + 1
while linedest[testline] == -1:
diff --git a/sky130/magic/sky130.tech b/sky130/magic/sky130.tech
index 7e31c4c..22125ce 100644
--- a/sky130/magic/sky130.tech
+++ b/sky130/magic/sky130.tech
@@ -1638,16 +1638,16 @@
or fomfill_pass2
or fomfill_coarse
or fomfill_fine
- calma 23 0
templayer polyfill polyfill_pass1
or polyfill_coarse
or polyfill_medium
or polyfill_fine
- calma 28 0
layer FOMMASK fomfill
+ calma 23 0
layer POLYMASK polyfill
+ calma 28 0
#---------------------------------------------------
# MET1 fill