Tim Edwards | 51f8142 | 2020-07-26 12:49:48 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # create_spice_library.py |
| 4 | # |
| 5 | #---------------------------------------------------------------------------- |
| 6 | # Given a destination directory holding individual SPICE netlists of a number |
| 7 | # of cells, create a single SPICE library file named <alllibname> and place |
| 8 | # it in the same directory. This is done for the option "compile" if specified |
| 9 | # for the "-spice" install. |
| 10 | #---------------------------------------------------------------------------- |
| 11 | |
| 12 | import sys |
| 13 | import os |
| 14 | import re |
| 15 | import glob |
| 16 | import fnmatch |
| 17 | |
| 18 | #---------------------------------------------------------------------------- |
| 19 | |
| 20 | def usage(): |
| 21 | print('') |
| 22 | print('Usage:') |
| 23 | print(' create_spice_library <destlibdir> <destlib> <spiext>') |
| 24 | print(' [-compile-only] [-stub] [-excludelist="file1,file2,..."]') |
| 25 | print('') |
| 26 | print('Create a single SPICE or CDL library from a set of individual files.') |
| 27 | print('') |
| 28 | print('where:') |
| 29 | print(' <destlibdir> is the directory containing the individual files') |
| 30 | print(' <destlib> is the root name of the library file') |
| 31 | print(' <spiext> is the extension used (with ".") by the SPICE or CDL files') |
| 32 | print(' -compile-only remove the indidual files if specified') |
| 33 | print(' -stub generate only .subckt ... .ends for each cell') |
| 34 | print(' -excludelist= is a comma-separated list of files to ignore') |
| 35 | print('') |
| 36 | |
| 37 | #---------------------------------------------------------------------------- |
| 38 | |
Tim Edwards | 9be4ac2 | 2020-07-26 12:59:30 -0400 | [diff] [blame] | 39 | def create_spice_library(destlibdir, destlib, spiext, do_compile_only=False, do_stub=False, excludelist=[]): |
Tim Edwards | 51f8142 | 2020-07-26 12:49:48 -0400 | [diff] [blame] | 40 | |
| 41 | fformat = 'CDL' if spiext == '.cdl' else 'SPICE' |
| 42 | |
| 43 | allstubname = destlibdir + '/stub' + spiext |
| 44 | alllibname = destlibdir + '/' + destlib + spiext |
| 45 | if do_stub: |
| 46 | outputname = allstubname |
| 47 | else: |
| 48 | outputname = alllibname |
| 49 | |
| 50 | print('Diagnostic: Creating consolidated ' + fformat + ' library ' + outputname) |
| 51 | |
| 52 | if os.path.isfile(outputname): |
| 53 | os.remove(outputname) |
| 54 | |
| 55 | if fformat == 'CDL': |
| 56 | slist = glob.glob(destlibdir + '/*.cdl') |
| 57 | else: |
| 58 | # Sadly, there is no consensus on what a SPICE file extension should be. |
| 59 | slist = glob.glob(destlibdir + '/*.spc') |
| 60 | slist.extend(glob.glob(destlibdir + '/*.spice')) |
| 61 | slist.extend(glob.glob(destlibdir + '/*.spi')) |
| 62 | slist.extend(glob.glob(destlibdir + '/*.ckt')) |
| 63 | slist.extend(glob.glob(destlibdir + '/*.cir')) |
| 64 | slist.extend(glob.glob(destlibdir + '/*' + spiext)) |
| 65 | |
| 66 | if alllibname in slist: |
| 67 | slist.remove(alllibname) |
| 68 | |
| 69 | if allstubname in slist: |
| 70 | slist.remove(allstubname) |
| 71 | |
| 72 | # Create exclude list with glob-style matching using fnmatch |
| 73 | if len(slist) > 0: |
| 74 | slistnames = list(os.path.split(item)[1] for item in slist) |
| 75 | notslist = [] |
| 76 | for exclude in excludelist: |
| 77 | notslist.extend(fnmatch.filter(slistnames, exclude)) |
| 78 | |
| 79 | # Apply exclude list |
| 80 | if len(notslist) > 0: |
| 81 | for file in slist[:]: |
| 82 | if os.path.split(file)[1] in notslist: |
| 83 | slist.remove(file) |
| 84 | |
| 85 | if len(slist) > 1: |
| 86 | with open(outputname, 'w') as ofile: |
| 87 | allsubckts = [] |
| 88 | for sfile in slist: |
| 89 | with open(sfile, 'r') as ifile: |
| 90 | # print('Adding ' + sfile + ' to library.') |
| 91 | stext = ifile.read() |
| 92 | subckts = re.findall(r'\.subckt[ \t]+([^ \t\n]+)', stext, flags=re.IGNORECASE) |
| 93 | sseen = list(item for item in subckts if item in allsubckts) |
| 94 | allsubckts.extend(list(item for item in subckts if item not in allsubckts)) |
| 95 | sfilter = remove_redundant_subckts(stext, allsubckts, sseen) |
| 96 | print(sfilter, file=ofile) |
| 97 | print('\n******* EOF\n', file=ofile) |
| 98 | |
| 99 | if do_compile_only == True: |
| 100 | print('Compile-only: Removing individual SPICE files') |
| 101 | for sfile in slist: |
| 102 | if os.path.isfile(sfile): |
| 103 | os.remove(sfile) |
| 104 | elif os.path.islink(sfile): |
| 105 | os.unlink(sfile) |
| 106 | else: |
| 107 | print('Only one file (' + str(slist) + '); ignoring "compile" option.') |
| 108 | |
| 109 | #---------------------------------------------------------------------------- |
| 110 | # Remove redundant subcircuit entries from a SPICE or CDL netlist file. "sseen" |
| 111 | # is a list of subcircuit names gleaned from all previously read files using |
| 112 | # re.findall(). "slist" is a list of subcircuits including those in "ntext". |
| 113 | # If a subcircuit is defined outside of "ntext", then remove all occurrences in |
| 114 | # "ntext". Otherwise, if a subcircuit is defined more than once in "ntext", |
| 115 | # remove all but one copy. The reason for doing this is that some netlists will |
| 116 | # include primitive device definitions used by all the standard cell subcircuits. |
| 117 | # |
| 118 | # It may be necessary to remove redundant .include statements and redundant .model |
| 119 | # and/or .option statements as well. |
| 120 | #---------------------------------------------------------------------------- |
| 121 | |
| 122 | def remove_redundant_subckts(ntext, slist, sseen): |
| 123 | updated = ntext |
| 124 | for subckt in slist: |
| 125 | if subckt in sseen: |
| 126 | # Remove all occurrences of subckt |
| 127 | updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', updated, flags=re.IGNORECASE | re.DOTALL) |
| 128 | |
| 129 | else: |
| 130 | # Determine the number of times the subcircuit appears in the text |
| 131 | n = len(re.findall(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', updated, flags=re.IGNORECASE | re.DOTALL)) |
| 132 | # Optimization: Just keep original text if n < 2 |
| 133 | if n < 2: |
| 134 | continue |
| 135 | |
| 136 | # Remove all but one |
| 137 | updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', n - 1, updated, flags=re.IGNORECASE | re.DOTALL) |
| 138 | return updated |
| 139 | |
| 140 | #---------------------------------------------------------------------------- |
| 141 | |
| 142 | if __name__ == '__main__': |
| 143 | |
| 144 | if len(sys.argv) == 1: |
| 145 | usage() |
| 146 | sys.exit(0) |
| 147 | |
| 148 | argumentlist = [] |
| 149 | |
| 150 | # Defaults |
| 151 | do_compile_only = False |
| 152 | do_stub = False |
| 153 | excludelist = [] |
| 154 | |
| 155 | # Break arguments into groups where the first word begins with "-". |
| 156 | # All following words not beginning with "-" are appended to the |
| 157 | # same list (optionlist). Then each optionlist is processed. |
| 158 | # Note that the first entry in optionlist has the '-' removed. |
| 159 | |
| 160 | for option in sys.argv[1:]: |
| 161 | if option.find('-', 0) == 0: |
| 162 | keyval = option[1:].split('=') |
| 163 | if keyval[0] == 'compile-only': |
| 164 | if len(keyval) > 0: |
| 165 | if keyval[1].tolower() == 'true' or keyval[1].tolower() == 'yes' or keyval[1] == '1': |
| 166 | do_compile_only = True |
| 167 | else: |
| 168 | do_compile_only = True |
| 169 | elif keyval[1] == 'exclude' or key == 'excludelist': |
| 170 | if len(keyval) > 0: |
| 171 | excludelist = keyval[1].trim('"').split(',') |
| 172 | else: |
| 173 | print("No items in exclude list (ignoring).") |
| 174 | elif keyval[1] == 'stub': |
| 175 | if len(keyval) > 0: |
| 176 | if keyval[1].tolower() == 'true' or keyval[1].tolower() == 'yes' or keyval[1] == '1': |
| 177 | do_stub = True |
| 178 | else: |
| 179 | do_stub = True |
| 180 | else: |
| 181 | print("Unknown option '" + keyval[0] + "' (ignoring).") |
| 182 | else: |
| 183 | argumentlist.append(option) |
| 184 | |
| 185 | if len(argumentlist) < 3: |
| 186 | print("Not enough arguments given to create_spice_library.py.") |
| 187 | usage() |
| 188 | sys.exit(1) |
| 189 | |
| 190 | destlibdir = argumentlist[0] |
| 191 | destlib = argumentlist[1] |
| 192 | startup_script = argumentlist[2] |
| 193 | |
| 194 | print('') |
| 195 | if spiext == '.cdl': |
| 196 | print('Create CDL library from files:') |
| 197 | else: |
| 198 | print('Create SPICE library from files:') |
| 199 | print('') |
| 200 | print('Path to files: ' + destlibdir) |
| 201 | print('Name of compiled library: ' + destlib + spiext) |
| 202 | print('Remove individual files: ' + 'Yes' if do_compile_only else 'No') |
| 203 | if len(excludelist) > 0: |
| 204 | print('List of files to exclude: ') |
| 205 | for file in excludelist: |
| 206 | print(file) |
| 207 | print('') |
| 208 | |
| 209 | create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist) |
| 210 | print('Done.') |
| 211 | sys.exit(0) |
| 212 | |
| 213 | #---------------------------------------------------------------------------- |