librelane 2.4.0.dev11__py3-none-any.whl → 2.4.1__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/__main__.py +29 -25
- librelane/common/__init__.py +2 -0
- librelane/common/drc.py +1 -0
- librelane/common/misc.py +56 -2
- librelane/config/__main__.py +1 -4
- librelane/container.py +48 -27
- librelane/env_info.py +129 -115
- librelane/flows/flow.py +66 -30
- 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/power_utils.py +8 -6
- librelane/scripts/odbpy/reader.py +2 -2
- librelane/state/state.py +11 -3
- librelane/steps/__main__.py +1 -2
- librelane/steps/magic.py +24 -14
- librelane/steps/odb.py +1 -4
- librelane/steps/openroad.py +2 -5
- {librelane-2.4.0.dev11.dist-info → librelane-2.4.1.dist-info}/METADATA +2 -2
- {librelane-2.4.0.dev11.dist-info → librelane-2.4.1.dist-info}/RECORD +27 -26
- {librelane-2.4.0.dev11.dist-info → librelane-2.4.1.dist-info}/entry_points.txt +1 -0
- {librelane-2.4.0.dev11.dist-info → librelane-2.4.1.dist-info}/WHEEL +0 -0
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()
|
librelane/flows/flow.py
CHANGED
|
@@ -375,7 +375,7 @@ class Flow(ABC):
|
|
|
375
375
|
self.progress_bar = FlowProgressBar(self.name)
|
|
376
376
|
|
|
377
377
|
@classmethod
|
|
378
|
-
def get_help_md(Self, myst_anchors: bool =
|
|
378
|
+
def get_help_md(Self, myst_anchors: bool = False) -> str: # pragma: no cover
|
|
379
379
|
"""
|
|
380
380
|
:returns: rendered Markdown help for this Flow
|
|
381
381
|
"""
|
|
@@ -415,10 +415,10 @@ class Flow(ABC):
|
|
|
415
415
|
flow_config_vars = Self.config_vars
|
|
416
416
|
|
|
417
417
|
if len(flow_config_vars):
|
|
418
|
+
config_var_anchors = f"({slugify(Self.__name__, lower=True)}-config-vars)="
|
|
418
419
|
result += textwrap.dedent(
|
|
419
420
|
f"""
|
|
420
|
-
|
|
421
|
-
|
|
421
|
+
{config_var_anchors * myst_anchors}
|
|
422
422
|
#### Flow-specific Configuration Variables
|
|
423
423
|
|
|
424
424
|
| Variable Name | Type | Description | Default | Units |
|
|
@@ -435,18 +435,14 @@ class Flow(ABC):
|
|
|
435
435
|
if len(Self.Steps):
|
|
436
436
|
result += "#### Included Steps\n"
|
|
437
437
|
for step in Self.Steps:
|
|
438
|
-
|
|
439
|
-
name = step.long_name
|
|
440
|
-
elif hasattr(step, "name"):
|
|
441
|
-
name = step.name
|
|
442
|
-
else:
|
|
443
|
-
name = step.id
|
|
438
|
+
imp_id = step.get_implementation_id()
|
|
444
439
|
if myst_anchors:
|
|
445
|
-
result += (
|
|
446
|
-
f"* [`{step.id}`](./step_config_vars.md#{slugify(name)})\n"
|
|
447
|
-
)
|
|
440
|
+
result += f"* [`{step.id}`](./step_config_vars.md#step-{slugify(imp_id, lower=True)})\n"
|
|
448
441
|
else:
|
|
449
|
-
|
|
442
|
+
variant_str = ""
|
|
443
|
+
if imp_id != step.id:
|
|
444
|
+
variant_str = f" (implementation: `{imp_id}`)"
|
|
445
|
+
result += f"* `{step.id}`{variant_str}\n"
|
|
450
446
|
|
|
451
447
|
return result
|
|
452
448
|
|
|
@@ -824,7 +820,6 @@ class Flow(ABC):
|
|
|
824
820
|
DesignFormat.POWERED_NETLIST: (os.path.join("verilog", "gl"), "v"),
|
|
825
821
|
DesignFormat.DEF: ("def", "def"),
|
|
826
822
|
DesignFormat.LEF: ("lef", "lef"),
|
|
827
|
-
DesignFormat.SDF: (os.path.join("sdf", "multicorner"), "sdf"),
|
|
828
823
|
DesignFormat.SPEF: (os.path.join("spef", "multicorner"), "spef"),
|
|
829
824
|
DesignFormat.LIB: (os.path.join("lib", "multicorner"), "lib"),
|
|
830
825
|
DesignFormat.GDS: ("gds", "gds"),
|
|
@@ -883,38 +878,67 @@ class Flow(ABC):
|
|
|
883
878
|
file_path, os.path.join(to_dir, file), follow_symlinks=True
|
|
884
879
|
)
|
|
885
880
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
881
|
+
def find_one(pattern):
|
|
882
|
+
result = glob.glob(pattern)
|
|
883
|
+
if len(result) == 0:
|
|
884
|
+
return None
|
|
885
|
+
return result[0]
|
|
890
886
|
|
|
891
|
-
|
|
887
|
+
signoff_dir = os.path.join(path, "signoff", self.config["DESIGN_NAME"])
|
|
888
|
+
openlane_signoff_dir = os.path.join(signoff_dir, "openlane-signoff")
|
|
889
|
+
mkdirp(openlane_signoff_dir)
|
|
890
|
+
|
|
891
|
+
## resolved.json
|
|
892
892
|
shutil.copyfile(
|
|
893
893
|
self.config_resolved_path,
|
|
894
|
-
os.path.join(
|
|
894
|
+
os.path.join(openlane_signoff_dir, "resolved.json"),
|
|
895
895
|
follow_symlinks=True,
|
|
896
896
|
)
|
|
897
897
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
898
|
+
## metrics
|
|
899
|
+
with open(os.path.join(signoff_dir, "metrics.csv"), "w", encoding="utf8") as f:
|
|
900
|
+
last_state.metrics_to_csv(f)
|
|
901
|
+
|
|
902
|
+
## flow logs
|
|
903
|
+
mkdirp(openlane_signoff_dir)
|
|
904
|
+
copy_dir_contents(self.run_dir, openlane_signoff_dir, "*.log")
|
|
901
905
|
|
|
902
|
-
|
|
906
|
+
### step-specific signoff logs and reports
|
|
903
907
|
for step in self.step_objects:
|
|
904
908
|
reports_dir = os.path.join(step.step_dir, "reports")
|
|
905
909
|
step_imp_id = step.get_implementation_id()
|
|
910
|
+
if step_imp_id == "Magic.DRC":
|
|
911
|
+
if drc_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
|
|
912
|
+
shutil.copyfile(
|
|
913
|
+
drc_rpt, os.path.join(openlane_signoff_dir, "drc.rpt")
|
|
914
|
+
)
|
|
915
|
+
if drc_xml := find_one(os.path.join(reports_dir, "*.xml")):
|
|
916
|
+
# Despite the name, this is the Magic DRC report simply
|
|
917
|
+
# converted into a KLayout-compatible format. Confusing!
|
|
918
|
+
drc_xml_out = os.path.join(openlane_signoff_dir, "drc.klayout.xml")
|
|
919
|
+
with open(drc_xml, encoding="utf8") as i, open(
|
|
920
|
+
drc_xml_out, "w", encoding="utf8"
|
|
921
|
+
) as o:
|
|
922
|
+
o.write(
|
|
923
|
+
"<!-- Despite the name, this is the Magic DRC report in KLayout format. -->\n"
|
|
924
|
+
)
|
|
925
|
+
shutil.copyfileobj(i, o)
|
|
926
|
+
if step_imp_id == "Netgen.LVS":
|
|
927
|
+
if lvs_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
|
|
928
|
+
shutil.copyfile(
|
|
929
|
+
lvs_rpt, os.path.join(openlane_signoff_dir, "lvs.rpt")
|
|
930
|
+
)
|
|
906
931
|
if step_imp_id.endswith("DRC") or step_imp_id.endswith("LVS"):
|
|
907
|
-
|
|
908
|
-
copy_dir_contents(reports_dir, signoff_folder)
|
|
909
|
-
if step_imp_id.endswith("LVS"):
|
|
910
|
-
copy_dir_contents(step.step_dir, signoff_folder, "*.log")
|
|
932
|
+
copy_dir_contents(step.step_dir, openlane_signoff_dir, "*.log")
|
|
911
933
|
if step_imp_id.endswith("CheckAntennas"):
|
|
912
934
|
if os.path.exists(reports_dir):
|
|
913
935
|
copy_dir_contents(
|
|
914
|
-
reports_dir,
|
|
936
|
+
reports_dir, openlane_signoff_dir, "antenna_summary.rpt"
|
|
915
937
|
)
|
|
916
938
|
if step_imp_id.endswith("STAPostPNR"):
|
|
917
|
-
timing_report_folder = os.path.join(
|
|
939
|
+
timing_report_folder = os.path.join(
|
|
940
|
+
openlane_signoff_dir, "timing-reports"
|
|
941
|
+
)
|
|
918
942
|
mkdirp(timing_report_folder)
|
|
919
943
|
copy_dir_contents(step.step_dir, timing_report_folder, "*summary.rpt")
|
|
920
944
|
for dir in os.listdir(step.step_dir):
|
|
@@ -925,6 +949,18 @@ class Flow(ABC):
|
|
|
925
949
|
mkdirp(target)
|
|
926
950
|
copy_dir_contents(dir_path, target, "*.rpt")
|
|
927
951
|
|
|
952
|
+
# 3. SDF
|
|
953
|
+
# (This one, as with many things in the Efabless format, is special)
|
|
954
|
+
if sdf := last_state[DesignFormat.SDF]:
|
|
955
|
+
assert isinstance(sdf, dict), "SDF is not a dictionary"
|
|
956
|
+
for corner, view in sdf.items():
|
|
957
|
+
assert isinstance(view, Path), "SDF state out returned multiple paths"
|
|
958
|
+
target_dir = os.path.join(signoff_dir, "sdf", corner)
|
|
959
|
+
mkdirp(target_dir)
|
|
960
|
+
shutil.copyfile(
|
|
961
|
+
view, os.path.join(target_dir, f"{self.config['DESIGN_NAME']}.sdf")
|
|
962
|
+
)
|
|
963
|
+
|
|
928
964
|
@deprecated(
|
|
929
965
|
version="2.0.0a46",
|
|
930
966
|
reason="Use .progress_bar.set_max_stage_count",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright 2025 LibreLane Contributors
|
|
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
|
+
from ..common.cli import formatter_settings
|
|
15
|
+
from ..flows import Flow
|
|
16
|
+
from ..steps import Step
|
|
17
|
+
from ..logging import console
|
|
18
|
+
|
|
19
|
+
import cloup
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cloup.command(formatter_settings=formatter_settings)
|
|
23
|
+
@cloup.argument("step_or_flow")
|
|
24
|
+
@cloup.pass_context
|
|
25
|
+
def cli(ctx, step_or_flow):
|
|
26
|
+
"""
|
|
27
|
+
Displays rich help for the step or flow in question.
|
|
28
|
+
"""
|
|
29
|
+
if TargetFlow := Flow.factory.get(step_or_flow):
|
|
30
|
+
TargetFlow.display_help()
|
|
31
|
+
elif TargetStep := Step.factory.get(step_or_flow):
|
|
32
|
+
TargetStep.display_help()
|
|
33
|
+
else:
|
|
34
|
+
console.log(f"Unknown Flow or Step '{step_or_flow}'.")
|
|
35
|
+
ctx.exit(-1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
cli()
|
librelane/scripts/magic/drc.tcl
CHANGED
|
@@ -17,7 +17,6 @@ import utl
|
|
|
17
17
|
|
|
18
18
|
import re
|
|
19
19
|
import json
|
|
20
|
-
import functools
|
|
21
20
|
from dataclasses import dataclass
|
|
22
21
|
from typing import Dict, List, Optional
|
|
23
22
|
|
|
@@ -49,11 +48,14 @@ class Design(object):
|
|
|
49
48
|
def get_verilog_net_name_by_bit(self, top_module: str, target_bit: int):
|
|
50
49
|
yosys_design_object = self.yosys_dict["modules"][top_module]
|
|
51
50
|
if top_module not in self.verilog_net_names_by_bit_by_module:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
# check git history for a version of this loop that is drunk on power
|
|
52
|
+
netname_by_bit = {}
|
|
53
|
+
|
|
54
|
+
for netname, info in yosys_design_object["netnames"].items():
|
|
55
|
+
for bit in info["bits"]:
|
|
56
|
+
netname_by_bit[bit] = netname
|
|
57
|
+
|
|
58
|
+
self.verilog_net_names_by_bit_by_module[top_module] = netname_by_bit
|
|
57
59
|
return self.verilog_net_names_by_bit_by_module[top_module][target_bit]
|
|
58
60
|
|
|
59
61
|
def get_pins(self, module_name: str) -> Dict[str, odb.dbMTerm]:
|
|
@@ -20,7 +20,7 @@ import sys
|
|
|
20
20
|
import json
|
|
21
21
|
import locale
|
|
22
22
|
import inspect
|
|
23
|
-
import
|
|
23
|
+
from functools import wraps
|
|
24
24
|
from decimal import Decimal
|
|
25
25
|
from fnmatch import fnmatch
|
|
26
26
|
from typing import Callable, Dict
|
|
@@ -188,7 +188,7 @@ class OdbReader(object):
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def click_odb(function):
|
|
191
|
-
@
|
|
191
|
+
@wraps(function)
|
|
192
192
|
def wrapper(input_db, input_lefs, config_path, **kwargs):
|
|
193
193
|
reader = OdbReader(input_db, config_path=config_path)
|
|
194
194
|
|
librelane/state/state.py
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import io
|
|
16
17
|
import os
|
|
18
|
+
import csv
|
|
17
19
|
import sys
|
|
18
20
|
import json
|
|
19
21
|
import shutil
|
|
@@ -214,14 +216,20 @@ class State(GenericImmutableDict[str, StateElement]):
|
|
|
214
216
|
self._walk(self, path, visitor)
|
|
215
217
|
metrics_csv_path = os.path.join(path, "metrics.csv")
|
|
216
218
|
with open(metrics_csv_path, "w", encoding="utf8") as f:
|
|
217
|
-
|
|
218
|
-
for metric in self.metrics:
|
|
219
|
-
f.write(f"{metric},{self.metrics[metric]}\n")
|
|
219
|
+
self.metrics_to_csv(f)
|
|
220
220
|
|
|
221
221
|
metrics_json_path = os.path.join(path, "metrics.json")
|
|
222
222
|
with open(metrics_json_path, "w", encoding="utf8") as f:
|
|
223
223
|
f.write(self.metrics.dumps())
|
|
224
224
|
|
|
225
|
+
def metrics_to_csv(
|
|
226
|
+
self, fp: io.TextIOWrapper, metrics_object: Optional[Dict[str, Any]] = None
|
|
227
|
+
):
|
|
228
|
+
w = csv.writer(fp)
|
|
229
|
+
w.writerow(("Metric", "Value"))
|
|
230
|
+
for entry in (metrics_object or self.metrics).items():
|
|
231
|
+
w.writerow(entry)
|
|
232
|
+
|
|
225
233
|
def validate(self):
|
|
226
234
|
"""
|
|
227
235
|
Ensures that all paths exist in a State.
|
librelane/steps/__main__.py
CHANGED
|
@@ -15,7 +15,6 @@ import os
|
|
|
15
15
|
import shlex
|
|
16
16
|
import shutil
|
|
17
17
|
import datetime
|
|
18
|
-
import functools
|
|
19
18
|
import subprocess
|
|
20
19
|
from functools import partial
|
|
21
20
|
from typing import IO, Any, Dict, Optional, Sequence, Union
|
|
@@ -239,7 +238,7 @@ def eject(ctx, output, state_in, config, id):
|
|
|
239
238
|
found_stdin_data = found_stdin.read()
|
|
240
239
|
raise Stop()
|
|
241
240
|
|
|
242
|
-
step.run_subprocess =
|
|
241
|
+
step.run_subprocess = partial(
|
|
243
242
|
step.run_subprocess,
|
|
244
243
|
_popen_callable=popen_substitute,
|
|
245
244
|
)
|