ciel 0.21.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.
ciel/build/sky130.py ADDED
@@ -0,0 +1,358 @@
1
+ # Copyright 2022-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
+ import os
15
+ import io
16
+ import json
17
+ import venv
18
+ import shlex
19
+ import shutil
20
+ import subprocess
21
+ from datetime import datetime
22
+ from typing import Optional, List, Tuple, Dict
23
+ from concurrent.futures import ThreadPoolExecutor
24
+
25
+ import pcpp
26
+ from rich.console import Console
27
+ from rich.progress import Progress
28
+
29
+ from .git_multi_clone import GitMultiClone
30
+ from .common import patch_open_pdks
31
+ from ..families import Family
32
+ from ..github import opdks_repo
33
+ from ..common import (
34
+ Version,
35
+ get_cielo_dir,
36
+ mkdirp,
37
+ )
38
+
39
+ MAGIC_DEFAULT_TAG = "085131b090cb511d785baf52a10cf6df8a657d44"
40
+
41
+
42
+ def get_open_pdks(
43
+ version, build_directory, jobs=1, repo_path=None
44
+ ) -> Tuple[str, Optional[str], Optional[str]]:
45
+ try:
46
+ console = Console()
47
+
48
+ open_pdks_repo = None
49
+ if repo_path is None:
50
+ with Progress() as progress:
51
+ with ThreadPoolExecutor(max_workers=jobs) as executor:
52
+ gmc = GitMultiClone(build_directory, progress)
53
+ open_pdks_future = executor.submit(
54
+ GitMultiClone.clone,
55
+ gmc,
56
+ opdks_repo.link,
57
+ version,
58
+ default_branch="master",
59
+ )
60
+ open_pdks_repo = open_pdks_future.result()
61
+ repo_path = open_pdks_repo.path
62
+
63
+ console.log(f"Done fetching {open_pdks_repo.name}.")
64
+ else:
65
+ console.log(f"Using open_pdks at {repo_path} unaltered.")
66
+
67
+ patch_open_pdks(repo_path)
68
+
69
+ try:
70
+ json_raw = open(f"{repo_path}/sky130/sky130.json").read()
71
+ cpp = pcpp.Preprocessor()
72
+ cpp.line_directive = None
73
+ cpp.parse(json_raw)
74
+ json_str = None
75
+ with io.StringIO() as sio:
76
+ cpp.write(sio)
77
+ json_str = sio.getvalue()
78
+ manifest = json.loads(json_str)
79
+ reference_commits = manifest["reference"]
80
+ print(f"Reference commits: {reference_commits}")
81
+ except FileNotFoundError:
82
+ console.log("Warning: Couldn't find open_pdks/sky130 JSON manifest.")
83
+ except json.JSONDecodeError:
84
+ console.log("Warning: Failed to parse open_pdks/sky130 JSON manifest..")
85
+ except KeyError:
86
+ console.log(
87
+ "Warning: Failed to extract reference commits from open_pdks/sky130 JSON manifest."
88
+ )
89
+
90
+ return repo_path
91
+
92
+ except subprocess.CalledProcessError as e:
93
+ print(e)
94
+ print(e.stderr)
95
+ exit(-1)
96
+
97
+
98
+ def build_sky130_timing(build_directory, sky130_path, log_dir, jobs=1):
99
+ try:
100
+ console = Console()
101
+ sky130_submodules = (
102
+ subprocess.check_output(
103
+ ["find", "./libraries", "-type", "d", "-name", "latest"],
104
+ stderr=subprocess.PIPE,
105
+ cwd=sky130_path,
106
+ )
107
+ .decode("utf8")
108
+ .strip()
109
+ .split("\n")
110
+ )
111
+
112
+ venv_path = os.path.join(build_directory, "venv")
113
+
114
+ with console.status("Building venv…"):
115
+ venv_builder = venv.EnvBuilder(with_pip=True)
116
+ venv_builder.create(venv_path)
117
+ console.log("Done building venv.")
118
+
119
+ with console.status("Installing python-skywater-pdk in venv…"), open(
120
+ f"{log_dir}/venv.log", "w"
121
+ ) as out:
122
+ subprocess.check_call(
123
+ [
124
+ "bash",
125
+ "-c",
126
+ f"""
127
+ set -e
128
+ source {venv_path}/bin/activate
129
+ python3 -m pip install wheel
130
+ python3 -m pip install {os.path.join(sky130_path, 'scripts', 'python-skywater-pdk')}
131
+ """,
132
+ ],
133
+ stdout=out,
134
+ stderr=out,
135
+ )
136
+ console.log("Done setting up venv.")
137
+
138
+ def do_submodule(submodule: str):
139
+ submodule_cleaned = submodule.strip("/.").replace("/", "_")
140
+ console.log(f"Generating timing files for {submodule}…")
141
+ with open(f"{log_dir}/timing.{submodule_cleaned}.log", "w") as out:
142
+ subprocess.check_call(
143
+ [
144
+ "bash",
145
+ "-c",
146
+ f"""
147
+ set -e
148
+ source {venv_path}/bin/activate
149
+ cd {sky130_path}
150
+ python3 -m skywater_pdk.liberty {submodule}
151
+ python3 -m skywater_pdk.liberty {submodule} all
152
+ python3 -m skywater_pdk.liberty {submodule} all --ccsnoise
153
+ """,
154
+ ],
155
+ stdout=out,
156
+ stderr=out,
157
+ )
158
+ console.log(f"Done with {submodule}.")
159
+
160
+ with ThreadPoolExecutor(max_workers=jobs) as executor:
161
+ for submodule in sky130_submodules:
162
+ submodule_path = os.path.join(sky130_path, submodule)
163
+ if (
164
+ not os.path.exists(os.path.join(submodule_path, "cells"))
165
+ or "_sc" not in submodule
166
+ ):
167
+ continue
168
+ executor.submit(
169
+ do_submodule,
170
+ submodule,
171
+ )
172
+ console.log("Created timing data.")
173
+
174
+ except subprocess.CalledProcessError as e:
175
+ print(e)
176
+ print(e.stderr)
177
+ exit(-1)
178
+
179
+
180
+ LIB_FLAG_MAP = {
181
+ "sky130_fd_io": "--enable-io-sky130",
182
+ "sky130_fd_pr": "--enable-primitive-sky130",
183
+ "sky130_ml_xx_hd": "--enable-alpha-sky130",
184
+ "sky130_fd_sc_hd": "--enable-sc-hd-sky130",
185
+ "sky130_fd_sc_hdll": "--enable-sc-hdll-sky130",
186
+ "sky130_fd_sc_lp": "--enable-sc-lp-sky130",
187
+ "sky130_fd_sc_hvl": "--enable-sc-hvl-sky130",
188
+ "sky130_fd_sc_ls": "--enable-sc-ls-sky130",
189
+ "sky130_fd_sc_ms": "--enable-sc-ms-sky130",
190
+ "sky130_fd_sc_hs": "--enable-sc-hs-sky130",
191
+ "sky130_sram_macros": "--enable-sram-sky130",
192
+ "sky130_fd_pr_reram": "--enable-reram-sky130",
193
+ }
194
+
195
+
196
+ def build_variants(
197
+ magic_bin,
198
+ build_directory,
199
+ open_pdks_path,
200
+ include_libraries,
201
+ log_dir,
202
+ jobs=1,
203
+ ):
204
+ try:
205
+ pdk_root_abs = os.path.abspath(build_directory)
206
+ console = Console()
207
+
208
+ def run_sh(script, log_to):
209
+ output_file = open(log_to, "w")
210
+ output_file.write(script + "\n")
211
+ output_file.write("---\n")
212
+ output_file.flush()
213
+ try:
214
+ subprocess.check_call(
215
+ ["sh", "-c", script],
216
+ cwd=open_pdks_path,
217
+ stdout=output_file,
218
+ stderr=output_file,
219
+ stdin=open(os.devnull),
220
+ )
221
+ except subprocess.CalledProcessError as e:
222
+ console.log(
223
+ f"An error occurred while building the PDK. Check {log_to} for more information."
224
+ )
225
+ raise e
226
+
227
+ magic_dirname = os.path.dirname(magic_bin)
228
+ library_flags = set([LIB_FLAG_MAP[library] for library in include_libraries])
229
+ library_flags_disable = set(
230
+ [
231
+ LIB_FLAG_MAP[library].replace("enable", "disable")
232
+ for library in LIB_FLAG_MAP
233
+ if library not in include_libraries
234
+ ]
235
+ )
236
+
237
+ configuration_flags = ["--enable-sky130-pdk", "--with-reference"] + list(
238
+ library_flags.union(library_flags_disable)
239
+ )
240
+ console.log(f"Configuring with flags {shlex.join(configuration_flags)}")
241
+
242
+ with console.status("Configuring open_pdks…"):
243
+ run_sh(
244
+ f"""
245
+ set -e
246
+ export PATH="{magic_dirname}:$PATH"
247
+ ./configure {shlex.join(configuration_flags)}
248
+ """,
249
+ log_to=os.path.join(log_dir, "config.log"),
250
+ )
251
+ console.log("Configured open_pdks.")
252
+
253
+ with console.status("Building variants using open_pdks…"):
254
+ run_sh(
255
+ f"""
256
+ set -e
257
+ export LC_ALL=en_US.UTF-8
258
+ export PATH="{magic_dirname}:$PATH"
259
+ make -j{jobs}
260
+ make 'SHARED_PDKS_PATH={pdk_root_abs}' install
261
+ """,
262
+ log_to=os.path.join(log_dir, "install.log"),
263
+ )
264
+ console.log("Built PDK variants.")
265
+
266
+ with console.status("Cleaning build artifacts…"):
267
+ run_sh(
268
+ """
269
+ set -e
270
+ rm -rf sources
271
+ """,
272
+ log_to=os.path.join(log_dir, "clean.log"),
273
+ )
274
+ console.log("Cleaned build artifacts.")
275
+
276
+ console.log("Done.")
277
+
278
+ except subprocess.CalledProcessError as e:
279
+ print(e)
280
+ print(e.stderr)
281
+ exit(-1)
282
+
283
+
284
+ def install_sky130(build_directory, pdk_root, version):
285
+ console = Console()
286
+ with console.status("Adding build to list of installed versions…"):
287
+ version_directory = Version(version, "sky130").get_dir(pdk_root)
288
+ if (
289
+ os.path.exists(version_directory)
290
+ and len(os.listdir(version_directory)) != 0
291
+ ):
292
+ backup_path = version_directory
293
+ it = 0
294
+ while os.path.exists(backup_path) and len(os.listdir(backup_path)) != 0:
295
+ it += 1
296
+ backup_path = Version(f"{version}.bk{it}", version).get_dir(pdk_root)
297
+ console.log(
298
+ f"Build already found at {version_directory}, moving to {backup_path}…"
299
+ )
300
+ shutil.move(version_directory, backup_path)
301
+
302
+ console.log("Copying…")
303
+ mkdirp(version_directory)
304
+
305
+ sky130_family = Family.by_name["sky130"]
306
+
307
+ for variant in sky130_family.variants:
308
+ variant_build_path = os.path.join(build_directory, variant)
309
+ variant_install_path = os.path.join(version_directory, variant)
310
+ if os.path.isdir(variant_build_path):
311
+ shutil.copytree(variant_build_path, variant_install_path)
312
+
313
+ console.log("Done.")
314
+
315
+
316
+ def build(
317
+ pdk_root: str,
318
+ version: str,
319
+ jobs: int = 1,
320
+ clear_build_artifacts: bool = True,
321
+ include_libraries: Optional[List[str]] = None,
322
+ using_repos: Optional[Dict[str, str]] = None,
323
+ ):
324
+ family = Family.by_name["sky130"]
325
+ library_set = family.resolve_libraries(include_libraries)
326
+
327
+ if using_repos is None:
328
+ using_repos = {}
329
+
330
+ build_directory = os.path.join(get_cielo_dir(pdk_root, "sky130"), "build", version)
331
+ timestamp = datetime.now().strftime("build_sky130-%Y-%m-%d-%H-%M-%S")
332
+ log_dir = os.path.join(build_directory, "logs", timestamp)
333
+ mkdirp(log_dir)
334
+
335
+ console = Console()
336
+ console.log(f"Logging to '{log_dir}'…")
337
+
338
+ open_pdks_path = get_open_pdks(
339
+ version, build_directory, jobs, using_repos.get("open_pdks")
340
+ )
341
+
342
+ magic_bin = shutil.which("magic")
343
+ if magic_bin is None:
344
+ print("Magic is either not installed or not in PATH.")
345
+ exit(-1)
346
+
347
+ build_variants(
348
+ magic_bin,
349
+ build_directory,
350
+ open_pdks_path,
351
+ library_set,
352
+ log_dir,
353
+ jobs,
354
+ ),
355
+ install_sky130(build_directory, pdk_root, version)
356
+
357
+ if clear_build_artifacts:
358
+ shutil.rmtree(build_directory)
ciel/click_common.py ADDED
@@ -0,0 +1,119 @@
1
+ # Copyright 2022-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
+ import os
15
+ from functools import partial
16
+ from typing import Callable, Optional
17
+
18
+ import click
19
+
20
+ from .common import VOLARE_RESOLVED_HOME
21
+ from .github import volare_repo, GitHubSession
22
+
23
+ opt = partial(click.option, show_default=True)
24
+
25
+
26
+ def opt_pdk_root(function: Callable):
27
+ function = opt(
28
+ "--pdk",
29
+ required=False,
30
+ default=os.getenv("PDK_FAMILY") or "sky130",
31
+ help="The PDK family to install",
32
+ )(function)
33
+ function = opt(
34
+ "--pdk-root",
35
+ required=False,
36
+ default=VOLARE_RESOLVED_HOME,
37
+ help="Path to the PDK root",
38
+ )(function)
39
+ return function
40
+
41
+
42
+ def opt_build(function: Callable):
43
+ function = opt(
44
+ "-l",
45
+ "--include-libraries",
46
+ multiple=True,
47
+ default=None,
48
+ help="Libraries to include. You can use -l multiple times to include multiple libraries. Pass 'all' to include all of them. A default of 'None' uses a default set for the particular PDK.",
49
+ )(function)
50
+ function = opt(
51
+ "-j",
52
+ "--jobs",
53
+ default=1,
54
+ help="Specifies the number of commands to run simultaneously.",
55
+ )(function)
56
+ function = opt(
57
+ "--sram/--no-sram",
58
+ default=True,
59
+ hidden=True,
60
+ expose_value=False,
61
+ )(function)
62
+ function = opt(
63
+ "--clear-build-artifacts/--keep-build-artifacts",
64
+ default=False,
65
+ help="Whether or not to remove the build artifacts. Keeping the build artifacts is useful when testing.",
66
+ )(function)
67
+ function = opt(
68
+ "-r",
69
+ "--use-repo-at",
70
+ default=None,
71
+ multiple=True,
72
+ hidden=True,
73
+ type=str,
74
+ help="Use this repository instead of cloning and checking out, in the format repo_name=/path/to/repo. You can pass it multiple times to replace multiple repos. This feature is intended for cielo and PDK developers.",
75
+ )(function)
76
+ return function
77
+
78
+
79
+ def opt_push(function: Callable):
80
+ function = opt("-o", "--owner", default=volare_repo.owner, help="Repository Owner")(
81
+ function
82
+ )
83
+ function = opt("-r", "--repository", default=volare_repo.name, help="Repository")(
84
+ function
85
+ )
86
+ function = opt(
87
+ "--pre/--prod", default=False, help="Push as pre-release or production"
88
+ )(function)
89
+ function = opt(
90
+ "-L",
91
+ "--push-library",
92
+ "push_libraries",
93
+ multiple=True,
94
+ default=None,
95
+ help="Push only libraries in this list. You can use -L multiple times to include multiple libraries. Pass 'None' to push all libraries built.",
96
+ )(function)
97
+ return function
98
+
99
+
100
+ def set_token_cb(
101
+ ctx: click.Context,
102
+ param: click.Parameter,
103
+ value: Optional[str],
104
+ ):
105
+ GitHubSession.Token.override = value
106
+
107
+
108
+ def opt_token(function: Callable) -> Callable:
109
+ function = opt(
110
+ "-t",
111
+ "--token",
112
+ "session",
113
+ default=None,
114
+ required=False,
115
+ expose_value=False,
116
+ help="Replace the GitHub token used for GitHub requests, which is by default the value of the environment variable GITHUB_TOKEN or None.",
117
+ callback=set_token_cb,
118
+ )(function)
119
+ return function