Tim Edwards | 9d3debb | 2020-10-20 20:52:18 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 2 | # Script to read all files in a directory of SPECTRE-compatible device model |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 3 | # files, and convert them to a form that is compatible with ngspice. |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 4 | |
| 5 | import os |
| 6 | import sys |
| 7 | import re |
| 8 | import glob |
| 9 | |
| 10 | def usage(): |
Tim Edwards | eba70cf | 2020-08-01 21:08:46 -0400 | [diff] [blame] | 11 | print('spectre_to_spice.py <path_to_spectre> <path_to_spice>') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 12 | |
| 13 | # Check if a parameter value is a valid number (real, float, integer) |
| 14 | # or is some kind of expression. |
| 15 | |
| 16 | def 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 30 | def parse_param_line(line, inparam, insub, iscall, ispassed): |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 31 | |
| 32 | # Regexp patterns |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 33 | parm1rex = re.compile('[ \t]*parameters[ \t]*(.*)') |
| 34 | parm2rex = re.compile('[ \t]*params:[ \t]*(.*)') |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 35 | parm3rex = re.compile('[ \t]*\+[ \t]*(.*)') |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 36 | parm4rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)') |
| 37 | parm5rex = re.compile('[ \t]*([^= \t]+)[ \t]*(.*)') |
Tim Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 38 | parm6rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([\'{][^\'}]+[\'}])[ \t]*(.*)') |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 39 | rtok = re.compile('([^ \t\n]+)[ \t]*(.*)') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 40 | |
| 41 | fmtline = [] |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 42 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 43 | if iscall: |
| 44 | rest = line |
| 45 | elif inparam: |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 46 | pmatch = parm3rex.match(line) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 47 | if pmatch: |
| 48 | fmtline.append('+') |
| 49 | rest = pmatch.group(1) |
| 50 | else: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 51 | return '', ispassed |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 52 | 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 61 | pmatch = parm2rex.match(line) |
| 62 | if pmatch: |
| 63 | if insub: |
| 64 | fmtline.append('+') |
| 65 | else: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 66 | return '', ispassed |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 67 | rest = pmatch.group(1) |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 68 | else: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 69 | return '', ispassed |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 70 | |
| 71 | while rest != '': |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 72 | 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 79 | pmatch = parm4rex.match(rest) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 80 | if pmatch: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 81 | if ispassed: |
| 82 | # End of passed parameters. Break line and generate ".param" |
| 83 | ispassed = False |
| 84 | fmtline.append('\n.param ') |
| 85 | |
Tim Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 86 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 93 | fmtline.append(pmatch.group(1)) |
| 94 | fmtline.append('=') |
| 95 | value = pmatch.group(2) |
| 96 | rest = pmatch.group(3) |
| 97 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 98 | # 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 Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 107 | if expch == '$': |
| 108 | break |
| 109 | elif expch.isalpha() and not needmore: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 110 | break |
| 111 | else: |
| 112 | needmore = False |
| 113 | value += rmatch.group(1) |
| 114 | rest = rmatch.group(2) |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 115 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 118 | needmore = True |
| 119 | else: |
| 120 | break |
| 121 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 122 | if is_number(value): |
| 123 | fmtline.append(value) |
Tim Edwards | 37d35dd | 2020-08-19 21:41:14 -0400 | [diff] [blame] | 124 | elif value.strip().startswith("'"): |
| 125 | fmtline.append(value) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 126 | else: |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 127 | # 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 Edwards | 3d84902 | 2020-07-23 19:37:31 -0400 | [diff] [blame] | 135 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 136 | # 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 Edwards | 3d84902 | 2020-07-23 19:37:31 -0400 | [diff] [blame] | 140 | |
| 141 | if rest != '': |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 142 | nmatch = parm4rex.match(rest) |
Tim Edwards | 3d84902 | 2020-07-23 19:37:31 -0400 | [diff] [blame] | 143 | if not nmatch: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 144 | if rest.lstrip().startswith('$ '): |
| 145 | fmtline.append(rest) |
| 146 | elif rest.strip() != '': |
| 147 | fmtline.append(' $ ' + rest.replace(' ', '').replace('\t', '')) |
Tim Edwards | 3d84902 | 2020-07-23 19:37:31 -0400 | [diff] [blame] | 148 | rest = '' |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 149 | else: |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 150 | # Match to a CDL subckt parameter that does not have an '=' and so |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 151 | # assumes that the parameter is always passed, and therefore must |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 152 | # be part of the .subckt line. A parameter without a value is not |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 153 | # legal SPICE, so supply a default value of 1. |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 154 | |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 155 | pmatch = parm5rex.match(rest) |
| 156 | if pmatch: |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 157 | # 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 169 | else: |
| 170 | break |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 171 | |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 172 | 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 Edwards | 88b2b7f | 2020-09-08 16:45:44 -0400 | [diff] [blame] | 186 | # 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 Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 192 | return finalline, ispassed |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 193 | |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 194 | def 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 216 | def 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 221 | insubrex = re.compile('[ \t]*inline[ \t]+subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)') |
| 222 | cdlsubrex = re.compile('\.?subckt[ \t]+([^ \t\(]+)[ \t]*\(([^)]*)') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 223 | endsubrex = re.compile('[ \t]*ends[ \t]+(.+)') |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 224 | endonlysubrex = re.compile('[ \t]*ends[ \t]*') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 225 | modelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+\{(.*)') |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 226 | cdlmodelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 227 | binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)') |
| 228 | shincrex = re.compile('\.inc[ \t]+') |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 229 | isexprrex = re.compile('[^0-9a-zA-Z_]') |
| 230 | paramrex = re.compile('\.param[ \t]+(.*)') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 231 | |
Tim Edwards | 4491858 | 2020-08-09 16:17:55 -0400 | [diff] [blame] | 232 | stdsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+(.*)') |
Tim Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 233 | stdmodelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 234 | stdendsubrex = re.compile('\.ends[ \t]+(.+)') |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 235 | stdendonlysubrex = re.compile('\.ends[ \t]*') |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 236 | |
| 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 Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 240 | 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 Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 242 | 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 245 | |
| 246 | with open(in_file, 'r') as ifile: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 247 | try: |
| 248 | speclines = ifile.read().splitlines() |
| 249 | except: |
| 250 | print('Failure to read ' + in_file + '; not an ASCII file?') |
| 251 | return |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 252 | |
| 253 | insub = False |
| 254 | inparam = False |
| 255 | inmodel = False |
| 256 | inpinlist = False |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 257 | isspectre = False |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 258 | ispassed = False |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 259 | spicelines = [] |
| 260 | calllines = [] |
| 261 | modellines = [] |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 262 | paramnames = [] |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 263 | savematch = None |
| 264 | blockskip = 0 |
| 265 | subname = '' |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 266 | subnames = [] |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 267 | modname = '' |
| 268 | modtype = '' |
| 269 | |
| 270 | for line in speclines: |
| 271 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 272 | # Item 1a. C++-style // comments get replaced with * comment character |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 273 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 283 | # Item 1b. In-line C++-style // comments get replaced with $ comment character |
| 284 | elif ' //' in line: |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 285 | line = line.replace(' //', ' $ ', 1) |
Tim Edwards | 4491858 | 2020-08-09 16:17:55 -0400 | [diff] [blame] | 286 | elif '//' in line: |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 287 | line = line.replace('//', ' $ ', 1) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 288 | elif '\t//' in line: |
| 289 | line = line.replace('\t//', '\t$ ', 1) |
| 290 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 291 | # 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 301 | # Item 4. Flag continuation lines |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 302 | if line.strip().startswith('+'): |
| 303 | contline = True |
| 304 | else: |
| 305 | contline = False |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 306 | if line.strip() != '': |
| 307 | if inparam: |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 308 | inparam = False |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 309 | if inpinlist: |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 310 | inpinlist = False |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 311 | |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 312 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 323 | 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 337 | # Item 6. Handle continuation lines |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 338 | if contline: |
| 339 | if inparam: |
| 340 | # Continue handling parameters |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 341 | fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 342 | 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 351 | # Item 7. Regexp matching |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 352 | |
| 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 365 | fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 366 | if fmtline != '': |
| 367 | inparam = True |
| 368 | spicelines.append(fmtline) |
| 369 | continue |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 370 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 371 | # 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 380 | iscdl = False |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 381 | if isspectre: |
| 382 | mmatch = modelrex.match(line) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 383 | if not mmatch: |
| 384 | mmatch = cdlmodelrex.match(line) |
Tim Edwards | 4491858 | 2020-08-09 16:17:55 -0400 | [diff] [blame] | 385 | if mmatch: |
| 386 | iscdl = True |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 387 | else: |
| 388 | mmatch = stdmodelrex.match(line) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 389 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 390 | if mmatch: |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 391 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 399 | fmtline, ispassed = parse_param_line(mmatch.group(3), True, False, True, ispassed) |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 400 | if isspectre and (modtype == 'resistor' or modtype == 'r2'): |
Tim Edwards | e90095a | 2020-08-19 22:21:16 -0400 | [diff] [blame] | 401 | modtype = 'r' |
| 402 | modellines.append('.model ' + modname + ' ' + modtype + ' ' + fmtline) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 403 | if fmtline != '': |
| 404 | inparam = True |
| 405 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 406 | inmodel = 2 |
| 407 | continue |
| 408 | |
| 409 | if not insub: |
| 410 | # Things to parse if not in a subcircuit |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 411 | imatch = insubrex.match(line) if isspectre else None |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 412 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 413 | if not imatch: |
| 414 | # Check for spectre format subckt or CDL format .subckt lines |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 415 | imatch = cdlsubrex.match(line) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 416 | |
| 417 | if not imatch: |
| 418 | if not isspectre: |
| 419 | # Check for standard SPICE format .subckt lines |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 420 | imatch = stdsubrex.match(line) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 421 | |
| 422 | if imatch: |
Tim Edwards | 4eb5c02 | 2020-07-28 11:46:53 -0400 | [diff] [blame] | 423 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 430 | insub = True |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 431 | ispassed = True |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 432 | subname = imatch.group(1) |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 433 | subnames.append(subname) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 434 | if isspectre: |
| 435 | devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 436 | else: |
| 437 | devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE) |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 438 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 443 | 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 Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 458 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 459 | else: |
| 460 | if isspectre: |
| 461 | ematch = endsubrex.match(line) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 462 | if not ematch: |
| 463 | ematch = endonlysubrex.match(line) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 464 | else: |
| 465 | ematch = stdendsubrex.match(line) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 466 | if not ematch: |
| 467 | ematch = stdendonlysubrex.match(line) |
| 468 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 469 | if ematch: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 470 | 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 Edwards | fd0ddc5 | 2020-08-28 20:48:39 -0400 | [diff] [blame] | 477 | print('"ends" name = ' + endname) |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 478 | print('"subckt" name = ' + subname) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 479 | 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 Edwards | fd0ddc5 | 2020-08-28 20:48:39 -0400 | [diff] [blame] | 499 | for line in calllines[1:]: |
| 500 | spicelines.append(line) |
| 501 | calllines = [] |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 502 | |
Tim Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 503 | # 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 Edwards | fd0ddc5 | 2020-08-28 20:48:39 -0400 | [diff] [blame] | 510 | modelname = mmatch.group(1).lower().split('.')[0] |
Tim Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 511 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 530 | # Now add any in-circuit models |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 531 | spicelines.append('') |
| 532 | for line in modellines: |
| 533 | spicelines.append(line) |
Tim Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 534 | modellines = [] |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 535 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 536 | # Complete the subcircuit definition |
| 537 | spicelines.append('.ends ' + subname) |
| 538 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 539 | insub = False |
| 540 | inmodel = False |
| 541 | subname = '' |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 542 | paramnames = [] |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 543 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 554 | fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 555 | 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 Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 565 | fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 566 | 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 Edwards | d9fe26c | 2020-08-01 21:31:04 -0400 | [diff] [blame] | 574 | cmatch = cdlrex.match(line) |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 575 | if not cmatch: |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 576 | if not isspectre or 'capacitor' in line or 'resistor' in line: |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 577 | cmatch = stddevrex.match(line) |
| 578 | |
Tim Edwards | d9fe26c | 2020-08-01 21:31:04 -0400 | [diff] [blame] | 579 | if cmatch: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 580 | ispassed = False |
Tim Edwards | d9fe26c | 2020-08-01 21:31:04 -0400 | [diff] [blame] | 581 | devtype = cmatch.group(1) |
| 582 | devmodel = cmatch.group(4) |
Tim Edwards | eba70cf | 2020-08-01 21:08:46 -0400 | [diff] [blame] | 583 | |
Tim Edwards | d9fe26c | 2020-08-01 21:31:04 -0400 | [diff] [blame] | 584 | # Handle spectreisms. . . |
| 585 | if devmodel == 'capacitor': |
| 586 | devtype = 'c' |
| 587 | devmodel = '' |
| 588 | elif devmodel == 'resistor': |
| 589 | devtype = 'r' |
| 590 | devmodel = '' |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 591 | elif devtype.lower() == 'n' or devtype.lower() == 'p': |
| 592 | # May be specific to SkyWater models, or is it a spectreism? |
Tim Edwards | fd0ddc5 | 2020-08-28 20:48:39 -0400 | [diff] [blame] | 593 | # NOTE: There is a check, below, to revisit this assignment |
| 594 | # and ensure that it matches the model type. |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 595 | 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 Edwards | d813045 | 2020-08-27 22:38:36 -0400 | [diff] [blame] | 604 | 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 Edwards | eba70cf | 2020-08-01 21:08:46 -0400 | [diff] [blame] | 624 | |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 625 | fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed) |
Tim Edwards | d9fe26c | 2020-08-01 21:31:04 -0400 | [diff] [blame] | 626 | 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 Edwards | 7f052a6 | 2020-07-28 11:07:26 -0400 | [diff] [blame] | 633 | |
Tim Edwards | 475b527 | 2020-08-25 14:05:50 -0400 | [diff] [blame] | 634 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 639 | # Check for a line that begins with the subcircuit name |
Tim Edwards | a421978 | 2020-08-09 21:13:58 -0400 | [diff] [blame] | 640 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 641 | dmatch = devrex.match(line) |
| 642 | if dmatch: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 643 | fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 644 | 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 Edwards | 4491858 | 2020-08-09 16:17:55 -0400 | [diff] [blame] | 671 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 675 | modellines.append('') |
| 676 | modellines.append('.model ' + modname + '.' + bin + ' ' + convtype) |
| 677 | continue |
| 678 | |
| 679 | else: |
Tim Edwards | 57220f1 | 2020-08-05 21:16:03 -0400 | [diff] [blame] | 680 | fmtline, ispassed = parse_param_line(line, True, True, False, ispassed) |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 681 | if fmtline != '': |
| 682 | modellines.append(fmtline) |
| 683 | continue |
| 684 | |
| 685 | # Copy line as-is |
| 686 | spicelines.append(line) |
| 687 | |
Tim Edwards | 862eeac | 2020-09-09 12:20:07 -0400 | [diff] [blame] | 688 | # 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 Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 695 | # 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 | |
| 700 | if __name__ == '__main__': |
| 701 | debug = False |
| 702 | |
| 703 | if len(sys.argv) == 1: |
Tim Edwards | eba70cf | 2020-08-01 21:08:46 -0400 | [diff] [blame] | 704 | print("No options given to spectre_to_spice.py.") |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 705 | 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 Edwards | eba70cf | 2020-08-01 21:08:46 -0400 | [diff] [blame] | 718 | print("Wrong number of arguments given to spectre_to_spice.py.") |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 719 | 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 Edwards | 0a0272b | 2020-07-28 14:40:10 -0400 | [diff] [blame] | 755 | # .scs files are purely spectre and meaningless to SPICE, so ignore them. |
| 756 | if fileext == '.scs': |
| 757 | continue |
| 758 | |
Tim Edwards | 031ccbc | 2020-07-22 22:30:14 -0400 | [diff] [blame] | 759 | froot = os.path.split(filename)[1] |
| 760 | convert_file(filename, spicepath + '/' + froot) |
| 761 | |
| 762 | print('Done.') |
| 763 | exit(0) |