|
11 | 11 | Cb = eps0 * eps_below * W / d_below parallel-plate to layer below |
12 | 12 | Cc = eps0 * eps_side * T / SMIN between sidewalls of min-spaced neighbors |
13 | 13 | """ |
| 14 | + |
14 | 15 | import re |
15 | 16 |
|
16 | 17 | EPS0 = 8.854e-3 |
17 | 18 | FRINGE_FACTOR = 1.5 |
18 | 19 | PF_PER_FF = 1.0e-3 |
19 | 20 |
|
20 | | -ROUTING = {'M0','M1','M2','M3','M4','M5','M6','M7','M8','M9','M10','M11','M12','M13', |
21 | | - 'RDL','BPR','BM1','BM2','BM3','BM4','BRDL'} |
| 21 | +ROUTING = { |
| 22 | + "M0", |
| 23 | + "M1", |
| 24 | + "M2", |
| 25 | + "M3", |
| 26 | + "M4", |
| 27 | + "M5", |
| 28 | + "M6", |
| 29 | + "M7", |
| 30 | + "M8", |
| 31 | + "M9", |
| 32 | + "M10", |
| 33 | + "M11", |
| 34 | + "M12", |
| 35 | + "M13", |
| 36 | + "RDL", |
| 37 | + "BPR", |
| 38 | + "BM1", |
| 39 | + "BM2", |
| 40 | + "BM3", |
| 41 | + "BM4", |
| 42 | + "BRDL", |
| 43 | +} |
22 | 44 |
|
23 | 45 | # dielectrics to ignore when looking up neighbors (file-end placeholder) |
24 | | -IGNORE_DIELS = {'dummy_diel'} |
| 46 | +IGNORE_DIELS = {"dummy_diel"} |
| 47 | + |
25 | 48 |
|
26 | 49 | def parse_itf(path): |
27 | | - text = re.sub(r'\$[^\n]*\n', '\n', open(path).read()) |
| 50 | + text = re.sub(r"\$[^\n]*\n", "\n", open(path).read()) |
28 | 51 | entries = [] |
29 | | - for m in re.finditer(r'(DIELECTRIC|CONDUCTOR|VIA)\s+(\w+)\s*\{([^}]*)\}', text): |
| 52 | + for m in re.finditer(r"(DIELECTRIC|CONDUCTOR|VIA)\s+(\w+)\s*\{([^}]*)\}", text): |
30 | 53 | kind, name, body = m.group(1), m.group(2), m.group(3) |
31 | | - params = {k: float(v) for k, v in re.findall(r'(\w+)\s*=\s*([\d.]+)', body)} |
| 54 | + params = {k: float(v) for k, v in re.findall(r"(\w+)\s*=\s*([\d.]+)", body)} |
32 | 55 | # also accept upper/lower text fields like FROM/TO for VIA |
33 | | - for k, v in re.findall(r'(\w+)\s*=\s*([A-Za-z]\w*)', body): |
| 56 | + for k, v in re.findall(r"(\w+)\s*=\s*([A-Za-z]\w*)", body): |
34 | 57 | params.setdefault(k, v) |
35 | 58 | entries.append((kind, name, params)) |
36 | 59 | return entries |
37 | 60 |
|
| 61 | + |
38 | 62 | def metal_rc(entries, idx): |
39 | 63 | cond = entries[idx][2] |
40 | 64 | # dielectric above (closest, ignoring placeholders) |
41 | 65 | above = None |
42 | | - for j in range(idx-1, -1, -1): |
43 | | - if entries[j][0]=='DIELECTRIC' and entries[j][1] not in IGNORE_DIELS: |
44 | | - above = entries[j][2]; break |
| 66 | + for j in range(idx - 1, -1, -1): |
| 67 | + if entries[j][0] == "DIELECTRIC" and entries[j][1] not in IGNORE_DIELS: |
| 68 | + above = entries[j][2] |
| 69 | + break |
45 | 70 | # dielectric below (closest, stop at next conductor) |
46 | 71 | below = None |
47 | | - for j in range(idx+1, len(entries)): |
48 | | - if entries[j][0]=='CONDUCTOR': break |
49 | | - if entries[j][0]=='DIELECTRIC' and entries[j][1] not in IGNORE_DIELS: |
50 | | - below = entries[j][2]; break |
51 | | - W, S, T = cond['WMIN'], cond['SMIN'], cond['THICKNESS'] |
52 | | - R = cond['RPSQ'] / W |
53 | | - def pp(d): return EPS0 * d['ER'] * W / d['THICKNESS'] if d else 0.0 |
| 72 | + for j in range(idx + 1, len(entries)): |
| 73 | + if entries[j][0] == "CONDUCTOR": |
| 74 | + break |
| 75 | + if entries[j][0] == "DIELECTRIC" and entries[j][1] not in IGNORE_DIELS: |
| 76 | + below = entries[j][2] |
| 77 | + break |
| 78 | + W, S, T = cond["WMIN"], cond["SMIN"], cond["THICKNESS"] |
| 79 | + R = cond["RPSQ"] / W |
| 80 | + |
| 81 | + def pp(d): |
| 82 | + return EPS0 * d["ER"] * W / d["THICKNESS"] if d else 0.0 |
| 83 | + |
54 | 84 | Ca, Cb = pp(above), pp(below) |
55 | | - eps_side = (above or below)['ER'] if (above or below) else 1.0 |
| 85 | + eps_side = (above or below)["ER"] if (above or below) else 1.0 |
56 | 86 | Cc = EPS0 * eps_side * T / S |
57 | | - C_fF = (Ca + Cb)*FRINGE_FACTOR + 2*Cc |
| 87 | + C_fF = (Ca + Cb) * FRINGE_FACTOR + 2 * Cc |
58 | 88 | return R, C_fF * PF_PER_FF |
59 | 89 |
|
60 | | -if __name__ == '__main__': |
61 | | - entries = parse_itf('/home/mrg/orfs/GT2N/nxtgrd/GT2.itf') |
62 | | - print('# Per-length metal R and C, derived analytically from GT2.itf.') |
63 | | - print('# R/um = RPSQ / WMIN (matches Fig 1(b) of the GT2N paper).') |
64 | | - print('# C/um = parallel-plate-to-neighbor + sidewall-coupling, fringe-scaled 1.5x.') |
65 | | - print('# pulling_resistance_unit=1ohm -> ohm/um ; capacitive_load_unit(1, pf) -> pf/um.') |
| 90 | + |
| 91 | +if __name__ == "__main__": |
| 92 | + entries = parse_itf("/home/mrg/orfs/GT2N/nxtgrd/GT2.itf") |
| 93 | + print("# Per-length metal R and C, derived analytically from GT2.itf.") |
| 94 | + print("# R/um = RPSQ / WMIN (matches Fig 1(b) of the GT2N paper).") |
| 95 | + print( |
| 96 | + "# C/um = parallel-plate-to-neighbor + sidewall-coupling, fringe-scaled 1.5x." |
| 97 | + ) |
| 98 | + print( |
| 99 | + "# pulling_resistance_unit=1ohm -> ohm/um ; capacitive_load_unit(1, pf) -> pf/um." |
| 100 | + ) |
66 | 101 | print() |
67 | | - for i,(kind,name,_) in enumerate(entries): |
68 | | - if kind=='CONDUCTOR' and name in ROUTING: |
| 102 | + for i, (kind, name, _) in enumerate(entries): |
| 103 | + if kind == "CONDUCTOR" and name in ROUTING: |
69 | 104 | R, C = metal_rc(entries, i) |
70 | | - print(f'set_layer_rc -layer {name:<5} -resistance {R:8.3f} -capacitance {C:.3e}') |
| 105 | + print( |
| 106 | + f"set_layer_rc -layer {name:<5} -resistance {R:8.3f} -capacitance {C:.3e}" |
| 107 | + ) |
71 | 108 | print() |
72 | | - print('# Via resistance (single via). Derived from ITF VIA RPV values.') |
| 109 | + print("# Via resistance (single via). Derived from ITF VIA RPV values.") |
73 | 110 | print() |
74 | | - for kind,name,p in entries: |
75 | | - if kind=='VIA' and name.startswith(('V','BV')) and 'RPV' in p: |
| 111 | + for kind, name, p in entries: |
| 112 | + if kind == "VIA" and name.startswith(("V", "BV")) and "RPV" in p: |
76 | 113 | print(f'set_layer_rc -via {name:<4} -resistance {p["RPV"]:.3f}') |
0 commit comments