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.

Files changed (170) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +479 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +63 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +246 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +456 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +116 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +155 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +743 -0
  27. librelane/container.py +285 -0
  28. librelane/env_info.py +320 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +327 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +1049 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/help/__main__.py +39 -0
  52. librelane/logging/__init__.py +40 -0
  53. librelane/logging/logger.py +323 -0
  54. librelane/open_pdks_rev +1 -0
  55. librelane/plugins.py +21 -0
  56. librelane/py.typed +0 -0
  57. librelane/scripts/base.sdc +80 -0
  58. librelane/scripts/klayout/Readme.md +2 -0
  59. librelane/scripts/klayout/open_design.py +63 -0
  60. librelane/scripts/klayout/render.py +121 -0
  61. librelane/scripts/klayout/stream_out.py +176 -0
  62. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  63. librelane/scripts/klayout/xor.drc +120 -0
  64. librelane/scripts/magic/Readme.md +1 -0
  65. librelane/scripts/magic/common/read.tcl +114 -0
  66. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  67. librelane/scripts/magic/def/mag.tcl +19 -0
  68. librelane/scripts/magic/def/mag_gds.tcl +79 -0
  69. librelane/scripts/magic/drc.tcl +78 -0
  70. librelane/scripts/magic/extract_spice.tcl +98 -0
  71. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  72. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  73. librelane/scripts/magic/gds/extras_mag.tcl +45 -0
  74. librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
  75. librelane/scripts/magic/get_bbox.tcl +11 -0
  76. librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
  77. librelane/scripts/magic/lef/maglef.tcl +26 -0
  78. librelane/scripts/magic/lef.tcl +57 -0
  79. librelane/scripts/magic/open.tcl +28 -0
  80. librelane/scripts/magic/wrapper.tcl +21 -0
  81. librelane/scripts/netgen/setup.tcl +28 -0
  82. librelane/scripts/odbpy/apply_def_template.py +49 -0
  83. librelane/scripts/odbpy/cell_frequency.py +107 -0
  84. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  85. librelane/scripts/odbpy/contextualize.py +109 -0
  86. librelane/scripts/odbpy/defutil.py +573 -0
  87. librelane/scripts/odbpy/diodes.py +373 -0
  88. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  89. librelane/scripts/odbpy/eco_buffer.py +181 -0
  90. librelane/scripts/odbpy/eco_diode.py +139 -0
  91. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  92. librelane/scripts/odbpy/io_place.py +482 -0
  93. librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
  94. librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
  95. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  96. librelane/scripts/odbpy/lefutil.py +97 -0
  97. librelane/scripts/odbpy/placers.py +162 -0
  98. librelane/scripts/odbpy/power_utils.py +397 -0
  99. librelane/scripts/odbpy/random_place.py +57 -0
  100. librelane/scripts/odbpy/reader.py +250 -0
  101. librelane/scripts/odbpy/remove_buffers.py +173 -0
  102. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  103. librelane/scripts/odbpy/wire_lengths.py +93 -0
  104. librelane/scripts/openroad/antenna_check.tcl +20 -0
  105. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  106. librelane/scripts/openroad/basic_mp.tcl +24 -0
  107. librelane/scripts/openroad/buffer_list.tcl +10 -0
  108. librelane/scripts/openroad/common/dpl.tcl +24 -0
  109. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  110. librelane/scripts/openroad/common/grt.tcl +32 -0
  111. librelane/scripts/openroad/common/io.tcl +540 -0
  112. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  113. librelane/scripts/openroad/common/resizer.tcl +103 -0
  114. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  115. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  116. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  117. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  118. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  119. librelane/scripts/openroad/cts.tcl +80 -0
  120. librelane/scripts/openroad/cut_rows.tcl +24 -0
  121. librelane/scripts/openroad/dpl.tcl +24 -0
  122. librelane/scripts/openroad/drt.tcl +37 -0
  123. librelane/scripts/openroad/fill.tcl +30 -0
  124. librelane/scripts/openroad/floorplan.tcl +145 -0
  125. librelane/scripts/openroad/gpl.tcl +88 -0
  126. librelane/scripts/openroad/grt.tcl +30 -0
  127. librelane/scripts/openroad/gui.tcl +37 -0
  128. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  129. librelane/scripts/openroad/ioplacer.tcl +67 -0
  130. librelane/scripts/openroad/irdrop.tcl +51 -0
  131. librelane/scripts/openroad/pdn.tcl +52 -0
  132. librelane/scripts/openroad/rcx.tcl +32 -0
  133. librelane/scripts/openroad/repair_design.tcl +70 -0
  134. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  135. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  136. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  137. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  138. librelane/scripts/openroad/sta/corner.tcl +393 -0
  139. librelane/scripts/openroad/tapcell.tcl +25 -0
  140. librelane/scripts/openroad/write_views.tcl +27 -0
  141. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  142. librelane/scripts/pyosys/json_header.py +84 -0
  143. librelane/scripts/pyosys/synthesize.py +493 -0
  144. librelane/scripts/pyosys/ys_common.py +153 -0
  145. librelane/scripts/tclsh/hello.tcl +1 -0
  146. librelane/state/__init__.py +24 -0
  147. librelane/state/__main__.py +61 -0
  148. librelane/state/design_format.py +195 -0
  149. librelane/state/state.py +359 -0
  150. librelane/steps/__init__.py +61 -0
  151. librelane/steps/__main__.py +510 -0
  152. librelane/steps/checker.py +637 -0
  153. librelane/steps/common_variables.py +340 -0
  154. librelane/steps/cvc_rv.py +169 -0
  155. librelane/steps/klayout.py +509 -0
  156. librelane/steps/magic.py +576 -0
  157. librelane/steps/misc.py +160 -0
  158. librelane/steps/netgen.py +253 -0
  159. librelane/steps/odb.py +1088 -0
  160. librelane/steps/openroad.py +2460 -0
  161. librelane/steps/openroad_alerts.py +102 -0
  162. librelane/steps/pyosys.py +640 -0
  163. librelane/steps/step.py +1571 -0
  164. librelane/steps/tclstep.py +288 -0
  165. librelane/steps/verilator.py +222 -0
  166. librelane/steps/yosys.py +371 -0
  167. librelane-2.4.0.dist-info/METADATA +169 -0
  168. librelane-2.4.0.dist-info/RECORD +170 -0
  169. librelane-2.4.0.dist-info/WHEEL +4 -0
  170. 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
@@ -0,0 +1,14 @@
1
+ #N
2
+ @min_distance=0.1
3
+ a.*
4
+
5
+ #S
6
+ $1
7
+ rst
8
+
9
+ #E
10
+ clk
11
+
12
+ #W
13
+ y
14
+ x