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