| #!/usr/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) |