Tim Edwards | 9d3debb | 2020-10-20 20:52:18 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Tim Edwards | c91f2d6 | 2020-08-19 14:27:17 -0400 | [diff] [blame] | 2 | # |
| 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 | |
| 19 | import os |
| 20 | import sys |
| 21 | import re |
| 22 | import glob |
| 23 | |
| 24 | def 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 | |
| 39 | def 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 | |
| 95 | def 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 | |
| 229 | if __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) |