lambdapdk 0.1.37__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 +6 -4
- lambdapdk/ihp130/libs/sg13g2_sram.py +1 -1
- lambdapdk/interposer/__init__.py +111 -0
- lambdapdk/interposer/_generator.py +533 -0
- lambdapdk/interposer/libs/bumps.py +31 -0
- {lambdapdk-0.1.37.dist-info → lambdapdk-0.1.38.dist-info}/METADATA +3 -1
- {lambdapdk-0.1.37.dist-info → lambdapdk-0.1.38.dist-info}/RECORD +10 -7
- {lambdapdk-0.1.37.dist-info → lambdapdk-0.1.38.dist-info}/WHEEL +1 -1
- {lambdapdk-0.1.37.dist-info → lambdapdk-0.1.38.dist-info}/LICENSE +0 -0
- {lambdapdk-0.1.37.dist-info → lambdapdk-0.1.38.dist-info}/top_level.txt +0 -0
lambdapdk/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import siliconcompiler.package as sc_package
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
__version__ = "0.1.
|
|
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}/
|
|
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.
|
|
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
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lambdapdk/__init__.py,sha256=
|
|
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=
|
|
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.
|
|
21
|
-
lambdapdk-0.1.
|
|
22
|
-
lambdapdk-0.1.
|
|
23
|
-
lambdapdk-0.1.
|
|
24
|
-
lambdapdk-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|