blob: 0e286abd64d35e4a1c7f4c63bd9feabdd4a7701f [file] [log] [blame]
#!/usr/bin/env python3
import collections
import json
import os
import pathlib
import pprint
import re
import sys
SPICE_X_DEF = re.compile('^(x.*?)$', flags=re.MULTILINE|re.I)
SPICE_MODEL = re.compile('^.model +([^. ]+)(.[0-9]*)? (.*?)$', flags=re.MULTILINE)
SPICE_SUBCKT = re.compile('^.subckt +([^ ]+) (.*?)$', flags=re.MULTILINE)
SPICE_INC = re.compile('^.include "([^"]*)"$', flags=re.MULTILINE)
def extract_inc(data, fname):
fdir = os.path.dirname(fname)
incs = []
for m in SPICE_INC.finditer(data):
incs.append(os.path.abspath(os.path.join(fdir, m.group(1))))
return incs
def extract_x(data):
"""
>>> extract_x('''
... xsky130_fd_pr__pfet_01v8 d g s b sky130_fd_pr__pfet_01v8__model l = {l} w = {w} ad = {ad} as = {as} pd = {pd} ps = {ps} nrd = {nrd} nrs = {nrs} sa = {sa} sb = {sb} sd = {sd} nf = {nf}
... ''')
['sky130_fd_pr__pfet_01v8__model']
>>> extract_x('''
... X0 Drain Gate Source BULK sky130_fd_pr__pfet_01v8 w=840000u l=150000u
... ''')
['sky130_fd_pr__pfet_01v8']
"""
defs = []
for m in SPICE_X_DEF.finditer(data):
xdef = m.group(0).split()
i = 0
while i < len(xdef)-1:
if '=' in xdef[i+1]:
break
if i+2 < len(xdef) and xdef[i+2] == '=':
break
i += 1
defs.append(xdef[i])
return defs
def extract_defines(data):
"""
>>> extract_defines('''
... .subckt sky130_fd_pr__rf_nfet_g5v0d10v5_bM02 d g s b
... ''')
['sky130_fd_pr__rf_nfet_g5v0d10v5_bM02']
>>> extract_defines('''
... .model sky130_fd_pr__nfet_05v0_nvt__model.9 nmos
... .model sky130_fd_pr__nfet_05v0_nvt__model.10 nmos
... ''')
['sky130_fd_pr__nfet_05v0_nvt__model']
>>> extract_defines('''
... .model mcp1f c tc1 = 0 tc2 = 0 cox = {cp1f} capsw = {cp1fsw} w = {wminp1} tref = 25.0
... ''')
['mcp1f']
"""
defines = set()
for m in SPICE_MODEL.finditer(data):
defines.add(m.group(1))
for m in SPICE_SUBCKT.finditer(data):
defines.add(m.group(1))
defines = list(defines)
defines.sort()
return defines
def p(s):
pprint.pprint(s)
def split_list(s):
"""
>>> split_list("nfom_dw=0.017u")
['nfom_dw', '=', '0.017u']
>>> split_list("nfom_dw={'0.017u'}")
['nfom_dw', '=', "'0.017u'"]
>>> split_list("nfom_dw={{'0.017u'}}")
['nfom_dw', '=', "{'0.017u'}"]
>>> p(split_list("sky130_fd_pr__nfet_20v0_nvt__vgb_max = 'sky130_fd_pr__nfet_20v0_nvt__vgs_max - sky130_fd_pr__nfet_20v0_nvt__vbs_min'"))
['sky130_fd_pr__nfet_20v0_nvt__vgb_max',
'=',
'sky130_fd_pr__nfet_20v0_nvt__vgs_max - sky130_fd_pr__nfet_20v0_nvt__vbs_min']
>>> split_list("nfom_dw= 0.017u")
['nfom_dw', '=', '0.017u']
>>> split_list("nfom_dw={0.017u}")
['nfom_dw', '=', '0.017u']
>>> split_list('.param sky130_fd_pr__rf_nfet_01v8_lvt__base__dlc_rotweak=0')
['.param', 'sky130_fd_pr__rf_nfet_01v8_lvt__base__dlc_rotweak', '=', '0']
>>> p(split_list("cnwvc_cdepmult=1 cnwvc_cintmult=1 cnwvc_vt1=0.3333 cnwvc_vt2=0.2380952 cnwvc_vtr=0.16 cnwvc_dwc=0.0 cnwvc_dlc=0.0 cnwvc_dld=0.0 cnwvc2_tox='41.7642*1' cnwvc2_cdepmult=1 cnwvc2_cintmult=1 cnwvc2_vt1=0.2 cnwvc2_vt2=0.33 cnwvc2_vtr=0.14 cnwvc2_dwc=0.0 cnwvc2_dlc=0.0 cnwvc2_dld=0.0"))
['cnwvc_cdepmult',
'=',
'1',
'cnwvc_cintmult',
'=',
'1',
'cnwvc_vt1',
'=',
'0.3333',
'cnwvc_vt2',
'=',
'0.2380952',
'cnwvc_vtr',
'=',
'0.16',
'cnwvc_dwc',
'=',
'0.0',
'cnwvc_dlc',
'=',
'0.0',
'cnwvc_dld',
'=',
'0.0',
'cnwvc2_tox',
'=',
'41.7642*1',
'cnwvc2_cdepmult',
'=',
'1',
'cnwvc2_cintmult',
'=',
'1',
'cnwvc2_vt1',
'=',
'0.2',
'cnwvc2_vt2',
'=',
'0.33',
'cnwvc2_vtr',
'=',
'0.14',
'cnwvc2_dwc',
'=',
'0.0',
'cnwvc2_dlc',
'=',
'0.0',
'cnwvc2_dld',
'=',
'0.0']
"""
COUNT = 0
OPEN = 1
CLOSED = 2
r = []
def add(o):
o = o.strip()
if not o:
return
if o[0] == "'":
assert o[-1] == "'", repr(o)
o = o[1:-1].strip()
if o[0] == "{":
assert o[-1] == "}", repr(o)
o = o[1:-1].strip()
if len(o) > 1 and o.startswith('='):
r.append('=')
o = o[1:]
r.append(o)
i = -1
spos = 0
inside_quoted = None
while i < len(s)-1:
i += 1
o = None
if inside_quoted:
if s[i] == inside_quoted[CLOSED]:
inside_quoted[COUNT] -= 1
elif s[i] == inside_quoted[OPEN]:
inside_quoted[COUNT] += 1
else:
continue
if inside_quoted[COUNT] != 0:
continue
inside_quoted = None
o = s[spos:i+1]
i += 1
elif s[i] == '{':
inside_quoted = [1, '{', '}']
o = s[spos:i]
elif s[i] == "'":
inside_quoted = [1, "'", "'"]
o = s[spos:i]
elif s[i] == '"':
inside_quoted = [1, '"', '"']
o = s[spos:i]
elif s[i] == ' ':
o = s[spos:i]
elif s[i] == '=':
o = s[spos:i]
if o:
add(o)
spos = i
if spos < i:
add(s[spos:])
return r
def extract_params(data):
"""
>>> p(extract_params('''
... * Number of bins: 11
... .param
... + sky130_fd_pr__nfet_05v0_nvt__toxe_mult = 0.948
...
... + sky130_fd_pr__nfet_05v0_nvt__rshn_mult = 1.0
... * -------------------------------------
... .subckt ...
... ....
... .param
... + sky130_fd_pr__nfet_05v0_nvt__pditsd_diff_1 = 0.0
... + sky130_fd_pr__nfet_05v0_nvt__pclm_diff_1 = 0.0
... '''))
[{'sky130_fd_pr__nfet_05v0_nvt__rshn_mult': '1.0',
'sky130_fd_pr__nfet_05v0_nvt__toxe_mult': '0.948'},
{'sky130_fd_pr__nfet_05v0_nvt__pclm_diff_1': '0.0',
'sky130_fd_pr__nfet_05v0_nvt__pditsd_diff_1': '0.0'}]
>>> p(extract_params('''
... * Number of bins: 11
... .param sky130_fd_pr__nfet_05v0_nvt__toxe_mult = 0.948
... .param sky130_fd_pr__nfet_05v0_nvt__toxe_mult2 = 0.948
... '''))
[{'sky130_fd_pr__nfet_05v0_nvt__toxe_mult': '0.948'},
{'sky130_fd_pr__nfet_05v0_nvt__toxe_mult2': '0.948'}]
>>> p(extract_params('''
... * Number of bins: 11
... .param sky130_fd_pr__nfet_05v0_nvt__toxe_mult = '0.948'
... .param sky130_fd_pr__nfet_05v0_nvt__toxe_mult2 = {0.948}
... '''))
[{'sky130_fd_pr__nfet_05v0_nvt__toxe_mult': '0.948'},
{'sky130_fd_pr__nfet_05v0_nvt__toxe_mult2': '0.948'}]
>>> p(extract_params('''
... .param
... + nfom_dw= 0.017u
... + pfom_dw= 0.004u
... + poly_dw= -0.056u
... + li_dw= 0.017u
... + m1_dw= -0.039u
... + m2_dw= -0.039u
... + m3_dw= -0.025u
... + m4_dw= -0.025u
... + m5_dw= -0.09u
... + rdl_dw= 0.0u
... '''))
[{'li_dw': '0.017u',
'm1_dw': '-0.039u',
'm2_dw': '-0.039u',
'm3_dw': '-0.025u',
'm4_dw': '-0.025u',
'm5_dw': '-0.09u',
'nfom_dw': '0.017u',
'pfom_dw': '0.004u',
'poly_dw': '-0.056u',
'rdl_dw': '0.0u'}]
>>> p(extract_params('''
... .param
... + cm0='5.828e-16*cnwvc2_cintmult'
... + cm1=4.596e-16 dev/gauss='sky130_fd_pr__cap_var_hvt__cmin_slope_l/sqrt(2*ld*vm)'
... + cm2=1.614e-16 dev/gauss='sky130_fd_pr__cap_var_hvt__cmin_slope_w/sqrt(2*wd*vm)'
... + cm3='1.541e-15*cnwvc2_cdepmult' dev/gauss='sky130_fd_pr__cap_var_hvt__cmin_slope_wl*cnwvc2_cdepmult/sqrt(2*ld*wd*vm)'
... + cx0=6.778e-16
... + cx1=6.461e-16 dev/gauss='sky130_fd_pr__cap_var_hvt__cmax_slope_l/sqrt(2*ld*vm)'
... + cx2=1.517e-16 dev/gauss='sky130_fd_pr__cap_var_hvt__cmax_slope_w/sqrt(2*wd*vm)'
... + cx3='8.854e-14*3.9/cnwvc2_tox' dev/gauss='sky130_fd_pr__cap_var_hvt__cmax_slope_wl/sqrt(2*ld*wd*vm)'
... '''))
[{'cm0': '5.828e-16*cnwvc2_cintmult',
'cm1': '4.596e-16',
'cm2': '1.614e-16',
'cm3': '1.541e-15*cnwvc2_cdepmult',
'cx0': '6.778e-16',
'cx1': '6.461e-16',
'cx2': '1.517e-16',
'cx3': '8.854e-14*3.9/cnwvc2_tox',
'dev/gauss': ['sky130_fd_pr__cap_var_hvt__cmin_slope_l/sqrt(2*ld*vm)',
'sky130_fd_pr__cap_var_hvt__cmin_slope_w/sqrt(2*wd*vm)',
'sky130_fd_pr__cap_var_hvt__cmin_slope_wl*cnwvc2_cdepmult/sqrt(2*ld*wd*vm)',
'sky130_fd_pr__cap_var_hvt__cmax_slope_l/sqrt(2*ld*vm)',
'sky130_fd_pr__cap_var_hvt__cmax_slope_w/sqrt(2*wd*vm)',
'sky130_fd_pr__cap_var_hvt__cmax_slope_wl/sqrt(2*ld*wd*vm)']}]
"""
params_groups = []
params = []
for l in data.splitlines():
if l.startswith('*'):
continue
if '$' in l:
l = l.split('$')[0]
l = l.strip()
if not l:
continue
if l.startswith('+'):
l = l[1:].strip()
if l and params:
params.append(l)
continue
if params:
params_groups.append(params)
params = []
if l.startswith('.param'):
params.append(l)
if params:
params_groups.append(params)
o = []
for params in params_groups:
oparams = {}
p = split_list(' '.join(params))
op = list(p)
assert p[0] == '.param', p
p.pop(0)
while p:
assert len(p) >= 3, (p, op, ' '.join(params))
assert p[1] == '=', (p, op)
k = p.pop(0)
_ = p.pop(0)
v = p.pop(0)
if v[0] == "'":
assert v[-1] == "'", repr(v)
v = v[1:-1]
if v[0] == "{":
assert v[-1] == "}", repr(v)
v = v[1:-1]
if '/' in k:
if k not in oparams:
oparams[k] = []
oparams[k].append(v)
else:
assert k not in oparams, k+'\n'+pprint.pformat(oparams)
oparams[k] = v
o.append(oparams)
return o
def pp(s, stream=None):
s = pprint.pformat(s)
l = s.splitlines()
if len(l) > 100:
s = "\n".join(l[:100]+['...'])
if stream is None:
stream = sys.stdout
stream.write(s)
stream.write('\n')
stream.flush()
SPICE_BITS = re.compile('((?P<num>-?[0-9][0-9]*(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|(?P<text>[A-Za-z][A-Za-z_0-9]*))')
def bits(s, drop_numbers=False):
"""
>>> bits('-9.28125e-13*ic_cap*ic_cap+-4.87500e-13*ic_cap+1.06000e-10')
[-9.28125e-13, 'ic_cap', 'ic_cap', -4.875e-13, 'ic_cap', 1.06e-10]
>>> bits('1/leff*(1+bp22/w+bp23*min(0.2,leff-0.5)*log(leff/w))')
[1.0, 'leff', 1.0, 'bp22', 'w', 'bp23', 'min', 0.2, 'leff', -0.5, 'log', 'leff', 'w']
>>> bits('1/leff*(1+bp22/w+bp23*min(0.2,leff-0.5)*log(leff/w))', drop_numbers=True)
['leff', 'bp22', 'w', 'bp23', 'min', 'leff', 'log', 'leff', 'w']
>>> bits('-9.28125e-13*ic_cap*ic_cap+-4.87500e-13*ic_cap+1.06000e-10', drop_numbers=True)
['ic_cap', 'ic_cap', 'ic_cap']
>>> bits('(rp1+rp1_mm)*l/(w-1e6*(-tol_poly-poly_dw))+r')
['rp1', 'rp1_mm', 'l', 'w', -1000000.0, 'tol_poly', 'poly_dw', 'r']
>>> bits('-1.0357e+4')
[-10357.0]
"""
o = []
for m in SPICE_BITS.finditer(s):
if not m.group(0):
continue
num = m.group('num')
if num:
if drop_numbers:
continue
try:
o.append(float(num))
except:
print('--->', s)
raise
else:
o.append(m.group('text'))
return o
def main(topdir):
includes = {}
includes_only = []
defs = collections.defaultdict(list)
params = collections.defaultdict(dict)
for s in pathlib.Path(os.path.abspath(topdir)).rglob("*.spice"):
s = str(s)
print()
print(s)
data = open(s).read()
s_includes = extract_inc(data, str(s))
s_defs = extract_defines(data)
s_x = extract_x(data)
s_params = extract_params(data)
includes[s] = s_includes
if s_includes and not (s_defs or s_x or s_params):
print("Includes only!")
pp(s_includes)
includes_only.append(s)
continue
for x in s_defs:
defs[x].append(s[len(topdir):])
for p in s_params:
for k, v in p.items():
if isinstance(v, str):
o = bits(v)
if len(o) == 1 and isinstance(o[0], float):
o = {'value': o}
else:
deps = list(set(bits(v, drop_numbers=True)))
deps.sort()
o = {'exp':v, 'deps':deps}
else:
o = v
params[k][s[len(topdir):]] = o
print("Includes:")
pp(s_includes)
print("Defines:")
pp(s_defs)
print("Uses:")
pp(s_x)
print("Params:")
pp(s_params)
def j(d, f):
if isinstance(d, dict):
d = dict(d)
json.dump(d, f, sort_keys=True, indent=" ")
with open('spice-only-includes.json', 'w') as f:
j(includes_only, f)
with open('spice-includes.json', 'w') as f:
j(includes, f)
with open('spice-defs.json', 'w') as f:
j(defs, f)
with open('spice-params.json', 'w') as f:
j(params, f)
if __name__ == "__main__":
import doctest
fails, _ = doctest.testmod()
if fails != 0:
sys.exit("Some test failed!")
assert len(sys.argv) == 2, sys.argv
sys.exit(main(sys.argv[1]))