Initial commit of public repository open_pdks.
diff --git a/common/fixspice.py b/common/fixspice.py
new file mode 100755
index 0000000..a12ae42
--- /dev/null
+++ b/common/fixspice.py
@@ -0,0 +1,348 @@
+#!/bin/env python3
+#
+# fixspice ---
+#
+# This script fixes problems in SPICE models to make them ngspice-compatible.
+# The methods searched and corrected in this file correspond to ngspice
+# version 30.
+#
+# This script is a filter to be run by setting the name of this script as
+# the value to "filter=" for the model install in the PDK Makefile in
+# open_pdks.
+#
+# This script converted from the bash script by Risto Bell, with improvements.
+#
+# This script is minimally invasive to the original SPICE file, making changes
+# while preserving comments and line continuations. In order to properly parse
+# the file, comments and line continuations are recorded and removed from the
+# file contents, then inserted again before the modified file is written.
+
+import re
+import os
+import sys
+import textwrap
+
+def filter(inname, outname, debug=False):
+ notparsed = []
+
+ # Read input. Note that splitlines() performs the additional fix of
+ # correcting carriage-return linefeed (CRLF) line endings.
+ try:
+ with open(inname, 'r') as inFile:
+ spitext = inFile.read()
+ except:
+ print('fixspice.py: failed to open ' + inname + ' for reading.', file=sys.stderr)
+ return 1
+ else:
+ if debug:
+ print('Fixing ngspice incompatibilities in file ' + inname + '.')
+
+ # Due to the complexity of comment lines embedded within continuation lines,
+ # the result needs to be processed line by line. Blank lines and comment
+ # lines are removed from the text, replaced with tab characters, and collected
+ # in a separate array. Then the continuation lines are unfolded, and each
+ # line processed. Then it is all put back together at the end.
+
+ # First replace all tabs with spaces so we can use tabs as markers.
+ spitext = spitext.replace('\t', ' ')
+
+ # Now do an initial line split
+ spilines = spitext.splitlines()
+
+ # Search lines for comments and blank lines and replace them with tabs
+ # Replace continuation lines with tabs and preserve the position.
+ spitext = ''
+ for line in spilines:
+ if len(line) == 0:
+ notparsed.append('\n')
+ spitext += '\t '
+ elif line[0] == '*':
+ notparsed.append('\n' + line)
+ spitext += '\t '
+ elif line[0] == '+':
+ notparsed.append('\n+')
+ spitext += '\t ' + line[1:]
+ else:
+ spitext += '\n' + line
+
+ # Now split back into an array of lines
+ spilines = spitext.splitlines()
+
+ # Process input with regexp
+
+ fixedlines = []
+ modified = False
+
+ # Regular expression to find 'agauss(a,b,c)' lines and record a, b, and c
+ grex = re.compile('[^{]agauss\(([^,]*),([^,]*),([^)]*)\)', re.IGNORECASE)
+
+ # Regular expression to determine if the line is a .PARAM card
+ paramrex = re.compile('^\.param', re.IGNORECASE)
+ # Regular expression to determine if the line is a .MODEL card
+ modelrex = re.compile('^\.model', re.IGNORECASE)
+ # Regular expression to detect a .SUBCKT card
+ subcktrex = re.compile('^\.subckt', re.IGNORECASE)
+
+ for line in spilines:
+ devtype = line[0].upper() if len(line) > 0 else 0
+
+ # NOTE: All filter functions below take variable fixedline, alter it, then
+ # set fixedline to the altered text for the next filter function.
+
+ fixedline = line
+
+ # Fix: Wrap "agauss(...)" in brackets and remove single quotes around expressions
+ # Example:
+ # before: + SD_DN_CJ=agauss(7.900e-04,'1.580e-05*__LOT__',1) dn_cj=SD_DN_CJ"
+ # after: + SD_DN_CJ={agauss(7.900e-04,1.580e-05*__LOT__,1)} dn_cj=SD_DN_CJ"
+
+ # for gmatch in grex.finditer(fixedline):
+ while True:
+ gmatch = grex.search(fixedline)
+ if gmatch:
+ fixpart1 = gmatch.group(1).strip("'")
+ fixpart2 = gmatch.group(2).strip("'")
+ fixpart3 = gmatch.group(3).strip("'")
+ fixedline = fixedline[0:gmatch.span(0)[0] + 1] + '{agauss(' + fixpart1 + ',' + fixpart2 + ',' + fixpart3 + ')}' + fixedline[gmatch.span(0)[1]:]
+ if debug:
+ print('Fixed agauss() call.')
+ else:
+ break
+
+ # Fix: Check for "dtemp=dtemp" and remove unless in a .param line
+ pmatch = paramrex.search(fixedline)
+ if not pmatch:
+ altered = re.sub(' dtemp=dtemp', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed dtemp=dtemp from instance call')
+
+ # Fixes related to .MODEL cards:
+
+ mmatch = modelrex.search(fixedline)
+ if mmatch:
+
+ modeltype = fixedline.split()[2].lower()
+
+ if modeltype == 'nmos' or modeltype == 'pmos':
+
+ # Fixes related specifically to MOS models:
+
+ # Fix: Look for hspver=98.2 in FET model
+ altered = re.sub(' hspver[ ]*=[ ]*98\.2', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed hspver=98.2 from ' + modeltype + ' model')
+
+ # Fix: Change level 53 FETs to level 49
+ altered = re.sub(' (level[ ]*=[ ]*)53', ' \g<1>49', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Changed level 53 ' + modeltype + ' to level 49')
+
+ # Fix: Look for version=4.3 or 4.5 FETs, change to 4.8.0 per recommendations
+ altered = re.sub(' (version[ ]*=[ ]*)4\.[35]', ' \g<1>4.8.0',
+ fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Changed version 4.3/4.5 ' + modeltype + ' to version 4.8.0')
+
+ # Fix: Look for mulu0= (NOTE: Might be supported for bsim4?)
+ altered = re.sub('mulu0[ ]*=[ ]*[0-9.e+-]*', '', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed mulu0= from ' + modeltype + ' model')
+
+ # Fix: Look for apwarn=
+ altered = re.sub(' apwarn[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed apwarn= from ' + modeltype + ' model')
+
+ # Fix: Look for lmlt=
+ altered = re.sub(' lmlt[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed lmlt= from ' + modeltype + ' model')
+
+ # Fix: Look for nf=
+ altered = re.sub(' nf[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed nf= from ' + modeltype + ' model')
+
+ # Fix: Look for sa/b/c/d/=
+ altered = re.sub(' s[abcd][ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed s[abcd]= from ' + modeltype + ' model')
+
+ # Fix: Look for binflag= in MOS .MODEL
+ altered = re.sub(' binflag[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed binflag= from ' + modeltype + ' model')
+
+ # Fix: Look for wref, lref= in MOS .MODEL (note: could be found in other models?)
+ altered = re.sub(' [wl]ref[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed lref= from MOS .MODEL')
+
+ # TREF is a known issue for (apparently?) all device types
+ # Fix: Look for tref= in .MODEL
+ altered = re.sub(' tref[ ]*=[ ]*[0-9.e+-]*', ' ', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed tref= from ' + modeltype + ' model')
+
+ # Fix: Look for double-dot model binning and replace with single dot
+ altered = re.sub('\.\.([0-9]+)', '.\g<1>', fixedline, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Collapsed double-dot model binning.')
+
+ # Various deleted parameters above may appear in instances, so those must be
+ # caught as well. Need to catch expressions and variables in addition to the
+ # usual numeric assignments.
+
+ if devtype == 'M':
+ altered = re.sub(' nf=[^ \'\t]+', ' ', fixedline, flags=re.IGNORECASE)
+ altered = re.sub(' nf=\'[^\'\t]+\'', ' ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed nf= from MOSFET device instance')
+
+ altered = re.sub(' mulu0=[^ \'\t]+', ' ', fixedline, flags=re.IGNORECASE)
+ altered = re.sub(' mulu0=\'[^\'\t]+\'', ' ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed mulu0= from MOSFET device instance')
+
+ altered = re.sub(' s[abcd]=[^ \'\t]+', ' ', fixedline, flags=re.IGNORECASE)
+ altered = re.sub(' s[abcd]=\'[^\'\t]+\'', ' ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed s[abcd]= from MOSFET device instance')
+
+ # Remove tref= from all device type instances
+ altered = re.sub(' tref=[^ \'\t]+', ' ', fixedline, flags=re.IGNORECASE)
+ altered = re.sub(' tref=\'[^\'\t]+\'', ' ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Removed tref= from device instance')
+
+ # Check for use of ".subckt ... <name>=l" (or <name>=w) with no antecedent
+ # for 'w' or 'l'. It is the responsibility of the technology file for extraction
+ # to produce the correct name to pass to the subcircuit for length or width.
+
+ smatch = subcktrex.match(fixedline)
+ if smatch:
+ altered = fixedline
+ if fixedline.lower().endswith('=l'):
+ if ' l=' not in fixedline.lower():
+ altered=re.sub( '=l$', '=0', fixedline, flags=re.IGNORECASE)
+ elif '=l ' in fixedline.lower():
+ if ' l=' not in fixedline.lower():
+ altered=re.sub( '=l ', '=0 ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Replaced use of "l" with no definition in .subckt line')
+
+ altered = fixedline
+ if fixedline.lower().endswith('=w'):
+ if ' w=' not in fixedline.lower():
+ altered=re.sub( '=w$', '=0', fixedline, flags=re.IGNORECASE)
+ elif '=w ' in fixedline.lower():
+ if ' w=' not in fixedline.lower():
+ altered=re.sub( '=w ', '=0 ', altered, flags=re.IGNORECASE)
+ if altered != fixedline:
+ fixedline = altered
+ if debug:
+ print('Replaced use of "w" with no definition in .subckt line')
+
+ fixedlines.append(fixedline)
+ if fixedline != line:
+ modified = True
+
+ # Reinsert embedded comments and continuation lines
+ if debug:
+ print('Reconstructing output')
+ olines = []
+ for line in fixedlines:
+ while '\t ' in line:
+ line = line.replace('\t ', notparsed.pop(0), 1)
+ olines.append(line)
+
+ fixedlines = '\n'.join(olines).strip()
+ olines = fixedlines.splitlines()
+
+ # Write output
+ if debug:
+ print('Writing output')
+ if outname == None:
+ for line in olines:
+ print(line)
+ else:
+ # If the output is a symbolic link but no modifications have been made,
+ # then leave it alone. If it was modified, then remove the symbolic
+ # link before writing.
+ if os.path.islink(outname):
+ if not modified:
+ return 0
+ else:
+ os.unlink(outname)
+ try:
+ with open(outname, 'w') as outFile:
+ for line in olines:
+ print(line, file=outFile)
+ except:
+ print('fixspice.py: failed to open ' + outname + ' for writing.', file=sys.stderr)
+ return 1
+
+
+if __name__ == '__main__':
+
+ # This script expects to get one or two arguments. One argument is
+ # mandatory and is the input file. The other argument is optional and
+ # is the output file. The output file and input file may be the same
+ # name, in which case the original input is overwritten.
+
+ options = []
+ arguments = []
+ for item in sys.argv[1:]:
+ if item.find('-', 0) == 0:
+ options.append(item[1:])
+ else:
+ arguments.append(item)
+
+ if len(arguments) > 0:
+ infilename = arguments[0]
+
+ if len(arguments) > 1:
+ outfilename = arguments[1]
+ else:
+ outfilename = None
+
+ debug = True if 'debug' in options else False
+
+ result = filter(infilename, outfilename, debug)
+ sys.exit(result)