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
librelane/container.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
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 2 and is not part of the API.
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import uuid
|
|
19
|
+
import shlex
|
|
20
|
+
import pathlib
|
|
21
|
+
import tempfile
|
|
22
|
+
import subprocess
|
|
23
|
+
from typing import List, NoReturn, Sequence, Optional, Union, Tuple
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
import semver
|
|
27
|
+
|
|
28
|
+
from .common import mkdirp
|
|
29
|
+
from .logging import err, info, warn
|
|
30
|
+
from .env_info import OSInfo
|
|
31
|
+
|
|
32
|
+
CONTAINER_ENGINE = os.getenv("OPENLANE_CONTAINER_ENGINE", "docker")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def permission_args(osinfo: OSInfo) -> List[str]:
|
|
36
|
+
if (
|
|
37
|
+
osinfo.kernel == "Linux"
|
|
38
|
+
and osinfo.container_info is not None
|
|
39
|
+
and osinfo.container_info.engine == "docker"
|
|
40
|
+
and not osinfo.container_info.rootless
|
|
41
|
+
):
|
|
42
|
+
uid = os.getuid()
|
|
43
|
+
gid = os.getgid()
|
|
44
|
+
|
|
45
|
+
return ["--user", f"{uid}:{gid}"]
|
|
46
|
+
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def gui_args(osinfo: OSInfo) -> List[str]:
|
|
51
|
+
args = []
|
|
52
|
+
if osinfo.kernel == "Linux":
|
|
53
|
+
if os.environ.get("DISPLAY") is None:
|
|
54
|
+
warn(
|
|
55
|
+
"DISPLAY environment variable not set. GUI features will not be available."
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
args += [
|
|
59
|
+
"-e",
|
|
60
|
+
f"DISPLAY={os.environ.get('DISPLAY')}",
|
|
61
|
+
"-v",
|
|
62
|
+
"/tmp/.X11-unix:/tmp/.X11-unix",
|
|
63
|
+
"-v",
|
|
64
|
+
f"{os.path.expanduser('~')}/.Xauthority:/.Xauthority",
|
|
65
|
+
"--network",
|
|
66
|
+
"host",
|
|
67
|
+
"--security-opt",
|
|
68
|
+
"seccomp=unconfined",
|
|
69
|
+
]
|
|
70
|
+
return args
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def image_exists(image: str) -> bool:
|
|
74
|
+
images = (
|
|
75
|
+
subprocess.check_output([CONTAINER_ENGINE, "images", image])
|
|
76
|
+
.decode("utf8")
|
|
77
|
+
.rstrip()
|
|
78
|
+
.split("\n")[1:]
|
|
79
|
+
)
|
|
80
|
+
return len(images) >= 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def remote_manifest_exists(image: str) -> bool:
|
|
84
|
+
registry = "docker.io"
|
|
85
|
+
image_elements = image.split("/", maxsplit=1)
|
|
86
|
+
if len(image_elements) > 1:
|
|
87
|
+
registry = image_elements[0]
|
|
88
|
+
image = image_elements[1]
|
|
89
|
+
elements = image.split(":")
|
|
90
|
+
repo = elements[0]
|
|
91
|
+
tag = "latest"
|
|
92
|
+
if len(elements) > 1:
|
|
93
|
+
tag = elements[1]
|
|
94
|
+
|
|
95
|
+
url = None
|
|
96
|
+
if registry == "docker.io":
|
|
97
|
+
url = f"https://registry.hub.docker.com/v2/repositories/{repo}/tags/{tag}"
|
|
98
|
+
elif registry == "ghcr.io":
|
|
99
|
+
url = f"https://ghcr.io/v2/{repo}/manifests/{tag}"
|
|
100
|
+
else:
|
|
101
|
+
err(f"Unknown registry '{registry}'.")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
httpx.Client(follow_redirects=True).get(
|
|
106
|
+
url, headers={"Accept": "application/json"}
|
|
107
|
+
)
|
|
108
|
+
except httpx.NetworkError:
|
|
109
|
+
err("Couldn't connect to the internet to pull container images.")
|
|
110
|
+
return False
|
|
111
|
+
except httpx.HTTPStatusError as e:
|
|
112
|
+
err(
|
|
113
|
+
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})"
|
|
114
|
+
)
|
|
115
|
+
return False
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ensure_image(image: str) -> bool:
|
|
120
|
+
if image_exists(image):
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
subprocess.check_call([CONTAINER_ENGINE, "pull", image])
|
|
125
|
+
except subprocess.CalledProcessError:
|
|
126
|
+
err(f"Failed to pull image {image} from the container registries.")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
dos_path_sep = re.compile(r"\\")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def sanitize_path(path: Union[str, os.PathLike]) -> Tuple[str, str]:
|
|
136
|
+
"""
|
|
137
|
+
:returns: A tuple of:
|
|
138
|
+
- The host path, processed ``abspath``
|
|
139
|
+
- The target path, on UNIX-like operating systems it's identical to the
|
|
140
|
+
host path, but on Windows, the path is translated to a valid UNIX path
|
|
141
|
+
as follows:
|
|
142
|
+
- Backslashes are converted into forward slashes
|
|
143
|
+
- The drive letter (e.g. C:) is converted to a root directory (e.g. /c)
|
|
144
|
+
"""
|
|
145
|
+
path_str = str(path)
|
|
146
|
+
abspath = os.path.abspath(path_str)
|
|
147
|
+
mountable_path = abspath
|
|
148
|
+
if os.path.sep == "\\":
|
|
149
|
+
mountable_path = f"/{abspath[0]}" + dos_path_sep.sub("/", abspath)[2:]
|
|
150
|
+
return (abspath, mountable_path)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def run_in_container(
|
|
154
|
+
image: str,
|
|
155
|
+
args: Sequence[str],
|
|
156
|
+
pdk_root: Optional[str] = None,
|
|
157
|
+
pdk: Optional[str] = None,
|
|
158
|
+
scl: Optional[str] = None,
|
|
159
|
+
other_mounts: Optional[Sequence[str]] = None,
|
|
160
|
+
tty: bool = False,
|
|
161
|
+
) -> NoReturn:
|
|
162
|
+
# If imported at the top level, would interfere with Conda where Volare
|
|
163
|
+
# would not be installed.
|
|
164
|
+
import ciel
|
|
165
|
+
|
|
166
|
+
osinfo = OSInfo.get()
|
|
167
|
+
if not osinfo.supported:
|
|
168
|
+
warn(
|
|
169
|
+
f"Unsupported host operating system '{osinfo.kernel}'. You may encounter unexpected issues."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if osinfo.container_info is None:
|
|
173
|
+
raise FileNotFoundError("No compatible container engine found.")
|
|
174
|
+
|
|
175
|
+
if osinfo.container_info.engine.lower() == "docker":
|
|
176
|
+
if semver.compare(osinfo.container_info.version, "25.0.5") < 0:
|
|
177
|
+
warn(
|
|
178
|
+
f"Your Docker engine version ({osinfo.container_info.version}) is out of date. You may encounter unexpected issues."
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
warn(
|
|
182
|
+
f"Unsupported container engine '{osinfo.container_info.engine}'. You may encounter unexpected issues."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not ensure_image(image):
|
|
186
|
+
raise ValueError(f"Failed to use image '{image}'.")
|
|
187
|
+
|
|
188
|
+
terminal_args = ["-i"]
|
|
189
|
+
if tty:
|
|
190
|
+
terminal_args.append("-t")
|
|
191
|
+
|
|
192
|
+
mount_args = []
|
|
193
|
+
from_home, to_home = sanitize_path(pathlib.Path.home())
|
|
194
|
+
|
|
195
|
+
mount_args += ["-v", f"{from_home}:{to_home}"]
|
|
196
|
+
|
|
197
|
+
from_pdk, to_pdk = sanitize_path(ciel.get_ciel_home(pdk_root))
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
mkdirp(from_pdk)
|
|
201
|
+
except FileExistsError:
|
|
202
|
+
raise ValueError(f"Invalid PDK root: '{from_pdk}' is a file")
|
|
203
|
+
|
|
204
|
+
mount_args += [
|
|
205
|
+
"-v",
|
|
206
|
+
f"{from_pdk}:{to_pdk}",
|
|
207
|
+
"-e",
|
|
208
|
+
f"PDK_ROOT={to_pdk}",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
if pdk is not None:
|
|
212
|
+
mount_args += ["-e", f"PDK={pdk}"]
|
|
213
|
+
|
|
214
|
+
if scl is not None:
|
|
215
|
+
mount_args += [
|
|
216
|
+
"-e",
|
|
217
|
+
f"STD_CELL_LIBRARY={scl}",
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
from_cwd, to_cwd = sanitize_path(os.getcwd())
|
|
221
|
+
if not from_cwd.startswith(from_home):
|
|
222
|
+
mount_args += ["-v", f"{from_cwd}:{to_cwd}"]
|
|
223
|
+
mount_args += ["-w", to_cwd]
|
|
224
|
+
|
|
225
|
+
tempdir = tempfile.mkdtemp("librelane_docker")
|
|
226
|
+
|
|
227
|
+
mount_args += [
|
|
228
|
+
"-v",
|
|
229
|
+
f"{tempdir}:/tmp",
|
|
230
|
+
"-e",
|
|
231
|
+
"TMPDIR=/tmp",
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
if other_mounts is not None:
|
|
235
|
+
for mount in other_mounts:
|
|
236
|
+
if os.path.isdir(mount):
|
|
237
|
+
mount_from, mount_to = sanitize_path(mount)
|
|
238
|
+
mount_args += ["-v", f"{mount_from}:{mount_to}"]
|
|
239
|
+
mkdirp(mount_from)
|
|
240
|
+
else:
|
|
241
|
+
mount_args += ["-v", f"{mount}"]
|
|
242
|
+
|
|
243
|
+
container_id = str(uuid.uuid4())
|
|
244
|
+
|
|
245
|
+
cmd = (
|
|
246
|
+
[
|
|
247
|
+
CONTAINER_ENGINE,
|
|
248
|
+
"run",
|
|
249
|
+
"--rm",
|
|
250
|
+
"--name",
|
|
251
|
+
container_id,
|
|
252
|
+
]
|
|
253
|
+
+ terminal_args
|
|
254
|
+
+ permission_args(osinfo)
|
|
255
|
+
+ mount_args
|
|
256
|
+
+ gui_args(osinfo)
|
|
257
|
+
+ [image]
|
|
258
|
+
+ list(args)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
info("Running containerized command:")
|
|
262
|
+
print(shlex.join(cmd))
|
|
263
|
+
|
|
264
|
+
os.execlp(CONTAINER_ENGINE, *cmd)
|
librelane/env_info.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
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 2 and is not part of the API.
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
import json
|
|
26
|
+
import tempfile
|
|
27
|
+
import platform
|
|
28
|
+
import subprocess
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from typing import Optional, Dict, List # noqa: F401
|
|
32
|
+
except ImportError:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
CONTAINER_ENGINE = os.getenv("OPENLANE_CONTAINER_ENGINE", "docker")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StringRepresentable(object):
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return str(self.__dict__)
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return str(self.__dict__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ContainerInfo(StringRepresentable):
|
|
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
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def get():
|
|
60
|
+
# type: () -> Optional['ContainerInfo']
|
|
61
|
+
try:
|
|
62
|
+
cinfo = ContainerInfo()
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
info_str = subprocess.check_output(
|
|
66
|
+
[CONTAINER_ENGINE, "info", "--format", "{{json .}}"]
|
|
67
|
+
).decode("utf8")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise Exception("Failed to get Docker info: %s" % str(e)) from None
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
info = json.loads(info_str)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise Exception(
|
|
75
|
+
"Result from 'docker info' was not valid JSON: %s" % str(e)
|
|
76
|
+
) from None
|
|
77
|
+
|
|
78
|
+
if info.get("host") is not None:
|
|
79
|
+
if info["host"].get("conmon") is not None:
|
|
80
|
+
cinfo.conmon = True
|
|
81
|
+
if (
|
|
82
|
+
info["host"].get("remoteSocket") is not None
|
|
83
|
+
and "podman" in info["host"]["remoteSocket"]["path"]
|
|
84
|
+
):
|
|
85
|
+
cinfo.engine = "podman"
|
|
86
|
+
|
|
87
|
+
cinfo.version = info["version"]["Version"]
|
|
88
|
+
elif (
|
|
89
|
+
info.get("Docker Root Dir") is not None
|
|
90
|
+
or info.get("DockerRootDir") is not None
|
|
91
|
+
):
|
|
92
|
+
cinfo.engine = "docker"
|
|
93
|
+
|
|
94
|
+
# Get Version
|
|
95
|
+
try:
|
|
96
|
+
version_output = (
|
|
97
|
+
subprocess.check_output([CONTAINER_ENGINE, "--version"])
|
|
98
|
+
.decode("utf8")
|
|
99
|
+
.strip()
|
|
100
|
+
)
|
|
101
|
+
cinfo.version = re.split(r"\s", version_output)[2].strip(",")
|
|
102
|
+
except Exception:
|
|
103
|
+
print("Could not extract Docker version.", file=sys.stderr)
|
|
104
|
+
|
|
105
|
+
security_options = info.get("SecurityOptions")
|
|
106
|
+
for option in security_options:
|
|
107
|
+
if "rootless" in option:
|
|
108
|
+
cinfo.rootless = True
|
|
109
|
+
|
|
110
|
+
return cinfo
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(e, file=sys.stderr)
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class NixInfo(StringRepresentable):
|
|
117
|
+
version_string = "" # type: str
|
|
118
|
+
channels = None # type: Optional[Dict[str, str]]
|
|
119
|
+
nix_command = False # type: bool
|
|
120
|
+
flakes = False # type: bool
|
|
121
|
+
|
|
122
|
+
def __init__(self) -> None:
|
|
123
|
+
self.version_string = ""
|
|
124
|
+
self.channels = None
|
|
125
|
+
self.nix_command = False
|
|
126
|
+
self.flakes = False
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def get():
|
|
130
|
+
# type: () -> Optional['NixInfo']
|
|
131
|
+
ninfo = NixInfo()
|
|
132
|
+
try:
|
|
133
|
+
try:
|
|
134
|
+
version_str = subprocess.check_output(
|
|
135
|
+
["nix", "--version"], encoding="utf8"
|
|
136
|
+
)
|
|
137
|
+
ninfo.version_string = version_str.strip()
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise Exception("Failed to get Nix info: %s" % str(e)) from None
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
channels = {}
|
|
143
|
+
channels_raw = subprocess.check_output(
|
|
144
|
+
["nix-channel", "--list"], encoding="utf8"
|
|
145
|
+
)
|
|
146
|
+
for channel in channels_raw.splitlines():
|
|
147
|
+
name, url = channel.split(maxsplit=1)
|
|
148
|
+
channels[name] = url
|
|
149
|
+
ninfo.channels = channels
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(
|
|
152
|
+
"Failed to get nix channels: %s" % str(e),
|
|
153
|
+
file=sys.stderr,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
with tempfile.TemporaryDirectory(prefix="librelane_env_report_") as d:
|
|
157
|
+
with open(os.path.join(d, "flake.nix"), "w") as f:
|
|
158
|
+
f.write("{}")
|
|
159
|
+
nix_command = subprocess.run(
|
|
160
|
+
["nix", "eval"],
|
|
161
|
+
stdout=subprocess.PIPE,
|
|
162
|
+
stderr=subprocess.STDOUT,
|
|
163
|
+
cwd=d,
|
|
164
|
+
encoding="utf8",
|
|
165
|
+
)
|
|
166
|
+
nix_command_result = nix_command.stdout
|
|
167
|
+
if "'nix-command'" in nix_command_result:
|
|
168
|
+
pass
|
|
169
|
+
elif "'flakes'" in nix_command_result:
|
|
170
|
+
ninfo.nix_command = True
|
|
171
|
+
elif "lacks attribute" in nix_command_result:
|
|
172
|
+
ninfo.nix_command = True
|
|
173
|
+
ninfo.flakes = True
|
|
174
|
+
else:
|
|
175
|
+
print(
|
|
176
|
+
"'nix flake' returned unexpected output: %s"
|
|
177
|
+
% nix_command_result,
|
|
178
|
+
file=sys.stderr,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return ninfo
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(e, file=sys.stderr)
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class OSInfo(StringRepresentable):
|
|
188
|
+
kernel = "" # type: str
|
|
189
|
+
kernel_version = "" # type: str
|
|
190
|
+
supported = False # type: bool
|
|
191
|
+
distro = None # type: Optional[str]
|
|
192
|
+
distro_version = None # type: Optional[str]
|
|
193
|
+
python_version = "" # type: str
|
|
194
|
+
python_path = [] # type: List[str]
|
|
195
|
+
container_info = None # type: Optional[ContainerInfo]
|
|
196
|
+
nix_info = None # type: Optional[NixInfo]
|
|
197
|
+
|
|
198
|
+
def __init__(self):
|
|
199
|
+
self.kernel = platform.system()
|
|
200
|
+
self.kernel_version = (
|
|
201
|
+
platform.release()
|
|
202
|
+
) # Unintuitively enough, it's the kernel's release
|
|
203
|
+
self.supported = self.kernel in ["Darwin", "Linux"]
|
|
204
|
+
self.distro = None
|
|
205
|
+
self.distro_version = None
|
|
206
|
+
self.python_version = platform.python_version()
|
|
207
|
+
self.python_path = sys.path.copy()
|
|
208
|
+
self.tkinter = False
|
|
209
|
+
try:
|
|
210
|
+
import tkinter # noqa: F401
|
|
211
|
+
|
|
212
|
+
self.tkinter = True
|
|
213
|
+
except ImportError:
|
|
214
|
+
pass
|
|
215
|
+
self.container_info = None
|
|
216
|
+
self.nix_info = None
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def get():
|
|
220
|
+
# type: () -> 'OSInfo'
|
|
221
|
+
osinfo = OSInfo()
|
|
222
|
+
|
|
223
|
+
if osinfo.kernel == "Windows":
|
|
224
|
+
osinfo.distro = "Windows"
|
|
225
|
+
osinfo.distro_version = platform.release()
|
|
226
|
+
osinfo.kernel_version = platform.version()
|
|
227
|
+
|
|
228
|
+
if osinfo.kernel == "Darwin":
|
|
229
|
+
osinfo.distro = "macOS"
|
|
230
|
+
osinfo.distro_version = platform.mac_ver()[0]
|
|
231
|
+
osinfo.kernel_version = platform.release()
|
|
232
|
+
|
|
233
|
+
if osinfo.kernel == "Linux":
|
|
234
|
+
os_release = ""
|
|
235
|
+
try:
|
|
236
|
+
os_release += open("/etc/lsb-release").read()
|
|
237
|
+
except FileNotFoundError:
|
|
238
|
+
pass
|
|
239
|
+
try:
|
|
240
|
+
os_release += open("/etc/os-release").read()
|
|
241
|
+
except FileNotFoundError:
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
if os_release.strip() != "":
|
|
245
|
+
config = {}
|
|
246
|
+
for line in os_release.split("\n"):
|
|
247
|
+
if line.strip() == "":
|
|
248
|
+
continue
|
|
249
|
+
if line.strip().startswith("#"):
|
|
250
|
+
continue
|
|
251
|
+
key, value = line.split("=")
|
|
252
|
+
value = value.strip('"')
|
|
253
|
+
|
|
254
|
+
config[key] = value
|
|
255
|
+
|
|
256
|
+
osinfo.distro = config.get("ID") or config.get("DISTRIB_ID")
|
|
257
|
+
osinfo.distro_version = config.get("VERSION_ID") or config.get(
|
|
258
|
+
"DISTRIB_RELEASE"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
print("Failed to get distribution info.", file=sys.stderr)
|
|
263
|
+
|
|
264
|
+
osinfo.container_info = ContainerInfo.get()
|
|
265
|
+
osinfo.nix_info = NixInfo.get()
|
|
266
|
+
return osinfo
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def env_info_cli():
|
|
270
|
+
def print_params(obj, indent=0):
|
|
271
|
+
if isinstance(obj, list):
|
|
272
|
+
for value in obj:
|
|
273
|
+
if isinstance(value, StringRepresentable) or isinstance(value, dict):
|
|
274
|
+
print("%s- " % (" " * indent), end="")
|
|
275
|
+
print_params(value, indent=indent + 2)
|
|
276
|
+
elif isinstance(value, list):
|
|
277
|
+
if len(value) == 0:
|
|
278
|
+
print("%s- []" % (" " * indent))
|
|
279
|
+
else:
|
|
280
|
+
print("%s- " % (" " * indent), end="")
|
|
281
|
+
print_params(value, indent=indent + 2)
|
|
282
|
+
else:
|
|
283
|
+
print("%s- %s" % (" " * indent, value))
|
|
284
|
+
|
|
285
|
+
else:
|
|
286
|
+
current = obj if isinstance(obj, dict) else obj.__dict__
|
|
287
|
+
for key in current:
|
|
288
|
+
value = current[key]
|
|
289
|
+
if isinstance(value, StringRepresentable) or isinstance(value, dict):
|
|
290
|
+
print("%s%s:" % (" " * indent, key))
|
|
291
|
+
print_params(value, indent=indent + 2)
|
|
292
|
+
elif isinstance(value, list):
|
|
293
|
+
if len(value) == 0:
|
|
294
|
+
print("%s%s: []" % (" " * indent, key))
|
|
295
|
+
else:
|
|
296
|
+
print("%s%s:" % (" " * indent, key))
|
|
297
|
+
print_params(value, indent=indent + 2)
|
|
298
|
+
else:
|
|
299
|
+
print("%s%s: %s" % (" " * indent, key, value))
|
|
300
|
+
|
|
301
|
+
info = OSInfo.get()
|
|
302
|
+
print_params(info)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
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
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
set clock_port __VIRTUAL_CLK__
|
|
2
|
+
if { [info exists ::env(CLOCK_PORT)] } {
|
|
3
|
+
set port_count [llength $::env(CLOCK_PORT)]
|
|
4
|
+
|
|
5
|
+
if { $port_count == "0" } {
|
|
6
|
+
puts "\[WARNING] No CLOCK_PORT found. A dummy clock will be used."
|
|
7
|
+
} elseif { $port_count != "1" } {
|
|
8
|
+
puts "\[WARNING] Multi-clock files are not currently supported by the base SDC file. Only the first clock will be constrained."
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if { $port_count > "0" } {
|
|
12
|
+
set ::clock_port [lindex $::env(CLOCK_PORT) 0]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
set port_args [get_ports $clock_port]
|
|
16
|
+
puts "\[INFO] Using clock $clock_port…"
|
|
17
|
+
create_clock {*}$port_args -name $clock_port -period $::env(CLOCK_PERIOD)
|
|
18
|
+
|
|
19
|
+
set input_delay_value [expr $::env(CLOCK_PERIOD) * $::env(IO_DELAY_CONSTRAINT) / 100]
|
|
20
|
+
set output_delay_value [expr $::env(CLOCK_PERIOD) * $::env(IO_DELAY_CONSTRAINT) / 100]
|
|
21
|
+
puts "\[INFO] Setting output delay to: $output_delay_value"
|
|
22
|
+
puts "\[INFO] Setting input delay to: $input_delay_value"
|
|
23
|
+
|
|
24
|
+
set_max_fanout $::env(MAX_FANOUT_CONSTRAINT) [current_design]
|
|
25
|
+
if { [info exists ::env(MAX_TRANSITION_CONSTRAINT)] } {
|
|
26
|
+
set_max_transition $::env(MAX_TRANSITION_CONSTRAINT) [current_design]
|
|
27
|
+
}
|
|
28
|
+
if { [info exists ::env(MAX_CAPACITANCE_CONSTRAINT)] } {
|
|
29
|
+
set_max_capacitance $::env(MAX_CAPACITANCE_CONSTRAINT) [current_design]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
set clk_input [get_port $clock_port]
|
|
33
|
+
set clk_indx [lsearch [all_inputs] $clk_input]
|
|
34
|
+
set all_inputs_wo_clk [lreplace [all_inputs] $clk_indx $clk_indx ""]
|
|
35
|
+
|
|
36
|
+
#set rst_input [get_port resetn]
|
|
37
|
+
#set rst_indx [lsearch [all_inputs] $rst_input]
|
|
38
|
+
#set all_inputs_wo_clk_rst [lreplace $all_inputs_wo_clk $rst_indx $rst_indx ""]
|
|
39
|
+
set all_inputs_wo_clk_rst $all_inputs_wo_clk
|
|
40
|
+
|
|
41
|
+
# correct resetn
|
|
42
|
+
set clocks [get_clocks $clock_port]
|
|
43
|
+
|
|
44
|
+
set_input_delay $input_delay_value -clock $clocks $all_inputs_wo_clk_rst
|
|
45
|
+
set_output_delay $output_delay_value -clock $clocks [all_outputs]
|
|
46
|
+
|
|
47
|
+
if { ![info exists ::env(SYNTH_CLK_DRIVING_CELL)] } {
|
|
48
|
+
set ::env(SYNTH_CLK_DRIVING_CELL) $::env(SYNTH_DRIVING_CELL)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
set_driving_cell \
|
|
52
|
+
-lib_cell [lindex [split $::env(SYNTH_DRIVING_CELL) "/"] 0] \
|
|
53
|
+
-pin [lindex [split $::env(SYNTH_DRIVING_CELL) "/"] 1] \
|
|
54
|
+
$all_inputs_wo_clk_rst
|
|
55
|
+
|
|
56
|
+
set_driving_cell \
|
|
57
|
+
-lib_cell [lindex [split $::env(SYNTH_CLK_DRIVING_CELL) "/"] 0] \
|
|
58
|
+
-pin [lindex [split $::env(SYNTH_CLK_DRIVING_CELL) "/"] 1] \
|
|
59
|
+
$clk_input
|
|
60
|
+
|
|
61
|
+
set cap_load [expr $::env(OUTPUT_CAP_LOAD) / 1000.0]
|
|
62
|
+
puts "\[INFO] Setting load to: $cap_load"
|
|
63
|
+
set_load $cap_load [all_outputs]
|
|
64
|
+
|
|
65
|
+
puts "\[INFO] Setting clock uncertainty to: $::env(CLOCK_UNCERTAINTY_CONSTRAINT)"
|
|
66
|
+
set_clock_uncertainty $::env(CLOCK_UNCERTAINTY_CONSTRAINT) $clocks
|
|
67
|
+
|
|
68
|
+
puts "\[INFO] Setting clock transition to: $::env(CLOCK_TRANSITION_CONSTRAINT)"
|
|
69
|
+
set_clock_transition $::env(CLOCK_TRANSITION_CONSTRAINT) $clocks
|
|
70
|
+
|
|
71
|
+
puts "\[INFO] Setting timing derate to: $::env(TIME_DERATING_CONSTRAINT)%"
|
|
72
|
+
set_timing_derate -early [expr 1-[expr $::env(TIME_DERATING_CONSTRAINT) / 100]]
|
|
73
|
+
set_timing_derate -late [expr 1+[expr $::env(TIME_DERATING_CONSTRAINT) / 100]]
|