librelane 2.4.0.dev2__py3-none-any.whl → 2.4.7__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.
- librelane/__init__.py +1 -1
- librelane/__main__.py +34 -27
- librelane/common/__init__.py +2 -0
- librelane/common/cli.py +1 -1
- librelane/common/drc.py +1 -0
- librelane/common/generic_dict.py +1 -1
- librelane/common/metrics/__main__.py +1 -1
- librelane/common/misc.py +58 -2
- librelane/common/tcl.py +2 -1
- librelane/common/types.py +2 -3
- librelane/config/__main__.py +1 -4
- librelane/config/flow.py +2 -2
- librelane/config/preprocessor.py +1 -1
- librelane/config/variable.py +136 -7
- librelane/container.py +55 -31
- librelane/env_info.py +129 -115
- librelane/examples/hold_eco_demo/config.yaml +18 -0
- librelane/examples/hold_eco_demo/demo.v +27 -0
- librelane/flows/cli.py +39 -23
- librelane/flows/flow.py +100 -36
- librelane/help/__main__.py +39 -0
- librelane/scripts/magic/def/mag_gds.tcl +0 -2
- librelane/scripts/magic/drc.tcl +0 -1
- librelane/scripts/magic/gds/extras_mag.tcl +0 -2
- librelane/scripts/magic/gds/mag_with_pointers.tcl +0 -1
- librelane/scripts/magic/lef/extras_maglef.tcl +0 -2
- librelane/scripts/magic/lef/maglef.tcl +0 -1
- librelane/scripts/magic/wrapper.tcl +2 -0
- librelane/scripts/odbpy/defutil.py +15 -10
- librelane/scripts/odbpy/eco_buffer.py +182 -0
- librelane/scripts/odbpy/eco_diode.py +140 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +1 -1
- librelane/scripts/odbpy/ioplace_parser/parse.py +1 -1
- librelane/scripts/odbpy/power_utils.py +8 -6
- librelane/scripts/odbpy/reader.py +17 -13
- librelane/scripts/openroad/common/io.tcl +66 -2
- librelane/scripts/openroad/gui.tcl +23 -1
- librelane/state/design_format.py +16 -1
- librelane/state/state.py +11 -3
- librelane/steps/__init__.py +1 -1
- librelane/steps/__main__.py +4 -4
- librelane/steps/checker.py +7 -8
- librelane/steps/klayout.py +11 -1
- librelane/steps/magic.py +24 -14
- librelane/steps/misc.py +5 -0
- librelane/steps/odb.py +193 -28
- librelane/steps/openroad.py +64 -47
- librelane/steps/pyosys.py +18 -1
- librelane/steps/step.py +36 -17
- librelane/steps/yosys.py +9 -1
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/METADATA +10 -11
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/RECORD +54 -50
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/entry_points.txt +1 -0
- librelane/scripts/odbpy/exception_codes.py +0 -17
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/WHEEL +0 -0
librelane/container.py
CHANGED
|
@@ -18,7 +18,6 @@ import re
|
|
|
18
18
|
import uuid
|
|
19
19
|
import shlex
|
|
20
20
|
import pathlib
|
|
21
|
-
import tempfile
|
|
22
21
|
import subprocess
|
|
23
22
|
from typing import List, NoReturn, Sequence, Optional, Union, Tuple
|
|
24
23
|
|
|
@@ -27,15 +26,15 @@ import semver
|
|
|
27
26
|
|
|
28
27
|
from .common import mkdirp
|
|
29
28
|
from .logging import err, info, warn
|
|
30
|
-
from .env_info import OSInfo
|
|
29
|
+
from .env_info import ContainerInfo, OSInfo
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
__file_dir__ = os.path.dirname(os.path.abspath(__file__))
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def permission_args(osinfo: OSInfo) -> List[str]:
|
|
36
35
|
if (
|
|
37
36
|
osinfo.kernel == "Linux"
|
|
38
|
-
and osinfo.container_info
|
|
37
|
+
and isinstance(osinfo.container_info, ContainerInfo)
|
|
39
38
|
and osinfo.container_info.engine == "docker"
|
|
40
39
|
and not osinfo.container_info.rootless
|
|
41
40
|
):
|
|
@@ -58,10 +57,13 @@ def gui_args(osinfo: OSInfo) -> List[str]:
|
|
|
58
57
|
args += [
|
|
59
58
|
"-e",
|
|
60
59
|
f"DISPLAY={os.environ.get('DISPLAY')}",
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"-v",
|
|
64
|
-
|
|
60
|
+
]
|
|
61
|
+
if os.path.isdir("/tmp/.X11-unix"):
|
|
62
|
+
args += ["-v", "/tmp/.X11-unix:/tmp/.X11-unix"]
|
|
63
|
+
homedir = os.path.expanduser("~")
|
|
64
|
+
if os.path.isfile(f"{homedir}/.Xauthority"):
|
|
65
|
+
args += ["-v", f"{homedir}/.Xauthority:/.Xauthority"]
|
|
66
|
+
args += [
|
|
65
67
|
"--network",
|
|
66
68
|
"host",
|
|
67
69
|
"--security-opt",
|
|
@@ -70,9 +72,9 @@ def gui_args(osinfo: OSInfo) -> List[str]:
|
|
|
70
72
|
return args
|
|
71
73
|
|
|
72
74
|
|
|
73
|
-
def image_exists(image: str) -> bool:
|
|
75
|
+
def image_exists(ce_path: str, image: str) -> bool:
|
|
74
76
|
images = (
|
|
75
|
-
subprocess.check_output([
|
|
77
|
+
subprocess.check_output([ce_path, "images", image])
|
|
76
78
|
.decode("utf8")
|
|
77
79
|
.rstrip()
|
|
78
80
|
.split("\n")[1:]
|
|
@@ -116,14 +118,14 @@ def remote_manifest_exists(image: str) -> bool:
|
|
|
116
118
|
return True
|
|
117
119
|
|
|
118
120
|
|
|
119
|
-
def ensure_image(image: str) -> bool:
|
|
120
|
-
if image_exists(image):
|
|
121
|
+
def ensure_image(ce_path: str, image: str) -> bool:
|
|
122
|
+
if image_exists(ce_path, image):
|
|
121
123
|
return True
|
|
122
124
|
|
|
123
125
|
try:
|
|
124
|
-
subprocess.check_call([
|
|
126
|
+
subprocess.check_call([ce_path, "pull", image])
|
|
125
127
|
except subprocess.CalledProcessError:
|
|
126
|
-
err(f"Failed to pull image {image} from the container registries.")
|
|
128
|
+
err(f"Failed to pull image '{image}' from the container registries.")
|
|
127
129
|
return False
|
|
128
130
|
|
|
129
131
|
return True
|
|
@@ -150,6 +152,22 @@ def sanitize_path(path: Union[str, os.PathLike]) -> Tuple[str, str]:
|
|
|
150
152
|
return (abspath, mountable_path)
|
|
151
153
|
|
|
152
154
|
|
|
155
|
+
def container_version_error(input: str, against: str) -> Optional[str]:
|
|
156
|
+
if input == "UNKNOWN":
|
|
157
|
+
return (
|
|
158
|
+
"Could not determine version for %s. You may encounter unexpected issues."
|
|
159
|
+
)
|
|
160
|
+
if semver.compare(input, against) < 0:
|
|
161
|
+
return f"Your %s version ({input}) is out of date. You may encounter unexpected issues."
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def ubuntu_version_at_least(current: str, minimum: str) -> bool:
|
|
166
|
+
if current == "UNKNOWN":
|
|
167
|
+
return False
|
|
168
|
+
return tuple(map(int, current.split("."))) >= tuple(map(int, minimum.split(".")))
|
|
169
|
+
|
|
170
|
+
|
|
153
171
|
def run_in_container(
|
|
154
172
|
image: str,
|
|
155
173
|
args: Sequence[str],
|
|
@@ -169,20 +187,31 @@ def run_in_container(
|
|
|
169
187
|
f"Unsupported host operating system '{osinfo.kernel}'. You may encounter unexpected issues."
|
|
170
188
|
)
|
|
171
189
|
|
|
172
|
-
if osinfo.container_info
|
|
190
|
+
if not isinstance(osinfo.container_info, ContainerInfo):
|
|
173
191
|
raise FileNotFoundError("No compatible container engine found.")
|
|
174
192
|
|
|
175
|
-
|
|
176
|
-
|
|
193
|
+
ce_path = osinfo.container_info.path
|
|
194
|
+
assert ce_path is not None
|
|
195
|
+
|
|
196
|
+
engine_name = osinfo.container_info.engine.lower()
|
|
197
|
+
if engine_name == "docker":
|
|
198
|
+
if error := container_version_error(osinfo.container_info.version, "25.0.5"):
|
|
199
|
+
warn(error % engine_name)
|
|
200
|
+
elif engine_name == "podman":
|
|
201
|
+
if osinfo.distro.lower() == "ubuntu" and not ubuntu_version_at_least(
|
|
202
|
+
osinfo.distro_version, "24.04"
|
|
203
|
+
):
|
|
177
204
|
warn(
|
|
178
|
-
|
|
205
|
+
"Versions of Podman for Ubuntu before Ubuntu 24.04 are generally pretty buggy. We recommend using Docker instead if possible."
|
|
179
206
|
)
|
|
207
|
+
elif error := container_version_error(osinfo.container_info.version, "4.1.0"):
|
|
208
|
+
warn(error % engine_name)
|
|
180
209
|
else:
|
|
181
210
|
warn(
|
|
182
|
-
f"Unsupported container engine '{osinfo.container_info.
|
|
211
|
+
f"Unsupported container engine referenced by '{osinfo.container_info.path}'. You may encounter unexpected issues."
|
|
183
212
|
)
|
|
184
213
|
|
|
185
|
-
if not ensure_image(image):
|
|
214
|
+
if not ensure_image(ce_path, image):
|
|
186
215
|
raise ValueError(f"Failed to use image '{image}'.")
|
|
187
216
|
|
|
188
217
|
terminal_args = ["-i"]
|
|
@@ -222,15 +251,6 @@ def run_in_container(
|
|
|
222
251
|
mount_args += ["-v", f"{from_cwd}:{to_cwd}"]
|
|
223
252
|
mount_args += ["-w", to_cwd]
|
|
224
253
|
|
|
225
|
-
tempdir = tempfile.mkdtemp("librelane_docker")
|
|
226
|
-
|
|
227
|
-
mount_args += [
|
|
228
|
-
"-v",
|
|
229
|
-
f"{tempdir}:/tmp",
|
|
230
|
-
"-e",
|
|
231
|
-
"TMPDIR=/tmp",
|
|
232
|
-
]
|
|
233
|
-
|
|
234
254
|
if other_mounts is not None:
|
|
235
255
|
for mount in other_mounts:
|
|
236
256
|
if os.path.isdir(mount):
|
|
@@ -242,9 +262,13 @@ def run_in_container(
|
|
|
242
262
|
|
|
243
263
|
container_id = str(uuid.uuid4())
|
|
244
264
|
|
|
265
|
+
if os.getenv("_MOUNT_HOST_LIBRELANE") == "1":
|
|
266
|
+
host_librelane_pythonpath = os.path.dirname(__file_dir__)
|
|
267
|
+
mount_args += ["-v", f"{host_librelane_pythonpath}:/host_librelane"]
|
|
268
|
+
|
|
245
269
|
cmd = (
|
|
246
270
|
[
|
|
247
|
-
|
|
271
|
+
ce_path,
|
|
248
272
|
"run",
|
|
249
273
|
"--rm",
|
|
250
274
|
"--name",
|
|
@@ -261,4 +285,4 @@ def run_in_container(
|
|
|
261
285
|
info("Running containerized command:")
|
|
262
286
|
print(shlex.join(cmd))
|
|
263
287
|
|
|
264
|
-
os.execlp(
|
|
288
|
+
os.execlp(ce_path, *cmd)
|
librelane/env_info.py
CHANGED
|
@@ -23,17 +23,16 @@ import os
|
|
|
23
23
|
import re
|
|
24
24
|
import sys
|
|
25
25
|
import json
|
|
26
|
+
import shutil
|
|
26
27
|
import tempfile
|
|
27
28
|
import platform
|
|
28
29
|
import subprocess
|
|
29
30
|
|
|
30
31
|
try:
|
|
31
|
-
from typing import Optional, Dict, List # noqa: F401
|
|
32
|
+
from typing import Union, Optional, Dict, List # noqa: F401
|
|
32
33
|
except ImportError:
|
|
33
34
|
pass
|
|
34
35
|
|
|
35
|
-
CONTAINER_ENGINE = os.getenv("OPENLANE_CONTAINER_ENGINE", "docker")
|
|
36
|
-
|
|
37
36
|
|
|
38
37
|
class StringRepresentable(object):
|
|
39
38
|
def __str__(self):
|
|
@@ -44,6 +43,7 @@ class StringRepresentable(object):
|
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
class ContainerInfo(StringRepresentable):
|
|
46
|
+
path = None # type: Optional[str]
|
|
47
47
|
engine = "UNKNOWN" # type: str
|
|
48
48
|
version = "UNKNOWN" # type: str
|
|
49
49
|
conmon = False # type: bool
|
|
@@ -54,63 +54,84 @@ class ContainerInfo(StringRepresentable):
|
|
|
54
54
|
self.version = "UNKNOWN"
|
|
55
55
|
self.conmon = False
|
|
56
56
|
self.rootless = False
|
|
57
|
+
self.seccomp = False
|
|
58
|
+
self.selinux = False
|
|
59
|
+
self.apparmor = False
|
|
57
60
|
|
|
58
61
|
@staticmethod
|
|
59
62
|
def get():
|
|
60
|
-
# type: () ->
|
|
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)"
|
|
61
81
|
try:
|
|
62
|
-
|
|
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
|
|
63
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
|
|
64
104
|
try:
|
|
65
|
-
|
|
66
|
-
[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
70
113
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
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
|
|
114
135
|
|
|
115
136
|
|
|
116
137
|
class NixInfo(StringRepresentable):
|
|
@@ -127,73 +148,65 @@ class NixInfo(StringRepresentable):
|
|
|
127
148
|
|
|
128
149
|
@staticmethod
|
|
129
150
|
def get():
|
|
130
|
-
# type: () ->
|
|
151
|
+
# type: () -> Union[NixInfo, str]
|
|
131
152
|
ninfo = NixInfo()
|
|
153
|
+
if shutil.which("nix") is None:
|
|
154
|
+
return "nix not found in PATH"
|
|
132
155
|
try:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
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)
|
|
140
160
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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:
|
|
151
192
|
print(
|
|
152
|
-
"
|
|
193
|
+
"'nix flake' returned unexpected output: %s" % nix_command_result,
|
|
153
194
|
file=sys.stderr,
|
|
154
195
|
)
|
|
155
196
|
|
|
156
|
-
|
|
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
|
|
197
|
+
return ninfo
|
|
185
198
|
|
|
186
199
|
|
|
187
200
|
class OSInfo(StringRepresentable):
|
|
188
201
|
kernel = "" # type: str
|
|
189
202
|
kernel_version = "" # type: str
|
|
190
203
|
supported = False # type: bool
|
|
191
|
-
distro =
|
|
192
|
-
distro_version =
|
|
204
|
+
distro = "UNKNOWN" # type: str
|
|
205
|
+
distro_version = "UNKNOWN" # type: str
|
|
193
206
|
python_version = "" # type: str
|
|
194
207
|
python_path = [] # type: List[str]
|
|
195
|
-
container_info = None # type:
|
|
196
|
-
nix_info = None # type:
|
|
208
|
+
container_info = None # type: Union[ContainerInfo, str]
|
|
209
|
+
nix_info = None # type: Union[NixInfo, str]
|
|
197
210
|
|
|
198
211
|
def __init__(self):
|
|
199
212
|
self.kernel = platform.system()
|
|
@@ -201,8 +214,8 @@ class OSInfo(StringRepresentable):
|
|
|
201
214
|
platform.release()
|
|
202
215
|
) # Unintuitively enough, it's the kernel's release
|
|
203
216
|
self.supported = self.kernel in ["Darwin", "Linux"]
|
|
204
|
-
self.distro =
|
|
205
|
-
self.distro_version =
|
|
217
|
+
self.distro = "UNKNOWN"
|
|
218
|
+
self.distro_version = "UNKNOWN"
|
|
206
219
|
self.python_version = platform.python_version()
|
|
207
220
|
self.python_path = sys.path.copy()
|
|
208
221
|
self.tkinter = False
|
|
@@ -212,8 +225,8 @@ class OSInfo(StringRepresentable):
|
|
|
212
225
|
self.tkinter = True
|
|
213
226
|
except ImportError:
|
|
214
227
|
pass
|
|
215
|
-
self.container_info =
|
|
216
|
-
self.nix_info =
|
|
228
|
+
self.container_info = ""
|
|
229
|
+
self.nix_info = ""
|
|
217
230
|
|
|
218
231
|
@staticmethod
|
|
219
232
|
def get():
|
|
@@ -253,13 +266,14 @@ class OSInfo(StringRepresentable):
|
|
|
253
266
|
|
|
254
267
|
config[key] = value
|
|
255
268
|
|
|
256
|
-
osinfo.distro =
|
|
257
|
-
|
|
258
|
-
|
|
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"
|
|
259
276
|
)
|
|
260
|
-
|
|
261
|
-
else:
|
|
262
|
-
print("Failed to get distribution info.", file=sys.stderr)
|
|
263
277
|
|
|
264
278
|
osinfo.container_info = ContainerInfo.get()
|
|
265
279
|
osinfo.nix_info = NixInfo.get()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
DESIGN_NAME: hold_violation
|
|
2
|
+
CLOCK_PORT: clk
|
|
3
|
+
CLOCK_PERIOD: 5
|
|
4
|
+
VERILOG_FILES: dir::demo.v
|
|
5
|
+
RUN_POST_CTS_RESIZER_TIMING: false
|
|
6
|
+
RUN_POST_GRT_RESIZER_TIMING: false
|
|
7
|
+
FP_SIZING: absolute
|
|
8
|
+
DIE_AREA: [0, 0, 100, 100]
|
|
9
|
+
INSERT_ECO_BUFFERS:
|
|
10
|
+
- target: u_ff1/Q
|
|
11
|
+
buffer: sky130_fd_sc_hd__buf_1
|
|
12
|
+
- target: u_ff1/Q
|
|
13
|
+
buffer: sky130_fd_sc_hd__buf_1
|
|
14
|
+
meta:
|
|
15
|
+
flow: Classic
|
|
16
|
+
substituting_steps:
|
|
17
|
+
"+OpenROAD.DetailedRouting": "Odb.InsertECOBuffers"
|
|
18
|
+
"+Odb.InsertECOBuffers": "OpenROAD.DetailedRouting"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module hold_violation(
|
|
2
|
+
input clk,
|
|
3
|
+
input d,
|
|
4
|
+
output q
|
|
5
|
+
);
|
|
6
|
+
wire intermediate;
|
|
7
|
+
wire clk_delayed;
|
|
8
|
+
|
|
9
|
+
sky130_fd_sc_hd__clkbuf_4 dly (
|
|
10
|
+
.A(clk),
|
|
11
|
+
.X(clk_delayed)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
sky130_fd_sc_hd__dfrtp_4 u_ff1 (
|
|
15
|
+
.CLK(clk),
|
|
16
|
+
.D(d),
|
|
17
|
+
.RESET_B(1'b1),
|
|
18
|
+
.Q(intermediate)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
sky130_fd_sc_hd__dfrtp_1 u_ff2 (
|
|
22
|
+
.CLK(clk_delayed),
|
|
23
|
+
.D(intermediate),
|
|
24
|
+
.RESET_B(1'b1),
|
|
25
|
+
.Q(q)
|
|
26
|
+
);
|
|
27
|
+
endmodule
|
librelane/flows/cli.py
CHANGED
|
@@ -35,7 +35,7 @@ from cloup.typing import Decorator
|
|
|
35
35
|
|
|
36
36
|
from .flow import Flow
|
|
37
37
|
from ..common import set_tpe, cli, get_opdks_rev, _get_process_limit
|
|
38
|
-
from ..logging import set_log_level, verbose, err, options, LogLevels
|
|
38
|
+
from ..logging import set_log_level, verbose, info, err, options, LogLevels
|
|
39
39
|
from ..state import State, InvalidState
|
|
40
40
|
|
|
41
41
|
|
|
@@ -146,27 +146,30 @@ def cloup_flow_opts(
|
|
|
146
146
|
function decorated with @cloup.command (https://cloup.readthedocs.io/en/stable/autoapi/cloup/index.html#cloup.command).
|
|
147
147
|
|
|
148
148
|
The following keyword arguments will be passed to the decorated function.
|
|
149
|
+
|
|
149
150
|
* Those postfixed ‡ are compatible with the constructor for :class:`Flow`.
|
|
150
151
|
* Those postfixed § are compatible with the :meth:`Flow.start`.
|
|
151
152
|
|
|
153
|
+
---
|
|
154
|
+
|
|
152
155
|
* Flow configuration options (if parameter ``config_options`` is ``True``):
|
|
153
156
|
* ``flow_name``: ``Optional[str]``: A valid flow ID to be used with :meth:`Flow.factory.get`
|
|
154
|
-
* ``config_override_strings
|
|
157
|
+
* ``config_override_strings`` ‡: ``Optional[Iterable[str]]``
|
|
155
158
|
* Sequential flow controls (if parameter ``sequential_flow_controls`` is ``True``)
|
|
156
|
-
* ``frm
|
|
157
|
-
* ``to
|
|
158
|
-
* ``skip
|
|
159
|
+
* ``frm`` §: ``Optional[str]``: Start from a step with this ID. Supported by sequential flows.
|
|
160
|
+
* ``to`` §: ``Optional[str]``: Stop at a step with this id. Supported by sequential flows.
|
|
161
|
+
* ``skip`` §: ``Iterable[str]``: Skip these steps. Supported by sequential flows.
|
|
159
162
|
* Sequential flow reproducible (if parameter ``sequential_flow_reproducible`` is ``True``)
|
|
160
|
-
* ``reproducible
|
|
163
|
+
* ``reproducible`` §: ``str``: Create a reproducible for a step with is ID, aborting the flow afterwards. Supported by sequential flows.
|
|
161
164
|
* Flow run options (if parameter ``run_options`` is ``True``):
|
|
162
|
-
* ``tag
|
|
163
|
-
* ``last_run
|
|
164
|
-
* ``with_initial_state
|
|
165
|
+
* ``tag`` §: ``Optional[str]``
|
|
166
|
+
* ``last_run`` §: ``bool``: If ``True``, ``tag`` is guaranteed to be None.
|
|
167
|
+
* ``with_initial_state`` §: ``Optional[State]``
|
|
165
168
|
* PDK options
|
|
166
|
-
* ``use_volare
|
|
167
|
-
* ``pdk_root
|
|
168
|
-
* ``pdk
|
|
169
|
-
* ``scl
|
|
169
|
+
* ``use_volare`` : ``bool``
|
|
170
|
+
* ``pdk_root`` ‡: ``Optional[str]``
|
|
171
|
+
* ``pdk`` ‡: ``str``
|
|
172
|
+
* ``scl`` ‡: ``Optional[str]``
|
|
170
173
|
* ``config_files``: ``Iterable[str]``: Paths to configuration files (if
|
|
171
174
|
parameter ``accept_config_files`` is ``True``)
|
|
172
175
|
|
|
@@ -443,16 +446,29 @@ def cloup_flow_opts(
|
|
|
443
446
|
err(f"Could not resolve the PDK '{pdk}'.")
|
|
444
447
|
exit(1)
|
|
445
448
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
"
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
if pdk_family == "ihp-sg13g2":
|
|
450
|
+
err(
|
|
451
|
+
"The IHP Open PDK is only supported in the development version of LibreLane, specifically 3.0.0.dev28 or higher."
|
|
452
|
+
)
|
|
453
|
+
info(
|
|
454
|
+
"If you're using Nix, switch to the 'dev' branch. If you're using the Python package, run \"python3 -m pip install 'librelane>=3.0.0.dev28'\"."
|
|
455
|
+
)
|
|
456
|
+
exit(1)
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
version = ciel.fetch(
|
|
460
|
+
ciel_home,
|
|
461
|
+
pdk_family,
|
|
462
|
+
opdks_rev,
|
|
463
|
+
data_source=StaticWebDataSource(
|
|
464
|
+
"https://fossi-foundation.github.io/ciel-releases"
|
|
465
|
+
),
|
|
466
|
+
include_libraries=include_libraries,
|
|
467
|
+
)
|
|
468
|
+
pdk_root = version.get_dir(ciel_home)
|
|
469
|
+
except ValueError as e:
|
|
470
|
+
err(f"Failed to download PDK: {e}")
|
|
471
|
+
exit(1)
|
|
456
472
|
|
|
457
473
|
return f(*args, pdk_root=pdk_root, pdk=pdk, scl=scl, **kwargs)
|
|
458
474
|
|