lambdapdk 0.1.36__py3-none-any.whl → 0.1.38__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lambdapdk/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import siliconcompiler.package as sc_package
2
2
 
3
3
 
4
- __version__ = "0.1.36"
4
+ __version__ = "0.1.38"
5
5
 
6
6
 
7
7
  def register_data_source(chip):
@@ -20,10 +20,10 @@ def get_pdks():
20
20
  Returns a list of pdk names in lambdapdk
21
21
  '''
22
22
 
23
- from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130
23
+ from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130, interposer
24
24
 
25
25
  all_pdks = []
26
- for pdk_mod in [asap7, freepdk45, sky130, gf180, ihp130]:
26
+ for pdk_mod in [asap7, freepdk45, sky130, gf180, ihp130, interposer]:
27
27
  pdks = pdk_mod.setup()
28
28
  if not isinstance(pdks, (list, tuple)):
29
29
  pdks = [pdks]
@@ -43,6 +43,7 @@ def get_libs():
43
43
  from lambdapdk.sky130.libs import sky130sc, sky130io, sky130sram
44
44
  from lambdapdk.gf180.libs import gf180mcu, gf180io, gf180sram
45
45
  from lambdapdk.ihp130.libs import sg13g2_stdcell, sg13g2_sram
46
+ from lambdapdk.interposer.libs import bumps as interposer_bumps
46
47
 
47
48
  all_libs = []
48
49
  for lib_mod in [
@@ -50,7 +51,8 @@ def get_libs():
50
51
  nangate45, fakeram45,
51
52
  sky130sc, sky130io, sky130sram,
52
53
  gf180mcu, gf180io, gf180sram,
53
- sg13g2_stdcell, sg13g2_sram]:
54
+ sg13g2_stdcell, sg13g2_sram,
55
+ interposer_bumps]:
54
56
  libs = lib_mod.setup()
55
57
  if not isinstance(libs, (list, tuple)):
56
58
  libs = [libs]
@@ -15,7 +15,7 @@ def setup():
15
15
  path_base = 'ihp-sg13g2/libs.ref/sg13g2_sram'
16
16
  lib.add('output', stackup, 'lef', f'{path_base}/lef/{mem_name}.lef')
17
17
  lib.add('output', stackup, 'gds', f'{path_base}/gds/{mem_name}.gds')
18
- lib.add('output', stackup, 'cdl', f'{path_base}/spice/{mem_name}.cdl')
18
+ lib.add('output', stackup, 'cdl', f'{path_base}/cdl/{mem_name}.cdl')
19
19
 
20
20
  lib.add('output', 'typ', 'nldm', f'{path_base}/lib/{mem_name}_typ_1p20V_25C.lib')
21
21
  lib.add('output', 'slow', 'nldm', f'{path_base}/lib/{mem_name}_slow_1p08V_125C.lib')
@@ -0,0 +1,111 @@
1
+ import os
2
+ import siliconcompiler
3
+ from lambdapdk import register_data_source
4
+
5
+
6
+ stackups = []
7
+ for m in ("3ML", "4ML", "5ML"):
8
+ for w in ("0400", "0800", "2000", "0400_2000"):
9
+ stackups.append(f'{m}_{w}')
10
+
11
+
12
+ ####################################################
13
+ # PDK Setup
14
+ ####################################################
15
+ def setup():
16
+ '''
17
+ The interposer PDK is a passive technology with a number of
18
+ simulated stackups. The PDK contains enablement for place and
19
+ route tools and design rule signoff.
20
+ Note that this process design kit is provided as an academic
21
+ and research aid only and the resulting designs are not manufacturable.
22
+ '''
23
+
24
+ foundry = 'virtual'
25
+ process = 'interposer'
26
+
27
+ libtype = 'none'
28
+
29
+ node = 130
30
+ # TODO: dummy numbers, only matter for cost estimation
31
+ wafersize = 300
32
+ hscribe = 0.1
33
+ vscribe = 0.1
34
+ edgemargin = 2
35
+
36
+ pdkdir = os.path.join('lambdapdk', 'interposer', 'base')
37
+
38
+ pdk = siliconcompiler.PDK(process, package='lambdapdk')
39
+ register_data_source(pdk)
40
+
41
+ # process name
42
+ pdk.set('pdk', process, 'foundry', foundry)
43
+ pdk.set('pdk', process, 'node', node)
44
+ pdk.set('pdk', process, 'version', 'v0.0.1')
45
+ pdk.set('pdk', process, 'stackup', stackups)
46
+ pdk.set('pdk', process, 'wafersize', wafersize)
47
+ pdk.set('pdk', process, 'edgemargin', edgemargin)
48
+ pdk.set('pdk', process, 'scribe', (hscribe, vscribe))
49
+
50
+ # APR Setup
51
+ for stackup in stackups:
52
+ for tool in ('openroad', 'klayout', 'magic'):
53
+ pdk.set('pdk', process, 'aprtech', tool, stackup, libtype, 'lef',
54
+ pdkdir + f'/apr/{stackup}.lef')
55
+
56
+ pdk.set('pdk', process, 'minlayer', stackup, 'metal1')
57
+ pdk.set('pdk', process, 'maxlayer', stackup, 'topmetal')
58
+
59
+ # DRC Runsets
60
+ pdk.set('pdk', process, 'drc', 'runset', 'klayout', stackup, 'drc',
61
+ pdkdir + f'/setup/klayout/{stackup}.drc')
62
+
63
+ key = 'drc_params:drc'
64
+ pdk.add('pdk', process, 'var', 'klayout', stackup, key, 'input=<input>')
65
+ pdk.add('pdk', process, 'var', 'klayout', stackup, key, 'topcell=<topcell>')
66
+ pdk.add('pdk', process, 'var', 'klayout', stackup, key, 'report=<report>')
67
+ pdk.add('pdk', process, 'var', 'klayout', stackup, key, 'threads=<threads>')
68
+
69
+ # Layer map and display file
70
+ pdk.set('pdk', process, 'layermap', 'klayout', 'def', 'gds', stackup,
71
+ pdkdir + f'/apr/{stackup}.layermap')
72
+ pdk.set('pdk', process, 'display', 'klayout', stackup,
73
+ pdkdir + f'/setup/klayout/{stackup}.lyp')
74
+
75
+ pdk.set('pdk', process, 'aprtech', 'openroad', stackup, libtype, 'fill',
76
+ pdkdir + f'/dfm/openroad/{stackup}.fill.json')
77
+
78
+ # Openroad global routing grid derating
79
+ openroad_layer_adjustments = {
80
+ 'metal1': 0.20,
81
+ 'metal2': 0.20,
82
+ 'metal3': 0.20,
83
+ 'metal4': 0.20,
84
+ 'metal5': 0.20,
85
+ 'metal6': 0.20,
86
+ 'topmetal': 0.20
87
+ }
88
+ for layer, adj in openroad_layer_adjustments.items():
89
+ if layer != 'topmetal' and int(layer[-1]) >= int(stackup[0]):
90
+ continue
91
+ pdk.set('pdk', process, 'var', 'openroad', f'{layer}_adjustment', stackup, adj)
92
+
93
+ pdk.set('pdk', process, 'var', 'openroad', 'rclayer_signal', stackup, 'metal2')
94
+ pdk.set('pdk', process, 'var', 'openroad', 'rclayer_clock', stackup, 'metal2')
95
+
96
+ pdk.set('pdk', process, 'var', 'openroad', 'pin_layer_vertical', stackup, 'metal2')
97
+ pdk.set('pdk', process, 'var', 'openroad', 'pin_layer_horizontal', stackup, 'metal3')
98
+
99
+ # PEX
100
+ for corner in ["minimum", "typical", "maximum"]:
101
+ pdk.set('pdk', process, 'pexmodel', 'openroad', stackup, corner,
102
+ pdkdir + '/pex/openroad/' + stackup + '.' + corner + '.tcl')
103
+
104
+ return pdk
105
+
106
+
107
+ #########################
108
+ if __name__ == "__main__":
109
+ pdk = setup()
110
+ pdk.write_manifest(f'{pdk.top()}.json')
111
+ pdk.check_filepaths()
@@ -0,0 +1,533 @@
1
+ import json
2
+ import os
3
+
4
+ from jinja2 import Environment, FileSystemLoader
5
+ import xml.etree.ElementTree as ET
6
+ import xml.dom.minidom
7
+
8
+ template_dir = os.path.join(os.path.dirname(__file__), 'base', 'templates')
9
+ jinja2_env = Environment(loader=FileSystemLoader(template_dir))
10
+
11
+
12
+ LICENSE = '''Copyright 2024 ZeroASIC Corp
13
+
14
+ Licensed under the Apache License, Version 2.0 (the "License");
15
+ you may not use this file except in compliance with the License.
16
+ You may obtain a copy of the License at
17
+
18
+ https://www.apache.org/licenses/LICENSE-2.0
19
+
20
+ Unless required by applicable law or agreed to in writing, software
21
+ distributed under the License is distributed on an "AS IS" BASIS,
22
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+ See the License for the specific language governing permissions and
24
+ limitations under the License.
25
+ '''
26
+
27
+
28
+ def __get_gds_type_name(gds_type):
29
+ if gds_type == 0:
30
+ return ""
31
+ if gds_type == 10:
32
+ return "pin"
33
+ if gds_type == 20:
34
+ return "text"
35
+ if gds_type == 30:
36
+ return "fill"
37
+
38
+ raise ValueError(str(gds_type))
39
+
40
+
41
+ def make_metal_layer(
42
+ name,
43
+ gds_mapping,
44
+ direction,
45
+ min_width,
46
+ min_spacing,
47
+ resistance_per_um,
48
+ capacitance_per_um,
49
+ max_width=None):
50
+
51
+ layer = {
52
+ "name": name,
53
+ "type": "ROUTING",
54
+ "direction": direction,
55
+ "gds": gds_mapping,
56
+ "width": {
57
+ "min": min_width
58
+ },
59
+ "spacing": {
60
+ "min": min_spacing
61
+ },
62
+ "parasitic": {
63
+ "resistance": resistance_per_um,
64
+ "capacitance": capacitance_per_um
65
+ }
66
+ }
67
+
68
+ if max_width:
69
+ layer["width"]["max"] = max_width
70
+
71
+ return layer
72
+
73
+
74
+ def make_cut_layer(
75
+ name,
76
+ gds_mapping,
77
+ width,
78
+ min_spacing,
79
+ enclosure_bot,
80
+ enclosure_top,
81
+ resistance_per_cut):
82
+
83
+ layer = {
84
+ "name": name,
85
+ "type": "CUT",
86
+ "gds": gds_mapping,
87
+ "width": {
88
+ "min": width
89
+ },
90
+ "spacing": {
91
+ "min": min_spacing
92
+ },
93
+ "enclosure": {
94
+ "bottom": enclosure_bot,
95
+ "top": enclosure_top
96
+ },
97
+ "parasitic": {
98
+ "resistance": resistance_per_cut
99
+ }
100
+ }
101
+
102
+ return layer
103
+
104
+
105
+ def build_tech(layer_count, name=None, width=None):
106
+ if not isinstance(width, (tuple, list)):
107
+ width = layer_count * [width]
108
+
109
+ layers = []
110
+ gds_layer = 1
111
+ for n in range(layer_count):
112
+ layeridx = n + 1
113
+
114
+ metal_name = f"metal{layeridx}"
115
+ max_width = 5.0
116
+ if layeridx == layer_count:
117
+ metal_name = "topmetal"
118
+ gds_layer = 100
119
+ max_width = None
120
+
121
+ layers.append(
122
+ make_metal_layer(
123
+ metal_name,
124
+ {
125
+ "number": gds_layer,
126
+ "types": {
127
+ "NET": 0,
128
+ "SPNET": 0,
129
+ "PIN": 10,
130
+ "LEFPIN": 10,
131
+ "FILL": 30
132
+ },
133
+ "name": {
134
+ "PIN": 20,
135
+ "SPNET": 20,
136
+ "TEXT": 20
137
+ }
138
+ },
139
+ "HORIZONTAL" if layeridx % 2 == 1 else "VERTICAL",
140
+ min_width=width[n],
141
+ min_spacing=width[n],
142
+ resistance_per_um=1.5000e-03,
143
+ capacitance_per_um=1.0000E-01,
144
+ max_width=max_width
145
+ ))
146
+
147
+ if layeridx != layer_count:
148
+ layers.append(
149
+ make_cut_layer(
150
+ f"via{layeridx}",
151
+ {
152
+ "number": gds_layer + 1,
153
+ "types": {
154
+ "NET": 0,
155
+ "SPNET": 0,
156
+ "PIN": 10,
157
+ "LEFPIN": 10,
158
+ "FILL": 30
159
+ },
160
+ "name": {
161
+ "PIN": 20,
162
+ "SPNET": 20,
163
+ "TEXT": 20
164
+ }
165
+ },
166
+ width[n] / 2,
167
+ width[n] / 2,
168
+ width[n] / 2,
169
+ width[n] / 2,
170
+ resistance_per_cut=10e-3
171
+ ))
172
+ gds_layer += 2
173
+
174
+ if not name:
175
+ name = f"{layer_count}ML"
176
+
177
+ tech = {
178
+ "name": name,
179
+ "grid": 0.005,
180
+ "layers": layers,
181
+ "outline": (0, 0)
182
+ }
183
+
184
+ return tech
185
+
186
+
187
+ def build_layermap(tech, path):
188
+ layermap = []
189
+ for layer in tech["layers"]:
190
+ name = layer["name"]
191
+ gds_number = layer["gds"]["number"]
192
+
193
+ for map_type, gds_type in layer["gds"]["types"].items():
194
+ layermap.append((
195
+ name,
196
+ map_type,
197
+ str(gds_number),
198
+ str(gds_type)
199
+ ))
200
+
201
+ for map_type, gds_type in layer["gds"]["name"].items():
202
+ layermap.append((
203
+ "NAME",
204
+ f"{name}/{map_type}",
205
+ str(gds_number),
206
+ str(gds_type)
207
+ ))
208
+
209
+ layermap.append((
210
+ "DIEAREA",
211
+ "ALL",
212
+ str(tech["outline"][0]),
213
+ str(tech["outline"][1])
214
+ ))
215
+
216
+ os.makedirs(path, exist_ok=True)
217
+ with open(f'{path}/{tech["name"]}.layermap', 'w') as f:
218
+ f.write(
219
+ jinja2_env.get_template('layermap.j2').render(
220
+ license=LICENSE,
221
+ layers=layermap
222
+ )
223
+ )
224
+ f.write('\n')
225
+
226
+
227
+ def build_openroad_pex(tech, path):
228
+ corners = {
229
+ "maximum": 0.3,
230
+ "typical": 0.0,
231
+ "minimum": -0.3
232
+ }
233
+
234
+ for corner, adjustment in corners.items():
235
+ metals = []
236
+ vias = []
237
+ for layer in tech["layers"]:
238
+ name = layer["name"]
239
+
240
+ if layer["type"] == "ROUTING":
241
+ metals.append((
242
+ name,
243
+ layer["parasitic"]["resistance"]*(1+adjustment),
244
+ layer["parasitic"]["capacitance"]*(1+adjustment)
245
+ ))
246
+ else:
247
+ vias.append((
248
+ name,
249
+ layer["parasitic"]["resistance"]*(1+adjustment)
250
+ ))
251
+
252
+ os.makedirs(path, exist_ok=True)
253
+ with open(f'{path}/{tech["name"]}.{corner}.tcl', 'w') as f:
254
+ f.write(
255
+ jinja2_env.get_template('pex.tcl.j2').render(
256
+ license=LICENSE,
257
+ metals=metals,
258
+ vias=vias
259
+ )
260
+ )
261
+ f.write('\n')
262
+
263
+
264
+ def build_klayout_drc(tech, path):
265
+ layers = []
266
+
267
+ for n, layer in enumerate(tech["layers"]):
268
+ layer_nm = {
269
+ "name": layer["name"],
270
+ "type": layer["type"],
271
+ "width": {
272
+ "min": int(layer["width"]["min"] * 1000)
273
+ },
274
+ "spacing": {
275
+ "min": int(layer["spacing"]["min"] * 1000)
276
+ }
277
+ }
278
+
279
+ if "max" in layer["width"]:
280
+ layer_nm["width"]["max"] = int(layer["width"]["max"] * 1000)
281
+
282
+ if layer["type"] == "CUT":
283
+ layer_nm["enclosure"] = {
284
+ "bottom": (
285
+ tech["layers"][n - 1]["name"],
286
+ int(layer["enclosure"]["bottom"] * 1000)
287
+ ),
288
+ "top": (
289
+ tech["layers"][n + 1]["name"],
290
+ int(layer["enclosure"]["top"] * 1000)
291
+ )
292
+ }
293
+
294
+ gds_types = sorted(set(layer["gds"]["types"].values()))
295
+ layer_nm["gds"] = [
296
+ (layer["gds"]["number"], gds_type) for gds_type in gds_types
297
+ ]
298
+
299
+ layers.append(layer_nm)
300
+
301
+ os.makedirs(path, exist_ok=True)
302
+ with open(f'{path}/{tech["name"]}.drc', 'w') as f:
303
+ f.write(
304
+ jinja2_env.get_template('drc.j2').render(
305
+ license=LICENSE,
306
+ grid=int(tech["grid"] * 1000),
307
+ layers=layers,
308
+ outline={"number": tech["outline"][0], "type": tech["outline"][1]}
309
+ )
310
+ )
311
+ f.write('\n')
312
+
313
+
314
+ def build_klayout_layer_properties(tech, path):
315
+ colors = [
316
+ "#ffc280",
317
+ "#ff9d9d",
318
+ "#ff80a8",
319
+ "#c080ff",
320
+ "#9580ff",
321
+ "#8086ff",
322
+ "#80a8ff",
323
+ "#ff0000",
324
+ "#ff0080",
325
+ "#ff00ff",
326
+ "#8000ff",
327
+ "#91ff00",
328
+ "#008000",
329
+ "#508000",
330
+ "#808000",
331
+ "#805000"
332
+ ]
333
+ patterns = [
334
+ "I5",
335
+ "I9"
336
+ ]
337
+
338
+ layeridx = 0
339
+
340
+ def make_layer(name, gds):
341
+ prop = ET.Element("properties")
342
+
343
+ color = colors[layeridx % len(colors)]
344
+ pattern = patterns[layeridx % len(patterns)]
345
+
346
+ for tag, value in [
347
+ ("frame-color", color),
348
+ ("frame-brightness", "0"),
349
+ ("fill-color", color),
350
+ ("fill-brightness", "0"),
351
+ ("dither-pattern", pattern),
352
+ ("line-style", None),
353
+ ("value", "true"),
354
+ ("visible", "true"),
355
+ ("transparent", "false"),
356
+ ("width", "1"),
357
+ ("marked", "false"),
358
+ ("xfill", "false"),
359
+ ("animation", "0"),
360
+ ("name", name),
361
+ ("source", f"{name} {gds[0]}/{gds[1]}@1")
362
+ ]:
363
+ el = ET.Element(tag)
364
+
365
+ if value is not None:
366
+ el.text = value
367
+ prop.append(el)
368
+
369
+ return prop
370
+
371
+ props = ET.Element("layer-properties")
372
+ props.append(make_layer("outline", (tech["outline"][0], tech["outline"][1])))
373
+ layeridx += 1
374
+ for layer in tech["layers"]:
375
+ gds_types = sorted(set([*layer["gds"]["types"].values(), *layer["gds"]["name"].values()]))
376
+ for gds_type in gds_types:
377
+ name = layer["name"]
378
+ gds_name = __get_gds_type_name(gds_type)
379
+ if gds_type:
380
+ name += "." + gds_name
381
+ props.append(make_layer(name, (layer["gds"]["number"], gds_type)))
382
+
383
+ layeridx += 1
384
+
385
+ props.insert(0, ET.Comment(f"\n{LICENSE}"))
386
+
387
+ os.makedirs(path, exist_ok=True)
388
+ with open(f'{path}/{tech["name"]}.lyp', 'w') as f:
389
+ f.write(
390
+ xml.dom.minidom.parseString(
391
+ ET.tostring(props)).toprettyxml(indent=" "))
392
+
393
+
394
+ def build_lef(tech, path):
395
+ vias = []
396
+ for n, layer in enumerate(tech["layers"]):
397
+ if layer["type"] == "ROUTING":
398
+ continue
399
+ bottom_layer = tech["layers"][n - 1]
400
+ top_layer = tech["layers"][n + 1]
401
+
402
+ name = layer["name"].upper() + "_1"
403
+
404
+ cut_egde = layer["width"]["min"]
405
+ cut = (-cut_egde / 2, -cut_egde / 2, cut_egde / 2, cut_egde / 2)
406
+ bot = (cut[0] - layer["enclosure"]['bottom'], cut[1] - layer["enclosure"]['bottom'],
407
+ cut[2] + layer["enclosure"]['bottom'], cut[3] + layer["enclosure"]['bottom'])
408
+ top = (cut[0] - layer["enclosure"]['top'], cut[1] - layer["enclosure"]['top'],
409
+ cut[2] + layer["enclosure"]['top'], cut[3] + layer["enclosure"]['top'])
410
+
411
+ vias.append({
412
+ "name": name,
413
+ "layers": [
414
+ (layer["name"], [f"{v:.3f}" for v in cut]),
415
+ (bottom_layer["name"], [f"{v:.3f}" for v in bot]),
416
+ (top_layer["name"], [f"{v:.3f}" for v in top])
417
+ ]})
418
+
419
+ os.makedirs(path, exist_ok=True)
420
+ with open(f'{path}/{tech["name"]}.lef', 'w') as f:
421
+ f.write(
422
+ jinja2_env.get_template('lef.j2').render(
423
+ license=LICENSE,
424
+ grid=tech["grid"],
425
+ layers=tech["layers"],
426
+ vias=vias
427
+ )
428
+ )
429
+
430
+
431
+ def build_openroad_fill(tech, path):
432
+ fill = {"layers": {}}
433
+
434
+ for layer in tech["layers"]:
435
+ if layer["type"] == "CUT":
436
+ continue
437
+
438
+ max_spacing = 5 * layer["spacing"]["min"]
439
+
440
+ max_width = 10 * layer["width"]["min"]
441
+ if "max" in layer["width"]:
442
+ max_width = layer["width"]["max"]
443
+
444
+ shapes = []
445
+ for ratio in (1.0, 0.75, 0.5, 0.25, 0.0):
446
+ width = layer["width"]["min"] + ratio * (max_width - layer["width"]["min"])
447
+ width = tech["grid"] * round(width / tech["grid"])
448
+
449
+ shapes.append(width)
450
+
451
+ fill["layers"][layer["name"]] = {
452
+ "name": layer["name"],
453
+ "layer": layer["gds"]["number"],
454
+ "datatype": layer["gds"]["types"]["NET"],
455
+ "space_to_outline": max_spacing,
456
+ "non-opc": {
457
+ "datatype": layer["gds"]["types"]["FILL"],
458
+ "width": shapes,
459
+ "height": shapes,
460
+ "space_to_fill": layer["spacing"]["min"],
461
+ "space_to_non_fill": max_spacing
462
+ }
463
+ }
464
+
465
+ os.makedirs(path, exist_ok=True)
466
+ with open(f'{path}/{tech["name"]}.fill.json', 'w') as f:
467
+ json.dump(fill, f, indent=2)
468
+
469
+
470
+ def build_readme(stackups):
471
+ from lambdapdk import interposer
472
+
473
+ for stackup in stackups:
474
+ metals = []
475
+ for layer in stackup["tech"]["layers"]:
476
+ if layer["type"] == "ROUTING":
477
+ metals.append((
478
+ layer["name"],
479
+ f'{int(layer["width"]["min"] * 1000)}nm',
480
+ f'{int(layer["spacing"]["min"] * 1000)}nm'
481
+ ))
482
+ stackup["metalstack"] = reversed(metals)
483
+
484
+ with open('README.md', 'w') as f:
485
+ f.write(
486
+ jinja2_env.get_template('README.md.j2').render(
487
+ desc=interposer.setup.__doc__,
488
+ license=LICENSE,
489
+ stackups=stackups
490
+ )
491
+ )
492
+
493
+
494
+ if __name__ == "__main__":
495
+ def tapered_metal(layer_count):
496
+ mid = int(layer_count // 2)
497
+ widths = mid * [0.4]
498
+ if layer_count % 2 != 0:
499
+ widths += [0.8]
500
+ widths += mid * [2.0]
501
+ return widths
502
+
503
+ tech_specs = {
504
+ "0400": 0.4,
505
+ "0800": 0.8,
506
+ "2000": 2.0,
507
+ "0400_2000": tapered_metal
508
+ }
509
+
510
+ stackups = []
511
+
512
+ for name, width in tech_specs.items():
513
+ for layer_count in range(3, 6):
514
+ tech_name = f"{layer_count}ML_{name}"
515
+
516
+ widths = width
517
+ if not isinstance(widths, (int, float, list, tuple)):
518
+ widths = widths(layer_count)
519
+
520
+ tech = build_tech(layer_count, name=tech_name, width=widths)
521
+ stackups.append({
522
+ "name": tech_name,
523
+ "tech": tech
524
+ })
525
+
526
+ build_lef(tech, 'base/apr')
527
+ build_layermap(tech, 'base/apr')
528
+ build_klayout_layer_properties(tech, 'base/setup/klayout')
529
+ build_klayout_drc(tech, 'base/setup/klayout')
530
+ build_openroad_pex(tech, 'base/pex/openroad')
531
+ build_openroad_fill(tech, 'base/dfm/openroad')
532
+
533
+ build_readme(stackups)
@@ -0,0 +1,31 @@
1
+ import os
2
+ import siliconcompiler
3
+ from lambdapdk import register_data_source
4
+ from lambdapdk.interposer import stackups
5
+
6
+
7
+ def setup():
8
+ '''
9
+ Interposer bump library
10
+ '''
11
+ libdir = "lambdapdk/interposer/libs/bumps/"
12
+
13
+ lib = siliconcompiler.Library('interposer_bumps', package='lambdapdk')
14
+ register_data_source(lib)
15
+
16
+ # pdk
17
+ lib.set('option', 'pdk', 'interposer')
18
+
19
+ for stackup in stackups:
20
+ lib.set('output', stackup, 'lef',
21
+ os.path.join(libdir, 'lef/bumps.lef'))
22
+ lib.add('output', stackup, 'gds',
23
+ os.path.join(libdir, 'gds/bumps.gds'))
24
+
25
+ return lib
26
+
27
+
28
+ #########################
29
+ if __name__ == "__main__":
30
+ lib = setup(siliconcompiler.Chip('<lib>'))
31
+ lib.write_manifest(f'{lib.top()}.json')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lambdapdk
3
- Version: 0.1.36
3
+ Version: 0.1.38
4
4
  Summary: Library of open source Process Design Kits
5
5
  Author: Zero ASIC
6
6
  License: Apache License
@@ -204,8 +204,9 @@ Requires-Dist: flake8 ==7.1.1 ; extra == 'test'
204
204
  Requires-Dist: pytest ==8.3.3 ; extra == 'test'
205
205
  Requires-Dist: pytest-timeout ==2.3.1 ; extra == 'test'
206
206
  Requires-Dist: tclint ==0.4.2 ; extra == 'test'
207
- Requires-Dist: lambdalib ==0.2.10 ; extra == 'test'
207
+ Requires-Dist: lambdalib ==0.3.0 ; extra == 'test'
208
208
  Requires-Dist: sc-leflib ==0.4.0 ; extra == 'test'
209
+ Requires-Dist: Jinja2 ==3.1.4 ; extra == 'test'
209
210
 
210
211
  # Lambdapdk Introduction
211
212
 
@@ -221,6 +222,7 @@ Supported PDKs:
221
222
  * [Skywater130](lambdapdk/sky130/base/README.md)
222
223
  * [Global Foundries 180](lambdapdk/gf180/README.md)
223
224
  * [IHP 180](https://github.com/IHP-GmbH/IHP-Open-PDK)
225
+ * [interposer](lambdapdk/interposer/README.md)
224
226
 
225
227
  # License
226
228
 
@@ -1,4 +1,4 @@
1
- lambdapdk/__init__.py,sha256=YzM7xvtEuCHwtRdhQroeJO2mWZNgUZPvwDeRf1D5nf0,1649
1
+ lambdapdk/__init__.py,sha256=EZLKri_azp9QL_rw8M1BDkJgI0_HacsVGi9zKD_qHl0,1771
2
2
  lambdapdk/asap7/__init__.py,sha256=Tb3du7LEisL_YoXVzVZgy9NqC835F79a9zqlV0MwBO0,4571
3
3
  lambdapdk/asap7/libs/asap7sc7p5t.py,sha256=mKymeYPmNTHgxfwyIShxUN5hO1iAzgx2MWoBbfPkChU,6159
4
4
  lambdapdk/asap7/libs/fakeio7.py,sha256=t4OQ7uljg4jh14bf3o4wsnI5gNkLEVpMs1drispc7LE,978
@@ -11,14 +11,17 @@ lambdapdk/gf180/libs/gf180io.py,sha256=6WgsALOaKWYG6RfNFz6sg36zmfZZFl2HnsDWOWYy1
11
11
  lambdapdk/gf180/libs/gf180mcu.py,sha256=NPwcviynLMXRlkvAdwNrp8sJxHzT78PE0Iwy-DNpgzA,6727
12
12
  lambdapdk/gf180/libs/gf180sram.py,sha256=uNtf4JOaT6wlD7D6hN5XwCoIOPmAXkVxSJXQuqNdY6k,2140
13
13
  lambdapdk/ihp130/__init__.py,sha256=dSXwH5rYOrJc9C3FTe9bxCJw9VZaK0fnRBMWaWT9NY8,5500
14
- lambdapdk/ihp130/libs/sg13g2_sram.py,sha256=3D5MKweUeOpWTHmtESbt8AyddjAvcpw9SeSbz4cfFG4,1887
14
+ lambdapdk/ihp130/libs/sg13g2_sram.py,sha256=oFg89-kKvuBBI9IsdGkuTsG9Q8i4gzImF66MXMXY4Fk,1885
15
15
  lambdapdk/ihp130/libs/sg13g2_stdcell.py,sha256=i92vJvo2bur2lL9YKtyrZ2kDP8QiqmbTqWnSQeSiKhE,5085
16
+ lambdapdk/interposer/__init__.py,sha256=UEAxcCLo-0OheGNCOTLpwnF05HFAz3agStFBTdcgS58,4039
17
+ lambdapdk/interposer/_generator.py,sha256=Uf9i7gfjgntUpLmxsRmOfzfgh26iQ3nfZmrdIx59UtA,15016
18
+ lambdapdk/interposer/libs/bumps.py,sha256=-b3cNOUBKhzzxoWI1Y047g9Ev3a_mXBMLKO3BaHo1sI,771
16
19
  lambdapdk/sky130/__init__.py,sha256=PXJEa-siz7HStlvDTCI794evU8qbHl472gHckSrZFWc,4124
17
20
  lambdapdk/sky130/libs/sky130io.py,sha256=wFmN9RC98f6OFM5E5acCUgPe35tVTKciJzIcUBlmumo,2029
18
21
  lambdapdk/sky130/libs/sky130sc.py,sha256=wW6sAaPpuqqCDqUD2cdJZJXqQz6KRLz9xztQ-hLNaC8,9446
19
22
  lambdapdk/sky130/libs/sky130sram.py,sha256=ILjAjbgDdvfiWqmjmj8mmHvU7YiYGZVHGO7jmyajnaY,1489
20
- lambdapdk-0.1.36.dist-info/LICENSE,sha256=2TIhku7H905BsYloYoCwat2JsdkGYc_qsnvU-p7P-IQ,10766
21
- lambdapdk-0.1.36.dist-info/METADATA,sha256=QReOv697L6LXL0Vq6xgYh2D8XygEBFPqphqQOfcqrHY,13835
22
- lambdapdk-0.1.36.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
23
- lambdapdk-0.1.36.dist-info/top_level.txt,sha256=5wk8psZwCcQgSjOlWTihBeDkSIViGn8I3j5yALbs59s,10
24
- lambdapdk-0.1.36.dist-info/RECORD,,
23
+ lambdapdk-0.1.38.dist-info/LICENSE,sha256=2TIhku7H905BsYloYoCwat2JsdkGYc_qsnvU-p7P-IQ,10766
24
+ lambdapdk-0.1.38.dist-info/METADATA,sha256=oul15DbF0GpVcV4tKvCEDcIN7GdLLYSwrkEY9BVBsf8,13929
25
+ lambdapdk-0.1.38.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
26
+ lambdapdk-0.1.38.dist-info/top_level.txt,sha256=5wk8psZwCcQgSjOlWTihBeDkSIViGn8I3j5yALbs59s,10
27
+ lambdapdk-0.1.38.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5