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.

Files changed (166) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +470 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +61 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +245 -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 +402 -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 +117 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +158 -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 +722 -0
  27. librelane/container.py +264 -0
  28. librelane/env_info.py +306 -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 +330 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +985 -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/logging/__init__.py +40 -0
  52. librelane/logging/logger.py +323 -0
  53. librelane/open_pdks_rev +1 -0
  54. librelane/plugins.py +21 -0
  55. librelane/py.typed +0 -0
  56. librelane/scripts/base.sdc +80 -0
  57. librelane/scripts/klayout/Readme.md +2 -0
  58. librelane/scripts/klayout/open_design.py +63 -0
  59. librelane/scripts/klayout/render.py +121 -0
  60. librelane/scripts/klayout/stream_out.py +176 -0
  61. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  62. librelane/scripts/klayout/xor.drc +120 -0
  63. librelane/scripts/magic/Readme.md +1 -0
  64. librelane/scripts/magic/common/read.tcl +114 -0
  65. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  66. librelane/scripts/magic/def/mag.tcl +19 -0
  67. librelane/scripts/magic/def/mag_gds.tcl +81 -0
  68. librelane/scripts/magic/drc.tcl +79 -0
  69. librelane/scripts/magic/extract_spice.tcl +98 -0
  70. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  71. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  72. librelane/scripts/magic/gds/extras_mag.tcl +47 -0
  73. librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
  74. librelane/scripts/magic/get_bbox.tcl +11 -0
  75. librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
  76. librelane/scripts/magic/lef/maglef.tcl +27 -0
  77. librelane/scripts/magic/lef.tcl +57 -0
  78. librelane/scripts/magic/open.tcl +28 -0
  79. librelane/scripts/magic/wrapper.tcl +19 -0
  80. librelane/scripts/netgen/setup.tcl +28 -0
  81. librelane/scripts/odbpy/apply_def_template.py +49 -0
  82. librelane/scripts/odbpy/cell_frequency.py +107 -0
  83. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  84. librelane/scripts/odbpy/contextualize.py +109 -0
  85. librelane/scripts/odbpy/defutil.py +574 -0
  86. librelane/scripts/odbpy/diodes.py +373 -0
  87. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  88. librelane/scripts/odbpy/exception_codes.py +17 -0
  89. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  90. librelane/scripts/odbpy/io_place.py +482 -0
  91. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  92. librelane/scripts/odbpy/lefutil.py +97 -0
  93. librelane/scripts/odbpy/placers.py +162 -0
  94. librelane/scripts/odbpy/power_utils.py +395 -0
  95. librelane/scripts/odbpy/random_place.py +57 -0
  96. librelane/scripts/odbpy/reader.py +246 -0
  97. librelane/scripts/odbpy/remove_buffers.py +173 -0
  98. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  99. librelane/scripts/odbpy/wire_lengths.py +93 -0
  100. librelane/scripts/openroad/antenna_check.tcl +20 -0
  101. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  102. librelane/scripts/openroad/basic_mp.tcl +24 -0
  103. librelane/scripts/openroad/buffer_list.tcl +10 -0
  104. librelane/scripts/openroad/common/dpl.tcl +24 -0
  105. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  106. librelane/scripts/openroad/common/grt.tcl +32 -0
  107. librelane/scripts/openroad/common/io.tcl +476 -0
  108. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  109. librelane/scripts/openroad/common/resizer.tcl +103 -0
  110. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  111. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  112. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  113. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  114. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  115. librelane/scripts/openroad/cts.tcl +80 -0
  116. librelane/scripts/openroad/cut_rows.tcl +24 -0
  117. librelane/scripts/openroad/dpl.tcl +24 -0
  118. librelane/scripts/openroad/drt.tcl +37 -0
  119. librelane/scripts/openroad/fill.tcl +30 -0
  120. librelane/scripts/openroad/floorplan.tcl +145 -0
  121. librelane/scripts/openroad/gpl.tcl +88 -0
  122. librelane/scripts/openroad/grt.tcl +30 -0
  123. librelane/scripts/openroad/gui.tcl +15 -0
  124. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  125. librelane/scripts/openroad/ioplacer.tcl +67 -0
  126. librelane/scripts/openroad/irdrop.tcl +51 -0
  127. librelane/scripts/openroad/pdn.tcl +52 -0
  128. librelane/scripts/openroad/rcx.tcl +32 -0
  129. librelane/scripts/openroad/repair_design.tcl +70 -0
  130. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  131. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  132. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  133. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  134. librelane/scripts/openroad/sta/corner.tcl +393 -0
  135. librelane/scripts/openroad/tapcell.tcl +25 -0
  136. librelane/scripts/openroad/write_views.tcl +27 -0
  137. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  138. librelane/scripts/pyosys/json_header.py +84 -0
  139. librelane/scripts/pyosys/synthesize.py +493 -0
  140. librelane/scripts/pyosys/ys_common.py +153 -0
  141. librelane/scripts/tclsh/hello.tcl +1 -0
  142. librelane/state/__init__.py +24 -0
  143. librelane/state/__main__.py +61 -0
  144. librelane/state/design_format.py +180 -0
  145. librelane/state/state.py +351 -0
  146. librelane/steps/__init__.py +61 -0
  147. librelane/steps/__main__.py +511 -0
  148. librelane/steps/checker.py +637 -0
  149. librelane/steps/common_variables.py +340 -0
  150. librelane/steps/cvc_rv.py +169 -0
  151. librelane/steps/klayout.py +509 -0
  152. librelane/steps/magic.py +566 -0
  153. librelane/steps/misc.py +160 -0
  154. librelane/steps/netgen.py +253 -0
  155. librelane/steps/odb.py +955 -0
  156. librelane/steps/openroad.py +2433 -0
  157. librelane/steps/openroad_alerts.py +102 -0
  158. librelane/steps/pyosys.py +629 -0
  159. librelane/steps/step.py +1547 -0
  160. librelane/steps/tclstep.py +288 -0
  161. librelane/steps/verilator.py +222 -0
  162. librelane/steps/yosys.py +371 -0
  163. librelane-2.4.0.dev0.dist-info/METADATA +151 -0
  164. librelane-2.4.0.dev0.dist-info/RECORD +166 -0
  165. librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
  166. 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,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
@@ -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]]