blob: f961326cac51e116b9903929e05d57102a2fb1dc [file] [log] [blame]
Tim Edwards9d3debb2020-10-20 20:52:18 -04001#!/usr/bin/env python3
Tim Edwardsc91f2d62020-08-19 14:27:17 -04002#
3# fix_subckt_params.py --
4#
5# Modify SPICE subcircuit definitions in files where parameters are listed
6# in a ".param" block in the subcircuit and are therefore local parameters
7# that cannot be passed to the subcircuit, and move them into the subcircuit
8# pin list as parameters that can be passed to the subcircuit.
9#
10# The arguments are <path_to_input>, <path_to_output>, and <param> ...
11# <path_to_input> should be the path to a single file, while
12# <path_to_output> is the path to a directory where the split files will
13# be put. <params> ... is a whitespace-separated (i.e., one parameter,
14# one argument) list of parameter names that should be moved up from the
15# ".param" section to the ".subckt" line. If <param> is preceded with "-",
16# then the parameter will be moved from the .subckt line down to the
17# .param section.
18
19import os
20import sys
21import re
22import glob
23
24def usage():
25 print('fix_subckt_params.py <path_to_input> <path_to_output> <param> ...')
26 print('where:')
27 print(' <path_to_input> is the path to the input file to parse')
28 print(' <path_to_output> is the directory to place the modified input file')
29 print(' <param> ... is a space-separated list of parameters that should')
30 print(' be in the subcircuit line and not the .param block')
31
32# Parse a parameter line for parameters, and divide into two parts,
33# returned as a list. If a parameter name matches an entry in 'params',
34# it goes in the second list. Otherwise, it goes in the first list.
35# The first list is returned as-is minus any parameters that were split
36# into the second list. The second list must begin with '+', as it will
37# be output as a continuation line for the subcircuit.
38
39def param_split(line, params, debug):
40 # Regexp patterns
41 parm1rex = re.compile('(\.param[ \t]+)(.*)')
42 parm2rex = re.compile('(\+[ \t]*)(.*)')
43 parm3rex = re.compile('([^= \t]+)([ \t]*=[ \t]*[^ \t]+[ \t]*)(.*)')
44 parm4rex = re.compile('([^= \t]+)([ \t]*)(.*)')
45
46 part1 = ''
47 part2 = ''
48
49 if debug:
50 print('Diagnostic: param line in = "' + line + '"')
51
52 pmatch = parm1rex.match(line)
53 if pmatch:
54 part1 = pmatch.group(1)
55 rest = pmatch.group(2)
56 else:
57 pmatch = parm2rex.match(line)
58 if pmatch:
59 rest = pmatch.group(2)
60 else:
61 # Could not parse; return list with line and empty string
62 return [line, '']
63
64 while rest != '':
65 pmatch = parm3rex.match(rest)
66 if pmatch:
67 rest = pmatch.group(3)
68 pname = pmatch.group(1)
69 if pname in params:
70 if part2 == '':
71 part2 = '+ '
72 part2 += pname + pmatch.group(2)
73 else:
74 if part1 == '':
75 part1 = '+ '
76 part1 += pname + pmatch.group(2)
77 else:
78 pmatch = parm4rex.match(rest)
79 if pmatch:
80 rest = pmatch.group(3)
81 pname = pmatch.group(1)
82 if pname in params:
83 if part2 == '':
84 part2 = '+ '
85 part2 += pname + pmatch.group(2)
86 else:
87 if part1 == '':
88 part1 = '+ '
89 part1 += pname + pmatch.group(2)
90
91 if debug:
92 print('Diagnostic: param line out = "' + part1 + '" and "' + part2 + '"')
93 return [part1, part2]
94
95def convert_file(in_file, out_path, params, debug):
96
97 # Regexp patterns
98 paramrex = re.compile('\.param[ \t]+(.*)')
99 subrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+([^ \t]*)')
100 modelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)')
101 endsubrex = re.compile('\.ends[ \t]+(.+)')
102 increx = re.compile('\.include[ \t]+')
103
104 with open(in_file, 'r') as ifile:
105 inplines = ifile.read().splitlines()
106
107 insubckt = False
108 inparam = False
109 inmodel = False
110 inpinlist = False
111
112 # Output lines
113 spicelines = []
114 paramlines = []
115 subparams = []
116
117 for line in inplines:
118
119 # Item 1. Handle comment lines
120 if line.startswith('*'):
121 spicelines.append(line.strip())
122 continue
123
124 # Item 2. Flag continuation lines.
125 if line.startswith('+'):
126 contline = True
127 else:
128 contline = False
129 if line.strip() != '':
130 if inpinlist:
131 inpinlist = False
132 if inparam:
133 # Dump additional subcircuit parameters and clear
134 if subparams:
135 spicelines.extend(subparams)
136 subparams = []
137
138 # Dump parameters to file contents and clear
139 spicelines.extend(paramlines)
140 paramlines = []
141 inparam = False
142
143 # Item 3. Handle blank lines like comment lines
144 if line.strip() == '':
145 if inparam:
146 paramlines.append(line)
147 else:
148 spicelines.append(line)
149 continue
150
151 # Item 4. Handle continuation lines
152 # Remove lines that have a continuation mark and nothing else.
153 if contline:
154 if inparam:
155 # Continue handling parameters
156 if insubckt:
157 # Find subcircuit parameters and record what line they were found on
158 psplit = param_split(line, params, debug)
159 if psplit[0]:
160 paramlines.append(psplit[0])
161 if psplit[1]:
162 subparams.append(psplit[1])
163 else:
164 paramlines.append(line)
165 else:
166 if line.strip() != '+':
167 spicelines.append(line)
168 continue
169
170 # Item 5. Regexp matching
171
172 # parameters
173 pmatch = paramrex.match(line)
174 if pmatch:
175 inparam = True
176 if insubckt:
177 # Find subcircuit parameters and record what line they were found on
178 psplit = param_split(line, params, debug)
179 if psplit[0]:
180 paramlines.append(psplit[0])
181 if psplit[1]:
182 subparams.append(psplit[1])
183 else:
184 paramlines.append(line)
185 continue
186
187 # model
188 mmatch = modelrex.match(line)
189 if mmatch:
190 spicelines.append(line)
191 continue
192 inmodel = 2
193 continue
194
195 if not insubckt:
196 # Things to parse if not in a subcircuit
197
198 imatch = subrex.match(line)
199 if imatch:
200 insubckt = True
201 inpinlist = True
202 spicelines.append(line)
203 continue
204
205 else:
206 # Things to parse when inside of a ".subckt" block
207
208 if inpinlist:
209 spicelines.append(line)
210 continue
211
212 else:
213 ematch = endsubrex.match(line)
214 if ematch:
215 spicelines.append(line)
216 insubckt = False
217 inmodel = False
218 continue
219
220 # Copy line as-is
221 spicelines.append(line)
222
223 # Output the result to out_file.
224 out_file = os.path.split(in_file)[1]
225 with open(out_path + '/' + out_file, 'w') as ofile:
226 for line in spicelines:
227 print(line, file=ofile)
228
229if __name__ == '__main__':
230 debug = False
231
232 if len(sys.argv) == 1:
233 print("No options given to fix_subckt_params.py.")
234 usage()
235 sys.exit(0)
236
237 optionlist = []
238 arguments = []
239
240 for option in sys.argv[1:]:
241 if option.find('-', 0) == 0:
242 optionlist.append(option)
243 else:
244 arguments.append(option)
245
246 if len(arguments) < 3:
247 print("Wrong number of arguments given to fix_subckt_params.py.")
248 usage()
249 sys.exit(0)
250
251 if '-debug' in optionlist:
252 debug = True
253
254 inpath = arguments[0]
255 outpath = arguments[1]
256 params = arguments[2:]
257
258 if not os.path.exists(inpath):
259 print('No such source file ' + inpath)
260 sys.exit(1)
261
262 if not os.path.isfile(inpath):
263 print('Input path ' + inpath + ' is not a file.')
264 sys.exit(1)
265
266 convert_file(inpath, outpath, params, debug)
267
268 print('Done.')
269 exit(0)