blob: 4243a1a3788117b60199455c5f14c3de42392368 [file] [log] [blame]
Tim Edwards9d3debb2020-10-20 20:52:18 -04001#!/usr/bin/env python3
Tim Edwards031ccbc2020-07-22 22:30:14 -04002# Script to read all files in a directory of SPECTRE-compatible device model
Tim Edwardsa4219782020-08-09 21:13:58 -04003# files, and convert them to a form that is compatible with ngspice.
Tim Edwards031ccbc2020-07-22 22:30:14 -04004
5import os
6import sys
7import re
8import glob
9
10def usage():
Tim Edwardseba70cf2020-08-01 21:08:46 -040011 print('spectre_to_spice.py <path_to_spectre> <path_to_spice>')
Tim Edwards031ccbc2020-07-22 22:30:14 -040012
13# Check if a parameter value is a valid number (real, float, integer)
14# or is some kind of expression.
15
16def is_number(s):
17 try:
18 float(s)
19 return True
20 except ValueError:
21 return False
22
23# Parse a parameter line. If "inparam" is true, then this is a continuation
24# line of an existing parameter statement. If "insub" is not true, then the
25# paramters are global parameters (not part of a subcircuit).
26#
27# If inside a subcircuit, remove the keyword "parameters". If outside,
28# change it to ".param"
29
Tim Edwards57220f12020-08-05 21:16:03 -040030def parse_param_line(line, inparam, insub, iscall, ispassed):
Tim Edwards031ccbc2020-07-22 22:30:14 -040031
32 # Regexp patterns
Tim Edwards7f052a62020-07-28 11:07:26 -040033 parm1rex = re.compile('[ \t]*parameters[ \t]*(.*)')
34 parm2rex = re.compile('[ \t]*params:[ \t]*(.*)')
Tim Edwards57220f12020-08-05 21:16:03 -040035 parm3rex = re.compile('[ \t]*\+[ \t]*(.*)')
Tim Edwards7f052a62020-07-28 11:07:26 -040036 parm4rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
37 parm5rex = re.compile('[ \t]*([^= \t]+)[ \t]*(.*)')
Tim Edwardsd8130452020-08-27 22:38:36 -040038 parm6rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([\'{][^\'}]+[\'}])[ \t]*(.*)')
Tim Edwards57220f12020-08-05 21:16:03 -040039 rtok = re.compile('([^ \t\n]+)[ \t]*(.*)')
Tim Edwards031ccbc2020-07-22 22:30:14 -040040
41 fmtline = []
Tim Edwardsa4219782020-08-09 21:13:58 -040042
Tim Edwards57220f12020-08-05 21:16:03 -040043 if iscall:
44 rest = line
45 elif inparam:
Tim Edwards7f052a62020-07-28 11:07:26 -040046 pmatch = parm3rex.match(line)
Tim Edwards031ccbc2020-07-22 22:30:14 -040047 if pmatch:
48 fmtline.append('+')
49 rest = pmatch.group(1)
50 else:
Tim Edwards57220f12020-08-05 21:16:03 -040051 return '', ispassed
Tim Edwards031ccbc2020-07-22 22:30:14 -040052 else:
53 pmatch = parm1rex.match(line)
54 if pmatch:
55 if insub:
56 fmtline.append('+')
57 else:
58 fmtline.append('.param')
59 rest = pmatch.group(1)
60 else:
Tim Edwards7f052a62020-07-28 11:07:26 -040061 pmatch = parm2rex.match(line)
62 if pmatch:
63 if insub:
64 fmtline.append('+')
65 else:
Tim Edwards57220f12020-08-05 21:16:03 -040066 return '', ispassed
Tim Edwards7f052a62020-07-28 11:07:26 -040067 rest = pmatch.group(1)
Tim Edwards7f052a62020-07-28 11:07:26 -040068 else:
Tim Edwards57220f12020-08-05 21:16:03 -040069 return '', ispassed
Tim Edwards031ccbc2020-07-22 22:30:14 -040070
71 while rest != '':
Tim Edwards475b5272020-08-25 14:05:50 -040072 if iscall:
73 # It is hard to believe that this is legal even in spectre.
74 # Parameter expression given with no braces or quotes around
75 # the expression. Fix the expression by removing the spaces
76 # around '*'.
77 rest = re.sub('[ \t]*\*[ \t]*', '*', rest)
78
Tim Edwards7f052a62020-07-28 11:07:26 -040079 pmatch = parm4rex.match(rest)
Tim Edwards031ccbc2020-07-22 22:30:14 -040080 if pmatch:
Tim Edwards57220f12020-08-05 21:16:03 -040081 if ispassed:
82 # End of passed parameters. Break line and generate ".param"
83 ispassed = False
84 fmtline.append('\n.param ')
85
Tim Edwardsd8130452020-08-27 22:38:36 -040086 # If expression is already in single quotes or braces, then catch
87 # everything inside the delimiters, including any spaces.
88 if pmatch.group(2).startswith("'") or pmatch.group(2).startswith('{'):
89 pmatchx = parm6rex.match(rest)
90 if pmatchx:
91 pmatch = pmatchx
92
Tim Edwards031ccbc2020-07-22 22:30:14 -040093 fmtline.append(pmatch.group(1))
94 fmtline.append('=')
95 value = pmatch.group(2)
96 rest = pmatch.group(3)
97
Tim Edwards57220f12020-08-05 21:16:03 -040098 # Watch for spaces in expressions (have they no rules??!)
99 # as indicated by something after a space not being an
100 # alphabetical character (parameter name) or '$' (comment)
101
102 needmore = False
103 while rest != '':
104 rmatch = rtok.match(rest)
105 if rmatch:
106 expch = rmatch.group(1)[0]
Tim Edwards862eeac2020-09-09 12:20:07 -0400107 if expch == '$':
108 break
109 elif expch.isalpha() and not needmore:
Tim Edwards57220f12020-08-05 21:16:03 -0400110 break
111 else:
112 needmore = False
113 value += rmatch.group(1)
114 rest = rmatch.group(2)
Tim Edwards862eeac2020-09-09 12:20:07 -0400115 if any((c in '+-*/({^~!') for c in rmatch.group(1)[-1]):
116 needmore = True
117 if rest != '' and any((c in '+-*/(){}^~!') for c in rest[0]):
Tim Edwards57220f12020-08-05 21:16:03 -0400118 needmore = True
119 else:
120 break
121
Tim Edwards031ccbc2020-07-22 22:30:14 -0400122 if is_number(value):
123 fmtline.append(value)
Tim Edwards37d35dd2020-08-19 21:41:14 -0400124 elif value.strip().startswith("'"):
125 fmtline.append(value)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400126 else:
Tim Edwards862eeac2020-09-09 12:20:07 -0400127 # It is not possible to know if a spectre expression continues
128 # on another line without some kind of look-ahead, but check
129 # if the parameter ends in an operator.
130 lastc = value.strip()[-1]
131 if any((c in '*+-/,(') for c in lastc):
132 fmtline.append('{' + value)
133 else:
134 fmtline.append('{' + value + '}')
Tim Edwards3d849022020-07-23 19:37:31 -0400135
Tim Edwards57220f12020-08-05 21:16:03 -0400136 # These parameter sub-expressions are related to monte carlo
137 # simulation and are incompatible with ngspice. So put them
138 # in an in-line comment. Avoid double-commenting things that
139 # were already in-line comments.
Tim Edwards3d849022020-07-23 19:37:31 -0400140
141 if rest != '':
Tim Edwards7f052a62020-07-28 11:07:26 -0400142 nmatch = parm4rex.match(rest)
Tim Edwards3d849022020-07-23 19:37:31 -0400143 if not nmatch:
Tim Edwards57220f12020-08-05 21:16:03 -0400144 if rest.lstrip().startswith('$ '):
145 fmtline.append(rest)
146 elif rest.strip() != '':
147 fmtline.append(' $ ' + rest.replace(' ', '').replace('\t', ''))
Tim Edwards3d849022020-07-23 19:37:31 -0400148 rest = ''
Tim Edwards57220f12020-08-05 21:16:03 -0400149 else:
Tim Edwards7f052a62020-07-28 11:07:26 -0400150 # Match to a CDL subckt parameter that does not have an '=' and so
Tim Edwards57220f12020-08-05 21:16:03 -0400151 # assumes that the parameter is always passed, and therefore must
Tim Edwardsa4219782020-08-09 21:13:58 -0400152 # be part of the .subckt line. A parameter without a value is not
Tim Edwards57220f12020-08-05 21:16:03 -0400153 # legal SPICE, so supply a default value of 1.
Tim Edwards862eeac2020-09-09 12:20:07 -0400154
Tim Edwards7f052a62020-07-28 11:07:26 -0400155 pmatch = parm5rex.match(rest)
156 if pmatch:
Tim Edwards862eeac2020-09-09 12:20:07 -0400157 # NOTE: Something that is not a parameters name should be
158 # extended from the previous line. Note that this parsing
159 # is not rigorous and is possible to break. . .
160 if any((c in '+-*/(){}^~!') for c in pmatch.group(1).strip()):
161 fmtline.append(rest)
162 if not any((c in '*+-/,(') for c in rest.strip()[-1]):
163 fmtline.append('}')
164 rest = ''
165 else:
166 fmtline.append(pmatch.group(1) + '=1')
167 ispassed = True
168 rest = pmatch.group(2)
Tim Edwards57220f12020-08-05 21:16:03 -0400169 else:
170 break
Tim Edwards031ccbc2020-07-22 22:30:14 -0400171
Tim Edwards862eeac2020-09-09 12:20:07 -0400172 finalline = ' '.join(fmtline)
173
174 # ngspice does not understand round(), so replace it with the equivalent
175 # floor() expression.
176
177 finalline = re.sub('round\(', 'floor(0.5+', finalline)
178
179 # use of "no" and "yes" as parameter values is not allowed in ngspice.
180
181 finalline = re.sub('sw_et[ \t]*=[ \t]*{no}', 'sw_et=0', finalline)
182 finalline = re.sub('sw_et[ \t]*=[ \t]*{yes}', 'sw_et=1', finalline)
183 finalline = re.sub('isnoisy[ \t]*=[ \t]*{no}', 'isnoisy=0', finalline)
184 finalline = re.sub('isnoisy[ \t]*=[ \t]*{yes}', 'isnoisy=1', finalline)
185
Tim Edwards88b2b7f2020-09-08 16:45:44 -0400186 # Use of "m" in parameters is forbidden. Specifically look for "{N*m}".
187 # e.g., replace "mult = {2*m}" with "mult = 2". Note that this usage
188 # is most likely an error in the source.
189
190 finalline = re.sub('\{([0-9]+)\*[mM]\}', r'\1', finalline)
191
Tim Edwards862eeac2020-09-09 12:20:07 -0400192 return finalline, ispassed
Tim Edwards031ccbc2020-07-22 22:30:14 -0400193
Tim Edwards475b5272020-08-25 14:05:50 -0400194def get_param_names(line):
195 # Find parameter names in a ".param" line and return a list of them.
196 # This is used to check if a bare word parameter name is passed to
197 # a capacitor or resistor device in the position of a value but
198 # without delimiters, so that it cannot be distinguished from a
199 # model name. There are only a few instances of this, so this
200 # routine is not rigorously checking all parameters, just entries
201 # on lines with ".param".
202 parmrex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
203 rest = line
204 paramnames = []
205 while rest != '':
206 pmatch = parmrex.match(rest)
207 if pmatch:
208 paramnames.append(pmatch.group(1))
209 rest = pmatch.group(3)
210 else:
211 break
212 return paramnames
213
214# Run the spectre-to-ngspice conversion
215
Tim Edwards031ccbc2020-07-22 22:30:14 -0400216def convert_file(in_file, out_file):
217
218 # Regexp patterns
219 statrex = re.compile('[ \t]*statistics[ \t]*\{(.*)')
220 simrex = re.compile('[ \t]*simulator[ \t]+([^= \t]+)[ \t]*=[ \t]*(.+)')
Tim Edwards57220f12020-08-05 21:16:03 -0400221 insubrex = re.compile('[ \t]*inline[ \t]+subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)')
222 cdlsubrex = re.compile('\.?subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400223 endsubrex = re.compile('[ \t]*ends[ \t]+(.+)')
Tim Edwards57220f12020-08-05 21:16:03 -0400224 endonlysubrex = re.compile('[ \t]*ends[ \t]*')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400225 modelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+\{(.*)')
Tim Edwards57220f12020-08-05 21:16:03 -0400226 cdlmodelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400227 binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)')
228 shincrex = re.compile('\.inc[ \t]+')
Tim Edwards475b5272020-08-25 14:05:50 -0400229 isexprrex = re.compile('[^0-9a-zA-Z_]')
230 paramrex = re.compile('\.param[ \t]+(.*)')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400231
Tim Edwards44918582020-08-09 16:17:55 -0400232 stdsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+(.*)')
Tim Edwardsd8130452020-08-27 22:38:36 -0400233 stdmodelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400234 stdendsubrex = re.compile('\.ends[ \t]+(.+)')
Tim Edwards57220f12020-08-05 21:16:03 -0400235 stdendonlysubrex = re.compile('\.ends[ \t]*')
Tim Edwards031ccbc2020-07-22 22:30:14 -0400236
237 # Devices (resistor, capacitor, subcircuit as resistor or capacitor)
238 caprex = re.compile('c([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*capacitor[ \t]*(.*)', re.IGNORECASE)
239 resrex = re.compile('r([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*resistor[ \t]*(.*)', re.IGNORECASE)
Tim Edwards475b5272020-08-25 14:05:50 -0400240 cdlrex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
241 stddevrex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
Tim Edwardsd8130452020-08-27 22:38:36 -0400242 stddev2rex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t\'{]+[\'{][^\'}]+[\'}])[ \t]*(.*)', re.IGNORECASE)
243 stddev3rex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]+(.*)', re.IGNORECASE)
244
Tim Edwards031ccbc2020-07-22 22:30:14 -0400245
246 with open(in_file, 'r') as ifile:
Tim Edwards57220f12020-08-05 21:16:03 -0400247 try:
248 speclines = ifile.read().splitlines()
249 except:
250 print('Failure to read ' + in_file + '; not an ASCII file?')
251 return
Tim Edwards031ccbc2020-07-22 22:30:14 -0400252
253 insub = False
254 inparam = False
255 inmodel = False
256 inpinlist = False
Tim Edwards7f052a62020-07-28 11:07:26 -0400257 isspectre = False
Tim Edwards57220f12020-08-05 21:16:03 -0400258 ispassed = False
Tim Edwards031ccbc2020-07-22 22:30:14 -0400259 spicelines = []
260 calllines = []
261 modellines = []
Tim Edwards475b5272020-08-25 14:05:50 -0400262 paramnames = []
Tim Edwards031ccbc2020-07-22 22:30:14 -0400263 savematch = None
264 blockskip = 0
265 subname = ''
Tim Edwards475b5272020-08-25 14:05:50 -0400266 subnames = []
Tim Edwards031ccbc2020-07-22 22:30:14 -0400267 modname = ''
268 modtype = ''
269
270 for line in speclines:
271
Tim Edwards57220f12020-08-05 21:16:03 -0400272 # Item 1a. C++-style // comments get replaced with * comment character
Tim Edwards031ccbc2020-07-22 22:30:14 -0400273 if line.strip().startswith('//'):
274 # Replace the leading "//" with SPICE-style comment "*".
275 if modellines != []:
276 modellines.append(line.strip().replace('//', '*', 1))
277 elif calllines != []:
278 calllines.append(line.strip().replace('//', '*', 1))
279 else:
280 spicelines.append(line.strip().replace('//', '*', 1))
281 continue
282
Tim Edwards57220f12020-08-05 21:16:03 -0400283 # Item 1b. In-line C++-style // comments get replaced with $ comment character
284 elif ' //' in line:
Tim Edwardsa4219782020-08-09 21:13:58 -0400285 line = line.replace(' //', ' $ ', 1)
Tim Edwards44918582020-08-09 16:17:55 -0400286 elif '//' in line:
Tim Edwardsa4219782020-08-09 21:13:58 -0400287 line = line.replace('//', ' $ ', 1)
Tim Edwards57220f12020-08-05 21:16:03 -0400288 elif '\t//' in line:
289 line = line.replace('\t//', '\t$ ', 1)
290
Tim Edwards031ccbc2020-07-22 22:30:14 -0400291 # Item 2. Handle SPICE-style comment lines
292 if line.strip().startswith('*'):
293 if modellines != []:
294 modellines.append(line.strip())
295 elif calllines != []:
296 calllines.append(line.strip())
297 else:
298 spicelines.append(line.strip())
299 continue
300
Tim Edwards7f052a62020-07-28 11:07:26 -0400301 # Item 4. Flag continuation lines
Tim Edwards031ccbc2020-07-22 22:30:14 -0400302 if line.strip().startswith('+'):
303 contline = True
304 else:
305 contline = False
Tim Edwards57220f12020-08-05 21:16:03 -0400306 if line.strip() != '':
307 if inparam:
Tim Edwardsa4219782020-08-09 21:13:58 -0400308 inparam = False
Tim Edwards57220f12020-08-05 21:16:03 -0400309 if inpinlist:
Tim Edwardsa4219782020-08-09 21:13:58 -0400310 inpinlist = False
Tim Edwards031ccbc2020-07-22 22:30:14 -0400311
Tim Edwards7f052a62020-07-28 11:07:26 -0400312 # Item 3. Handle blank lines like comment lines
313 if line.strip() == '':
314 if modellines != []:
315 modellines.append(line.strip())
316 elif calllines != []:
317 calllines.append(line.strip())
318 else:
319 spicelines.append(line.strip())
320 continue
321
322 # Item 5. Count through { ... } blocks that are not SPICE syntax
Tim Edwards031ccbc2020-07-22 22:30:14 -0400323 if blockskip > 0:
324 # Warning: Assumes one brace per line, may or may not be true
325 if '{' in line:
326 blockskip = blockskip + 1
327 elif '}' in line:
328 blockskip = blockskip - 1
329 if blockskip == 0:
330 spicelines.append('* ' + line)
331 continue
332
333 if blockskip > 0:
334 spicelines.append('* ' + line)
335 continue
336
Tim Edwards7f052a62020-07-28 11:07:26 -0400337 # Item 6. Handle continuation lines
Tim Edwards031ccbc2020-07-22 22:30:14 -0400338 if contline:
339 if inparam:
340 # Continue handling parameters
Tim Edwards57220f12020-08-05 21:16:03 -0400341 fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400342 if fmtline != '':
343 if modellines != []:
344 modellines.append(fmtline)
345 elif calllines != []:
346 calllines.append(fmtline)
347 else:
348 spicelines.append(fmtline)
349 continue
350
Tim Edwards7f052a62020-07-28 11:07:26 -0400351 # Item 7. Regexp matching
Tim Edwards031ccbc2020-07-22 22:30:14 -0400352
353 # Catch "simulator lang="
354 smatch = simrex.match(line)
355 if smatch:
356 if smatch.group(1) == 'lang':
357 if smatch.group(2) == 'spice':
358 isspectre = False
359 elif smatch.group(2) == 'spectre':
360 isspectre = True
361 continue
362
363 # If inside a subcircuit, remove "parameters". If outside,
364 # change it to ".param"
Tim Edwards57220f12020-08-05 21:16:03 -0400365 fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400366 if fmtline != '':
367 inparam = True
368 spicelines.append(fmtline)
369 continue
Tim Edwardsa4219782020-08-09 21:13:58 -0400370
Tim Edwards031ccbc2020-07-22 22:30:14 -0400371 # statistics---not sure if it is always outside an inline subcircuit
372 smatch = statrex.match(line)
373 if smatch:
374 if '}' not in smatch.group(1):
375 blockskip = 1
376 spicelines.append('* ' + line)
377 continue
378
379 # model---not sure if it is always inside an inline subcircuit
Tim Edwards57220f12020-08-05 21:16:03 -0400380 iscdl = False
Tim Edwards031ccbc2020-07-22 22:30:14 -0400381 if isspectre:
382 mmatch = modelrex.match(line)
Tim Edwards57220f12020-08-05 21:16:03 -0400383 if not mmatch:
384 mmatch = cdlmodelrex.match(line)
Tim Edwards44918582020-08-09 16:17:55 -0400385 if mmatch:
386 iscdl = True
Tim Edwards031ccbc2020-07-22 22:30:14 -0400387 else:
388 mmatch = stdmodelrex.match(line)
Tim Edwards57220f12020-08-05 21:16:03 -0400389
Tim Edwards031ccbc2020-07-22 22:30:14 -0400390 if mmatch:
Tim Edwards031ccbc2020-07-22 22:30:14 -0400391 modname = mmatch.group(1)
392 modtype = mmatch.group(2)
393
394 if isspectre and '}' in mmatch.group(1):
395 savematch = mmatch
396 inmodel = 1
397 # Continue to "if inmodel == 1" block below
398 else:
Tim Edwards57220f12020-08-05 21:16:03 -0400399 fmtline, ispassed = parse_param_line(mmatch.group(3), True, False, True, ispassed)
Tim Edwards862eeac2020-09-09 12:20:07 -0400400 if isspectre and (modtype == 'resistor' or modtype == 'r2'):
Tim Edwardse90095a2020-08-19 22:21:16 -0400401 modtype = 'r'
402 modellines.append('.model ' + modname + ' ' + modtype + ' ' + fmtline)
Tim Edwards57220f12020-08-05 21:16:03 -0400403 if fmtline != '':
404 inparam = True
405
Tim Edwards031ccbc2020-07-22 22:30:14 -0400406 inmodel = 2
407 continue
408
409 if not insub:
410 # Things to parse if not in a subcircuit
Tim Edwards57220f12020-08-05 21:16:03 -0400411 imatch = insubrex.match(line) if isspectre else None
Tim Edwards031ccbc2020-07-22 22:30:14 -0400412
Tim Edwards57220f12020-08-05 21:16:03 -0400413 if not imatch:
414 # Check for spectre format subckt or CDL format .subckt lines
Tim Edwards7f052a62020-07-28 11:07:26 -0400415 imatch = cdlsubrex.match(line)
Tim Edwards57220f12020-08-05 21:16:03 -0400416
417 if not imatch:
418 if not isspectre:
419 # Check for standard SPICE format .subckt lines
Tim Edwards7f052a62020-07-28 11:07:26 -0400420 imatch = stdsubrex.match(line)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400421
422 if imatch:
Tim Edwards4eb5c022020-07-28 11:46:53 -0400423 # If a model block is pending, then dump it
424 if modellines != []:
425 for line in modellines:
426 spicelines.append(line)
427 modellines = []
428 inmodel = False
429
Tim Edwards031ccbc2020-07-22 22:30:14 -0400430 insub = True
Tim Edwards57220f12020-08-05 21:16:03 -0400431 ispassed = True
Tim Edwards031ccbc2020-07-22 22:30:14 -0400432 subname = imatch.group(1)
Tim Edwards475b5272020-08-25 14:05:50 -0400433 subnames.append(subname)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400434 if isspectre:
435 devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400436 else:
437 devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
Tim Edwards7f052a62020-07-28 11:07:26 -0400438 # If there is no close-parenthesis then we should expect it on
439 # a continuation line
440 inpinlist = True if ')' not in line else False
441 # Remove parentheses groups from subcircuit arguments
442 spicelines.append('.subckt ' + ' ' + subname + ' ' + imatch.group(2))
Tim Edwards031ccbc2020-07-22 22:30:14 -0400443 continue
444
445 else:
446 # Things to parse when inside of an "inline subckt" block
447
448 if inpinlist:
449 # Watch for pin list continuation line.
450 if isspectre:
451 if ')' in line:
452 inpinlist = False
453 pinlist = line.replace(')', '')
454 spicelines.append(pinlist)
455 else:
456 spicelines.append(line)
457 continue
Tim Edwardsa4219782020-08-09 21:13:58 -0400458
Tim Edwards031ccbc2020-07-22 22:30:14 -0400459 else:
460 if isspectre:
461 ematch = endsubrex.match(line)
Tim Edwards57220f12020-08-05 21:16:03 -0400462 if not ematch:
463 ematch = endonlysubrex.match(line)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400464 else:
465 ematch = stdendsubrex.match(line)
Tim Edwards57220f12020-08-05 21:16:03 -0400466 if not ematch:
467 ematch = stdendonlysubrex.match(line)
468
Tim Edwards031ccbc2020-07-22 22:30:14 -0400469 if ematch:
Tim Edwards57220f12020-08-05 21:16:03 -0400470 try:
471 endname = ematch.group(1).strip()
472 except:
473 pass
474 else:
475 if endname != subname and endname != '':
476 print('Error: "ends" name does not match "subckt" name!')
Tim Edwardsfd0ddc52020-08-28 20:48:39 -0400477 print('"ends" name = ' + endname)
Tim Edwards57220f12020-08-05 21:16:03 -0400478 print('"subckt" name = ' + subname)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400479 if len(calllines) > 0:
480 line = calllines[0]
481 if modtype.startswith('bsim'):
482 line = 'M' + line
483 elif modtype.startswith('nmos'):
484 line = 'M' + line
485 elif modtype.startswith('pmos'):
486 line = 'M' + line
487 elif modtype.startswith('res'):
488 line = 'R' + line
489 elif modtype.startswith('cap'):
490 line = 'C' + line
491 elif modtype.startswith('pnp'):
492 line = 'Q' + line
493 elif modtype.startswith('npn'):
494 line = 'Q' + line
495 elif modtype.startswith('d'):
496 line = 'D' + line
497 spicelines.append(line)
498
Tim Edwardsfd0ddc52020-08-28 20:48:39 -0400499 for line in calllines[1:]:
500 spicelines.append(line)
501 calllines = []
Tim Edwards031ccbc2020-07-22 22:30:14 -0400502
Tim Edwardsd8130452020-08-27 22:38:36 -0400503 # Last check: Do any model types confict with the way they
504 # are called within the subcircuit? Spectre makes it very
505 # hard to know what type of device is being instantiated. . .
506
507 for modelline in modellines:
508 mmatch = stdmodelrex.match(modelline)
509 if mmatch:
Tim Edwardsfd0ddc52020-08-28 20:48:39 -0400510 modelname = mmatch.group(1).lower().split('.')[0]
Tim Edwardsd8130452020-08-27 22:38:36 -0400511 modeltype = mmatch.group(2).lower()
512 newspicelines = []
513 for line in spicelines:
514 cmatch = stddev3rex.match(line)
515 if cmatch:
516 devtype = cmatch.group(1).lower()
517 if modelname in cmatch.group(3):
518 if devtype == 'x':
519 if modeltype == 'pnp' or modeltype == 'npn':
520 line = 'q' + line[1:]
521 elif modeltype == 'c' or modeltype == 'r':
522 line = modeltype + line[1:]
523 elif modeltype == 'd':
524 line = modeltype + line[1:]
525 elif modeltype == 'nmos' or modeltype == 'pmos':
526 line = 'm' + line[1:]
527 newspicelines.append(line)
528 spicelines = newspicelines
529
Tim Edwards57220f12020-08-05 21:16:03 -0400530 # Now add any in-circuit models
Tim Edwards031ccbc2020-07-22 22:30:14 -0400531 spicelines.append('')
532 for line in modellines:
533 spicelines.append(line)
Tim Edwards7f052a62020-07-28 11:07:26 -0400534 modellines = []
Tim Edwardsa4219782020-08-09 21:13:58 -0400535
Tim Edwards57220f12020-08-05 21:16:03 -0400536 # Complete the subcircuit definition
537 spicelines.append('.ends ' + subname)
538
Tim Edwards031ccbc2020-07-22 22:30:14 -0400539 insub = False
540 inmodel = False
541 subname = ''
Tim Edwards475b5272020-08-25 14:05:50 -0400542 paramnames = []
Tim Edwards031ccbc2020-07-22 22:30:14 -0400543 continue
544
545 # Check for close of model
546 if isspectre and inmodel:
547 if '}' in line:
548 inmodel = False
549 continue
550
551 # Check for devices R and C.
552 dmatch = caprex.match(line)
553 if dmatch:
Tim Edwards57220f12020-08-05 21:16:03 -0400554 fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400555 if fmtline != '':
556 inparam = True
557 spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
558 continue
559 else:
560 spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
561 continue
562
563 dmatch = resrex.match(line)
564 if dmatch:
Tim Edwards57220f12020-08-05 21:16:03 -0400565 fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400566 if fmtline != '':
567 inparam = True
568 spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
569 continue
570 else:
571 spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
572 continue
573
Tim Edwardsd9fe26c2020-08-01 21:31:04 -0400574 cmatch = cdlrex.match(line)
Tim Edwards475b5272020-08-25 14:05:50 -0400575 if not cmatch:
Tim Edwards862eeac2020-09-09 12:20:07 -0400576 if not isspectre or 'capacitor' in line or 'resistor' in line:
Tim Edwards475b5272020-08-25 14:05:50 -0400577 cmatch = stddevrex.match(line)
578
Tim Edwardsd9fe26c2020-08-01 21:31:04 -0400579 if cmatch:
Tim Edwards57220f12020-08-05 21:16:03 -0400580 ispassed = False
Tim Edwardsd9fe26c2020-08-01 21:31:04 -0400581 devtype = cmatch.group(1)
582 devmodel = cmatch.group(4)
Tim Edwardseba70cf2020-08-01 21:08:46 -0400583
Tim Edwardsd9fe26c2020-08-01 21:31:04 -0400584 # Handle spectreisms. . .
585 if devmodel == 'capacitor':
586 devtype = 'c'
587 devmodel = ''
588 elif devmodel == 'resistor':
589 devtype = 'r'
590 devmodel = ''
Tim Edwards475b5272020-08-25 14:05:50 -0400591 elif devtype.lower() == 'n' or devtype.lower() == 'p':
592 # May be specific to SkyWater models, or is it a spectreism?
Tim Edwardsfd0ddc52020-08-28 20:48:39 -0400593 # NOTE: There is a check, below, to revisit this assignment
594 # and ensure that it matches the model type.
Tim Edwards475b5272020-08-25 14:05:50 -0400595 devtype = 'x' + devtype
596
597 # If a capacitor or resistor value is a parameter or expression,
598 # it must be enclosed in single quotes. Otherwise, if the named
599 # device model is a subcircuit, then the devtype must be "x".
600
601 elif devtype.lower() == 'c' or devtype.lower() == 'r':
602 if devmodel in subnames:
603 devtype = 'x' + devtype
Tim Edwardsd8130452020-08-27 22:38:36 -0400604 else:
605 devvalue = devmodel.split('=')
606 if len(devvalue) > 1:
607 if "'" in devvalue[1] or "{" in devvalue[1]:
608 # Re-parse this catching everything in delimiters,
609 # including spaces.
610 cmatch2 = stddev2rex.match(line)
611 if cmatch2:
612 cmatch = cmatch2
613 devtype = cmatch.group(1)
614 devmodel = cmatch.group(4)
615 devvalue = devmodel.split('=')
616
617 if isexprrex.search(devvalue[1]):
618 if devvalue[1].strip("'") == devvalue[1]:
619 devmodel = devvalue[0] + "='" + devvalue[1] + "'"
620 else:
621 if devmodel in paramnames or isexprrex.search(devmodel):
622 if devmodel.strip("'") == devmodel:
623 devmodel = "'" + devmodel + "'"
Tim Edwardseba70cf2020-08-01 21:08:46 -0400624
Tim Edwards57220f12020-08-05 21:16:03 -0400625 fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed)
Tim Edwardsd9fe26c2020-08-01 21:31:04 -0400626 if fmtline != '':
627 inparam = True
628 spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + fmtline)
629 continue
630 else:
631 spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + cmatch.group(5))
632 continue
Tim Edwards7f052a62020-07-28 11:07:26 -0400633
Tim Edwards475b5272020-08-25 14:05:50 -0400634 # Check for a .param line and collect parameter names
635 pmatch = paramrex.match(line)
636 if pmatch:
637 paramnames.extend(get_param_names(pmatch.group(1)))
638
Tim Edwards031ccbc2020-07-22 22:30:14 -0400639 # Check for a line that begins with the subcircuit name
Tim Edwardsa4219782020-08-09 21:13:58 -0400640
Tim Edwards031ccbc2020-07-22 22:30:14 -0400641 dmatch = devrex.match(line)
642 if dmatch:
Tim Edwards57220f12020-08-05 21:16:03 -0400643 fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400644 if fmtline != '':
645 inparam = True
646 calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline)
647 continue
648 else:
649 calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
650 continue
651
652 if inmodel == 1 or inmodel == 2:
653 # This line should have the model bin, if there is one, and a type.
654 if inmodel == 1:
655 bmatch = binrex.match(savematch.group(3))
656 savematch = None
657 else:
658 bmatch = binrex.match(line)
659
660 if bmatch:
661 bin = bmatch.group(1)
662 type = bmatch.group(2)
663
664 if type == 'n':
665 convtype = 'nmos'
666 elif type == 'p':
667 convtype = 'pmos'
668 else:
669 convtype = type
670
Tim Edwards44918582020-08-09 16:17:55 -0400671 # If there is a binned model then it replaces any original
672 # model line that was saved.
673 if modellines[-1].startswith('.model'):
674 modellines = modellines[0:-1]
Tim Edwards031ccbc2020-07-22 22:30:14 -0400675 modellines.append('')
676 modellines.append('.model ' + modname + '.' + bin + ' ' + convtype)
677 continue
678
679 else:
Tim Edwards57220f12020-08-05 21:16:03 -0400680 fmtline, ispassed = parse_param_line(line, True, True, False, ispassed)
Tim Edwards031ccbc2020-07-22 22:30:14 -0400681 if fmtline != '':
682 modellines.append(fmtline)
683 continue
684
685 # Copy line as-is
686 spicelines.append(line)
687
Tim Edwards862eeac2020-09-09 12:20:07 -0400688 # If any model lines remain at end, append them before output
689 if modellines != []:
690 for line in modellines:
691 spicelines.append(line)
692 modellines = []
693 inmodel = False
694
Tim Edwards031ccbc2020-07-22 22:30:14 -0400695 # Output the result to out_file.
696 with open(out_file, 'w') as ofile:
697 for line in spicelines:
698 print(line, file=ofile)
699
700if __name__ == '__main__':
701 debug = False
702
703 if len(sys.argv) == 1:
Tim Edwardseba70cf2020-08-01 21:08:46 -0400704 print("No options given to spectre_to_spice.py.")
Tim Edwards031ccbc2020-07-22 22:30:14 -0400705 usage()
706 sys.exit(0)
707
708 optionlist = []
709 arguments = []
710
711 for option in sys.argv[1:]:
712 if option.find('-', 0) == 0:
713 optionlist.append(option)
714 else:
715 arguments.append(option)
716
717 if len(arguments) != 2:
Tim Edwardseba70cf2020-08-01 21:08:46 -0400718 print("Wrong number of arguments given to spectre_to_spice.py.")
Tim Edwards031ccbc2020-07-22 22:30:14 -0400719 usage()
720 sys.exit(0)
721
722 if '-debug' in optionlist:
723 debug = True
724
725 specpath = arguments[0]
726 spicepath = arguments[1]
727 do_one_file = False
728
729 if not os.path.exists(specpath):
730 print('No such source directory ' + specpath)
731 sys.exit(1)
732
733 if os.path.isfile(specpath):
734 do_one_file = True
735
736 if do_one_file:
737 if os.path.exists(spicepath):
738 print('Error: File ' + spicepath + ' exists.')
739 sys.exit(1)
740 convert_file(specpath, spicepath)
741
742 else:
743 if not os.path.exists(spicepath):
744 os.makedirs(spicepath)
745
746 specfilelist = glob.glob(specpath + '/*')
747
748 for filename in specfilelist:
749 fileext = os.path.splitext(filename)[1]
750
751 # Ignore verilog or verilog-A files that might be in a model directory
752 if fileext == '.v' or fileext == '.va':
753 continue
754
Tim Edwards0a0272b2020-07-28 14:40:10 -0400755 # .scs files are purely spectre and meaningless to SPICE, so ignore them.
756 if fileext == '.scs':
757 continue
758
Tim Edwards031ccbc2020-07-22 22:30:14 -0400759 froot = os.path.split(filename)[1]
760 convert_file(filename, spicepath + '/' + froot)
761
762 print('Done.')
763 exit(0)