| #!/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])) |