librelane 2.4.0__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 +479 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +63 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +246 -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 +456 -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 +116 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +155 -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 +743 -0
- librelane/container.py +285 -0
- librelane/env_info.py +320 -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 +327 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +1049 -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/help/__main__.py +39 -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 +79 -0
- librelane/scripts/magic/drc.tcl +78 -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 +45 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
- librelane/scripts/magic/lef/maglef.tcl +26 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +21 -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 +573 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -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 +397 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +250 -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 +540 -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 +37 -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 +195 -0
- librelane/state/state.py +359 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +510 -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 +576 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +1088 -0
- librelane/steps/openroad.py +2460 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +640 -0
- librelane/steps/step.py +1571 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dist-info/METADATA +169 -0
- librelane-2.4.0.dist-info/RECORD +170 -0
- librelane-2.4.0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dist-info/entry_points.txt +9 -0
librelane/container.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
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
|
+
|
|
15
|
+
## This file is internal to LibreLane and is not part of the API.
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import uuid
|
|
19
|
+
import shlex
|
|
20
|
+
import pathlib
|
|
21
|
+
import subprocess
|
|
22
|
+
from typing import List, NoReturn, Sequence, Optional, Union, Tuple
|
|
23
|
+
|
|
24
|
+
import httpx
|
|
25
|
+
import semver
|
|
26
|
+
|
|
27
|
+
from .common import mkdirp
|
|
28
|
+
from .logging import err, info, warn
|
|
29
|
+
from .env_info import ContainerInfo, OSInfo
|
|
30
|
+
|
|
31
|
+
__file_dir__ = os.path.dirname(os.path.abspath(__file__))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def permission_args(osinfo: OSInfo) -> List[str]:
|
|
35
|
+
if (
|
|
36
|
+
osinfo.kernel == "Linux"
|
|
37
|
+
and isinstance(osinfo.container_info, ContainerInfo)
|
|
38
|
+
and osinfo.container_info.engine == "docker"
|
|
39
|
+
and not osinfo.container_info.rootless
|
|
40
|
+
):
|
|
41
|
+
uid = os.getuid()
|
|
42
|
+
gid = os.getgid()
|
|
43
|
+
|
|
44
|
+
return ["--user", f"{uid}:{gid}"]
|
|
45
|
+
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def gui_args(osinfo: OSInfo) -> List[str]:
|
|
50
|
+
args = []
|
|
51
|
+
if osinfo.kernel == "Linux":
|
|
52
|
+
if os.environ.get("DISPLAY") is None:
|
|
53
|
+
warn(
|
|
54
|
+
"DISPLAY environment variable not set. GUI features will not be available."
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
args += [
|
|
58
|
+
"-e",
|
|
59
|
+
f"DISPLAY={os.environ.get('DISPLAY')}",
|
|
60
|
+
"-v",
|
|
61
|
+
"/tmp/.X11-unix:/tmp/.X11-unix",
|
|
62
|
+
"-v",
|
|
63
|
+
f"{os.path.expanduser('~')}/.Xauthority:/.Xauthority",
|
|
64
|
+
"--network",
|
|
65
|
+
"host",
|
|
66
|
+
"--security-opt",
|
|
67
|
+
"seccomp=unconfined",
|
|
68
|
+
]
|
|
69
|
+
return args
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def image_exists(ce_path: str, image: str) -> bool:
|
|
73
|
+
images = (
|
|
74
|
+
subprocess.check_output([ce_path, "images", image])
|
|
75
|
+
.decode("utf8")
|
|
76
|
+
.rstrip()
|
|
77
|
+
.split("\n")[1:]
|
|
78
|
+
)
|
|
79
|
+
return len(images) >= 1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def remote_manifest_exists(image: str) -> bool:
|
|
83
|
+
registry = "docker.io"
|
|
84
|
+
image_elements = image.split("/", maxsplit=1)
|
|
85
|
+
if len(image_elements) > 1:
|
|
86
|
+
registry = image_elements[0]
|
|
87
|
+
image = image_elements[1]
|
|
88
|
+
elements = image.split(":")
|
|
89
|
+
repo = elements[0]
|
|
90
|
+
tag = "latest"
|
|
91
|
+
if len(elements) > 1:
|
|
92
|
+
tag = elements[1]
|
|
93
|
+
|
|
94
|
+
url = None
|
|
95
|
+
if registry == "docker.io":
|
|
96
|
+
url = f"https://registry.hub.docker.com/v2/repositories/{repo}/tags/{tag}"
|
|
97
|
+
elif registry == "ghcr.io":
|
|
98
|
+
url = f"https://ghcr.io/v2/{repo}/manifests/{tag}"
|
|
99
|
+
else:
|
|
100
|
+
err(f"Unknown registry '{registry}'.")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
httpx.Client(follow_redirects=True).get(
|
|
105
|
+
url, headers={"Accept": "application/json"}
|
|
106
|
+
)
|
|
107
|
+
except httpx.NetworkError:
|
|
108
|
+
err("Couldn't connect to the internet to pull container images.")
|
|
109
|
+
return False
|
|
110
|
+
except httpx.HTTPStatusError as e:
|
|
111
|
+
err(
|
|
112
|
+
f"The image {image} was not found. This may be because the CI for this image is running- in which case, please try again later. (error: {e})"
|
|
113
|
+
)
|
|
114
|
+
return False
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def ensure_image(ce_path: str, image: str) -> bool:
|
|
119
|
+
if image_exists(ce_path, image):
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
subprocess.check_call([ce_path, "pull", image])
|
|
124
|
+
except subprocess.CalledProcessError:
|
|
125
|
+
err(f"Failed to pull image '{image}' from the container registries.")
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
dos_path_sep = re.compile(r"\\")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def sanitize_path(path: Union[str, os.PathLike]) -> Tuple[str, str]:
|
|
135
|
+
"""
|
|
136
|
+
:returns: A tuple of:
|
|
137
|
+
- The host path, processed ``abspath``
|
|
138
|
+
- The target path, on UNIX-like operating systems it's identical to the
|
|
139
|
+
host path, but on Windows, the path is translated to a valid UNIX path
|
|
140
|
+
as follows:
|
|
141
|
+
- Backslashes are converted into forward slashes
|
|
142
|
+
- The drive letter (e.g. C:) is converted to a root directory (e.g. /c)
|
|
143
|
+
"""
|
|
144
|
+
path_str = str(path)
|
|
145
|
+
abspath = os.path.abspath(path_str)
|
|
146
|
+
mountable_path = abspath
|
|
147
|
+
if os.path.sep == "\\":
|
|
148
|
+
mountable_path = f"/{abspath[0]}" + dos_path_sep.sub("/", abspath)[2:]
|
|
149
|
+
return (abspath, mountable_path)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def container_version_error(input: str, against: str) -> Optional[str]:
|
|
153
|
+
if input == "UNKNOWN":
|
|
154
|
+
return (
|
|
155
|
+
"Could not determine version for %s. You may encounter unexpected issues."
|
|
156
|
+
)
|
|
157
|
+
if semver.compare(input, against) < 0:
|
|
158
|
+
return f"Your %s version ({input}) is out of date. You may encounter unexpected issues."
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def ubuntu_version_at_least(current: str, minimum: str) -> bool:
|
|
163
|
+
if current == "UNKNOWN":
|
|
164
|
+
return False
|
|
165
|
+
return tuple(map(int, current.split("."))) >= tuple(map(int, minimum.split(".")))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def run_in_container(
|
|
169
|
+
image: str,
|
|
170
|
+
args: Sequence[str],
|
|
171
|
+
pdk_root: Optional[str] = None,
|
|
172
|
+
pdk: Optional[str] = None,
|
|
173
|
+
scl: Optional[str] = None,
|
|
174
|
+
other_mounts: Optional[Sequence[str]] = None,
|
|
175
|
+
tty: bool = False,
|
|
176
|
+
) -> NoReturn:
|
|
177
|
+
# If imported at the top level, would interfere with Conda where Volare
|
|
178
|
+
# would not be installed.
|
|
179
|
+
import ciel
|
|
180
|
+
|
|
181
|
+
osinfo = OSInfo.get()
|
|
182
|
+
if not osinfo.supported:
|
|
183
|
+
warn(
|
|
184
|
+
f"Unsupported host operating system '{osinfo.kernel}'. You may encounter unexpected issues."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if not isinstance(osinfo.container_info, ContainerInfo):
|
|
188
|
+
raise FileNotFoundError("No compatible container engine found.")
|
|
189
|
+
|
|
190
|
+
ce_path = osinfo.container_info.path
|
|
191
|
+
assert ce_path is not None
|
|
192
|
+
|
|
193
|
+
engine_name = osinfo.container_info.engine.lower()
|
|
194
|
+
if engine_name == "docker":
|
|
195
|
+
if error := container_version_error(osinfo.container_info.version, "25.0.5"):
|
|
196
|
+
warn(error % engine_name)
|
|
197
|
+
elif engine_name == "podman":
|
|
198
|
+
if osinfo.distro.lower() == "ubuntu" and not ubuntu_version_at_least(
|
|
199
|
+
osinfo.distro_version, "24.04"
|
|
200
|
+
):
|
|
201
|
+
warn(
|
|
202
|
+
"Versions of Podman for Ubuntu before Ubuntu 24.04 are generally pretty buggy. We recommend using Docker instead if possible."
|
|
203
|
+
)
|
|
204
|
+
elif error := container_version_error(osinfo.container_info.version, "4.1.0"):
|
|
205
|
+
warn(error % engine_name)
|
|
206
|
+
else:
|
|
207
|
+
warn(
|
|
208
|
+
f"Unsupported container engine referenced by '{osinfo.container_info.path}'. You may encounter unexpected issues."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if not ensure_image(ce_path, image):
|
|
212
|
+
raise ValueError(f"Failed to use image '{image}'.")
|
|
213
|
+
|
|
214
|
+
terminal_args = ["-i"]
|
|
215
|
+
if tty:
|
|
216
|
+
terminal_args.append("-t")
|
|
217
|
+
|
|
218
|
+
mount_args = []
|
|
219
|
+
from_home, to_home = sanitize_path(pathlib.Path.home())
|
|
220
|
+
|
|
221
|
+
mount_args += ["-v", f"{from_home}:{to_home}"]
|
|
222
|
+
|
|
223
|
+
from_pdk, to_pdk = sanitize_path(ciel.get_ciel_home(pdk_root))
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
mkdirp(from_pdk)
|
|
227
|
+
except FileExistsError:
|
|
228
|
+
raise ValueError(f"Invalid PDK root: '{from_pdk}' is a file")
|
|
229
|
+
|
|
230
|
+
mount_args += [
|
|
231
|
+
"-v",
|
|
232
|
+
f"{from_pdk}:{to_pdk}",
|
|
233
|
+
"-e",
|
|
234
|
+
f"PDK_ROOT={to_pdk}",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
if pdk is not None:
|
|
238
|
+
mount_args += ["-e", f"PDK={pdk}"]
|
|
239
|
+
|
|
240
|
+
if scl is not None:
|
|
241
|
+
mount_args += [
|
|
242
|
+
"-e",
|
|
243
|
+
f"STD_CELL_LIBRARY={scl}",
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
from_cwd, to_cwd = sanitize_path(os.getcwd())
|
|
247
|
+
if not from_cwd.startswith(from_home):
|
|
248
|
+
mount_args += ["-v", f"{from_cwd}:{to_cwd}"]
|
|
249
|
+
mount_args += ["-w", to_cwd]
|
|
250
|
+
|
|
251
|
+
if other_mounts is not None:
|
|
252
|
+
for mount in other_mounts:
|
|
253
|
+
if os.path.isdir(mount):
|
|
254
|
+
mount_from, mount_to = sanitize_path(mount)
|
|
255
|
+
mount_args += ["-v", f"{mount_from}:{mount_to}"]
|
|
256
|
+
mkdirp(mount_from)
|
|
257
|
+
else:
|
|
258
|
+
mount_args += ["-v", f"{mount}"]
|
|
259
|
+
|
|
260
|
+
container_id = str(uuid.uuid4())
|
|
261
|
+
|
|
262
|
+
if os.getenv("_MOUNT_HOST_LIBRELANE") == "1":
|
|
263
|
+
host_librelane_pythonpath = os.path.dirname(__file_dir__)
|
|
264
|
+
mount_args += ["-v", f"{host_librelane_pythonpath}:/host_librelane"]
|
|
265
|
+
|
|
266
|
+
cmd = (
|
|
267
|
+
[
|
|
268
|
+
ce_path,
|
|
269
|
+
"run",
|
|
270
|
+
"--rm",
|
|
271
|
+
"--name",
|
|
272
|
+
container_id,
|
|
273
|
+
]
|
|
274
|
+
+ terminal_args
|
|
275
|
+
+ permission_args(osinfo)
|
|
276
|
+
+ mount_args
|
|
277
|
+
+ gui_args(osinfo)
|
|
278
|
+
+ [image]
|
|
279
|
+
+ list(args)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
info("Running containerized command:")
|
|
283
|
+
print(shlex.join(cmd))
|
|
284
|
+
|
|
285
|
+
os.execlp(ce_path, *cmd)
|
librelane/env_info.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2021-2023 Efabless Corporation
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
# Note: This file may be run with no dependencies installed as part of
|
|
18
|
+
# environment surveys. Please ensure all code as compatible as possible
|
|
19
|
+
# with ancient versions of Python.
|
|
20
|
+
|
|
21
|
+
## This file is internal to LibreLane and is not part of the API.
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
import json
|
|
26
|
+
import shutil
|
|
27
|
+
import tempfile
|
|
28
|
+
import platform
|
|
29
|
+
import subprocess
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from typing import Union, Optional, Dict, List # noqa: F401
|
|
33
|
+
except ImportError:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StringRepresentable(object):
|
|
38
|
+
def __str__(self):
|
|
39
|
+
return str(self.__dict__)
|
|
40
|
+
|
|
41
|
+
def __repr__(self):
|
|
42
|
+
return str(self.__dict__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ContainerInfo(StringRepresentable):
|
|
46
|
+
path = None # type: Optional[str]
|
|
47
|
+
engine = "UNKNOWN" # type: str
|
|
48
|
+
version = "UNKNOWN" # type: str
|
|
49
|
+
conmon = False # type: bool
|
|
50
|
+
rootless = False # type : bool
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.engine = "UNKNOWN"
|
|
54
|
+
self.version = "UNKNOWN"
|
|
55
|
+
self.conmon = False
|
|
56
|
+
self.rootless = False
|
|
57
|
+
self.seccomp = False
|
|
58
|
+
self.selinux = False
|
|
59
|
+
self.apparmor = False
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def get():
|
|
63
|
+
# type: () -> Union[ContainerInfo, str]
|
|
64
|
+
cinfo = ContainerInfo()
|
|
65
|
+
# Here are the rules:
|
|
66
|
+
# 1. If LIBRELANE_CONTAINER_ENGINE exists, use that uncritically.
|
|
67
|
+
# 2. Else, if OPENLANE_CONTAINER_ENGINE exists, use that uncritically.
|
|
68
|
+
# 3. Else, if "docker" is in PATH, always use it.
|
|
69
|
+
# 4. Else, see if "podman" is in PATH, and use THAT.
|
|
70
|
+
# 5. If none exist, halt and return early.
|
|
71
|
+
|
|
72
|
+
container_engine = os.getenv(
|
|
73
|
+
"LIBRELANE_CONTAINER_ENGINE", os.getenv("OPENLANE_CONTAINER_ENGINE")
|
|
74
|
+
)
|
|
75
|
+
if container_engine is None or container_engine == "":
|
|
76
|
+
container_engine = shutil.which("docker")
|
|
77
|
+
if container_engine is None:
|
|
78
|
+
container_engine = shutil.which("podman")
|
|
79
|
+
if container_engine is None:
|
|
80
|
+
return "no compatible container engine found in PATH (tried docker, podman)"
|
|
81
|
+
try:
|
|
82
|
+
info_str = subprocess.check_output(
|
|
83
|
+
[container_engine, "info", "--format", "{{json .}}"]
|
|
84
|
+
).decode("utf8")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return "failed to get container engine info: %s" % str(e)
|
|
87
|
+
cinfo.path = container_engine
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
info = json.loads(info_str)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return "result from '%s info' was not valid JSON: %s" % (
|
|
93
|
+
container_engine,
|
|
94
|
+
str(e),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
info.get("Docker Root Dir") is not None
|
|
99
|
+
or info.get("DockerRootDir") is not None
|
|
100
|
+
):
|
|
101
|
+
cinfo.engine = "docker"
|
|
102
|
+
|
|
103
|
+
# Get Version
|
|
104
|
+
try:
|
|
105
|
+
version_output = (
|
|
106
|
+
subprocess.check_output([container_engine, "--version"])
|
|
107
|
+
.decode("utf8")
|
|
108
|
+
.strip()
|
|
109
|
+
)
|
|
110
|
+
cinfo.version = re.split(r"\s", version_output)[2].strip(",")
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
security_options = info.get("SecurityOptions")
|
|
115
|
+
for option in security_options:
|
|
116
|
+
if "rootless" in option:
|
|
117
|
+
cinfo.rootless = True
|
|
118
|
+
elif info.get("host") is not None:
|
|
119
|
+
host = info["host"]
|
|
120
|
+
conmon = host.get("conmon")
|
|
121
|
+
remote_socket = host.get("remoteSocket")
|
|
122
|
+
security = host.get("security")
|
|
123
|
+
if conmon is not None:
|
|
124
|
+
cinfo.conmon = True
|
|
125
|
+
if remote_socket is not None and "podman" in remote_socket["path"]:
|
|
126
|
+
cinfo.engine = "podman"
|
|
127
|
+
cinfo.version = info["version"]["Version"]
|
|
128
|
+
if security is not None:
|
|
129
|
+
cinfo.rootless = security.get("rootless", False)
|
|
130
|
+
cinfo.apparmor = security.get("apparmorEnabled", False)
|
|
131
|
+
cinfo.seccomp = security.get("seccompEnabled", False)
|
|
132
|
+
cinfo.selinux = security.get("selinuxEnabled", False)
|
|
133
|
+
|
|
134
|
+
return cinfo
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class NixInfo(StringRepresentable):
|
|
138
|
+
version_string = "" # type: str
|
|
139
|
+
channels = None # type: Optional[Dict[str, str]]
|
|
140
|
+
nix_command = False # type: bool
|
|
141
|
+
flakes = False # type: bool
|
|
142
|
+
|
|
143
|
+
def __init__(self) -> None:
|
|
144
|
+
self.version_string = ""
|
|
145
|
+
self.channels = None
|
|
146
|
+
self.nix_command = False
|
|
147
|
+
self.flakes = False
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def get():
|
|
151
|
+
# type: () -> Union[NixInfo, str]
|
|
152
|
+
ninfo = NixInfo()
|
|
153
|
+
if shutil.which("nix") is None:
|
|
154
|
+
return "nix not found in PATH"
|
|
155
|
+
try:
|
|
156
|
+
version_str = subprocess.check_output(["nix", "--version"], encoding="utf8")
|
|
157
|
+
ninfo.version_string = version_str.strip()
|
|
158
|
+
except Exception as e:
|
|
159
|
+
return "could not get nix version: %s" % str(e)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
channels = {}
|
|
163
|
+
channels_raw = subprocess.check_output(
|
|
164
|
+
["nix-channel", "--list"], encoding="utf8"
|
|
165
|
+
)
|
|
166
|
+
for channel in channels_raw.splitlines():
|
|
167
|
+
name, url = channel.split(maxsplit=1)
|
|
168
|
+
channels[name] = url
|
|
169
|
+
ninfo.channels = channels
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
with tempfile.TemporaryDirectory(prefix="librelane_env_report_") as d:
|
|
174
|
+
with open(os.path.join(d, "flake.nix"), "w") as f:
|
|
175
|
+
f.write("{}")
|
|
176
|
+
nix_command = subprocess.run(
|
|
177
|
+
["nix", "eval"],
|
|
178
|
+
stdout=subprocess.PIPE,
|
|
179
|
+
stderr=subprocess.STDOUT,
|
|
180
|
+
cwd=d,
|
|
181
|
+
encoding="utf8",
|
|
182
|
+
)
|
|
183
|
+
nix_command_result = nix_command.stdout
|
|
184
|
+
if "'nix-command'" in nix_command_result:
|
|
185
|
+
pass
|
|
186
|
+
elif "'flakes'" in nix_command_result:
|
|
187
|
+
ninfo.nix_command = True
|
|
188
|
+
elif "lacks attribute" in nix_command_result:
|
|
189
|
+
ninfo.nix_command = True
|
|
190
|
+
ninfo.flakes = True
|
|
191
|
+
else:
|
|
192
|
+
print(
|
|
193
|
+
"'nix flake' returned unexpected output: %s" % nix_command_result,
|
|
194
|
+
file=sys.stderr,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return ninfo
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class OSInfo(StringRepresentable):
|
|
201
|
+
kernel = "" # type: str
|
|
202
|
+
kernel_version = "" # type: str
|
|
203
|
+
supported = False # type: bool
|
|
204
|
+
distro = "UNKNOWN" # type: str
|
|
205
|
+
distro_version = "UNKNOWN" # type: str
|
|
206
|
+
python_version = "" # type: str
|
|
207
|
+
python_path = [] # type: List[str]
|
|
208
|
+
container_info = None # type: Union[ContainerInfo, str]
|
|
209
|
+
nix_info = None # type: Union[NixInfo, str]
|
|
210
|
+
|
|
211
|
+
def __init__(self):
|
|
212
|
+
self.kernel = platform.system()
|
|
213
|
+
self.kernel_version = (
|
|
214
|
+
platform.release()
|
|
215
|
+
) # Unintuitively enough, it's the kernel's release
|
|
216
|
+
self.supported = self.kernel in ["Darwin", "Linux"]
|
|
217
|
+
self.distro = "UNKNOWN"
|
|
218
|
+
self.distro_version = "UNKNOWN"
|
|
219
|
+
self.python_version = platform.python_version()
|
|
220
|
+
self.python_path = sys.path.copy()
|
|
221
|
+
self.tkinter = False
|
|
222
|
+
try:
|
|
223
|
+
import tkinter # noqa: F401
|
|
224
|
+
|
|
225
|
+
self.tkinter = True
|
|
226
|
+
except ImportError:
|
|
227
|
+
pass
|
|
228
|
+
self.container_info = ""
|
|
229
|
+
self.nix_info = ""
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def get():
|
|
233
|
+
# type: () -> 'OSInfo'
|
|
234
|
+
osinfo = OSInfo()
|
|
235
|
+
|
|
236
|
+
if osinfo.kernel == "Windows":
|
|
237
|
+
osinfo.distro = "Windows"
|
|
238
|
+
osinfo.distro_version = platform.release()
|
|
239
|
+
osinfo.kernel_version = platform.version()
|
|
240
|
+
|
|
241
|
+
if osinfo.kernel == "Darwin":
|
|
242
|
+
osinfo.distro = "macOS"
|
|
243
|
+
osinfo.distro_version = platform.mac_ver()[0]
|
|
244
|
+
osinfo.kernel_version = platform.release()
|
|
245
|
+
|
|
246
|
+
if osinfo.kernel == "Linux":
|
|
247
|
+
os_release = ""
|
|
248
|
+
try:
|
|
249
|
+
os_release += open("/etc/lsb-release").read()
|
|
250
|
+
except FileNotFoundError:
|
|
251
|
+
pass
|
|
252
|
+
try:
|
|
253
|
+
os_release += open("/etc/os-release").read()
|
|
254
|
+
except FileNotFoundError:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
if os_release.strip() != "":
|
|
258
|
+
config = {}
|
|
259
|
+
for line in os_release.split("\n"):
|
|
260
|
+
if line.strip() == "":
|
|
261
|
+
continue
|
|
262
|
+
if line.strip().startswith("#"):
|
|
263
|
+
continue
|
|
264
|
+
key, value = line.split("=")
|
|
265
|
+
value = value.strip('"')
|
|
266
|
+
|
|
267
|
+
config[key] = value
|
|
268
|
+
|
|
269
|
+
osinfo.distro = (
|
|
270
|
+
config.get("ID") or config.get("DISTRIB_ID") or "UNKNOWN"
|
|
271
|
+
)
|
|
272
|
+
osinfo.distro_version = (
|
|
273
|
+
config.get("VERSION_ID")
|
|
274
|
+
or config.get("DISTRIB_RELEASE")
|
|
275
|
+
or "UNKNOWN"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
osinfo.container_info = ContainerInfo.get()
|
|
279
|
+
osinfo.nix_info = NixInfo.get()
|
|
280
|
+
return osinfo
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def env_info_cli():
|
|
284
|
+
def print_params(obj, indent=0):
|
|
285
|
+
if isinstance(obj, list):
|
|
286
|
+
for value in obj:
|
|
287
|
+
if isinstance(value, StringRepresentable) or isinstance(value, dict):
|
|
288
|
+
print("%s- " % (" " * indent), end="")
|
|
289
|
+
print_params(value, indent=indent + 2)
|
|
290
|
+
elif isinstance(value, list):
|
|
291
|
+
if len(value) == 0:
|
|
292
|
+
print("%s- []" % (" " * indent))
|
|
293
|
+
else:
|
|
294
|
+
print("%s- " % (" " * indent), end="")
|
|
295
|
+
print_params(value, indent=indent + 2)
|
|
296
|
+
else:
|
|
297
|
+
print("%s- %s" % (" " * indent, value))
|
|
298
|
+
|
|
299
|
+
else:
|
|
300
|
+
current = obj if isinstance(obj, dict) else obj.__dict__
|
|
301
|
+
for key in current:
|
|
302
|
+
value = current[key]
|
|
303
|
+
if isinstance(value, StringRepresentable) or isinstance(value, dict):
|
|
304
|
+
print("%s%s:" % (" " * indent, key))
|
|
305
|
+
print_params(value, indent=indent + 2)
|
|
306
|
+
elif isinstance(value, list):
|
|
307
|
+
if len(value) == 0:
|
|
308
|
+
print("%s%s: []" % (" " * indent, key))
|
|
309
|
+
else:
|
|
310
|
+
print("%s%s:" % (" " * indent, key))
|
|
311
|
+
print_params(value, indent=indent + 2)
|
|
312
|
+
else:
|
|
313
|
+
print("%s%s: %s" % (" " * indent, key, value))
|
|
314
|
+
|
|
315
|
+
info = OSInfo.get()
|
|
316
|
+
print_params(info)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if __name__ == "__main__":
|
|
320
|
+
env_info_cli()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Basics
|
|
2
|
+
DESIGN_NAME: spm
|
|
3
|
+
VERILOG_FILES: dir::src/*.v
|
|
4
|
+
CLOCK_PERIOD: 10
|
|
5
|
+
CLOCK_PORT: clk
|
|
6
|
+
PNR_SDC_FILE: dir::src/impl.sdc
|
|
7
|
+
SIGNOFF_SDC_FILE: dir::src/signoff.sdc
|
|
8
|
+
|
|
9
|
+
# PDN
|
|
10
|
+
FP_PDN_VOFFSET: 5
|
|
11
|
+
FP_PDN_HOFFSET: 5
|
|
12
|
+
FP_PDN_VWIDTH: 2
|
|
13
|
+
FP_PDN_HWIDTH: 2
|
|
14
|
+
FP_PDN_VPITCH: 30
|
|
15
|
+
FP_PDN_HPITCH: 30
|
|
16
|
+
FP_PDN_SKIPTRIM: true
|
|
17
|
+
|
|
18
|
+
# Pin Order
|
|
19
|
+
FP_PIN_ORDER_CFG: dir::pin_order.cfg
|
|
20
|
+
|
|
21
|
+
# Technology-Specific Configs
|
|
22
|
+
pdk::sky130*:
|
|
23
|
+
FP_CORE_UTIL: 45
|
|
24
|
+
CLOCK_PERIOD: 10.0
|
|
25
|
+
scl::sky130_fd_sc_hs:
|
|
26
|
+
CLOCK_PERIOD: 8
|
|
27
|
+
scl::sky130_fd_sc_ls:
|
|
28
|
+
MAX_FANOUT_CONSTRAINT: 5
|
|
29
|
+
pdk::gf180mcu*:
|
|
30
|
+
CLOCK_PERIOD: 24.0
|
|
31
|
+
FP_CORE_UTIL: 40
|
|
32
|
+
MAX_FANOUT_CONSTRAINT: 4
|
|
33
|
+
PL_TARGET_DENSITY: 0.5
|