librelane 2.4.0.dev0__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.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__init__.py +38 -0
- librelane/__main__.py +470 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +61 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +245 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +402 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +117 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +158 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +722 -0
- librelane/container.py +264 -0
- librelane/env_info.py +306 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +330 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +985 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +81 -0
- librelane/scripts/magic/drc.tcl +79 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +47 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
- librelane/scripts/magic/lef/maglef.tcl +27 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +19 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +574 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/exception_codes.py +17 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +395 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +246 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +476 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +15 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +180 -0
- librelane/state/state.py +351 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +511 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +566 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +955 -0
- librelane/steps/openroad.py +2433 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +629 -0
- librelane/steps/step.py +1547 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dev0.dist-info/METADATA +151 -0
- librelane-2.4.0.dev0.dist-info/RECORD +166 -0
- librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dev0.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import site
|
|
17
|
+
import shlex
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
from os.path import abspath
|
|
21
|
+
from base64 import b64encode
|
|
22
|
+
from typing import Any, Dict, Optional, List, Sequence, Tuple, Union
|
|
23
|
+
|
|
24
|
+
from .step import ViewsUpdate, MetricsUpdate, Step, StepError, StepException
|
|
25
|
+
|
|
26
|
+
from ..config import Variable
|
|
27
|
+
from ..logging import info
|
|
28
|
+
from ..state import DesignFormat, State
|
|
29
|
+
from ..common import Path, get_script_dir, mkdirp, _get_process_limit
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class KLayoutStep(Step):
|
|
33
|
+
config_vars = [
|
|
34
|
+
Variable(
|
|
35
|
+
"KLAYOUT_TECH",
|
|
36
|
+
Path,
|
|
37
|
+
"A path to the KLayout layer technology (.lyt) file.",
|
|
38
|
+
pdk=True,
|
|
39
|
+
),
|
|
40
|
+
Variable(
|
|
41
|
+
"KLAYOUT_PROPERTIES",
|
|
42
|
+
Path,
|
|
43
|
+
"A path to the KLayout layer properties (.lyp) file.",
|
|
44
|
+
pdk=True,
|
|
45
|
+
),
|
|
46
|
+
Variable(
|
|
47
|
+
"KLAYOUT_DEF_LAYER_MAP",
|
|
48
|
+
Path,
|
|
49
|
+
"A path to the KLayout LEF/DEF layer mapping (.map) file.",
|
|
50
|
+
pdk=True,
|
|
51
|
+
),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
def run_pya_script(
|
|
55
|
+
self,
|
|
56
|
+
cmd: Sequence[Union[str, os.PathLike]],
|
|
57
|
+
log_to: Optional[Union[str, os.PathLike]] = None,
|
|
58
|
+
silent: bool = False,
|
|
59
|
+
report_dir: Optional[Union[str, os.PathLike]] = None,
|
|
60
|
+
env: Optional[Dict[str, Any]] = None,
|
|
61
|
+
**kwargs,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
env = env or os.environ.copy()
|
|
64
|
+
# Pass site packages
|
|
65
|
+
python_path_elements = site.getsitepackages() + sys.path
|
|
66
|
+
if current_pythonpath := env.get("PYTHONPATH"):
|
|
67
|
+
python_path_elements.append(current_pythonpath)
|
|
68
|
+
|
|
69
|
+
env["PYTHONPATH"] = ":".join(python_path_elements)
|
|
70
|
+
return super().run_subprocess(cmd, log_to, silent, report_dir, env, **kwargs)
|
|
71
|
+
|
|
72
|
+
def get_cli_args(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
layer_info: bool = True,
|
|
76
|
+
include_lefs: bool = False,
|
|
77
|
+
include_gds: bool = False,
|
|
78
|
+
) -> List[str]:
|
|
79
|
+
result = []
|
|
80
|
+
if layer_info:
|
|
81
|
+
lyp = abspath(self.config["KLAYOUT_PROPERTIES"])
|
|
82
|
+
lyt = abspath(self.config["KLAYOUT_TECH"])
|
|
83
|
+
lym = abspath(self.config["KLAYOUT_DEF_LAYER_MAP"])
|
|
84
|
+
if None in [lyp, lyt, lym]:
|
|
85
|
+
raise StepError(
|
|
86
|
+
"Cannot open design in KLayout as the PDK does not appear to support KLayout."
|
|
87
|
+
)
|
|
88
|
+
result += ["--lyp", lyp, "--lyt", lyt, "--lym", lym]
|
|
89
|
+
|
|
90
|
+
if include_lefs:
|
|
91
|
+
tech_lefs = self.toolbox.filter_views(self.config, self.config["TECH_LEFS"])
|
|
92
|
+
if len(tech_lefs) != 1:
|
|
93
|
+
raise StepException(
|
|
94
|
+
"Misconfigured SCL: 'TECH_LEFS' must return exactly one Tech LEF for its default timing corner."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
lef_args = [
|
|
98
|
+
"--input-lef",
|
|
99
|
+
abspath(tech_lefs[0]),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for lef in self.config["CELL_LEFS"]:
|
|
103
|
+
lef_args.append("--input-lef")
|
|
104
|
+
lef_args.append(abspath(lef))
|
|
105
|
+
|
|
106
|
+
macro_lefs = self.toolbox.get_macro_views(self.config, DesignFormat.LEF)
|
|
107
|
+
for lef in macro_lefs:
|
|
108
|
+
lef_args.append("--input-lef")
|
|
109
|
+
lef_args.append(abspath(lef))
|
|
110
|
+
|
|
111
|
+
if extra_lefs := self.config["EXTRA_LEFS"]:
|
|
112
|
+
for lef in extra_lefs:
|
|
113
|
+
lef_args.append("--input-lef")
|
|
114
|
+
lef_args.append(abspath(lef))
|
|
115
|
+
|
|
116
|
+
result += lef_args
|
|
117
|
+
|
|
118
|
+
if include_gds:
|
|
119
|
+
gds_args = []
|
|
120
|
+
for gds in self.config["CELL_GDS"]:
|
|
121
|
+
gds_args.append("--with-gds-file")
|
|
122
|
+
gds_args.append(gds)
|
|
123
|
+
for gds in self.toolbox.get_macro_views(self.config, DesignFormat.GDS):
|
|
124
|
+
gds_args.append("--with-gds-file")
|
|
125
|
+
gds_args.append(gds)
|
|
126
|
+
if extra_gds := self.config["EXTRA_GDS_FILES"]:
|
|
127
|
+
for gds in extra_gds:
|
|
128
|
+
gds_args.append("--with-gds-file")
|
|
129
|
+
gds_args.append(gds)
|
|
130
|
+
result += gds_args
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@Step.factory.register()
|
|
136
|
+
class Render(KLayoutStep):
|
|
137
|
+
"""
|
|
138
|
+
Renders a PNG of the layout using KLayout.
|
|
139
|
+
|
|
140
|
+
DEF is required as an input, but if a GDS-II view
|
|
141
|
+
exists in the input state, it will be used instead.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
id = "KLayout.Render"
|
|
145
|
+
name = "Render Image (w/ KLayout)"
|
|
146
|
+
|
|
147
|
+
inputs = [DesignFormat.DEF]
|
|
148
|
+
outputs = []
|
|
149
|
+
|
|
150
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
151
|
+
input_view = state_in[DesignFormat.DEF]
|
|
152
|
+
if gds := state_in[DesignFormat.GDS]:
|
|
153
|
+
input_view = gds
|
|
154
|
+
|
|
155
|
+
assert isinstance(input_view, Path)
|
|
156
|
+
|
|
157
|
+
self.run_pya_script(
|
|
158
|
+
[
|
|
159
|
+
sys.executable,
|
|
160
|
+
os.path.join(get_script_dir(), "klayout", "render.py"),
|
|
161
|
+
abspath(input_view),
|
|
162
|
+
"--output",
|
|
163
|
+
abspath(os.path.join(self.step_dir, "out.png")),
|
|
164
|
+
]
|
|
165
|
+
+ self.get_cli_args(include_lefs=True),
|
|
166
|
+
silent=True,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return {}, {}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@Step.factory.register()
|
|
173
|
+
class StreamOut(KLayoutStep):
|
|
174
|
+
"""
|
|
175
|
+
Converts DEF views into GDSII streams using KLayout.
|
|
176
|
+
|
|
177
|
+
The PDK must support KLayout for this step to work, otherwise
|
|
178
|
+
it will be skipped.
|
|
179
|
+
|
|
180
|
+
If ``PRIMARY_GDSII_STREAMOUT_TOOL`` is set to ``"klayout"``, both GDS and KLAYOUT_GDS
|
|
181
|
+
will be updated, and if set to another tool, only ``KLAYOUT_GDS`` will be
|
|
182
|
+
updated.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
id = "KLayout.StreamOut"
|
|
186
|
+
name = "GDSII Stream Out (KLayout)"
|
|
187
|
+
|
|
188
|
+
inputs = [DesignFormat.DEF]
|
|
189
|
+
outputs = [DesignFormat.GDS, DesignFormat.KLAYOUT_GDS]
|
|
190
|
+
|
|
191
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
192
|
+
views_updates: ViewsUpdate = {}
|
|
193
|
+
|
|
194
|
+
klayout_gds_out = os.path.join(
|
|
195
|
+
self.step_dir,
|
|
196
|
+
f"{self.config['DESIGN_NAME']}.{DesignFormat.KLAYOUT_GDS.value.extension}",
|
|
197
|
+
)
|
|
198
|
+
kwargs, env = self.extract_env(kwargs)
|
|
199
|
+
|
|
200
|
+
self.run_pya_script(
|
|
201
|
+
[
|
|
202
|
+
sys.executable,
|
|
203
|
+
os.path.join(
|
|
204
|
+
get_script_dir(),
|
|
205
|
+
"klayout",
|
|
206
|
+
"stream_out.py",
|
|
207
|
+
),
|
|
208
|
+
state_in[DesignFormat.DEF.value.id],
|
|
209
|
+
"--output",
|
|
210
|
+
abspath(klayout_gds_out),
|
|
211
|
+
"--top",
|
|
212
|
+
self.config["DESIGN_NAME"],
|
|
213
|
+
]
|
|
214
|
+
+ self.get_cli_args(include_lefs=True, include_gds=True),
|
|
215
|
+
env=env,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
views_updates[DesignFormat.KLAYOUT_GDS] = Path(klayout_gds_out)
|
|
219
|
+
|
|
220
|
+
if self.config["PRIMARY_GDSII_STREAMOUT_TOOL"] == "klayout":
|
|
221
|
+
gds_path = os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.gds")
|
|
222
|
+
shutil.copy(klayout_gds_out, gds_path)
|
|
223
|
+
views_updates[DesignFormat.GDS] = Path(gds_path)
|
|
224
|
+
|
|
225
|
+
return views_updates, {}
|
|
226
|
+
|
|
227
|
+
def layout_preview(self) -> Optional[str]:
|
|
228
|
+
if self.state_out is None:
|
|
229
|
+
return None
|
|
230
|
+
assert self.toolbox is not None
|
|
231
|
+
|
|
232
|
+
if image := self.toolbox.render_png(self.config, self.state_out):
|
|
233
|
+
image_encoded = b64encode(image).decode("utf8")
|
|
234
|
+
return f'<img src="data:image/png;base64,{image_encoded}" />'
|
|
235
|
+
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@Step.factory.register()
|
|
240
|
+
class XOR(KLayoutStep):
|
|
241
|
+
"""
|
|
242
|
+
Performs an XOR operation on the Magic and KLayout GDS views. The idea is:
|
|
243
|
+
if there's any difference between the GDSII streams between the two tools,
|
|
244
|
+
one of them have it wrong and that may lead to ambiguity.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
id = "KLayout.XOR"
|
|
248
|
+
name = "KLayout vs. Magic XOR"
|
|
249
|
+
|
|
250
|
+
inputs = [
|
|
251
|
+
DesignFormat.MAG_GDS,
|
|
252
|
+
DesignFormat.KLAYOUT_GDS,
|
|
253
|
+
]
|
|
254
|
+
outputs = []
|
|
255
|
+
|
|
256
|
+
config_vars = KLayoutStep.config_vars + [
|
|
257
|
+
Variable(
|
|
258
|
+
"KLAYOUT_XOR_THREADS",
|
|
259
|
+
Optional[int],
|
|
260
|
+
"Specifies number of threads used in the KLayout XOR check. If unset, this will be equal to your machine's thread count.",
|
|
261
|
+
),
|
|
262
|
+
Variable(
|
|
263
|
+
"KLAYOUT_XOR_IGNORE_LAYERS",
|
|
264
|
+
Optional[List[str]],
|
|
265
|
+
"KLayout layers to ignore during XOR operations.",
|
|
266
|
+
pdk=True,
|
|
267
|
+
),
|
|
268
|
+
Variable(
|
|
269
|
+
"KLAYOUT_XOR_TILE_SIZE",
|
|
270
|
+
Optional[int],
|
|
271
|
+
"A tile size for the XOR process in µm.",
|
|
272
|
+
pdk=True,
|
|
273
|
+
),
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
277
|
+
ignored = ""
|
|
278
|
+
if ignore_list := self.config["KLAYOUT_XOR_IGNORE_LAYERS"]:
|
|
279
|
+
ignored = ";".join(ignore_list)
|
|
280
|
+
|
|
281
|
+
layout_a = state_in[DesignFormat.MAG_GDS]
|
|
282
|
+
if layout_a is None:
|
|
283
|
+
self.warn("No Magic stream-out has been performed. Skipping XOR process…")
|
|
284
|
+
return {}, {}
|
|
285
|
+
layout_b = state_in[DesignFormat.KLAYOUT_GDS]
|
|
286
|
+
if layout_b is None:
|
|
287
|
+
self.warn("No KLayout stream-out has been performed. Skipping XOR process…")
|
|
288
|
+
return {}, {}
|
|
289
|
+
|
|
290
|
+
assert isinstance(layout_a, Path)
|
|
291
|
+
assert isinstance(layout_b, Path)
|
|
292
|
+
|
|
293
|
+
kwargs, env = self.extract_env(kwargs)
|
|
294
|
+
|
|
295
|
+
tile_size_options = []
|
|
296
|
+
if tile_size := self.config["KLAYOUT_XOR_TILE_SIZE"]:
|
|
297
|
+
tile_size_options += ["--tile-size", str(tile_size)]
|
|
298
|
+
|
|
299
|
+
thread_count = self.config["KLAYOUT_XOR_THREADS"] or _get_process_limit()
|
|
300
|
+
info(f"Running XOR with {thread_count} threads…")
|
|
301
|
+
|
|
302
|
+
subprocess_result = self.run_subprocess(
|
|
303
|
+
[
|
|
304
|
+
"ruby",
|
|
305
|
+
os.path.join(
|
|
306
|
+
get_script_dir(),
|
|
307
|
+
"klayout",
|
|
308
|
+
"xor.drc",
|
|
309
|
+
),
|
|
310
|
+
"--output",
|
|
311
|
+
abspath(os.path.join(self.step_dir, "xor.xml")),
|
|
312
|
+
"--top",
|
|
313
|
+
self.config["DESIGN_NAME"],
|
|
314
|
+
"--threads",
|
|
315
|
+
thread_count,
|
|
316
|
+
"--ignore",
|
|
317
|
+
ignored,
|
|
318
|
+
abspath(layout_a),
|
|
319
|
+
abspath(layout_b),
|
|
320
|
+
]
|
|
321
|
+
+ tile_size_options,
|
|
322
|
+
env=env,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return {}, subprocess_result["generated_metrics"]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@Step.factory.register()
|
|
329
|
+
class DRC(KLayoutStep):
|
|
330
|
+
id = "KLayout.DRC"
|
|
331
|
+
name = "Design Rule Check (KLayout)"
|
|
332
|
+
|
|
333
|
+
inputs = [
|
|
334
|
+
DesignFormat.GDS,
|
|
335
|
+
]
|
|
336
|
+
outputs = []
|
|
337
|
+
|
|
338
|
+
config_vars = KLayoutStep.config_vars + [
|
|
339
|
+
Variable(
|
|
340
|
+
"KLAYOUT_DRC_RUNSET",
|
|
341
|
+
Optional[Path],
|
|
342
|
+
"A path to KLayout DRC runset.",
|
|
343
|
+
pdk=True,
|
|
344
|
+
deprecated_names=["KLAYOUT_DRC_TECH_SCRIPT"],
|
|
345
|
+
),
|
|
346
|
+
Variable(
|
|
347
|
+
"KLAYOUT_DRC_OPTIONS",
|
|
348
|
+
Optional[Dict[str, Union[bool, int]]],
|
|
349
|
+
"Options passed directly to the KLayout DRC runset. They vary from one PDK to another.",
|
|
350
|
+
pdk=True,
|
|
351
|
+
),
|
|
352
|
+
Variable(
|
|
353
|
+
"KLAYOUT_DRC_THREADS",
|
|
354
|
+
Optional[int],
|
|
355
|
+
"Specifies the number of threads to be used in KLayout DRC"
|
|
356
|
+
+ "If unset, this will be equal to your machine's thread count.",
|
|
357
|
+
),
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
def run_sky130(self, state_in: State, **kwargs) -> MetricsUpdate:
|
|
361
|
+
kwargs, env = self.extract_env(kwargs)
|
|
362
|
+
reports_dir = os.path.join(self.step_dir, "reports")
|
|
363
|
+
mkdirp(reports_dir)
|
|
364
|
+
drc_script_path = self.config["KLAYOUT_DRC_RUNSET"]
|
|
365
|
+
xml_report = os.path.join(reports_dir, "drc_violations.klayout.xml")
|
|
366
|
+
json_report = os.path.join(reports_dir, "drc_violations.klayout.json")
|
|
367
|
+
feol = str(self.config["KLAYOUT_DRC_OPTIONS"]["feol"]).lower()
|
|
368
|
+
beol = str(self.config["KLAYOUT_DRC_OPTIONS"]["beol"]).lower()
|
|
369
|
+
floating_metal = str(
|
|
370
|
+
self.config["KLAYOUT_DRC_OPTIONS"]["floating_metal"]
|
|
371
|
+
).lower()
|
|
372
|
+
offgrid = str(self.config["KLAYOUT_DRC_OPTIONS"]["offgrid"]).lower()
|
|
373
|
+
seal = str(self.config["KLAYOUT_DRC_OPTIONS"]["seal"]).lower()
|
|
374
|
+
threads = self.config["KLAYOUT_DRC_THREADS"] or _get_process_limit()
|
|
375
|
+
info(f"Running KLayout DRC with {threads} threads…")
|
|
376
|
+
|
|
377
|
+
input_view = state_in[DesignFormat.GDS]
|
|
378
|
+
assert isinstance(input_view, Path)
|
|
379
|
+
|
|
380
|
+
# Not pya script - DRC script is not part of LibreLane
|
|
381
|
+
self.run_subprocess(
|
|
382
|
+
[
|
|
383
|
+
"klayout",
|
|
384
|
+
"-b",
|
|
385
|
+
"-zz",
|
|
386
|
+
"-r",
|
|
387
|
+
drc_script_path,
|
|
388
|
+
"-rd",
|
|
389
|
+
f"input={abspath(input_view)}",
|
|
390
|
+
"-rd",
|
|
391
|
+
f"topcell={self.config['DESIGN_NAME']}",
|
|
392
|
+
"-rd",
|
|
393
|
+
f"report={abspath(xml_report)}",
|
|
394
|
+
"-rd",
|
|
395
|
+
f"feol={feol}",
|
|
396
|
+
"-rd",
|
|
397
|
+
f"beol={beol}",
|
|
398
|
+
"-rd",
|
|
399
|
+
f"floating_metal={floating_metal}",
|
|
400
|
+
"-rd",
|
|
401
|
+
f"offgrid={offgrid}",
|
|
402
|
+
"-rd",
|
|
403
|
+
f"seal={seal}",
|
|
404
|
+
"-rd",
|
|
405
|
+
f"threads={threads}",
|
|
406
|
+
],
|
|
407
|
+
env=env,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
subprocess_result = self.run_pya_script(
|
|
411
|
+
[
|
|
412
|
+
"python3",
|
|
413
|
+
os.path.join(
|
|
414
|
+
get_script_dir(),
|
|
415
|
+
"klayout",
|
|
416
|
+
"xml_drc_report_to_json.py",
|
|
417
|
+
),
|
|
418
|
+
f"--xml-file={abspath(xml_report)}",
|
|
419
|
+
f"--json-file={abspath(json_report)}",
|
|
420
|
+
],
|
|
421
|
+
env=env,
|
|
422
|
+
log_to=os.path.join(self.step_dir, "xml_drc_report_to_json.log"),
|
|
423
|
+
)
|
|
424
|
+
return subprocess_result["generated_metrics"]
|
|
425
|
+
|
|
426
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
427
|
+
metrics_updates: MetricsUpdate = {}
|
|
428
|
+
if self.config["PDK"] in ["sky130A", "sky130B"]:
|
|
429
|
+
metrics_updates = self.run_sky130(state_in, **kwargs)
|
|
430
|
+
else:
|
|
431
|
+
self.warn(
|
|
432
|
+
f"KLayout DRC is not supported for the {self.config['PDK']} PDK. This step will be skipped."
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
return {}, metrics_updates
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@Step.factory.register()
|
|
439
|
+
class OpenGUI(KLayoutStep):
|
|
440
|
+
"""
|
|
441
|
+
Opens the DEF view in the KLayout GUI, with layers loaded and mapped
|
|
442
|
+
properly. Useful to inspect ``.klayout.xml`` database files and the like.
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
id = "KLayout.OpenGUI"
|
|
446
|
+
name = "Open In GUI"
|
|
447
|
+
|
|
448
|
+
inputs = [DesignFormat.DEF]
|
|
449
|
+
outputs = []
|
|
450
|
+
|
|
451
|
+
config_vars = KLayoutStep.config_vars + [
|
|
452
|
+
Variable(
|
|
453
|
+
"KLAYOUT_EDITOR_MODE",
|
|
454
|
+
bool,
|
|
455
|
+
"Whether to run the KLayout GUI in editor mode or in viewer mode.",
|
|
456
|
+
default=False,
|
|
457
|
+
),
|
|
458
|
+
Variable(
|
|
459
|
+
"KLAYOUT_GUI_USE_GDS",
|
|
460
|
+
bool,
|
|
461
|
+
"Whether to prioritize GDS (if found) when running this step.",
|
|
462
|
+
default=True,
|
|
463
|
+
deprecated_names=["KLAYOUT_PRIORITIZE_GDS"],
|
|
464
|
+
),
|
|
465
|
+
]
|
|
466
|
+
|
|
467
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
468
|
+
kwargs, env = self.extract_env(kwargs)
|
|
469
|
+
mode_args = []
|
|
470
|
+
if self.config["KLAYOUT_EDITOR_MODE"]:
|
|
471
|
+
mode_args.append("--editor")
|
|
472
|
+
|
|
473
|
+
layout = state_in[DesignFormat.DEF]
|
|
474
|
+
if self.config["KLAYOUT_GUI_USE_GDS"]:
|
|
475
|
+
if gds := state_in[DesignFormat.GDS]:
|
|
476
|
+
layout = gds
|
|
477
|
+
assert isinstance(layout, Path)
|
|
478
|
+
|
|
479
|
+
env["KLAYOUT_ARGV"] = shlex.join(
|
|
480
|
+
[
|
|
481
|
+
abspath(layout),
|
|
482
|
+
]
|
|
483
|
+
+ self.get_cli_args(include_lefs=True)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
cmd = (
|
|
487
|
+
[
|
|
488
|
+
shutil.which("klayout") or "klayout",
|
|
489
|
+
]
|
|
490
|
+
+ mode_args
|
|
491
|
+
+ [
|
|
492
|
+
"-rm",
|
|
493
|
+
os.path.join(
|
|
494
|
+
get_script_dir(),
|
|
495
|
+
"klayout",
|
|
496
|
+
"open_design.py",
|
|
497
|
+
),
|
|
498
|
+
]
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Not run_subprocess- need stdin, stdout, stderr to be accessible to the
|
|
502
|
+
# user normally
|
|
503
|
+
subprocess.check_call(
|
|
504
|
+
cmd,
|
|
505
|
+
env=env,
|
|
506
|
+
cwd=self.step_dir,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
return {}, {}
|