Coverage for tsfpga / examples / build_fpga_utils.py: 0%
88 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-25 20:52 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-25 20:52 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import argparse
12from pathlib import Path
13from shutil import copy2, make_archive
14from typing import TYPE_CHECKING
16from hdl_registers.generator.c.header import CHeaderGenerator
17from hdl_registers.generator.cpp.header import CppHeaderGenerator
18from hdl_registers.generator.cpp.implementation import CppImplementationGenerator
19from hdl_registers.generator.cpp.interface import CppInterfaceGenerator
20from hdl_registers.generator.html.page import HtmlPageGenerator
21from hdl_registers.generator.python.accessor import PythonAccessorGenerator
22from hdl_registers.generator.python.pickle import PythonPickleGenerator
24from tsfpga.system_utils import create_directory, delete
26if TYPE_CHECKING:
27 from collections.abc import Callable
29 from tsfpga.build_project_list import BuildProjectList
30 from tsfpga.module_list import ModuleList
31 from tsfpga.vivado.project import VivadoProject
34def arguments(default_temp_dir: Path) -> argparse.Namespace:
35 """
36 Setup of arguments for the example build flow.
38 Arguments:
39 default_temp_dir: Default value for output paths
40 (can be overridden by command line argument).
41 """
42 parser = argparse.ArgumentParser(
43 "Create, synth and build an FPGA project",
44 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
45 )
47 group = parser.add_mutually_exclusive_group()
49 group.add_argument("--list-only", action="store_true", help="list the available projects")
51 group.add_argument(
52 "--generate-registers-only",
53 action="store_true",
54 help="only generate the register artifacts (C/C++ code, HTML, ...) for inspection",
55 )
57 group.add_argument("--create-only", action="store_true", help="only create projects")
59 group.add_argument("--synth-only", action="store_true", help="only synthesize projects")
61 group.add_argument(
62 "--from-impl",
63 action="store_true",
64 help="run impl and onwards on an existing synthesized projects",
65 )
67 group.add_argument("--open", action="store_true", help="open existing projects in the GUI")
69 group.add_argument(
70 "--collect-artifacts-only",
71 action="store_true",
72 help="collect artifacts of previously successful builds",
73 )
75 parser.add_argument(
76 "--use-existing-project",
77 action="store_true",
78 help="build existing projects, or create first if they do not exist",
79 )
81 parser.add_argument(
82 "--netlist-builds",
83 action="store_true",
84 help="use netlist build projects instead of top level build projects",
85 )
87 parser.add_argument(
88 "--projects-path",
89 type=Path,
90 default=default_temp_dir / "projects",
91 help="the FPGA build projects will be placed here",
92 )
94 parser.add_argument(
95 "--ip-cache-path",
96 type=Path,
97 default=default_temp_dir / "vivado_ip_cache",
98 help="location of Vivado IP cache",
99 )
101 parser.add_argument(
102 "--output-path",
103 type=Path,
104 required=False,
105 help="the output products (bit file, ...) will be placed here",
106 )
108 parser.add_argument(
109 "--num-parallel-builds", type=int, default=8, help="number of parallel builds to launch"
110 )
112 parser.add_argument(
113 "--num-threads-per-build",
114 type=int,
115 default=4,
116 help="number of threads for each build process",
117 )
119 parser.add_argument("--no-color", action="store_true", help="disable color in printouts")
121 parser.add_argument(
122 "project_filters",
123 nargs="*",
124 help="filter for which projects to build. Can use wildcards. Leave empty for all.",
125 )
127 args = parser.parse_args()
129 assert args.use_existing_project or not args.from_impl, (
130 "Must set --use-existing-project when using --from-impl"
131 )
133 return args
136def setup_and_run( # noqa: C901, PLR0911
137 modules: ModuleList,
138 project_list: BuildProjectList,
139 args: argparse.Namespace,
140 collect_artifacts_function: Callable[[VivadoProject, Path], bool] | None,
141) -> int:
142 """
143 Example of a function to setup and execute build projects.
144 As instructed by the arguments.
146 Arguments:
147 modules: When running a register generation, registers from these
148 modules will be included.
149 project_list: These build projects will be built.
150 args: Command line argument namespace.
151 collect_artifacts_function: Function pointer to a function that collects build artifacts.
152 Will be run after a successful implementation build.
153 The function must return ``True`` if successful and ``False`` otherwise.
154 It will receive the ``project`` and ``output_path`` as arguments.
156 Can be ``None`` if no special artifact collection operation shall be run.
157 Which is typically the case for synthesis-only builds such as netlist builds.
159 Return:
160 0 if everything passed, otherwise non-zero.
161 Can be used for system exit code.
162 """
163 if args.list_only:
164 print(project_list)
165 return 0
167 if args.generate_registers_only:
168 # Generate register output from all modules. Note that this is not used by the
169 # build flow or simulation flow, it is only for the user to inspect the artifacts.
170 generate_register_artifacts(
171 modules=modules, output_path=args.projects_path.parent / "registers"
172 )
173 return 0
175 if args.open:
176 project_list.open(projects_path=args.projects_path)
177 return 0
179 if args.collect_artifacts_only:
180 # We have to assume that the projects exist if the user sent this argument.
181 # The 'collect_artifacts_function' call below will probably fail if it does not.
182 create_ok = True
184 elif args.use_existing_project:
185 create_ok = project_list.create_unless_exists(
186 projects_path=args.projects_path,
187 num_parallel_builds=args.num_parallel_builds,
188 ip_cache_path=args.ip_cache_path,
189 )
191 else:
192 create_ok = project_list.create(
193 projects_path=args.projects_path,
194 num_parallel_builds=args.num_parallel_builds,
195 ip_cache_path=args.ip_cache_path,
196 )
198 if not create_ok:
199 return 1
201 if args.create_only:
202 return 0
204 # If doing only synthesis, there are no artifacts to collect.
205 collect_artifacts_function = (
206 None if (args.synth_only or args.netlist_builds) else collect_artifacts_function
207 )
209 if args.collect_artifacts_only:
210 assert collect_artifacts_function is not None, "No artifact collection available"
212 for project in project_list.projects:
213 # Assign the arguments in the exact same way as within the call to
214 # 'projects.build()' below.
215 # Ensures that the correct output path is used in all scenarios.
216 assert collect_artifacts_function(
217 project=project,
218 output_path=project_list.get_build_project_output_path(
219 project=project, projects_path=args.projects_path, output_path=args.output_path
220 ),
221 )
223 return 0
225 build_ok = project_list.build(
226 projects_path=args.projects_path,
227 num_parallel_builds=args.num_parallel_builds,
228 num_threads_per_build=args.num_threads_per_build,
229 output_path=args.output_path,
230 collect_artifacts=collect_artifacts_function,
231 synth_only=args.synth_only,
232 from_impl=args.from_impl,
233 )
235 if build_ok:
236 return 0
238 return 1
241def generate_register_artifacts(modules: ModuleList, output_path: Path) -> None:
242 """
243 Example of a function to generate register artifacts from the given modules.
244 Will generate pretty much all register artifacts available.
245 In your own build flow you might want to only generate a subset of these.
247 Arguments:
248 modules: Registers from these modules will be included.
249 output_path: Register artifacts will be placed here.
250 """
251 print(f"Generating register artifacts in {output_path.resolve()}...")
253 # Empty the output directory so we don't have leftover old artifacts.
254 create_directory(directory=output_path, empty=True)
256 for module in modules:
257 if module.registers is not None:
258 CHeaderGenerator(module.registers, output_path / "c").create()
260 CppInterfaceGenerator(module.registers, output_path / "cpp" / "include").create()
261 CppHeaderGenerator(module.registers, output_path / "cpp" / "include").create()
262 CppImplementationGenerator(module.registers, output_path / "cpp").create()
264 HtmlPageGenerator(module.registers, output_path / "html").create()
266 PythonPickleGenerator(module.registers, output_path / "python").create()
267 PythonAccessorGenerator(module.registers, output_path / "python").create()
270def collect_artifacts(project: VivadoProject, output_path: Path) -> bool:
271 """
272 Example of a function to collect build artifacts.
273 Will create a zip file with the bitstream, hardware definition (.xsa) and register artifacts.
275 Arguments:
276 project: Project object that has been built, and who's artifacts shall now be collected.
277 output_path: Path to the build output. Artifact zip will be placed here as well.
279 Return:
280 True if everything went well.
281 """
282 version = "0.0.0"
283 release_dir = create_directory(output_path / f"{project.name}-{version}", empty=True)
284 print(f"Creating release in {release_dir.resolve()}.zip")
286 generate_register_artifacts(modules=project.modules, output_path=release_dir / "registers")
287 copy2(output_path / f"{project.name}.bit", release_dir)
288 copy2(output_path / f"{project.name}.bin", release_dir)
289 if (output_path / f"{project.name}.xsa").exists():
290 copy2(output_path / f"{project.name}.xsa", release_dir)
292 make_archive(str(release_dir), "zip", release_dir)
294 # Remove folder so that only zip remains
295 delete(release_dir)
297 return True