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/__init__.py ADDED
@@ -0,0 +1,267 @@
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 uuid
16
+ import pathlib
17
+ import tarfile
18
+ import tempfile
19
+ import importlib
20
+ import subprocess
21
+ from typing import Optional, List, Dict
22
+
23
+ import click
24
+ import zstandard as zstd
25
+ from rich.console import Console
26
+ from rich.progress import Progress
27
+
28
+ from ..github import (
29
+ GitHubSession,
30
+ get_commit_date,
31
+ volare_repo,
32
+ )
33
+ from ..common import (
34
+ Version,
35
+ mkdirp,
36
+ resolve_version,
37
+ date_to_iso8601,
38
+ )
39
+ from ..click_common import (
40
+ opt_push,
41
+ opt_build,
42
+ opt_pdk_root,
43
+ opt_token,
44
+ )
45
+ from ..families import Family
46
+
47
+
48
+ def build(
49
+ pdk_root: str,
50
+ pdk: str,
51
+ version: str,
52
+ jobs: int = 1,
53
+ sram: bool = True, # Deprecated
54
+ clear_build_artifacts: bool = True,
55
+ include_libraries: Optional[List[str]] = None,
56
+ use_repo_at: Optional[List[str]] = None,
57
+ ):
58
+ use_repos = {}
59
+ if use_repo_at is not None:
60
+ for repo in use_repo_at:
61
+ name, path = repo.split("=")
62
+ use_repos[name] = os.path.abspath(path)
63
+
64
+ if pdk not in Family.by_name:
65
+ raise Exception(f"Unsupported PDK family '{pdk}'.")
66
+
67
+ kwargs = {
68
+ "pdk_root": pdk_root,
69
+ "version": version,
70
+ "jobs": jobs,
71
+ "clear_build_artifacts": clear_build_artifacts,
72
+ "include_libraries": include_libraries,
73
+ "using_repos": use_repos,
74
+ }
75
+
76
+ build_module = importlib.import_module(f".{pdk}", package=__name__)
77
+ build_function = build_module.build
78
+ build_function(**kwargs)
79
+
80
+
81
+ @click.command("build")
82
+ @opt_token
83
+ @opt_pdk_root
84
+ @opt_build
85
+ @click.option(
86
+ "-f",
87
+ "--metadata-file",
88
+ "tool_metadata_file_path",
89
+ default=None,
90
+ help="Explicitly define a tool metadata file instead of searching for a metadata file",
91
+ )
92
+ @click.argument("version", required=False)
93
+ def build_cmd(
94
+ include_libraries,
95
+ jobs,
96
+ pdk_root,
97
+ pdk,
98
+ clear_build_artifacts,
99
+ tool_metadata_file_path,
100
+ version,
101
+ use_repo_at,
102
+ ):
103
+ """
104
+ Builds the requested PDK.
105
+
106
+ Parameters: <version> (Optional)
107
+
108
+ If a version is not given, and you run this in the top level directory of
109
+ tools with a tool_metadata.yml file, for example OpenLane or DFFRAM,
110
+ the appropriate version will be enabled automatically.
111
+ """
112
+ if include_libraries == ():
113
+ include_libraries = None
114
+
115
+ console = Console()
116
+ try:
117
+ version = resolve_version(version, tool_metadata_file_path)
118
+ except Exception as e:
119
+ console.print(f"Could not determine open_pdks version: {e}")
120
+ exit(-1)
121
+
122
+ build(
123
+ pdk_root=pdk_root,
124
+ pdk=pdk,
125
+ version=version,
126
+ jobs=jobs,
127
+ clear_build_artifacts=clear_build_artifacts,
128
+ include_libraries=include_libraries,
129
+ use_repo_at=use_repo_at,
130
+ )
131
+
132
+
133
+ def push(
134
+ pdk_root,
135
+ pdk,
136
+ version,
137
+ *,
138
+ owner=volare_repo.owner,
139
+ repository=volare_repo.name,
140
+ pre=False,
141
+ push_libraries=None,
142
+ session: Optional[GitHubSession] = None,
143
+ ):
144
+ family = Family.by_name[pdk]
145
+
146
+ if session is None:
147
+ session = GitHubSession()
148
+ if session.github_token is None:
149
+ raise TypeError("No GitHub token was provided.")
150
+
151
+ console = Console()
152
+
153
+ if push_libraries is None or len(push_libraries) == 0:
154
+ push_libraries = family.all_libraries
155
+ library_list = set(push_libraries)
156
+
157
+ version_object = Version(version, pdk)
158
+ version_directory = version_object.get_dir(pdk_root)
159
+ if not os.path.isdir(version_directory):
160
+ raise FileNotFoundError(f"Version {version} not found.")
161
+
162
+ tempdir = tempfile.gettempdir()
163
+ tarball_directory = os.path.join(tempdir, "cielo", f"{uuid.uuid4()}", version)
164
+ mkdirp(tarball_directory)
165
+
166
+ final_tarballs = []
167
+
168
+ with Progress() as progress:
169
+ collections: Dict[str, List[str]] = {"common": []}
170
+ path_it = pathlib.Path(version_directory).glob("**/*")
171
+ for path in path_it:
172
+ if not path.is_file():
173
+ continue
174
+ relative = os.path.relpath(path, version_directory)
175
+ path_components = relative.split(os.sep)
176
+ if path_components[1] == "libs.ref":
177
+ lib = path_components[2]
178
+ if lib not in library_list:
179
+ continue
180
+ collections[lib] = collections.get(lib) or []
181
+ collections[lib].append(str(path))
182
+ else:
183
+ collections["common"].append(str(path))
184
+
185
+ for name, files in collections.items():
186
+ tarball_path = os.path.join(tarball_directory, f"{name}.tar.zst")
187
+ task = progress.add_task(f"Compressing {name}…", total=len(files))
188
+ with zstd.open(tarball_path, mode="wb") as stream:
189
+ with tarfile.TarFile(fileobj=stream, mode="w") as tf:
190
+ for i, file in enumerate(files):
191
+ progress.update(task, completed=i + 1)
192
+ path_in_tarball = os.path.relpath(file, version_directory)
193
+ tf.add(file, arcname=path_in_tarball)
194
+ console.log(f"\nCompressed to {tarball_path}.")
195
+ progress.remove_task(task)
196
+ final_tarballs.append(tarball_path)
197
+
198
+ tag = f"{pdk}-{version}"
199
+
200
+ # If someone wants to rewrite this to not use ghr, please, by all means.
201
+ console.log("Starting upload…")
202
+
203
+ body = f"{pdk} variants built using cielo"
204
+ date = get_commit_date(version, family.repo, session)
205
+ if date is not None:
206
+ body = f"{pdk} variants (released on {date_to_iso8601(date)})"
207
+
208
+ for tarball_path in final_tarballs:
209
+ subprocess.check_call(
210
+ [
211
+ "ghr",
212
+ "-owner",
213
+ owner,
214
+ "-repository",
215
+ repository,
216
+ "-token",
217
+ session.github_token,
218
+ "-body",
219
+ body,
220
+ "-commitish",
221
+ "releases",
222
+ "-replace",
223
+ ]
224
+ + (["-prerelease"] if pre else [])
225
+ + [
226
+ tag,
227
+ tarball_path,
228
+ ]
229
+ )
230
+ console.log("Done.")
231
+
232
+
233
+ @click.command("push", hidden=True)
234
+ @opt_token
235
+ @opt_pdk_root
236
+ @opt_push
237
+ @click.argument("version")
238
+ def push_cmd(
239
+ owner,
240
+ repository,
241
+ pre,
242
+ pdk_root,
243
+ pdk,
244
+ version,
245
+ push_libraries,
246
+ ):
247
+ """
248
+ For maintainers: Package and release a build to the public.
249
+
250
+ Requires ghr: github.com/tcnksm/ghr
251
+
252
+ Parameters: <version> (required)
253
+ """
254
+ console = Console()
255
+ try:
256
+ push(
257
+ pdk_root,
258
+ pdk,
259
+ version,
260
+ owner=owner,
261
+ repository=repository,
262
+ pre=pre,
263
+ push_libraries=push_libraries,
264
+ )
265
+ except Exception as e:
266
+ console.print(f"[red]Failed to push version: {e}")
267
+ exit(-1)
ciel/build/common.py ADDED
@@ -0,0 +1,98 @@
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 shutil
16
+ import subprocess
17
+
18
+ from cielo.github import GitHubSession
19
+
20
+
21
+ def open_pdks_fix_makefile(at_path: str):
22
+ backup_path = f"{at_path}.bak"
23
+ shutil.move(at_path, backup_path)
24
+
25
+ fix_fi = False
26
+
27
+ with open(backup_path, "r") as file_in, open(at_path, "w") as file_out:
28
+ for line in file_in:
29
+ if "_COMMIT = `" in line:
30
+ line = line.replace("_COMMIT = ", "_COMMIT=")
31
+ if fix_fi:
32
+ file_out.write(line.replace("fi", "fi ; \\"))
33
+ fix_fi = False
34
+ else:
35
+ file_out.write(line)
36
+ if "_COMMIT=`" in line:
37
+ fix_fi = True
38
+
39
+
40
+ def patch_open_pdks(at_path: str):
41
+ """
42
+ This functions applies various patches based on the current version of
43
+ open_pdks in use.
44
+ """
45
+ head = subprocess.check_output(
46
+ ["git", "rev-parse", "HEAD"], cwd=at_path, encoding="utf8"
47
+ ).strip()
48
+
49
+ def is_ancestor(commit: str):
50
+ nonlocal head, at_path
51
+ return (
52
+ subprocess.call(
53
+ ["git", "merge-base", "--is-ancestor", commit, head],
54
+ stdout=open(os.devnull, "w"),
55
+ stderr=open(os.devnull, "w"),
56
+ cwd=at_path,
57
+ )
58
+ == 0
59
+ )
60
+
61
+ can_build = is_ancestor(
62
+ "c74daac794c83327e54b91cbaf426f722574665c"
63
+ ) # First one with --with-reference
64
+ if not can_build:
65
+ print(
66
+ f"Commit {head} cannot be built using Cielo: the minimum version of open_pdks buildable with Cielo is 1.0.381."
67
+ )
68
+ exit(-1)
69
+
70
+ gf180mcu_sources_ok = is_ancestor(
71
+ "c1e2118846fd216b2c065a216950e75d2d67ccb8"
72
+ ) # gf180mcu sources fix
73
+ if not gf180mcu_sources_ok:
74
+ print(
75
+ "Patching gf180mcu Makefile.in…",
76
+ )
77
+ open_pdks_fix_makefile(os.path.join(at_path, "gf180mcu", "Makefile.in"))
78
+
79
+ download_script_ok = is_ancestor(
80
+ "ebffedd16788db327af050ac01c3fb1558ebffd1"
81
+ ) # download script fix
82
+ if not download_script_ok:
83
+ print("Replacing download.sh…")
84
+ session = GitHubSession()
85
+ r = session.get(
86
+ "https://raw.githubusercontent.com/RTimothyEdwards/open_pdks/ebffedd16788db327af050ac01c3fb1558ebffd1/scripts/download.sh"
87
+ )
88
+ with open(os.path.join(at_path, "scripts", "download.sh"), "wb") as f:
89
+ f.write(r.content)
90
+
91
+ sky130_sources_ok = is_ancestor(
92
+ "274040274a7dfb5fd2c69e0e9c643f80507df3fe"
93
+ ) # sky130 sources fix
94
+ if not sky130_sources_ok:
95
+ print(
96
+ "Patching sky130 Makefile.in…",
97
+ )
98
+ open_pdks_fix_makefile(os.path.join(at_path, "sky130", "Makefile.in"))
ciel/build/gf180mcu.py ADDED
@@ -0,0 +1,263 @@
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 shlex
18
+ import shutil
19
+ import subprocess
20
+ from datetime import datetime
21
+ from typing import Optional, List, Tuple, Dict
22
+ from concurrent.futures import ThreadPoolExecutor
23
+
24
+ import pcpp
25
+ from rich.console import Console
26
+ from rich.progress import Progress
27
+
28
+ from .git_multi_clone import GitMultiClone
29
+ from .common import patch_open_pdks
30
+ from ..families import Family
31
+ from ..github import opdks_repo
32
+ from ..common import (
33
+ Version,
34
+ get_cielo_dir,
35
+ mkdirp,
36
+ )
37
+
38
+ MAGIC_DEFAULT_TAG = "085131b090cb511d785baf52a10cf6df8a657d44"
39
+
40
+
41
+ def get_open_pdks(
42
+ version, build_directory, jobs=1, repo_path=None
43
+ ) -> Tuple[str, Optional[str], Optional[str]]:
44
+ try:
45
+ console = Console()
46
+
47
+ open_pdks_repo = None
48
+ if repo_path is None:
49
+ with Progress() as progress:
50
+ with ThreadPoolExecutor(max_workers=jobs) as executor:
51
+ gmc = GitMultiClone(build_directory, progress)
52
+ open_pdks_future = executor.submit(
53
+ GitMultiClone.clone,
54
+ gmc,
55
+ opdks_repo.link,
56
+ version,
57
+ default_branch="master",
58
+ )
59
+ open_pdks_repo = open_pdks_future.result()
60
+ repo_path = open_pdks_repo.path
61
+
62
+ console.log(f"Done fetching {open_pdks_repo.name}.")
63
+ else:
64
+ console.log(f"Using open_pdks at {repo_path} unaltered.")
65
+
66
+ patch_open_pdks(repo_path)
67
+
68
+ try:
69
+ json_raw = open(f"{repo_path}/gf180mcu/gf180mcu.json").read()
70
+ cpp = pcpp.Preprocessor()
71
+ cpp.line_directive = None
72
+ cpp.parse(json_raw)
73
+ json_str = None
74
+ with io.StringIO() as sio:
75
+ cpp.write(sio)
76
+ json_str = sio.getvalue()
77
+ manifest = json.loads(json_str)
78
+ reference_commits = manifest["reference"]
79
+ print(f"Reference commits: {reference_commits}")
80
+ except FileNotFoundError:
81
+ console.log("Warning: Couldn't find open_pdks/sky130 JSON manifest.")
82
+ except json.JSONDecodeError:
83
+ console.log("Warning: Failed to parse open_pdks/sky130 JSON manifest..")
84
+ except KeyError:
85
+ console.log(
86
+ "Warning: Failed to extract reference commits from open_pdks/sky130 JSON manifest."
87
+ )
88
+
89
+ return repo_path
90
+
91
+ except subprocess.CalledProcessError as e:
92
+ print(e)
93
+ print(e.stderr)
94
+ exit(-1)
95
+
96
+
97
+ LIB_FLAG_MAP = {
98
+ "gf180mcu_fd_pr": "--enable-primitive-gf180mcu",
99
+ "gf180mcu_fd_sc_mcu7t5v0": "--enable-sc-7t5v0-gf180mcu",
100
+ "gf180mcu_fd_sc_mcu9t5v0": "--enable-sc-9t5v0-gf180mcu",
101
+ "gf180mcu_fd_io": "--enable-io-gf180mcu",
102
+ "gf180mcu_fd_ip_sram": "--enable-sram-gf180mcu",
103
+ "gf180mcu_osu_sc_gp12t3v3": "--enable-osu-sc-gf180mcu",
104
+ "gf180mcu_osu_sc_gp9t3v3": "--enable-osu-sc-gf180mcu",
105
+ }
106
+
107
+
108
+ def build_variants(
109
+ magic_bin, include_libraries, build_directory, open_pdks_path, log_dir, jobs=1
110
+ ):
111
+ try:
112
+ pdk_root_abs = os.path.abspath(build_directory)
113
+ console = Console()
114
+
115
+ def run_sh(script, log_to):
116
+ output_file = open(log_to, "w")
117
+ try:
118
+ subprocess.check_call(
119
+ ["sh", "-c", script],
120
+ cwd=open_pdks_path,
121
+ stdout=output_file,
122
+ stderr=output_file,
123
+ stdin=open(os.devnull),
124
+ )
125
+ except subprocess.CalledProcessError as e:
126
+ console.log(
127
+ f"An error occurred while building the PDK. Check {log_to} for more information."
128
+ )
129
+ raise e
130
+
131
+ library_flags = set([LIB_FLAG_MAP[library] for library in include_libraries])
132
+ library_flags_disable = set(
133
+ [
134
+ LIB_FLAG_MAP[library].replace("enable", "disable")
135
+ for library in LIB_FLAG_MAP
136
+ if library not in include_libraries
137
+ ]
138
+ )
139
+ magic_dirname = os.path.dirname(magic_bin)
140
+
141
+ configuration_flags = ["--enable-gf180mcu-pdk", "--with-reference"] + list(
142
+ library_flags.union(library_flags_disable)
143
+ )
144
+ console.log(f"Configuring with flags {shlex.join(configuration_flags)}")
145
+
146
+ with console.status("Configuring open_pdks…"):
147
+ run_sh(
148
+ f"""
149
+ set -e
150
+ export PATH="{magic_dirname}:$PATH"
151
+ ./configure {shlex.join(configuration_flags)}
152
+ """,
153
+ log_to=os.path.join(log_dir, "config.log"),
154
+ )
155
+ console.log("Configured open_pdks.")
156
+
157
+ with console.status("Building variants using open_pdks…"):
158
+ run_sh(
159
+ f"""
160
+ set -e
161
+ export LC_ALL=en_US.UTF-8
162
+ export PATH="{magic_dirname}:$PATH"
163
+ make -j{jobs}
164
+ make 'SHARED_PDKS_PATH={pdk_root_abs}' install
165
+ """,
166
+ log_to=os.path.join(log_dir, "install.log"),
167
+ )
168
+ console.log("Built PDK variants.")
169
+ with console.status("Cleaning build artifacts…"):
170
+ run_sh(
171
+ """
172
+ set -e
173
+ rm -rf sources
174
+ """,
175
+ log_to=os.path.join(log_dir, "clean.log"),
176
+ )
177
+ console.log("Cleaned build artifacts.")
178
+
179
+ console.log("Done.")
180
+
181
+ except subprocess.CalledProcessError as e:
182
+ print(e)
183
+ print(e.stderr)
184
+ exit(-1)
185
+
186
+
187
+ def install_gf180mcu(build_directory, pdk_root, version):
188
+ console = Console()
189
+ with console.status("Adding build to list of installed versions…"):
190
+ version_directory = Version(version, "gf180mcu").get_dir(pdk_root)
191
+ if (
192
+ os.path.exists(version_directory)
193
+ and len(os.listdir(version_directory)) != 0
194
+ ):
195
+ backup_path = version_directory
196
+ it = 0
197
+ while os.path.exists(backup_path) and len(os.listdir(backup_path)) != 0:
198
+ it += 1
199
+ backup_path = Version(f"{version}.bk{it}", "gf180mcu").get_dir(pdk_root)
200
+ console.log(
201
+ f"Build already found at {version_directory}, moving to {backup_path}…"
202
+ )
203
+ shutil.move(version_directory, backup_path)
204
+
205
+ console.log("Copying…")
206
+ mkdirp(version_directory)
207
+
208
+ gf180mcu_family = Family.by_name["gf180mcu"]
209
+
210
+ for variant in gf180mcu_family.variants:
211
+ variant_build_path = os.path.join(build_directory, variant)
212
+ variant_install_path = os.path.join(version_directory, variant)
213
+ if os.path.isdir(variant_build_path):
214
+ shutil.copytree(variant_build_path, variant_install_path)
215
+
216
+ console.log("Done.")
217
+
218
+
219
+ def build(
220
+ pdk_root: str,
221
+ version: str,
222
+ jobs: int = 1,
223
+ clear_build_artifacts: bool = True,
224
+ include_libraries: Optional[List[str]] = None,
225
+ using_repos: Optional[Dict[str, str]] = None,
226
+ ):
227
+ family = Family.by_name["gf180mcu"]
228
+ library_set = family.resolve_libraries(include_libraries)
229
+
230
+ if using_repos is None:
231
+ using_repos = {}
232
+
233
+ timestamp = datetime.now().strftime("build_gf180mcu-%Y-%m-%d-%H-%M-%S")
234
+ build_directory = os.path.join(
235
+ get_cielo_dir(pdk_root, "gf180mcu"), "build", version
236
+ )
237
+ log_dir = os.path.join(build_directory, "logs", timestamp)
238
+ mkdirp(log_dir)
239
+
240
+ console = Console()
241
+ console.log(f"Logging to '{log_dir}'…")
242
+
243
+ open_pdks_path = get_open_pdks(
244
+ version, build_directory, jobs, using_repos.get("open_pdks")
245
+ )
246
+
247
+ magic_bin = shutil.which("magic")
248
+ if magic_bin is None:
249
+ print("Magic is either not installed or not in PATH.")
250
+ exit(-1)
251
+
252
+ build_variants(
253
+ magic_bin,
254
+ library_set,
255
+ build_directory,
256
+ open_pdks_path,
257
+ log_dir,
258
+ jobs,
259
+ )
260
+ install_gf180mcu(build_directory, pdk_root, version)
261
+
262
+ if clear_build_artifacts:
263
+ shutil.rmtree(build_directory)