lambdapdk 0.1.37__tar.gz → 0.1.38__tar.gz

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.
Files changed (37) hide show
  1. {lambdapdk-0.1.37/lambdapdk.egg-info → lambdapdk-0.1.38}/PKG-INFO +3 -1
  2. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/README.md +1 -0
  3. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/__init__.py +6 -4
  4. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/ihp130/libs/sg13g2_sram.py +1 -1
  5. lambdapdk-0.1.38/lambdapdk/interposer/__init__.py +111 -0
  6. lambdapdk-0.1.38/lambdapdk/interposer/_generator.py +533 -0
  7. lambdapdk-0.1.38/lambdapdk/interposer/libs/bumps.py +31 -0
  8. {lambdapdk-0.1.37 → lambdapdk-0.1.38/lambdapdk.egg-info}/PKG-INFO +3 -1
  9. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk.egg-info/SOURCES.txt +3 -0
  10. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk.egg-info/requires.txt +1 -0
  11. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/pyproject.toml +2 -1
  12. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/tests/test_getters.py +5 -3
  13. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/tests/test_paths.py +7 -4
  14. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/LICENSE +0 -0
  15. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/MANIFEST.in +0 -0
  16. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/asap7/__init__.py +0 -0
  17. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/asap7/libs/asap7sc7p5t.py +0 -0
  18. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/asap7/libs/fakeio7.py +0 -0
  19. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/asap7/libs/fakeram7.py +0 -0
  20. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/freepdk45/__init__.py +0 -0
  21. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/freepdk45/libs/fakeram45.py +0 -0
  22. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/freepdk45/libs/nangate45.py +0 -0
  23. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/gf180/__init__.py +0 -0
  24. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/gf180/libs/gf180io.py +0 -0
  25. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/gf180/libs/gf180mcu.py +0 -0
  26. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/gf180/libs/gf180sram.py +0 -0
  27. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/ihp130/__init__.py +0 -0
  28. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/ihp130/libs/sg13g2_stdcell.py +0 -0
  29. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/sky130/__init__.py +0 -0
  30. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/sky130/libs/sky130io.py +0 -0
  31. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/sky130/libs/sky130sc.py +0 -0
  32. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk/sky130/libs/sky130sram.py +0 -0
  33. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk.egg-info/dependency_links.txt +0 -0
  34. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/lambdapdk.egg-info/top_level.txt +0 -0
  35. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/setup.cfg +0 -0
  36. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/tests/test_lambda.py +0 -0
  37. {lambdapdk-0.1.37 → lambdapdk-0.1.38}/tests/test_local_detect.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lambdapdk
3
- Version: 0.1.37
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
@@ -206,6 +206,7 @@ Requires-Dist: pytest-timeout==2.3.1; extra == "test"
206
206
  Requires-Dist: tclint==0.4.2; extra == "test"
207
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
 
@@ -12,6 +12,7 @@ Supported PDKs:
12
12
  * [Skywater130](lambdapdk/sky130/base/README.md)
13
13
  * [Global Foundries 180](lambdapdk/gf180/README.md)
14
14
  * [IHP 180](https://github.com/IHP-GmbH/IHP-Open-PDK)
15
+ * [interposer](lambdapdk/interposer/README.md)
15
16
 
16
17
  # License
17
18
 
@@ -1,7 +1,7 @@
1
1
  import siliconcompiler.package as sc_package
2
2
 
3
3
 
4
- __version__ = "0.1.37"
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.37
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
@@ -206,6 +206,7 @@ Requires-Dist: pytest-timeout==2.3.1; extra == "test"
206
206
  Requires-Dist: tclint==0.4.2; extra == "test"
207
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
 
@@ -22,6 +22,9 @@ lambdapdk/gf180/libs/gf180sram.py
22
22
  lambdapdk/ihp130/__init__.py
23
23
  lambdapdk/ihp130/libs/sg13g2_sram.py
24
24
  lambdapdk/ihp130/libs/sg13g2_stdcell.py
25
+ lambdapdk/interposer/__init__.py
26
+ lambdapdk/interposer/_generator.py
27
+ lambdapdk/interposer/libs/bumps.py
25
28
  lambdapdk/sky130/__init__.py
26
29
  lambdapdk/sky130/libs/sky130io.py
27
30
  lambdapdk/sky130/libs/sky130sc.py
@@ -7,3 +7,4 @@ pytest-timeout==2.3.1
7
7
  tclint==0.4.2
8
8
  lambdalib==0.3.0
9
9
  sc-leflib==0.4.0
10
+ Jinja2==3.1.4
@@ -32,7 +32,8 @@ test = [
32
32
  "pytest-timeout == 2.3.1",
33
33
  "tclint == 0.4.2",
34
34
  "lambdalib == 0.3.0",
35
- "sc-leflib == 0.4.0"
35
+ "sc-leflib == 0.4.0",
36
+ "Jinja2 == 3.1.4"
36
37
  ]
37
38
 
38
39
  [tool.tclint]
@@ -2,15 +2,16 @@ import pytest
2
2
  from siliconcompiler import Chip
3
3
  import lambdapdk
4
4
 
5
- from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130
5
+ from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130, interposer
6
6
  from lambdapdk.asap7.libs import asap7sc7p5t, fakeram7, fakeio7
7
7
  from lambdapdk.freepdk45.libs import nangate45, fakeram45
8
8
  from lambdapdk.sky130.libs import sky130sc, sky130io, sky130sram
9
9
  from lambdapdk.gf180.libs import gf180mcu, gf180io, gf180sram
10
10
  from lambdapdk.ihp130.libs import sg13g2_stdcell, sg13g2_sram
11
+ from lambdapdk.interposer.libs import bumps as interposer_bumps
11
12
 
12
13
 
13
- @pytest.mark.parametrize('pdk', [asap7, freepdk45, sky130, gf180, ihp130])
14
+ @pytest.mark.parametrize('pdk', [asap7, freepdk45, sky130, gf180, ihp130, interposer])
14
15
  def test_pdk(pdk):
15
16
  chip = Chip('<pdk>')
16
17
  chip.use(pdk)
@@ -24,7 +25,8 @@ def test_pdk(pdk):
24
25
  nangate45, fakeram45, # freepdk45
25
26
  sky130sc, sky130io, sky130sram, # sky130
26
27
  gf180mcu, gf180io, gf180sram, # gf180
27
- sg13g2_stdcell, sg13g2_sram # ihp130
28
+ sg13g2_stdcell, sg13g2_sram, # ihp130
29
+ interposer_bumps # interposer
28
30
  ])
29
31
  def test_lib(lib):
30
32
  chip = Chip('<lib>')
@@ -2,16 +2,18 @@ import pytest
2
2
  from siliconcompiler import Chip
3
3
  import os
4
4
 
5
- from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130
5
+ from lambdapdk import asap7, freepdk45, sky130, gf180, ihp130, interposer
6
6
  from lambdapdk.asap7.libs import asap7sc7p5t, fakeram7, fakeio7
7
7
  from lambdapdk.freepdk45.libs import nangate45, fakeram45
8
8
  from lambdapdk.sky130.libs import sky130sc, sky130io, sky130sram
9
9
  from lambdapdk.gf180.libs import gf180mcu, gf180io, gf180sram
10
- from lambdapdk.ihp130.libs import sg13g2_stdcell
10
+ from lambdapdk.ihp130.libs import sg13g2_stdcell, sg13g2_sram
11
+ from lambdapdk.interposer.libs import bumps as interposer_bumps
11
12
 
12
13
 
13
14
  @pytest.mark.parametrize('pdk', [
14
- asap7, freepdk45, sky130, gf180, ihp130])
15
+ asap7, freepdk45, sky130, gf180, ihp130,
16
+ interposer])
15
17
  def test_pdk_paths(pdk):
16
18
  chip = Chip('<pdk>')
17
19
  chip.use(pdk)
@@ -23,7 +25,8 @@ def test_pdk_paths(pdk):
23
25
  nangate45, fakeram45, # freepdk45
24
26
  sky130sc, sky130io, sky130sram, # sky130
25
27
  gf180mcu, gf180io, gf180sram, # gf180
26
- sg13g2_stdcell # ihp130
28
+ sg13g2_stdcell, sg13g2_sram, # ihp130
29
+ interposer_bumps
27
30
  ])
28
31
  def test_lib_paths(lib):
29
32
  chip = Chip('<lib>')
File without changes
File without changes
File without changes