Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | cdl2spi.py : netlist processor |
| 4 | Copyright (c) 2016, 2020 efabless Corporation. |
| 5 | All rights reserved. |
| 6 | |
| 7 | usage: cdl2spi.py <inCDLfile> [<outSPCfile>] [options...] |
| 8 | Writes to .spi to outSPCfile, or stdout if no output argument given. Sets exit |
| 9 | status if there were non-zero errors. Most errors/warnings are annotated in-line |
| 10 | in the stdout each before the relevant line. |
| 11 | """ |
| 12 | |
| 13 | import sys, getopt |
| 14 | import os |
| 15 | import re |
| 16 | import textwrap |
| 17 | |
| 18 | # Convert linear scale to area scale suffix |
| 19 | # (e.g., if linear scale is 1e-6 ('u') then area scales as 1e-12 ('p')) |
| 20 | |
| 21 | def getAreaScale(dscale): |
| 22 | ascale = '' |
| 23 | if dscale == 'm': |
| 24 | ascale = 'u' |
| 25 | elif dscale == 'u': |
| 26 | ascale = 'p' |
| 27 | elif dscale == 'n': |
| 28 | ascale = 'a' |
| 29 | return ascale |
| 30 | |
| 31 | # Check nm (instanceName) in the context of sub (subckt): is it used yet? |
| 32 | # If not used yet, mark it used, and return as-is. |
| 33 | # Else generate a unique suffixed version, and mark it used, return it. |
| 34 | # If 1M suffixes don't generate a unique name, throw exception. |
| 35 | # hasInm : global hash, key of hash is (subckt, iname) |
| 36 | |
| 37 | hasInm = {} |
| 38 | def uniqInm(sub, nm): |
| 39 | subl=sub.lower() |
| 40 | nml=nm.lower() |
| 41 | if not (subl, nml) in hasInm: |
| 42 | hasInm[ (subl, nml) ] = 1 |
| 43 | return nm |
| 44 | for i in range(1000000): |
| 45 | nm2 = nm + "_q" + str(i) |
| 46 | nm2l = nm2.lower() |
| 47 | if not (subl, nm2l) in hasInm: |
| 48 | hasInm[ (subl, nm2l) ] = 1 |
| 49 | return nm2 |
| 50 | # not caught anywhere, and gives (intended) non-zero exit status |
| 51 | raise AssertionError("uniqInm: range overflow for (%s,%s)" % (sub, nm)) |
| 52 | |
| 53 | # Map illegal characters in an nm (instanceName) in context of sub (subckt). |
| 54 | # For ngspice, '/' is illegal in instanceNames. Replace it with '|', BUT |
| 55 | # then make sure the new name is still unique: does not collide with a name |
| 56 | # used so far or another already derived unique name. |
| 57 | |
| 58 | inmBadChars='/' |
| 59 | inmRplChars='|' |
| 60 | inmBadCharREX=re.compile( "["+ inmBadChars+"]" ) |
| 61 | |
| 62 | def mapInm(sub, nm): |
| 63 | nm2 = inmBadCharREX.sub(inmRplChars, nm) |
| 64 | return uniqInm(sub, nm2) |
| 65 | |
| 66 | # Process subckt line (array of tokens). Return new array of tokens. |
| 67 | # There might be a ' /' in the line that needs to be deleted. It may be standalone ' / ', or |
| 68 | # butting against the next token. It may be before all pins, after all pins, or between pins. |
| 69 | # Do not touch / in a parameter assignment expression. |
| 70 | # Do not touch / embedded in a pinName. |
| 71 | # May touch / butting front of very first parameter assignment expression. |
| 72 | # .subckt NM / p1 p2 p3 x=y g=h |
| 73 | # .subckt NM /p1 p2 p3 x=y g=h |
| 74 | # .subckt NM p1 p2 / p3 x=y g=h |
| 75 | # .subckt NM p1 p2 /p3 x=y g=h |
| 76 | # .subckt NM p1 p2 p3 / x=y g=h |
| 77 | # .subckt NM p1 p2 p3 /x=y g=h |
| 78 | # .subckt NM p1 p2 p3 x=y g=(a/b) (don't touch this /) |
| 79 | # .subckt NM p1 p2/3/4 p3 x=y g=(a/b) (don't touch these /) |
| 80 | |
| 81 | def mapSubcktDef(tok): |
| 82 | # find index of one-past first token (beyond ".subckt NM") containing an =, if any |
| 83 | param0 = len(tok) |
| 84 | for i in range(2, len(tok)): |
| 85 | if '=' in tok[i]: |
| 86 | param0 = i+1 |
| 87 | break |
| 88 | # find first token before or including that 1st-param, starting with /: |
| 89 | # strip the slash. |
| 90 | for i in range(2, param0): |
| 91 | if tok[i][0] == '/': |
| 92 | tok[i] = tok[i][1:] |
| 93 | if tok[i] == "": |
| 94 | del tok[i] |
| 95 | break |
| 96 | return tok |
| 97 | |
| 98 | def test_mapSubcktInst1(): |
| 99 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3".split()))) |
| 100 | print( " ".join(mapSubcktDef( ".subckt abc / p1 p2 p3".split()))) |
| 101 | print( " ".join(mapSubcktDef( ".subckt abc /p1 p2 p3".split()))) |
| 102 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 /p3".split()))) |
| 103 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 / p3".split()))) |
| 104 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4 /y=5".split()))) |
| 105 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4/2 y=5".split()))) |
| 106 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 / x=4/2 y=5".split()))) |
| 107 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4/2 /y=5".split()))) |
| 108 | print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 /x=4/2 y=5".split()))) |
| 109 | print( " ".join(mapSubcktDef( ".subckt abc p1/2/3 p2 p3 /x=4/2 y=5".split()))) |
| 110 | |
| 111 | # Process subckt instance line (array of tokens). Return new array of tokens. |
| 112 | # (This function does not map possible illegal-chars in instanceName). |
| 113 | # There might be a ' /' in the line that needs to be deleted. It may be standalone ' / ', or |
| 114 | # butting against the next token. It can only be after pins, before or butting subcktName. |
| 115 | # |
| 116 | # Do not touch / in, butting, or after 1st parameter assignment expression. |
| 117 | # Do not touch / embedded in a netName. |
| 118 | # Do not touch / embedded in instanceName (they are handled separately elsewhere). |
| 119 | # xa/b/c p1 p2 p3 / NM x=y g=h |
| 120 | # xa/b/c p1 p2 p3 /NM x=y g=h |
| 121 | # xabc p1 p2/3/4 p3 /NM x=(a/b) g=h |
| 122 | # xabc p1 p2/3/4 p3 / NM x=(a/b) g=h |
| 123 | # xabc p1 p2/3/4 p3 NM x=(a/b) / g=h (don't touch; perhaps needs to be an error trapped somewhere) |
| 124 | # xabc p1 p2/3/4 p3 NM / x=(a/b) g=h (don't touch; perhaps needs to be an error trapped somewhere) |
| 125 | # xa/b/c p1 p2/3/4 p3 NM x=(a/b) g=h (don't touch these /) |
| 126 | |
| 127 | def mapSubcktInst(tok): |
| 128 | # find index of first token (beyond "x<iname>") containing an =, if any |
| 129 | param0 = tlen = len(tok) |
| 130 | for i in range(1, tlen): |
| 131 | if '=' in tok[i]: |
| 132 | param0 = i |
| 133 | break |
| 134 | # Determine modelName index. Either just prior to 1st-param (if any) else last token. |
| 135 | modndx = tlen - 1 |
| 136 | if param0 < tlen: |
| 137 | modndx = param0 - 1; |
| 138 | # If modndx now points to a standalone /, that can't be (would yield missing/empty modelName). |
| 139 | # Actual modelName must be before it. We need to check, possibly strip / on/before actual modelName. |
| 140 | # (Even though standlone / after model are most likely an independent error: we don't touch 'em). |
| 141 | while modndx > 1 and tok[modndx] == "/": |
| 142 | modndx-=1 |
| 143 | # Check for standalone / before modelName. Else for modelName starting with /. |
| 144 | slashndx = modndx - 1 |
| 145 | if slashndx > 0 and tok[slashndx] == "/": |
| 146 | del tok[slashndx] |
| 147 | else: |
| 148 | if modndx > 0 and tok[modndx].startswith("/"): |
| 149 | tok[modndx] = tok[modndx][1:] |
| 150 | return tok |
| 151 | |
| 152 | def test_mapSubcktInst2(): |
| 153 | print( " ".join(mapSubcktInst( "xa/b/c p1 p2 p3 / NM x=y g=h".split()))) |
| 154 | print( " ".join(mapSubcktInst( "xa/b/c p1 p2 p3 /NM x=y g=h".split()))) |
| 155 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 /NM x=(a/b) g=h".split()))) |
| 156 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 / NM x=(a/b) g=h".split()))) |
| 157 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 NM x=(a/b) / g=h".split()))) |
| 158 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 NM / x=(a/b) g=h".split()))) |
| 159 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 /NM / x=(a/b) g=h".split()))) |
| 160 | print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 / NM / x=(a/b) g=h".split()))) |
| 161 | print( " ".join(mapSubcktInst( "xa/b/c p1 p2/3/4 p3 NM x=(a/b) g=h".split()))) |
| 162 | print( " ".join(mapSubcktInst( "xa/b/c NM x=(a/b) g=h".split()))) |
| 163 | print( " ".join(mapSubcktInst( "xa/b/c / NM x=(a/b) g=h".split()))) |
| 164 | print( " ".join(mapSubcktInst( "xa/b/c /NM x=(a/b) g=h".split()))) |
| 165 | print( " ".join(mapSubcktInst( "xa/b/c /NM".split()))) |
| 166 | |
| 167 | # Primitives with M=<n> need to add additional par1=<n>. |
| 168 | # Process token list, return new token list. |
| 169 | # note: line at this point may be like: m... p1 p2 p3 p4 NMOS M=1 $blah W=... L=... |
| 170 | # meaning M=1 is not necessarily in a block of all parameter-assignments at EOL. |
| 171 | # But by processing the line from end backwards, we pick up LAST M= if there are |
| 172 | # multiple (which condition really should get flagged as an error). |
| 173 | # And M= is more likely towards end of the line than front of line (thus faster). |
| 174 | # If "M=" with no value, do nothing (should also be a flagged error). |
| 175 | |
| 176 | def mapMfactor(tok, options={}): |
| 177 | # find index of M=* if any, starting from end. |
| 178 | # "addinm" is an additional parameter that takes the same argument as M |
| 179 | addinm = options['addinm'] if 'addinm' in options else [] |
| 180 | mndx = 0 |
| 181 | val = "" |
| 182 | for i in range(len(tok)-1, 0, -1): |
| 183 | if tok[i].lower().startswith("m="): |
| 184 | mndx = i |
| 185 | break |
| 186 | if mndx > 0: |
| 187 | val = tok[i][2:] |
| 188 | if val != "": |
| 189 | for p in addinm: |
| 190 | tok += [ addinm + val] |
| 191 | return tok |
| 192 | |
| 193 | def test_mapMfactor(): |
| 194 | print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M=joe".split()))) |
| 195 | print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M= $SUB=agnd".split()))) |
| 196 | print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M=2 $SUB=agnd WM=4".split()))) |
| 197 | print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM".split()))) |
| 198 | |
| 199 | # From $nm=... strip the $. Preserve order on the line. No attempt to |
| 200 | # detect any resultant collisions. "W=5 $W=10" becomes "W=5 W=10". |
| 201 | # Don't touch $SUB=... or $[...] or $.model=... or $blah (no assigment). |
| 202 | |
| 203 | def mapCDLparam(tok): |
| 204 | for i in range(1, len(tok)): |
| 205 | if not tok[i].startswith("$"): |
| 206 | continue |
| 207 | eqi = tok[i].find("=") |
| 208 | if eqi > 1: |
| 209 | pnm = tok[i][1:eqi] |
| 210 | pnml = pnm.lower() |
| 211 | if pnml in ("sub",".model"): |
| 212 | continue |
| 213 | tok[i] = tok[i][1:] |
| 214 | return tok |
| 215 | |
| 216 | def test_CDLparam(): |
| 217 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M=joe".split()))) |
| 218 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $.model=NM3 $LDD".split()))) |
| 219 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $[NM3]".split()))) |
| 220 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M=joe $X=y".split()))) |
| 221 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $.model=NM3 $Z=4 $Z=5".split()))) |
| 222 | print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= W=1 $W=2 W=3 $SUB=agnd $[NM3]".split()))) |
| 223 | |
| 224 | # Extract $SUB=<tname>. and $[mnm] (or $.model=<mnm>) from tokens. |
| 225 | # Return array of three items: [ <tname>, <mnm>, tok ] where tok is remainder. |
| 226 | # Absent $SUB= or model directives give "". |
| 227 | # Since we delete tokens, process tokens in reverse order. |
| 228 | |
| 229 | def mapCDLtermModel(tok): |
| 230 | cdlTerm="" |
| 231 | cdlModel="" |
| 232 | for i in range(len(tok)-1, 0, -1): |
| 233 | if not tok[i].startswith("$"): |
Tim Edwards | f00bbc6 | 2020-10-14 17:29:58 -0400 | [diff] [blame] | 234 | if '=' in tok[i]: |
Tim Edwards | dc8149d | 2020-10-13 14:28:03 -0400 | [diff] [blame] | 235 | continue |
| 236 | elif cdlModel == '': |
| 237 | cdlModel = tok[i] |
| 238 | del tok[i] |
| 239 | break |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 240 | tokl = tok[i].lower() |
| 241 | if tokl.startswith("$sub="): |
| 242 | if cdlTerm == "": |
| 243 | cdlTerm = tok[i][5:] |
| 244 | del tok[i] |
| 245 | continue |
| 246 | if tokl.startswith("$.model="): |
| 247 | if cdlModel == "": |
| 248 | cdlModel = tok[i][8:] |
| 249 | del tok[i] |
| 250 | continue |
| 251 | if tokl.startswith("$[") and tokl.endswith("]"): |
| 252 | if cdlModel == "": |
| 253 | cdlModel = tok[i][2:-1] |
| 254 | del tok[i] |
| 255 | continue |
| 256 | return [ cdlTerm, cdlModel, tok ] |
| 257 | |
| 258 | def test_CDLtermModel(): |
| 259 | print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM M=joe".split())) |
| 260 | print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $SUB=agnd".split())) |
| 261 | print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $SUB= $[PMOS] M=joe".split())) |
| 262 | print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $sUb=vssa $.MoDeL=PM4 M=joe".split())) |
| 263 | |
| 264 | # Determine if a single word looks like a plain numeric spice value. |
| 265 | # It means a real-number with optional scale suffix, and optional unit suffix. |
| 266 | # Only unit-suffix we support is m (meters) (because CDL-std describes it). |
| 267 | # Only scale factors supported are: t,g,meg,k,mil,m,u,n,p,f |
| 268 | # This does not arithmetically compute anything. |
| 269 | # Just returns True or False. |
| 270 | # 220p 10nm -40g 2milm .34e+3 3.1e-4 .34e+3pm 3.1e-4meg |
| 271 | # (Arguable we should strip a unit-suffix)? |
| 272 | # def isPlainNumeric(word): |
| 273 | |
| 274 | # Segregate any remaining $* items from input tokens. |
| 275 | # Return [ assignments, directives, remaining ] where each are lists. |
| 276 | # Those that look like assigments $nm=... are separated from $blah. |
| 277 | |
| 278 | def mapDiscard(tok): |
| 279 | tlen = len(tok) |
| 280 | assign=[] |
| 281 | directive=[] |
| 282 | for i in range(len(tok)-1, 0, -1): |
| 283 | if not tok[i].startswith("$"): |
| 284 | continue |
| 285 | if "=" in tok[i]: |
| 286 | assign += [ tok[i] ] |
| 287 | del tok[i] |
| 288 | continue |
| 289 | directive += [ tok[i] ] |
| 290 | del tok[i] |
| 291 | return [ assign, directive, tok ] |
| 292 | |
| 293 | def test_mapDiscard(): |
| 294 | print( mapDiscard( "m1 p1 p2 p3 p4 NM $X=4 $LDD M=joe $SUB=agnd ".split())) |
| 295 | print( mapDiscard( "m1 p1 p2 p3 p4 NM $X $LDD M=joe $SUB=agnd ".split())) |
| 296 | print( mapDiscard( "m1 p1 p2 p3 p4 NM M=joe SUB=agnd ".split())) |
| 297 | |
| 298 | # From a token-slice, partition into assignments and non-assignments. |
| 299 | # Return [ assigns, nonAssigns] where each are lists. |
| 300 | |
| 301 | def mapPartAssign(tok): |
| 302 | tlen = len(tok) |
| 303 | assign=[] |
| 304 | nona=[] |
| 305 | for i in range(len(tok)): |
| 306 | if "=" in tok[i]: |
| 307 | assign += [ tok[i] ] |
| 308 | continue |
| 309 | nona += [ tok[i] ] |
| 310 | return [ assign, nona ] |
| 311 | |
| 312 | def test_mapPartAssign(): |
| 313 | print( mapPartAssign( "NM X=4 220nm -1.2e-5g LDD M=joe".split())) |
| 314 | print( mapPartAssign( "X=4 M=joe".split())) |
| 315 | print( mapPartAssign( "NM 220nm -1.2e-5g LDD".split())) |
| 316 | print( mapPartAssign( "".split())) |
| 317 | |
| 318 | # Find an assignment to nm in the token list (nm=val). |
| 319 | # Return [val, tok]. If edit is True, the nm=val is removed from return tok. |
| 320 | # If multiple nm=... the last one is used. If del is True, all nm=... are removed. |
| 321 | |
| 322 | def mapLookup(tok, nm, edit): |
| 323 | tlen = len(tok) |
| 324 | val="" |
| 325 | nmeq = nm.lower() + "=" |
| 326 | nmeqlen = len(nmeq) |
| 327 | for i in range(len(tok)-1, 0, -1): |
| 328 | if not tok[i].lower().startswith(nmeq): |
| 329 | continue |
| 330 | if val == "": |
| 331 | val = tok[i][nmeqlen:] |
| 332 | if edit: |
| 333 | del tok[i] |
| 334 | return [ val, tok ] |
| 335 | |
| 336 | def test_mapLookup(): |
| 337 | print( mapLookup( "cnm t1 t2 area=220p PERimeter=100u M=joe par1=1".split(), "periMETER", True)) |
| 338 | print( mapLookup( "m1 p1 p2 p3 p4 NM $X=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) |
| 339 | print( mapLookup( "m1 p1 p2 p3 p4 NM X=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) |
| 340 | print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) |
| 341 | print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 X=5 xy=6 $LDD M=joe $SUB=agnd ".split(), "x", True)) |
| 342 | print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 X=5 xy=6 $LDD M=joe $SUB=agnd ".split(), "x", False)) |
| 343 | |
| 344 | # Format a diode. cdlTerm and cdlModel are passed in but ignored/unused. |
| 345 | # Processes tok and returns a final token list to be output. |
| 346 | # If after "dnm t1 t2 modelName ", there are plain numerics (index 4,5), take them as area and peri, |
| 347 | # (override other area= peri= parameters), format them as area=... peri=... |
| 348 | # (Caller already error checked the 1st minimum FOUR fields are there). |
| 349 | |
| 350 | def mapDiode(cdlTerm, cdlModel, tok, options={}): |
| 351 | ignore = options['ignore'] if 'ignore' in options else [] |
| 352 | # strip remaining $* directives |
| 353 | [ ign, ign, tok ] = mapDiscard(tok) |
| 354 | # Find explicit area= peri=, remove from tok. |
| 355 | [area, tok] = mapLookup(tok, "area", True) |
| 356 | [peri, tok] = mapLookup(tok, "peri", True) |
| 357 | for p in ignore: |
| 358 | [ign, tok] = mapLookup(tok, p, True) |
| 359 | # For just token-slice after modelName, partition into assignments and non-assigns. |
| 360 | [assign, nona] = mapPartAssign(tok[4:]) |
| 361 | tok = tok[0:4] |
| 362 | # TODO: If we have more than two non-assignments it should be an error? |
| 363 | # Override area/peri with 1st/2nd non-assigment values. |
| 364 | if len(nona) > 0: |
| 365 | area = nona.pop(0) |
| 366 | if len(nona) > 0: |
| 367 | peri = nona.pop(0) |
| 368 | if area != "": |
| 369 | tok += [ "area=" + area ] |
| 370 | if peri != "": |
| 371 | tok += [ "peri=" + peri ] |
| 372 | tok += nona |
| 373 | tok += assign |
| 374 | return tok |
| 375 | |
| 376 | def test_mapDiode(): |
| 377 | print( mapDiode( "", "", "dnm t1 t2 DN 220p 100u M=joe par1=1".split())) |
| 378 | print( mapDiode( "", "", "dnm t1 t2 DN peri=100u area=220p M=joe par1=1".split())) |
| 379 | print( mapDiode( "", "", "dnm t1 t2 DN M=joe par1=1".split())) |
| 380 | |
| 381 | # Format a mosfet. cdlTerm and cdlModel are passed in but ignored/unused. |
| 382 | # Processes tok and returns a final token list to be output. |
| 383 | # If after "mnm t1 t2 t3 t4 modelName ", there are plain numerics (index 6,7), take them as W and L, |
| 384 | # (override other W= L= parameters), format them as W=... L=... |
| 385 | # (Caller already error checked the 1st minimum SIX fields are there). |
| 386 | |
| 387 | def mapMos(cdlTerm, cdlModel, tok, options={}): |
| 388 | ignore = options['ignore'] if 'ignore' in options else [] |
| 389 | # strip remaining $* directives |
| 390 | [ ign, ign, tok ] = mapDiscard(tok) |
| 391 | # Find explicit W= L=, remove from tok. |
| 392 | [w, tok] = mapLookup(tok, "w", True) |
| 393 | [l, tok] = mapLookup(tok, "l", True) |
| 394 | for p in ignore: |
| 395 | [ign, tok] = mapLookup(tok, p, True) |
| 396 | # For scaling, find AS, PS, AD, PD, SA, SB, SC, and SD |
| 397 | [sarea, tok] = mapLookup(tok, "as", True) |
| 398 | [darea, tok] = mapLookup(tok, "ad", True) |
| 399 | [sperim, tok] = mapLookup(tok, "ps", True) |
| 400 | [dperim, tok] = mapLookup(tok, "pd", True) |
| 401 | [sa, tok] = mapLookup(tok, "sa", True) |
| 402 | [sb, tok] = mapLookup(tok, "sb", True) |
| 403 | [sd, tok] = mapLookup(tok, "sd", True) |
| 404 | |
| 405 | dscale = options['dscale'] if 'dscale' in options else '' |
| 406 | ascale = getAreaScale(dscale) |
| 407 | |
| 408 | # For just token-slice after modelName, partition into assignments and non-assigns. |
| 409 | [assign, nona] = mapPartAssign(tok[6:]) |
| 410 | tok = tok[0:6] |
| 411 | # TODO: If we have more than two non-assignments it should be an error? |
| 412 | # Override W/L with 1st/2nd non-assigment values. |
| 413 | if len(nona) > 0: |
| 414 | w = nona.pop(0) |
| 415 | if len(nona) > 0: |
| 416 | l = nona.pop(0) |
| 417 | if w != "": |
| 418 | tok += ["W=" + w + dscale] |
| 419 | if l != "": |
| 420 | tok += ["L=" + l + dscale] |
| 421 | if darea != "": |
| 422 | tok += ["AD=" + darea + ascale] |
| 423 | if sarea != "": |
| 424 | tok += ["AS=" + sarea + ascale] |
| 425 | if dperim != "": |
| 426 | tok += ["PD=" + dperim + dscale] |
| 427 | if sperim != "": |
| 428 | tok += ["PS=" + sperim + dscale] |
| 429 | if sa != "": |
| 430 | tok += ["SA=" + sa + dscale] |
| 431 | if sb != "": |
| 432 | tok += ["SB=" + sb + dscale] |
| 433 | if sd != "": |
| 434 | tok += ["SD=" + sd + dscale] |
| 435 | tok += nona |
| 436 | tok += assign |
| 437 | return tok |
| 438 | |
| 439 | def test_mapMos(): |
| 440 | print( mapMos( "", "", "mnm t1 t2 t3 t4 NM 220p 100u M=joe par1=1".split())) |
| 441 | print( mapMos( "", "", "mnm t1 t2 t3 t4 NM L=100u W=220p M=joe par1=1".split())) |
| 442 | print( mapMos( "", "", "mnm t1 t2 t3 t4 PM M=joe par1=1".split())) |
| 443 | |
| 444 | # Format a cap. |
| 445 | # Processes tok and returns a final token list to be output. |
| 446 | # Optional cdlTerm adds a 3rd terminal. |
| 447 | # If after "cnm t1 t2 ", there is plain numeric or C=numeric they are DISCARDED. |
| 448 | # area/peri/perimeter assignments are respected. Both peri/perimeter assign to perm= |
| 449 | # in the output. No perimeter= appears in the output. |
| 450 | # (Caller already error checked the 1st minimum 3 fields are there; plus cdlModel is non-null). |
| 451 | |
| 452 | def mapCap(cdlTerm, cdlModel, tok, options={}): |
| 453 | ignore = options['ignore'] if 'ignore' in options else [] |
| 454 | # strip remaining $* directives |
| 455 | [ ign, ign, tok ] = mapDiscard(tok) |
| 456 | # Find explicit area= peri= perimeter=, remove from tok. peri overwrites perimeter, |
| 457 | # both assign to perim. Lookup/discard a C=. |
| 458 | [area, tok] = mapLookup(tok, "area", True) |
| 459 | [perim, tok] = mapLookup(tok, "perimeter", True) |
| 460 | [length, tok] = mapLookup(tok, "l", True) |
| 461 | [width, tok] = mapLookup(tok, "w", True) |
| 462 | [peri, tok] = mapLookup(tok, "peri", True) |
| 463 | if peri == "": |
| 464 | peri = perim |
| 465 | [ign, tok] = mapLookup(tok, "c", True) |
| 466 | for p in ignore: |
| 467 | [ign, tok] = mapLookup(tok, p, True) |
| 468 | # For just token-slice after modelName, partition into assignments and non-assigns. |
| 469 | # We ignore the nonassignments. Need remaining assignments for M= par1=. |
| 470 | [assign, nona] = mapPartAssign(tok[3:]) |
| 471 | dscale = options['dscale'] if 'dscale' in options else '' |
| 472 | ascale = getAreaScale(dscale) |
| 473 | tok = tok[0:3] |
| 474 | if cdlTerm != "": |
| 475 | tok += [ cdlTerm ] |
| 476 | if cdlModel != "": |
| 477 | tok += [ cdlModel ] |
| 478 | if area != "": |
| 479 | tok += [ "area=" + area + ascale] |
| 480 | if peri != "": |
| 481 | tok += [ "peri=" + peri + dscale] |
| 482 | if length != "": |
| 483 | tok += [ "L=" + length + dscale] |
| 484 | if width != "": |
| 485 | tok += [ "W=" + width + dscale] |
| 486 | tok += assign |
| 487 | return tok |
| 488 | |
| 489 | def test_mapCap(): |
| 490 | print( mapCap( "", "CPP", "cnm t1 t2 area=220p peri=100u M=joe par1=1".split())) |
| 491 | print( mapCap( "", "CPP", "cnm t1 t2 area=220p perimeter=100u M=joe par1=1".split())) |
| 492 | print( mapCap( "", "CPP", "cnm t1 t2 area=220p peri=199u perimeter=100u M=joe par1=1".split())) |
| 493 | print( mapCap( "", "CPP", "cnm t1 t2 M=joe par1=1".split())) |
| 494 | print( mapCap( "", "CPP", "cnm t1 t2 C=444 area=220p peri=199u perimeter=100u M=joe par1=1".split())) |
| 495 | print( mapCap( "", "CPP", "cnm t1 t2 444 M=joe par1=1".split())) |
| 496 | print( mapCap( "agnd", "CPP2", "cnm t1 t2 $LDD 220p M=joe par1=1".split())) |
| 497 | |
| 498 | # Format a res. |
| 499 | # Processes tok and returns a final token list to be output. |
| 500 | # Optional cdlTerm adds a 3rd terminal. |
| 501 | # If after "rnm t1 t2 ", there is plain numeric or R=numeric they are DISCARDED. |
| 502 | # W/L assignments are respected. |
| 503 | # (Caller already error checked the 1st minimum 3 fields are there; plus cdlModel is non-null). |
| 504 | |
| 505 | def mapRes(cdlTerm, cdlModel, tok, options={}): |
| 506 | dscale = options['dscale'] if 'dscale' in options else '' |
| 507 | ignore = options['ignore'] if 'ignore' in options else [] |
| 508 | # strip remaining $* directives |
| 509 | [ ign, ign, tok ] = mapDiscard(tok) |
| 510 | # Find explicit w/l, remove from tok. |
| 511 | # Lookup/discard a R=. |
| 512 | [w, tok] = mapLookup(tok, "w", True) |
| 513 | [l, tok] = mapLookup(tok, "l", True) |
| 514 | [r, tok] = mapLookup(tok, "r", True) |
| 515 | for p in ignore: |
| 516 | [ign, tok] = mapLookup(tok, p, True) |
| 517 | # For just token-slice after modelName, partition into assignments and non-assigns. |
| 518 | # We ignore the nonassignments. Need remaining assignments for M= par1=. |
| 519 | [assign, nona] = mapPartAssign(tok[3:]) |
| 520 | if len(nona) > 0: |
| 521 | r = nona.pop(0) |
| 522 | tok = tok[0:3] |
| 523 | if cdlTerm != "": |
| 524 | tok += [ cdlTerm ] |
| 525 | if cdlModel != "": |
| 526 | tok += [ cdlModel ] |
| 527 | if w != "": |
| 528 | tok += [ "W=" + w + dscale] |
| 529 | if l != "": |
| 530 | tok += [ "L=" + l + dscale] |
| 531 | # Convert name "short" to zero resistance |
| 532 | if r == "short": |
| 533 | tok += [ "0" ] |
| 534 | tok += assign |
| 535 | return tok |
| 536 | |
| 537 | def test_mapRes(): |
| 538 | print( mapRes( "", "RPP1", "rnm t1 t2 w=2 L=1 M=joe par1=1".split())) |
| 539 | print( mapRes( "", "RPP1", "rnm t1 t2 444 w=2 L=1 M=joe par1=1".split())) |
| 540 | print( mapRes( "", "RPP1", "rnm t1 t2 R=444 w=2 L=1 M=joe par1=1".split())) |
| 541 | print( mapRes( "", "R2", "rnm t1 t2 L=2 W=10 M=joe par1=1".split())) |
| 542 | print( mapRes( "", "RM2", "rnm t1 t2 area=220p perim=199u perimeter=100u M=joe par1=1".split())) |
| 543 | print( mapRes( "", "RM2", "rnm t1 t2 M=joe par1=1".split())) |
| 544 | print( mapRes( "agnd", "RM3", "rnm t1 t2 $LDD 220p M=joe par1=1".split())) |
| 545 | print( mapRes( "agnd", "RM3", "rnm t1 t2 $LDD 220p L=4 W=12 M=joe par1=1".split())) |
| 546 | |
| 547 | # Format a bipolar. cdlTerm is optional. cdlModel is ignored. |
| 548 | # Processes tok and returns a final token list to be output. |
| 549 | # Optional cdlTerm adds an optional 4th terminal. |
| 550 | # If after "qnm t1 t2 t3 model", there are plain numeric (not x=y) they are DISCARDED. |
| 551 | # (Caller already error checked the 1st minimum 5 fields are there; plus cdlModel is null). |
| 552 | |
| 553 | def mapBipolar(cdlTerm, cdlModel, tok, options={}): |
| 554 | # strip remaining $* directives |
| 555 | ignore = options['ignore'] if 'ignore' in options else [] |
| 556 | [ ign, ign, tok ] = mapDiscard(tok) |
| 557 | for p in ignore: |
| 558 | [ign, tok] = mapLookup(tok, p, True) |
| 559 | # For just token-slice after modelName, partition into assignments and non-assigns. |
| 560 | # We ignore the nonassignments. Need remaining assignments for M= par1=. |
| 561 | [assign, nona] = mapPartAssign(tok[5:]) |
| 562 | # Start with "qnm t1 t2 t3". Insert optional 4th term. Then insert modelName. |
| 563 | model = tok[4] |
| 564 | tok = tok[0:4] |
| 565 | if cdlTerm != "": |
| 566 | tok += [ cdlTerm ] |
| 567 | tok += [ model ] |
| 568 | tok += assign |
| 569 | return tok |
| 570 | |
| 571 | def test_mapBipolar(): |
| 572 | print( mapBipolar( "", "any", "qnm t1 t2 t3 QP1 M=joe par1=1".split())) |
| 573 | print( mapBipolar( "", "", "qnm t1 t2 t3 QP2 M=joe par1=1".split())) |
| 574 | print( mapBipolar( "", "", "qnm t1 t2 t3 QP2 $EA=12 M=joe par1=1".split())) |
| 575 | print( mapBipolar( "", "", "qnm t1 t2 t3 QP3 M=joe EA=14 par1=1".split())) |
| 576 | print( mapBipolar( "agnd", "", "qnm t1 t2 t3 QP4 $LDD 220p M=joe par1=1".split())) |
| 577 | print( mapBipolar( "agnd", "any", "qnm t1 t2 t3 QP4 $LDD 220p L=4 W=12 M=joe par1=1".split())) |
| 578 | |
| 579 | #------------------------------------------------------------------------ |
| 580 | # Main routine to do the conversion from CDL format to SPICE format |
| 581 | #------------------------------------------------------------------------ |
| 582 | |
| 583 | def cdl2spice(fnmIn, fnmOut, options): |
| 584 | |
| 585 | err = 0 |
| 586 | warn = 0 |
| 587 | |
| 588 | # Open and read input file |
| 589 | |
| 590 | try: |
| 591 | with open(fnmIn, 'r') as inFile: |
| 592 | cdltext = inFile.read() |
| 593 | # Unwrap continuation lines |
| 594 | lines = cdltext.replace('\n+', ' ').splitlines() |
| 595 | except: |
| 596 | print('cdl2spi.py: failed to open ' + fnmIn + ' for reading.', file=sys.stderr) |
| 597 | return 1 |
| 598 | |
| 599 | # Loop over original CDL: |
| 600 | # record existing instanceNames (in subckt-context), for efficient membership |
| 601 | # tests later. Track the subckt-context, instanceNames only need to be unique |
| 602 | # within current subckt. |
| 603 | |
| 604 | sub = "" |
| 605 | for i in lines: |
| 606 | if i == "": |
| 607 | continue |
| 608 | tok = i.split() |
| 609 | tlen = len(tok) |
| 610 | if tlen == 0: |
| 611 | continue |
| 612 | t0 = tok[0].lower() |
| 613 | if t0 == '.subckt' and tlen > 1: |
| 614 | sub = tok[1].lower() |
| 615 | continue |
| 616 | if t0 == '.ends': |
| 617 | sub = "" |
| 618 | continue |
| 619 | c0 = tok[0][0].lower() |
| 620 | if c0 in '.*': |
| 621 | continue |
| 622 | # this will ignore primitive-devices (jfet) we don't support. |
| 623 | # TODO: flag them somewhere else as an ERROR. |
| 624 | if not c0 in primch2: |
| 625 | continue |
| 626 | # a primitive-device or subckt-instance we care about and support |
| 627 | # For subckt-instances record the instanceName MINUS lead x. |
| 628 | nm = tok[0] |
| 629 | if c0 == 'x': |
| 630 | nm = nm[1:] |
| 631 | hasInm[ (sub, nm) ] = 1 |
| 632 | |
| 633 | |
| 634 | # loop over original CDL: do conversions. |
| 635 | # Track the subckt-context while we go; instanceNames only need to be unique |
| 636 | # within current subckt. |
| 637 | |
| 638 | sub = "" |
| 639 | tmp = [] |
| 640 | for i in lines: |
| 641 | tok = i.split() |
| 642 | tlen = len(tok) |
| 643 | # AS-IS: empty line or all (preserved) whitespace |
| 644 | if tlen == 0: |
| 645 | tmp += [ i ] |
| 646 | continue |
| 647 | |
| 648 | # get 1st-token original, as lowercase, and 1st-char of 1st-token lowercase. |
| 649 | T0 = tok[0] |
| 650 | t0 = T0.lower() |
| 651 | c0 = t0[0] |
| 652 | |
| 653 | # AS-IS: comment |
| 654 | if c0 == '*': |
| 655 | tmp += [i] |
| 656 | continue |
| 657 | |
| 658 | # AS-IS: .ends; update subckt-context to outside-of-a-subckt |
| 659 | if t0 == '.ends': |
| 660 | sub = "" |
| 661 | tmp += [i] |
| 662 | continue |
| 663 | |
| 664 | # change .param to a comment, output it |
| 665 | if t0 == '.param': |
| 666 | tmp += ["*"+i] |
| 667 | continue |
| 668 | |
| 669 | # track .subckt context; process / in .subckt line, and output it. |
| 670 | if t0 == '.subckt': |
| 671 | if tlen < 2: |
| 672 | err+=1 |
| 673 | msg = "*cdl2spi.py: ERROR: Missing subckt name:" |
| 674 | tmp += [ msg, i ] |
| 675 | continue |
| 676 | T1 = tok[1] |
| 677 | sub = T1.lower() |
| 678 | tok = mapSubcktDef(tok) |
| 679 | tmp += [ " ".join(tok) ] |
| 680 | continue |
| 681 | |
| 682 | # subckt instance line. Process /, map instanceName (exclude x), and output it. |
| 683 | if c0 == 'x': |
| 684 | nm = T0[1:] |
| 685 | if nm == "": |
| 686 | err+=1 |
| 687 | msg = "*cdl2spi.py: ERROR: Missing subckt instance name:" |
| 688 | tmp += [ msg, i ] |
| 689 | continue |
| 690 | inm = mapInm(sub, nm) |
| 691 | tok[0] = T0[0] + inm |
| 692 | tok = mapSubcktInst(tok) |
| 693 | tmp += [ " ".join(tok) ] |
| 694 | continue |
| 695 | |
| 696 | # all primitives: need instanceName mapped, including 1st char in name. |
| 697 | # all primitives: need M=n copied to an added par1=n |
| 698 | # all primitives: Except for $SUB=... $[...] strip $ from $nm=... parameters. |
| 699 | # all primitives: Isolate $SUB and $[...] for further processing (in |
| 700 | # primitive-specific sections). |
| 701 | |
| 702 | cdlTerm="" |
| 703 | cdlModel="" |
| 704 | if c0 in primch: |
| 705 | nm = T0[1:] |
| 706 | if nm == "": |
| 707 | err+=1 |
| 708 | msg = "*cdl2spi.py: ERROR: Missing primitive instance name:" |
| 709 | tmp += [ msg, i ] |
| 710 | continue |
| 711 | nm = T0 |
| 712 | nm = mapInm(sub, nm) |
| 713 | tok[0] = nm |
| 714 | tok = mapMfactor(tok, options) |
| 715 | tok = mapCDLparam(tok) |
| 716 | [cdlTerm, cdlModel, tok] = mapCDLtermModel(tok) |
| 717 | |
| 718 | # diode formats: |
| 719 | # dname t1 t2 model <numericA> <numericP> m=... |
| 720 | # l:dname t1 t2 model {<numericA>} {<numericP>} {m=...} {$SUB=...} |
| 721 | # out format: |
| 722 | # Xdname t1 t2 model area=<numericA> peri=<numericP> m=... par1=... |
| 723 | # We flag $SUB=... : because so far (for XFAB) we CHOOSE not to support three |
| 724 | # terminal diodes. |
| 725 | # CDL-std does not define $[...] as available for diodes, so we silently ignore |
| 726 | # it. |
| 727 | # Always 2 terminals and a modelName. |
| 728 | # We already have peri=... and area=... and have ambiguity with plain numerics. |
| 729 | # TODO: generate a warning in case of ambiguity, but prefer plain numerics |
| 730 | # (with nm= added). |
| 731 | |
| 732 | if c0 == "d": |
| 733 | tlen = len(tok) |
| 734 | if tlen < 4: |
| 735 | err+=1 |
| 736 | msg = "*cdl2spi.py: ERROR: Diode does not have minimum two terminals and model:" |
| 737 | tmp += [ msg, i ] |
| 738 | continue |
| 739 | if cdlTerm != "": |
| 740 | err+=1 |
| 741 | msg = "*cdl2spi.py: ERROR: Diode does not support $SUB=...:" |
| 742 | tmp += [ msg, i ] |
| 743 | continue |
| 744 | tok = mapDiode(cdlTerm, cdlModel, tok, options) |
| 745 | # add X to tok0. |
| 746 | if options['subckt']: |
| 747 | tok[0] = "X" + tok[0] |
| 748 | tmp += [ " ".join(tok) ] |
| 749 | continue |
| 750 | |
| 751 | # mosfet formats: |
| 752 | # mname t1 t2 t3 t4 model W=... L=... m=... |
| 753 | # l:mname t1 t2 t3 t4 model {W=... L=...} {m=...} {$NONSWAP} {$LDD[type]} |
| 754 | # l:mname t1 t2 t3 t4 model <width> <length> {m=...} {$NONSWAP} {$LDD[type]} |
| 755 | # output format: |
| 756 | # Xmname t1 t2 t3 t4 model W=... L=... m=... par1=... |
| 757 | # Fixed 4 terminals and a modelName. |
| 758 | # May already have W= L= and ambiguity with plain numerics. |
| 759 | # TODO: generate a warning in case of ambiguity, but prefer plain numerics |
| 760 | # (with nm= added). |
| 761 | if c0 == "m": |
| 762 | tlen = len(tok) |
| 763 | if tlen < 6: |
| 764 | err+=1 |
| 765 | msg = "*cdl2spi.py: ERROR: Mosfet does not have minimum four terminals and model:" |
| 766 | tmp += [ msg, i ] |
| 767 | continue |
| 768 | if cdlTerm != "": |
| 769 | err+=1 |
| 770 | msg = "*cdl2spi.py: ERROR: Mosfet does not support $SUB=...:" |
| 771 | tmp += [ msg, i ] |
| 772 | continue |
| 773 | tok = mapMos(cdlTerm, cdlModel, tok, options) |
| 774 | # add X to tok0. |
| 775 | if options['subckt']: |
| 776 | tok[0] = "X" + tok[0] |
| 777 | tmp += [ " ".join(tok) ] |
| 778 | continue |
| 779 | |
| 780 | # cap formats: |
| 781 | # cname t1 t2 <numeric0> $[model] $SUB=t3 m=... |
| 782 | # cname t1 t2 <numeric0> $[model] m=... |
| 783 | #? cname t1 t2 C=<numeric0> $[model] $SUB=t3 m=... |
| 784 | #? cname t1 t2 <numeric0> $[model] $SUB=t3 area=<numericA> perimeter=<numericP> m=... |
| 785 | #? cname t1 t2 <numeric0> $[model] $SUB=t3 area=<numericA> peri=<numericP> m=... |
| 786 | #l:cname t1 t2 {<numeric0>} {$[model]} {$SUB=t3} {m=...} |
| 787 | # out formats: |
| 788 | # Xcname t1 t2 model area=<numericA> peri=<numericP> m=... par1=... |
| 789 | # Xcname t1 t2 t3 model area=<numericA> peri=<numericP> m=... par1=... |
| 790 | # We require inm, two terminals. Require $[model]. Optional 3rd-term $SUB=... |
| 791 | # If both peri and perimeter, peri overrides. |
| 792 | # Both area/peri are optional. The optional [C=]numeric0 is discarded always. |
| 793 | |
| 794 | if c0 == "c": |
| 795 | tlen = len(tok) |
| 796 | if tlen < 3: |
| 797 | err+=1 |
| 798 | msg = "*cdl2spi.py: ERROR: Cap does not have minimum two terminals:" |
| 799 | tmp += [ msg, i ] |
| 800 | continue |
| 801 | if cdlModel == "": |
| 802 | err+=1 |
| 803 | msg = "*cdl2spi.py: ERROR: Cap missing required $[<model>] directive:" |
| 804 | tmp += [ msg, i ] |
| 805 | continue |
| 806 | tok = mapCap(cdlTerm, cdlModel, tok, options) |
| 807 | # add X to tok0. |
| 808 | if options['subckt']: |
| 809 | tok[0] = "X" + tok[0] |
| 810 | tmp += [ " ".join(tok) ] |
| 811 | continue |
| 812 | |
| 813 | # res formats: |
| 814 | # rname n1 n2 <numeric> $SUB=t3 $[model] $w=... $l=... m=... |
| 815 | # c:rname n1 n2 R=<numeric> $[model] w=... l=... m=... $SUB=t3 |
| 816 | # l:rname n1 n2 {<numeric>} {$SUB=t3} {$[model]} {$w=...} {$l=...} {m=...} |
| 817 | # (all after n1,n2 optional) |
| 818 | # We require $[model]. And add 3rd term IFF $SUB=. |
| 819 | # out format: |
| 820 | # Xrname n1 n2 t3 model w=... l=... m=... par1=... |
| 821 | if c0 == "r": |
| 822 | tlen = len(tok) |
| 823 | if tlen < 3: |
| 824 | err+=1 |
| 825 | msg = "*cdl2spi.py: ERROR: Res does not have minimum two terminals:" |
| 826 | tmp += [ msg, i ] |
| 827 | continue |
| 828 | if cdlModel == "": |
| 829 | err+=1 |
| 830 | msg = "*cdl2spi.py: ERROR: Res missing required $[<model>] directive:" |
| 831 | tmp += [ msg, i ] |
| 832 | continue |
| 833 | tok = mapRes(cdlTerm, cdlModel, tok, options) |
| 834 | # add X to tok0. |
| 835 | if options['subckt']: |
| 836 | tok[0] = "X" + tok[0] |
| 837 | tmp += [ " ".join(tok) ] |
| 838 | continue |
| 839 | |
| 840 | # bipolar formats: |
| 841 | # qname n1 n2 n3 model <numeric> M=... $EA=... |
| 842 | # qname n1 n2 n3 model $EA=... <numeric> M=... |
| 843 | # qname n1 n2 n3 model {$EA=...} {$W=...} {$L=...} {$SUB=...} {M=...} |
| 844 | # No: l:qname n1 n2 n3 {nsub} model {$EA=...} {$W=...} {$L=...} {$SUB=...} {M=...} |
| 845 | # CDL-std adds {nsub} way to add substrate before model: We don't support it. |
| 846 | # Add 3rd term IFF $SUB=. We propagate optional W/L (or derived from $W/$L). |
| 847 | # EA is emitterSize; not supported by XFAB: deleted. |
| 848 | # We require 3-terminals and model. It is an error to specify $[model]. |
| 849 | # |
| 850 | # out format: |
| 851 | # Xqname n1 n2 n3 model M=... par1=... |
| 852 | if c0 == "q": |
| 853 | tlen = len(tok) |
| 854 | if tlen < 5: |
| 855 | err+=1 |
| 856 | msg = "*cdl2spi.py: ERROR: Bipolar does not have minimum three terminals and a model:" |
| 857 | tmp += [ msg, i ] |
| 858 | continue |
| 859 | if cdlModel != "": |
| 860 | err+=1 |
| 861 | msg = "*cdl2spi.py: ERROR: Bipolar does not support $[<model>] directive:" |
| 862 | tmp += [ msg, i ] |
| 863 | continue |
| 864 | tok = mapBipolar(cdlTerm, cdlModel, tok, options) |
| 865 | # add X to tok0. |
| 866 | if options['subckt']: |
| 867 | tok[0] = "X" + tok[0] |
| 868 | tmp += [ " ".join(tok) ] |
| 869 | continue |
| 870 | |
| 871 | # Anything else. What to do, preserve AS-IS with warning, or |
| 872 | # flag them as ERRORs? |
| 873 | tmp += [ "*cdl2spi.py: ERROR: unrecognized line:", i ] |
| 874 | err+=1 |
| 875 | # tmp += [ "*cdl2spi.py: WARNING: unrecognized line:", " ".join(tok) ] |
| 876 | # tmp += [ "*cdl2spi.py: WARNING: unrecognized line:", i ] |
| 877 | # warn+=1 |
| 878 | |
| 879 | # Re-wrap continuation lines at 80 characters |
| 880 | lines = [] |
| 881 | for line in tmp: |
| 882 | lines.append('\n+ '.join(textwrap.wrap(line, 80))) |
| 883 | |
| 884 | # Write output |
| 885 | |
| 886 | if fnmOut == sys.stdout: |
| 887 | for i in lines: |
| 888 | print(i) |
| 889 | else: |
| 890 | try: |
| 891 | with open(fnmOut, 'w') as outFile: |
| 892 | for i in lines: |
| 893 | print(i, file=outFile) |
| 894 | except: |
| 895 | print('cdl2spi.py: failed to open ' + fnmOut + ' for writing.', file=sys.stderr) |
| 896 | return 1 |
| 897 | |
| 898 | # exit status: indicates if there were errors. |
| 899 | print( "*cdl2spi.py: %d errors, %d warnings" % (err, warn)) |
| 900 | return err |
| 901 | |
| 902 | if __name__ == '__main__': |
| 903 | |
| 904 | options = {} |
| 905 | |
| 906 | # Set option defaults |
| 907 | options['debug'] = False |
| 908 | options['subckt'] = False |
| 909 | options['dscale'] = '' |
| 910 | options['addinm'] = [] |
| 911 | options['ignore'] = [] |
| 912 | |
| 913 | arguments = [] |
| 914 | for item in sys.argv[1:]: |
| 915 | if item.find('-', 0) == 0: |
| 916 | thisopt = item.split('=') |
| 917 | optname = thisopt[0][1:] |
| 918 | optval = '='.join(thisopt[1:]) |
| 919 | if not optname in options: |
| 920 | print('Unknown option -' + optname + '; ignoring.') |
| 921 | else: |
| 922 | lastoptval = options[optname] |
| 923 | if len(thisopt) == 1: |
| 924 | options[optname] = True |
| 925 | elif lastoptval == '': |
| 926 | options[optname] = optval |
| 927 | else: |
| 928 | options[optname].append(optval) |
| 929 | else: |
| 930 | arguments.append(item) |
| 931 | |
| 932 | # Supported primitive devices (FET, diode, resistor, capacitor, bipolar) |
| 933 | primch = 'mdrcq' |
| 934 | primch2 = 'mdrcqx' |
| 935 | |
| 936 | if len(arguments) > 0: |
| 937 | fnmIn = arguments[0] |
| 938 | |
| 939 | if len(arguments) > 1: |
| 940 | fnmOut = arguments[1] |
| 941 | else: |
| 942 | fnmOut = sys.stdout |
| 943 | |
| 944 | if options['debug']: |
| 945 | test_mapSubcktInst1() |
| 946 | test_mapSubcktInst2() |
| 947 | test_mapMfactor() |
| 948 | test_CDLparam() |
| 949 | test_CDLtermModel() |
| 950 | test_mapDiscard() |
| 951 | test_mapPartAssign() |
| 952 | test_mapLookup() |
| 953 | test_mapDiode() |
| 954 | test_mapMos() |
| 955 | test_mapCap() |
| 956 | test_mapRes() |
| 957 | test_mapBipolar() |
| 958 | |
| 959 | elif len(arguments) > 2 or len(arguments) < 1 : |
| 960 | print('Usage: cdl2spi.py <cdlFileName> [<spiFileName>]') |
| 961 | print(' Options:' ) |
| 962 | print(' -debug run debug tests') |
| 963 | print(' -dscale=<suffix> rescale lengths with <suffix>') |
| 964 | print(' -addinm=<param> add multiplier parameter <param>') |
| 965 | print(' -ignore=<param> ignore parameter <param>') |
| 966 | print(' -subckt convert primitive devices to subcircuits') |
| 967 | sys.exit(1) |
| 968 | |
| 969 | else: |
| 970 | if options['debug'] == True: |
| 971 | print('Diagnostic: options = ' + str(options)) |
| 972 | result = cdl2spice(fnmIn, fnmOut, options) |
| 973 | sys.exit(result) |
| 974 | |