forked from RTimothyEdwards/open_pdks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfixspice.py
executable file
·348 lines (293 loc) · 13.9 KB
/
fixspice.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#!/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)