blob: a25ac34a5cfd9f591b21a9025aa236afa4b5ab38 [file] [log] [blame]
Tim Edwards51f81422020-07-26 12:49:48 -04001#!/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
12import sys
13import os
14import re
15import glob
16import fnmatch
17
18#----------------------------------------------------------------------------
19
20def 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 Edwards9be4ac22020-07-26 12:59:30 -040039def create_spice_library(destlibdir, destlib, spiext, do_compile_only=False, do_stub=False, excludelist=[]):
Tim Edwards51f81422020-07-26 12:49:48 -040040
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
122def 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
142if __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#----------------------------------------------------------------------------