The fix to add "-S" to "env" to allow the "-B" switch to be passed to python apparently doesn't work in CentOS, so both switches have been removed. Moved the cdl2spi.py script back to common/ because it is used by scripts in both common/ and runtime/; an additional line in the top-level Makefile is then needed to install cdl2spi.py.
diff --git a/runtime/cace.py b/runtime/cace.py index 9de1a7e..6106677 100755 --- a/runtime/cace.py +++ b/runtime/cace.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env -S python3 -B +#!/usr/bin/env python3 # #-------------------------------------------------------- # Project Manager GUI.
diff --git a/runtime/cace_datasheet_upload.py b/runtime/cace_datasheet_upload.py index 7c6b019..af5db63 100755 --- a/runtime/cace_datasheet_upload.py +++ b/runtime/cace_datasheet_upload.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env -S python3 -B +#!/usr/bin/env python3 # # cace_datasheet_upload.py #
diff --git a/runtime/cdl2spi.py b/runtime/cdl2spi.py deleted file mode 100755 index f597a90..0000000 --- a/runtime/cdl2spi.py +++ /dev/null
@@ -1,974 +0,0 @@ -#!/usr/bin/env python3 -""" -cdl2spi.py : netlist processor -Copyright (c) 2016, 2020 efabless Corporation. -All rights reserved. - -usage: cdl2spi.py <inCDLfile> [<outSPCfile>] [options...] -Writes to .spi to outSPCfile, or stdout if no output argument given. Sets exit -status if there were non-zero errors. Most errors/warnings are annotated in-line -in the stdout each before the relevant line. -""" - -import sys, getopt -import os -import re -import textwrap - -# Convert linear scale to area scale suffix -# (e.g., if linear scale is 1e-6 ('u') then area scales as 1e-12 ('p')) - -def getAreaScale(dscale): - ascale = '' - if dscale == 'm': - ascale = 'u' - elif dscale == 'u': - ascale = 'p' - elif dscale == 'n': - ascale = 'a' - return ascale - -# Check nm (instanceName) in the context of sub (subckt): is it used yet? -# If not used yet, mark it used, and return as-is. -# Else generate a unique suffixed version, and mark it used, return it. -# If 1M suffixes don't generate a unique name, throw exception. -# hasInm : global hash, key of hash is (subckt, iname) - -hasInm = {} -def uniqInm(sub, nm): - subl=sub.lower() - nml=nm.lower() - if not (subl, nml) in hasInm: - hasInm[ (subl, nml) ] = 1 - return nm - for i in range(1000000): - nm2 = nm + "_q" + str(i) - nm2l = nm2.lower() - if not (subl, nm2l) in hasInm: - hasInm[ (subl, nm2l) ] = 1 - return nm2 - # not caught anywhere, and gives (intended) non-zero exit status - raise AssertionError("uniqInm: range overflow for (%s,%s)" % (sub, nm)) - -# Map illegal characters in an nm (instanceName) in context of sub (subckt). -# For ngspice, '/' is illegal in instanceNames. Replace it with '|', BUT -# then make sure the new name is still unique: does not collide with a name -# used so far or another already derived unique name. - -inmBadChars='/' -inmRplChars='|' -inmBadCharREX=re.compile( "["+ inmBadChars+"]" ) - -def mapInm(sub, nm): - nm2 = inmBadCharREX.sub(inmRplChars, nm) - return uniqInm(sub, nm2) - -# Process subckt line (array of tokens). Return new array of tokens. -# There might be a ' /' in the line that needs to be deleted. It may be standalone ' / ', or -# butting against the next token. It may be before all pins, after all pins, or between pins. -# Do not touch / in a parameter assignment expression. -# Do not touch / embedded in a pinName. -# May touch / butting front of very first parameter assignment expression. -# .subckt NM / p1 p2 p3 x=y g=h -# .subckt NM /p1 p2 p3 x=y g=h -# .subckt NM p1 p2 / p3 x=y g=h -# .subckt NM p1 p2 /p3 x=y g=h -# .subckt NM p1 p2 p3 / x=y g=h -# .subckt NM p1 p2 p3 /x=y g=h -# .subckt NM p1 p2 p3 x=y g=(a/b) (don't touch this /) -# .subckt NM p1 p2/3/4 p3 x=y g=(a/b) (don't touch these /) - -def mapSubcktDef(tok): - # find index of one-past first token (beyond ".subckt NM") containing an =, if any - param0 = len(tok) - for i in range(2, len(tok)): - if '=' in tok[i]: - param0 = i+1 - break - # find first token before or including that 1st-param, starting with /: - # strip the slash. - for i in range(2, param0): - if tok[i][0] == '/': - tok[i] = tok[i][1:] - if tok[i] == "": - del tok[i] - break - return tok - -def test_mapSubcktInst1(): - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3".split()))) - print( " ".join(mapSubcktDef( ".subckt abc / p1 p2 p3".split()))) - print( " ".join(mapSubcktDef( ".subckt abc /p1 p2 p3".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 /p3".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 / p3".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4 /y=5".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4/2 y=5".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 / x=4/2 y=5".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 x=4/2 /y=5".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1 p2 p3 /x=4/2 y=5".split()))) - print( " ".join(mapSubcktDef( ".subckt abc p1/2/3 p2 p3 /x=4/2 y=5".split()))) - -# Process subckt instance line (array of tokens). Return new array of tokens. -# (This function does not map possible illegal-chars in instanceName). -# There might be a ' /' in the line that needs to be deleted. It may be standalone ' / ', or -# butting against the next token. It can only be after pins, before or butting subcktName. -# -# Do not touch / in, butting, or after 1st parameter assignment expression. -# Do not touch / embedded in a netName. -# Do not touch / embedded in instanceName (they are handled separately elsewhere). -# xa/b/c p1 p2 p3 / NM x=y g=h -# xa/b/c p1 p2 p3 /NM x=y g=h -# xabc p1 p2/3/4 p3 /NM x=(a/b) g=h -# xabc p1 p2/3/4 p3 / NM x=(a/b) g=h -# xabc p1 p2/3/4 p3 NM x=(a/b) / g=h (don't touch; perhaps needs to be an error trapped somewhere) -# xabc p1 p2/3/4 p3 NM / x=(a/b) g=h (don't touch; perhaps needs to be an error trapped somewhere) -# xa/b/c p1 p2/3/4 p3 NM x=(a/b) g=h (don't touch these /) - -def mapSubcktInst(tok): - # find index of first token (beyond "x<iname>") containing an =, if any - param0 = tlen = len(tok) - for i in range(1, tlen): - if '=' in tok[i]: - param0 = i - break - # Determine modelName index. Either just prior to 1st-param (if any) else last token. - modndx = tlen - 1 - if param0 < tlen: - modndx = param0 - 1; - # If modndx now points to a standalone /, that can't be (would yield missing/empty modelName). - # Actual modelName must be before it. We need to check, possibly strip / on/before actual modelName. - # (Even though standlone / after model are most likely an independent error: we don't touch 'em). - while modndx > 1 and tok[modndx] == "/": - modndx-=1 - # Check for standalone / before modelName. Else for modelName starting with /. - slashndx = modndx - 1 - if slashndx > 0 and tok[slashndx] == "/": - del tok[slashndx] - else: - if modndx > 0 and tok[modndx].startswith("/"): - tok[modndx] = tok[modndx][1:] - return tok - -def test_mapSubcktInst2(): - print( " ".join(mapSubcktInst( "xa/b/c p1 p2 p3 / NM x=y g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c p1 p2 p3 /NM x=y g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 /NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 / NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 NM x=(a/b) / g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 NM / x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 /NM / x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xabc p1 p2/3/4 p3 / NM / x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c p1 p2/3/4 p3 NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c / NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c /NM x=(a/b) g=h".split()))) - print( " ".join(mapSubcktInst( "xa/b/c /NM".split()))) - -# Primitives with M=<n> need to add additional par1=<n>. -# Process token list, return new token list. -# note: line at this point may be like: m... p1 p2 p3 p4 NMOS M=1 $blah W=... L=... -# meaning M=1 is not necessarily in a block of all parameter-assignments at EOL. -# But by processing the line from end backwards, we pick up LAST M= if there are -# multiple (which condition really should get flagged as an error). -# And M= is more likely towards end of the line than front of line (thus faster). -# If "M=" with no value, do nothing (should also be a flagged error). - -def mapMfactor(tok, options={}): - # find index of M=* if any, starting from end. - # "addinm" is an additional parameter that takes the same argument as M - addinm = options['addinm'] if 'addinm' in options else [] - mndx = 0 - val = "" - for i in range(len(tok)-1, 0, -1): - if tok[i].lower().startswith("m="): - mndx = i - break - if mndx > 0: - val = tok[i][2:] - if val != "": - for p in addinm: - tok += [ addinm + val] - return tok - -def test_mapMfactor(): - print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M=joe".split()))) - print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M= $SUB=agnd".split()))) - print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM M=2 $SUB=agnd WM=4".split()))) - print( " ".join(mapMfactor( "m1 p1 p2 p3 p4 NM".split()))) - -# From $nm=... strip the $. Preserve order on the line. No attempt to -# detect any resultant collisions. "W=5 $W=10" becomes "W=5 W=10". -# Don't touch $SUB=... or $[...] or $.model=... or $blah (no assigment). - -def mapCDLparam(tok): - for i in range(1, len(tok)): - if not tok[i].startswith("$"): - continue - eqi = tok[i].find("=") - if eqi > 1: - pnm = tok[i][1:eqi] - pnml = pnm.lower() - if pnml in ("sub",".model"): - continue - tok[i] = tok[i][1:] - return tok - -def test_CDLparam(): - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M=joe".split()))) - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $.model=NM3 $LDD".split()))) - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $[NM3]".split()))) - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M=joe $X=y".split()))) - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= $SUB=agnd $.model=NM3 $Z=4 $Z=5".split()))) - print( " ".join(mapCDLparam( "m1 p1 p2 p3 p4 NM M= W=1 $W=2 W=3 $SUB=agnd $[NM3]".split()))) - -# Extract $SUB=<tname>. and $[mnm] (or $.model=<mnm>) from tokens. -# Return array of three items: [ <tname>, <mnm>, tok ] where tok is remainder. -# Absent $SUB= or model directives give "". -# Since we delete tokens, process tokens in reverse order. - -def mapCDLtermModel(tok): - cdlTerm="" - cdlModel="" - for i in range(len(tok)-1, 0, -1): - if not tok[i].startswith("$"): - if '=' in tok[i]: - continue - elif cdlModel == '': - cdlModel = tok[i] - del tok[i] - break - tokl = tok[i].lower() - if tokl.startswith("$sub="): - if cdlTerm == "": - cdlTerm = tok[i][5:] - del tok[i] - continue - if tokl.startswith("$.model="): - if cdlModel == "": - cdlModel = tok[i][8:] - del tok[i] - continue - if tokl.startswith("$[") and tokl.endswith("]"): - if cdlModel == "": - cdlModel = tok[i][2:-1] - del tok[i] - continue - return [ cdlTerm, cdlModel, tok ] - -def test_CDLtermModel(): - print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM M=joe".split())) - print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $SUB=agnd".split())) - print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $SUB= $[PMOS] M=joe".split())) - print( mapCDLtermModel( "m1 p1 p2 p3 p4 NM $sUb=vssa $.MoDeL=PM4 M=joe".split())) - -# Determine if a single word looks like a plain numeric spice value. -# It means a real-number with optional scale suffix, and optional unit suffix. -# Only unit-suffix we support is m (meters) (because CDL-std describes it). -# Only scale factors supported are: t,g,meg,k,mil,m,u,n,p,f -# This does not arithmetically compute anything. -# Just returns True or False. -# 220p 10nm -40g 2milm .34e+3 3.1e-4 .34e+3pm 3.1e-4meg -# (Arguable we should strip a unit-suffix)? -# def isPlainNumeric(word): - -# Segregate any remaining $* items from input tokens. -# Return [ assignments, directives, remaining ] where each are lists. -# Those that look like assigments $nm=... are separated from $blah. - -def mapDiscard(tok): - tlen = len(tok) - assign=[] - directive=[] - for i in range(len(tok)-1, 0, -1): - if not tok[i].startswith("$"): - continue - if "=" in tok[i]: - assign += [ tok[i] ] - del tok[i] - continue - directive += [ tok[i] ] - del tok[i] - return [ assign, directive, tok ] - -def test_mapDiscard(): - print( mapDiscard( "m1 p1 p2 p3 p4 NM $X=4 $LDD M=joe $SUB=agnd ".split())) - print( mapDiscard( "m1 p1 p2 p3 p4 NM $X $LDD M=joe $SUB=agnd ".split())) - print( mapDiscard( "m1 p1 p2 p3 p4 NM M=joe SUB=agnd ".split())) - -# From a token-slice, partition into assignments and non-assignments. -# Return [ assigns, nonAssigns] where each are lists. - -def mapPartAssign(tok): - tlen = len(tok) - assign=[] - nona=[] - for i in range(len(tok)): - if "=" in tok[i]: - assign += [ tok[i] ] - continue - nona += [ tok[i] ] - return [ assign, nona ] - -def test_mapPartAssign(): - print( mapPartAssign( "NM X=4 220nm -1.2e-5g LDD M=joe".split())) - print( mapPartAssign( "X=4 M=joe".split())) - print( mapPartAssign( "NM 220nm -1.2e-5g LDD".split())) - print( mapPartAssign( "".split())) - -# Find an assignment to nm in the token list (nm=val). -# Return [val, tok]. If edit is True, the nm=val is removed from return tok. -# If multiple nm=... the last one is used. If del is True, all nm=... are removed. - -def mapLookup(tok, nm, edit): - tlen = len(tok) - val="" - nmeq = nm.lower() + "=" - nmeqlen = len(nmeq) - for i in range(len(tok)-1, 0, -1): - if not tok[i].lower().startswith(nmeq): - continue - if val == "": - val = tok[i][nmeqlen:] - if edit: - del tok[i] - return [ val, tok ] - -def test_mapLookup(): - print( mapLookup( "cnm t1 t2 area=220p PERimeter=100u M=joe par1=1".split(), "periMETER", True)) - print( mapLookup( "m1 p1 p2 p3 p4 NM $X=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) - print( mapLookup( "m1 p1 p2 p3 p4 NM X=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) - print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 $LDD M=joe $SUB=agnd ".split(), "x", True)) - print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 X=5 xy=6 $LDD M=joe $SUB=agnd ".split(), "x", True)) - print( mapLookup( "m1 p1 p2 p3 p4 NM x=4 X=5 xy=6 $LDD M=joe $SUB=agnd ".split(), "x", False)) - -# Format a diode. cdlTerm and cdlModel are passed in but ignored/unused. -# Processes tok and returns a final token list to be output. -# If after "dnm t1 t2 modelName ", there are plain numerics (index 4,5), take them as area and peri, -# (override other area= peri= parameters), format them as area=... peri=... -# (Caller already error checked the 1st minimum FOUR fields are there). - -def mapDiode(cdlTerm, cdlModel, tok, options={}): - ignore = options['ignore'] if 'ignore' in options else [] - # strip remaining $* directives - [ ign, ign, tok ] = mapDiscard(tok) - # Find explicit area= peri=, remove from tok. - [area, tok] = mapLookup(tok, "area", True) - [peri, tok] = mapLookup(tok, "peri", True) - for p in ignore: - [ign, tok] = mapLookup(tok, p, True) - # For just token-slice after modelName, partition into assignments and non-assigns. - [assign, nona] = mapPartAssign(tok[4:]) - tok = tok[0:4] - # TODO: If we have more than two non-assignments it should be an error? - # Override area/peri with 1st/2nd non-assigment values. - if len(nona) > 0: - area = nona.pop(0) - if len(nona) > 0: - peri = nona.pop(0) - if area != "": - tok += [ "area=" + area ] - if peri != "": - tok += [ "peri=" + peri ] - tok += nona - tok += assign - return tok - -def test_mapDiode(): - print( mapDiode( "", "", "dnm t1 t2 DN 220p 100u M=joe par1=1".split())) - print( mapDiode( "", "", "dnm t1 t2 DN peri=100u area=220p M=joe par1=1".split())) - print( mapDiode( "", "", "dnm t1 t2 DN M=joe par1=1".split())) - -# Format a mosfet. cdlTerm and cdlModel are passed in but ignored/unused. -# Processes tok and returns a final token list to be output. -# If after "mnm t1 t2 t3 t4 modelName ", there are plain numerics (index 6,7), take them as W and L, -# (override other W= L= parameters), format them as W=... L=... -# (Caller already error checked the 1st minimum SIX fields are there). - -def mapMos(cdlTerm, cdlModel, tok, options={}): - ignore = options['ignore'] if 'ignore' in options else [] - # strip remaining $* directives - [ ign, ign, tok ] = mapDiscard(tok) - # Find explicit W= L=, remove from tok. - [w, tok] = mapLookup(tok, "w", True) - [l, tok] = mapLookup(tok, "l", True) - for p in ignore: - [ign, tok] = mapLookup(tok, p, True) - # For scaling, find AS, PS, AD, PD, SA, SB, SC, and SD - [sarea, tok] = mapLookup(tok, "as", True) - [darea, tok] = mapLookup(tok, "ad", True) - [sperim, tok] = mapLookup(tok, "ps", True) - [dperim, tok] = mapLookup(tok, "pd", True) - [sa, tok] = mapLookup(tok, "sa", True) - [sb, tok] = mapLookup(tok, "sb", True) - [sd, tok] = mapLookup(tok, "sd", True) - - dscale = options['dscale'] if 'dscale' in options else '' - ascale = getAreaScale(dscale) - - # For just token-slice after modelName, partition into assignments and non-assigns. - [assign, nona] = mapPartAssign(tok[6:]) - tok = tok[0:6] - # TODO: If we have more than two non-assignments it should be an error? - # Override W/L with 1st/2nd non-assigment values. - if len(nona) > 0: - w = nona.pop(0) - if len(nona) > 0: - l = nona.pop(0) - if w != "": - tok += ["W=" + w + dscale] - if l != "": - tok += ["L=" + l + dscale] - if darea != "": - tok += ["AD=" + darea + ascale] - if sarea != "": - tok += ["AS=" + sarea + ascale] - if dperim != "": - tok += ["PD=" + dperim + dscale] - if sperim != "": - tok += ["PS=" + sperim + dscale] - if sa != "": - tok += ["SA=" + sa + dscale] - if sb != "": - tok += ["SB=" + sb + dscale] - if sd != "": - tok += ["SD=" + sd + dscale] - tok += nona - tok += assign - return tok - -def test_mapMos(): - print( mapMos( "", "", "mnm t1 t2 t3 t4 NM 220p 100u M=joe par1=1".split())) - print( mapMos( "", "", "mnm t1 t2 t3 t4 NM L=100u W=220p M=joe par1=1".split())) - print( mapMos( "", "", "mnm t1 t2 t3 t4 PM M=joe par1=1".split())) - -# Format a cap. -# Processes tok and returns a final token list to be output. -# Optional cdlTerm adds a 3rd terminal. -# If after "cnm t1 t2 ", there is plain numeric or C=numeric they are DISCARDED. -# area/peri/perimeter assignments are respected. Both peri/perimeter assign to perm= -# in the output. No perimeter= appears in the output. -# (Caller already error checked the 1st minimum 3 fields are there; plus cdlModel is non-null). - -def mapCap(cdlTerm, cdlModel, tok, options={}): - ignore = options['ignore'] if 'ignore' in options else [] - # strip remaining $* directives - [ ign, ign, tok ] = mapDiscard(tok) - # Find explicit area= peri= perimeter=, remove from tok. peri overwrites perimeter, - # both assign to perim. Lookup/discard a C=. - [area, tok] = mapLookup(tok, "area", True) - [perim, tok] = mapLookup(tok, "perimeter", True) - [length, tok] = mapLookup(tok, "l", True) - [width, tok] = mapLookup(tok, "w", True) - [peri, tok] = mapLookup(tok, "peri", True) - if peri == "": - peri = perim - [ign, tok] = mapLookup(tok, "c", True) - for p in ignore: - [ign, tok] = mapLookup(tok, p, True) - # For just token-slice after modelName, partition into assignments and non-assigns. - # We ignore the nonassignments. Need remaining assignments for M= par1=. - [assign, nona] = mapPartAssign(tok[3:]) - dscale = options['dscale'] if 'dscale' in options else '' - ascale = getAreaScale(dscale) - tok = tok[0:3] - if cdlTerm != "": - tok += [ cdlTerm ] - if cdlModel != "": - tok += [ cdlModel ] - if area != "": - tok += [ "area=" + area + ascale] - if peri != "": - tok += [ "peri=" + peri + dscale] - if length != "": - tok += [ "L=" + length + dscale] - if width != "": - tok += [ "W=" + width + dscale] - tok += assign - return tok - -def test_mapCap(): - print( mapCap( "", "CPP", "cnm t1 t2 area=220p peri=100u M=joe par1=1".split())) - print( mapCap( "", "CPP", "cnm t1 t2 area=220p perimeter=100u M=joe par1=1".split())) - print( mapCap( "", "CPP", "cnm t1 t2 area=220p peri=199u perimeter=100u M=joe par1=1".split())) - print( mapCap( "", "CPP", "cnm t1 t2 M=joe par1=1".split())) - print( mapCap( "", "CPP", "cnm t1 t2 C=444 area=220p peri=199u perimeter=100u M=joe par1=1".split())) - print( mapCap( "", "CPP", "cnm t1 t2 444 M=joe par1=1".split())) - print( mapCap( "agnd", "CPP2", "cnm t1 t2 $LDD 220p M=joe par1=1".split())) - -# Format a res. -# Processes tok and returns a final token list to be output. -# Optional cdlTerm adds a 3rd terminal. -# If after "rnm t1 t2 ", there is plain numeric or R=numeric they are DISCARDED. -# W/L assignments are respected. -# (Caller already error checked the 1st minimum 3 fields are there; plus cdlModel is non-null). - -def mapRes(cdlTerm, cdlModel, tok, options={}): - dscale = options['dscale'] if 'dscale' in options else '' - ignore = options['ignore'] if 'ignore' in options else [] - # strip remaining $* directives - [ ign, ign, tok ] = mapDiscard(tok) - # Find explicit w/l, remove from tok. - # Lookup/discard a R=. - [w, tok] = mapLookup(tok, "w", True) - [l, tok] = mapLookup(tok, "l", True) - [r, tok] = mapLookup(tok, "r", True) - for p in ignore: - [ign, tok] = mapLookup(tok, p, True) - # For just token-slice after modelName, partition into assignments and non-assigns. - # We ignore the nonassignments. Need remaining assignments for M= par1=. - [assign, nona] = mapPartAssign(tok[3:]) - if len(nona) > 0: - r = nona.pop(0) - tok = tok[0:3] - if cdlTerm != "": - tok += [ cdlTerm ] - if cdlModel != "": - tok += [ cdlModel ] - if w != "": - tok += [ "W=" + w + dscale] - if l != "": - tok += [ "L=" + l + dscale] - # Convert name "short" to zero resistance - if r == "short": - tok += [ "0" ] - tok += assign - return tok - -def test_mapRes(): - print( mapRes( "", "RPP1", "rnm t1 t2 w=2 L=1 M=joe par1=1".split())) - print( mapRes( "", "RPP1", "rnm t1 t2 444 w=2 L=1 M=joe par1=1".split())) - print( mapRes( "", "RPP1", "rnm t1 t2 R=444 w=2 L=1 M=joe par1=1".split())) - print( mapRes( "", "R2", "rnm t1 t2 L=2 W=10 M=joe par1=1".split())) - print( mapRes( "", "RM2", "rnm t1 t2 area=220p perim=199u perimeter=100u M=joe par1=1".split())) - print( mapRes( "", "RM2", "rnm t1 t2 M=joe par1=1".split())) - print( mapRes( "agnd", "RM3", "rnm t1 t2 $LDD 220p M=joe par1=1".split())) - print( mapRes( "agnd", "RM3", "rnm t1 t2 $LDD 220p L=4 W=12 M=joe par1=1".split())) - -# Format a bipolar. cdlTerm is optional. cdlModel is ignored. -# Processes tok and returns a final token list to be output. -# Optional cdlTerm adds an optional 4th terminal. -# If after "qnm t1 t2 t3 model", there are plain numeric (not x=y) they are DISCARDED. -# (Caller already error checked the 1st minimum 5 fields are there; plus cdlModel is null). - -def mapBipolar(cdlTerm, cdlModel, tok, options={}): - # strip remaining $* directives - ignore = options['ignore'] if 'ignore' in options else [] - [ ign, ign, tok ] = mapDiscard(tok) - for p in ignore: - [ign, tok] = mapLookup(tok, p, True) - # For just token-slice after modelName, partition into assignments and non-assigns. - # We ignore the nonassignments. Need remaining assignments for M= par1=. - [assign, nona] = mapPartAssign(tok[5:]) - # Start with "qnm t1 t2 t3". Insert optional 4th term. Then insert modelName. - model = tok[4] - tok = tok[0:4] - if cdlTerm != "": - tok += [ cdlTerm ] - tok += [ model ] - tok += assign - return tok - -def test_mapBipolar(): - print( mapBipolar( "", "any", "qnm t1 t2 t3 QP1 M=joe par1=1".split())) - print( mapBipolar( "", "", "qnm t1 t2 t3 QP2 M=joe par1=1".split())) - print( mapBipolar( "", "", "qnm t1 t2 t3 QP2 $EA=12 M=joe par1=1".split())) - print( mapBipolar( "", "", "qnm t1 t2 t3 QP3 M=joe EA=14 par1=1".split())) - print( mapBipolar( "agnd", "", "qnm t1 t2 t3 QP4 $LDD 220p M=joe par1=1".split())) - print( mapBipolar( "agnd", "any", "qnm t1 t2 t3 QP4 $LDD 220p L=4 W=12 M=joe par1=1".split())) - -#------------------------------------------------------------------------ -# Main routine to do the conversion from CDL format to SPICE format -#------------------------------------------------------------------------ - -def cdl2spice(fnmIn, fnmOut, options): - - err = 0 - warn = 0 - - # Open and read input file - - try: - with open(fnmIn, 'r') as inFile: - cdltext = inFile.read() - # Unwrap continuation lines - lines = cdltext.replace('\n+', ' ').splitlines() - except: - print('cdl2spi.py: failed to open ' + fnmIn + ' for reading.', file=sys.stderr) - return 1 - - # Loop over original CDL: - # record existing instanceNames (in subckt-context), for efficient membership - # tests later. Track the subckt-context, instanceNames only need to be unique - # within current subckt. - - sub = "" - for i in lines: - if i == "": - continue - tok = i.split() - tlen = len(tok) - if tlen == 0: - continue - t0 = tok[0].lower() - if t0 == '.subckt' and tlen > 1: - sub = tok[1].lower() - continue - if t0 == '.ends': - sub = "" - continue - c0 = tok[0][0].lower() - if c0 in '.*': - continue - # this will ignore primitive-devices (jfet) we don't support. - # TODO: flag them somewhere else as an ERROR. - if not c0 in primch2: - continue - # a primitive-device or subckt-instance we care about and support - # For subckt-instances record the instanceName MINUS lead x. - nm = tok[0] - if c0 == 'x': - nm = nm[1:] - hasInm[ (sub, nm) ] = 1 - - - # loop over original CDL: do conversions. - # Track the subckt-context while we go; instanceNames only need to be unique - # within current subckt. - - sub = "" - tmp = [] - for i in lines: - tok = i.split() - tlen = len(tok) - # AS-IS: empty line or all (preserved) whitespace - if tlen == 0: - tmp += [ i ] - continue - - # get 1st-token original, as lowercase, and 1st-char of 1st-token lowercase. - T0 = tok[0] - t0 = T0.lower() - c0 = t0[0] - - # AS-IS: comment - if c0 == '*': - tmp += [i] - continue - - # AS-IS: .ends; update subckt-context to outside-of-a-subckt - if t0 == '.ends': - sub = "" - tmp += [i] - continue - - # change .param to a comment, output it - if t0 == '.param': - tmp += ["*"+i] - continue - - # track .subckt context; process / in .subckt line, and output it. - if t0 == '.subckt': - if tlen < 2: - err+=1 - msg = "*cdl2spi.py: ERROR: Missing subckt name:" - tmp += [ msg, i ] - continue - T1 = tok[1] - sub = T1.lower() - tok = mapSubcktDef(tok) - tmp += [ " ".join(tok) ] - continue - - # subckt instance line. Process /, map instanceName (exclude x), and output it. - if c0 == 'x': - nm = T0[1:] - if nm == "": - err+=1 - msg = "*cdl2spi.py: ERROR: Missing subckt instance name:" - tmp += [ msg, i ] - continue - inm = mapInm(sub, nm) - tok[0] = T0[0] + inm - tok = mapSubcktInst(tok) - tmp += [ " ".join(tok) ] - continue - - # all primitives: need instanceName mapped, including 1st char in name. - # all primitives: need M=n copied to an added par1=n - # all primitives: Except for $SUB=... $[...] strip $ from $nm=... parameters. - # all primitives: Isolate $SUB and $[...] for further processing (in - # primitive-specific sections). - - cdlTerm="" - cdlModel="" - if c0 in primch: - nm = T0[1:] - if nm == "": - err+=1 - msg = "*cdl2spi.py: ERROR: Missing primitive instance name:" - tmp += [ msg, i ] - continue - nm = T0 - nm = mapInm(sub, nm) - tok[0] = nm - tok = mapMfactor(tok, options) - tok = mapCDLparam(tok) - [cdlTerm, cdlModel, tok] = mapCDLtermModel(tok) - - # diode formats: - # dname t1 t2 model <numericA> <numericP> m=... - # l:dname t1 t2 model {<numericA>} {<numericP>} {m=...} {$SUB=...} - # out format: - # Xdname t1 t2 model area=<numericA> peri=<numericP> m=... par1=... - # We flag $SUB=... : because so far (for XFAB) we CHOOSE not to support three - # terminal diodes. - # CDL-std does not define $[...] as available for diodes, so we silently ignore - # it. - # Always 2 terminals and a modelName. - # We already have peri=... and area=... and have ambiguity with plain numerics. - # TODO: generate a warning in case of ambiguity, but prefer plain numerics - # (with nm= added). - - if c0 == "d": - tlen = len(tok) - if tlen < 4: - err+=1 - msg = "*cdl2spi.py: ERROR: Diode does not have minimum two terminals and model:" - tmp += [ msg, i ] - continue - if cdlTerm != "": - err+=1 - msg = "*cdl2spi.py: ERROR: Diode does not support $SUB=...:" - tmp += [ msg, i ] - continue - tok = mapDiode(cdlTerm, cdlModel, tok, options) - # add X to tok0. - if options['subckt']: - tok[0] = "X" + tok[0] - tmp += [ " ".join(tok) ] - continue - - # mosfet formats: - # mname t1 t2 t3 t4 model W=... L=... m=... - # l:mname t1 t2 t3 t4 model {W=... L=...} {m=...} {$NONSWAP} {$LDD[type]} - # l:mname t1 t2 t3 t4 model <width> <length> {m=...} {$NONSWAP} {$LDD[type]} - # output format: - # Xmname t1 t2 t3 t4 model W=... L=... m=... par1=... - # Fixed 4 terminals and a modelName. - # May already have W= L= and ambiguity with plain numerics. - # TODO: generate a warning in case of ambiguity, but prefer plain numerics - # (with nm= added). - if c0 == "m": - tlen = len(tok) - if tlen < 6: - err+=1 - msg = "*cdl2spi.py: ERROR: Mosfet does not have minimum four terminals and model:" - tmp += [ msg, i ] - continue - if cdlTerm != "": - err+=1 - msg = "*cdl2spi.py: ERROR: Mosfet does not support $SUB=...:" - tmp += [ msg, i ] - continue - tok = mapMos(cdlTerm, cdlModel, tok, options) - # add X to tok0. - if options['subckt']: - tok[0] = "X" + tok[0] - tmp += [ " ".join(tok) ] - continue - - # cap formats: - # cname t1 t2 <numeric0> $[model] $SUB=t3 m=... - # cname t1 t2 <numeric0> $[model] m=... - #? cname t1 t2 C=<numeric0> $[model] $SUB=t3 m=... - #? cname t1 t2 <numeric0> $[model] $SUB=t3 area=<numericA> perimeter=<numericP> m=... - #? cname t1 t2 <numeric0> $[model] $SUB=t3 area=<numericA> peri=<numericP> m=... - #l:cname t1 t2 {<numeric0>} {$[model]} {$SUB=t3} {m=...} - # out formats: - # Xcname t1 t2 model area=<numericA> peri=<numericP> m=... par1=... - # Xcname t1 t2 t3 model area=<numericA> peri=<numericP> m=... par1=... - # We require inm, two terminals. Require $[model]. Optional 3rd-term $SUB=... - # If both peri and perimeter, peri overrides. - # Both area/peri are optional. The optional [C=]numeric0 is discarded always. - - if c0 == "c": - tlen = len(tok) - if tlen < 3: - err+=1 - msg = "*cdl2spi.py: ERROR: Cap does not have minimum two terminals:" - tmp += [ msg, i ] - continue - if cdlModel == "": - err+=1 - msg = "*cdl2spi.py: ERROR: Cap missing required $[<model>] directive:" - tmp += [ msg, i ] - continue - tok = mapCap(cdlTerm, cdlModel, tok, options) - # add X to tok0. - if options['subckt']: - tok[0] = "X" + tok[0] - tmp += [ " ".join(tok) ] - continue - - # res formats: - # rname n1 n2 <numeric> $SUB=t3 $[model] $w=... $l=... m=... - # c:rname n1 n2 R=<numeric> $[model] w=... l=... m=... $SUB=t3 - # l:rname n1 n2 {<numeric>} {$SUB=t3} {$[model]} {$w=...} {$l=...} {m=...} - # (all after n1,n2 optional) - # We require $[model]. And add 3rd term IFF $SUB=. - # out format: - # Xrname n1 n2 t3 model w=... l=... m=... par1=... - if c0 == "r": - tlen = len(tok) - if tlen < 3: - err+=1 - msg = "*cdl2spi.py: ERROR: Res does not have minimum two terminals:" - tmp += [ msg, i ] - continue - if cdlModel == "": - err+=1 - msg = "*cdl2spi.py: ERROR: Res missing required $[<model>] directive:" - tmp += [ msg, i ] - continue - tok = mapRes(cdlTerm, cdlModel, tok, options) - # add X to tok0. - if options['subckt']: - tok[0] = "X" + tok[0] - tmp += [ " ".join(tok) ] - continue - - # bipolar formats: - # qname n1 n2 n3 model <numeric> M=... $EA=... - # qname n1 n2 n3 model $EA=... <numeric> M=... - # qname n1 n2 n3 model {$EA=...} {$W=...} {$L=...} {$SUB=...} {M=...} - # No: l:qname n1 n2 n3 {nsub} model {$EA=...} {$W=...} {$L=...} {$SUB=...} {M=...} - # CDL-std adds {nsub} way to add substrate before model: We don't support it. - # Add 3rd term IFF $SUB=. We propagate optional W/L (or derived from $W/$L). - # EA is emitterSize; not supported by XFAB: deleted. - # We require 3-terminals and model. It is an error to specify $[model]. - # - # out format: - # Xqname n1 n2 n3 model M=... par1=... - if c0 == "q": - tlen = len(tok) - if tlen < 5: - err+=1 - msg = "*cdl2spi.py: ERROR: Bipolar does not have minimum three terminals and a model:" - tmp += [ msg, i ] - continue - if cdlModel != "": - err+=1 - msg = "*cdl2spi.py: ERROR: Bipolar does not support $[<model>] directive:" - tmp += [ msg, i ] - continue - tok = mapBipolar(cdlTerm, cdlModel, tok, options) - # add X to tok0. - if options['subckt']: - tok[0] = "X" + tok[0] - tmp += [ " ".join(tok) ] - continue - - # Anything else. What to do, preserve AS-IS with warning, or - # flag them as ERRORs? - tmp += [ "*cdl2spi.py: ERROR: unrecognized line:", i ] - err+=1 - # tmp += [ "*cdl2spi.py: WARNING: unrecognized line:", " ".join(tok) ] - # tmp += [ "*cdl2spi.py: WARNING: unrecognized line:", i ] - # warn+=1 - - # Re-wrap continuation lines at 80 characters - lines = [] - for line in tmp: - lines.append('\n+ '.join(textwrap.wrap(line, 80))) - - # Write output - - if fnmOut == sys.stdout: - for i in lines: - print(i) - else: - try: - with open(fnmOut, 'w') as outFile: - for i in lines: - print(i, file=outFile) - except: - print('cdl2spi.py: failed to open ' + fnmOut + ' for writing.', file=sys.stderr) - return 1 - - # exit status: indicates if there were errors. - print( "*cdl2spi.py: %d errors, %d warnings" % (err, warn)) - return err - -if __name__ == '__main__': - - options = {} - - # Set option defaults - options['debug'] = False - options['subckt'] = False - options['dscale'] = '' - options['addinm'] = [] - options['ignore'] = [] - - arguments = [] - for item in sys.argv[1:]: - if item.find('-', 0) == 0: - thisopt = item.split('=') - optname = thisopt[0][1:] - optval = '='.join(thisopt[1:]) - if not optname in options: - print('Unknown option -' + optname + '; ignoring.') - else: - lastoptval = options[optname] - if len(thisopt) == 1: - options[optname] = True - elif lastoptval == '': - options[optname] = optval - else: - options[optname].append(optval) - else: - arguments.append(item) - - # Supported primitive devices (FET, diode, resistor, capacitor, bipolar) - primch = 'mdrcq' - primch2 = 'mdrcqx' - - if len(arguments) > 0: - fnmIn = arguments[0] - - if len(arguments) > 1: - fnmOut = arguments[1] - else: - fnmOut = sys.stdout - - if options['debug']: - test_mapSubcktInst1() - test_mapSubcktInst2() - test_mapMfactor() - test_CDLparam() - test_CDLtermModel() - test_mapDiscard() - test_mapPartAssign() - test_mapLookup() - test_mapDiode() - test_mapMos() - test_mapCap() - test_mapRes() - test_mapBipolar() - - elif len(arguments) > 2 or len(arguments) < 1 : - print('Usage: cdl2spi.py <cdlFileName> [<spiFileName>]') - print(' Options:' ) - print(' -debug run debug tests') - print(' -dscale=<suffix> rescale lengths with <suffix>') - print(' -addinm=<param> add multiplier parameter <param>') - print(' -ignore=<param> ignore parameter <param>') - print(' -subckt convert primitive devices to subcircuits') - sys.exit(1) - - else: - if options['debug'] == True: - print('Diagnostic: options = ' + str(options)) - result = cdl2spice(fnmIn, fnmOut, options) - sys.exit(result) -
diff --git a/runtime/make_icon_from_soft.py b/runtime/make_icon_from_soft.py index 2323646..46f27e8 100644 --- a/runtime/make_icon_from_soft.py +++ b/runtime/make_icon_from_soft.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env -S python3 -B +#!/usr/bin/env python3 #-------------------------------------------------------- # make_icon_from_soft.py -- #
diff --git a/runtime/project_manager.py b/runtime/project_manager.py index 245eeee..b47f23e 100755 --- a/runtime/project_manager.py +++ b/runtime/project_manager.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env -S python3 -B +#!/usr/bin/env python3 # #-------------------------------------------------------- # Open Galaxy Project Manager GUI.
diff --git a/runtime/rename_project.py b/runtime/rename_project.py index 01ccf0b..4d541f0 100755 --- a/runtime/rename_project.py +++ b/runtime/rename_project.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env -S python3 -B +#!/usr/bin/env python3 # # rename_project.py --- Perform all tasks required for renaming a project. #