Coverage for tsfpga/examples/build_fpga_utils.py: 0%
88 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-08-29 20:51 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-08-29 20:51 +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, Callable
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 tsfpga.build_project_list import BuildProjectList
28 from tsfpga.module_list import ModuleList
29 from tsfpga.vivado.project import VivadoProject
32def arguments(default_temp_dir: Path) -> argparse.Namespace:
33 """
34 Setup of arguments for the example build flow.
36 Arguments:
37 default_temp_dir: Default value for output paths
38 (can be overridden by command line argument).
39 """
40 parser = argparse.ArgumentParser(
41 "Create, synth and build an FPGA project",
42 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
43 )
45 group = parser.add_mutually_exclusive_group()
47 group.add_argument("--list-only", action="store_true", help="list the available projects")
49 group.add_argument(
50 "--generate-registers-only",
51 action="store_true",
52 help="only generate the register artifacts (C/C++ code, HTML, ...) for inspection",
53 )
55 group.add_argument("--create-only", action="store_true", help="only create projects")
57 group.add_argument("--synth-only", action="store_true", help="only synthesize projects")
59 group.add_argument(
60 "--from-impl",
61 action="store_true",
62 help="run impl and onwards on an existing synthesized projects",
63 )
65 group.add_argument("--open", action="store_true", help="open existing projects in the GUI")
67 group.add_argument(
68 "--collect-artifacts-only",
69 action="store_true",
70 help="collect artifacts of previously successful builds",
71 )
73 parser.add_argument(
74 "--use-existing-project",
75 action="store_true",
76 help="build existing projects, or create first if they do not exist",
77 )
79 parser.add_argument(
80 "--netlist-builds",
81 action="store_true",
82 help="use netlist build projects instead of top level build projects",
83 )
85 parser.add_argument(
86 "--projects-path",
87 type=Path,
88 default=default_temp_dir / "projects",
89 help="the FPGA build projects will be placed here",
90 )
92 parser.add_argument(
93 "--ip-cache-path",
94 type=Path,
95 default=default_temp_dir / "vivado_ip_cache",
96 help="location of Vivado IP cache",
97 )
99 parser.add_argument(
100 "--output-path",
101 type=Path,
102 required=False,
103 help="the output products (bit file, ...) will be placed here",
104 )
106 parser.add_argument(
107 "--num-parallel-builds", type=int, default=8, help="Number of parallel builds to launch"
108 )
110 parser.add_argument(
111 "--num-threads-per-build",
112 type=int,
113 default=4,
114 help="number of threads for each build process",
115 )
117 parser.add_argument("--no-color", action="store_true", help="disable color in printouts")
119 parser.add_argument(
120 "project_filters",
121 nargs="*",
122 help="filter for which projects to build. Can use wildcards. Leave empty for all.",
123 )
125 args = parser.parse_args()
127 assert args.use_existing_project or not args.from_impl, (
128 "Must set --use-existing-project when using --from-impl"
129 )
131 return args
134def setup_and_run( # noqa: C901, PLR0911
135 modules: ModuleList,
136 projects: BuildProjectList,
137 args: argparse.Namespace,
138 collect_artifacts_function: Callable[[VivadoProject, Path], bool] | None,
139) -> int:
140 """
141 Example of a function to setup and execute build projects.
142 As instructed by the arguments.
144 Arguments:
145 modules: When running a register generation, registers from these
146 modules will be included.
147 projects: These build projects will be built.
148 args: Command line argument namespace.
149 collect_artifacts_function: Function pointer to a function that collects build artifacts.
150 Will be run after a successful implementation build.
151 The function must return ``True`` if successful and ``False`` otherwise.
152 It will receive the ``project`` and ``output_path`` as arguments.
154 Can be ``None`` if no special artifact collection operation shall be run.
155 Which is typically the case for synthesis-only builds such as netlist builds.
157 Return:
158 0 if everything passed, otherwise non-zero.
159 Can be used for system exit code.
160 """
161 if args.list_only:
162 print(projects)
163 return 0
165 if args.generate_registers_only:
166 # Generate register output from all modules. Note that this is not used by the
167 # build flow or simulation flow, it is only for the user to inspect the artifacts.
168 generate_register_artifacts(
169 modules=modules, output_path=args.projects_path.parent / "registers"
170 )
171 return 0
173 if args.open:
174 projects.open(projects_path=args.projects_path)
175 return 0
177 if args.collect_artifacts_only:
178 # We have to assume that the projects exist if the user sent this argument.
179 # The 'collect_artifacts_function' call below will probably fail if it does not.
180 create_ok = True
182 elif args.use_existing_project:
183 create_ok = projects.create_unless_exists(
184 projects_path=args.projects_path,
185 num_parallel_builds=args.num_parallel_builds,
186 ip_cache_path=args.ip_cache_path,
187 )
189 else:
190 create_ok = projects.create(
191 projects_path=args.projects_path,
192 num_parallel_builds=args.num_parallel_builds,
193 ip_cache_path=args.ip_cache_path,
194 )
196 if not create_ok:
197 return 1
199 if args.create_only:
200 return 0
202 # If doing only synthesis, there are no artifacts to collect.
203 collect_artifacts_function = (
204 None if (args.synth_only or args.netlist_builds) else collect_artifacts_function
205 )
207 if args.collect_artifacts_only:
208 assert collect_artifacts_function is not None, "No artifact collection available"
210 for project in projects.projects:
211 # Assign the arguments in the exact same way as within the call to
212 # 'projects.build()' below.
213 # Ensures that the correct output path is used in all scenarios.
214 assert collect_artifacts_function(
215 project=project,
216 output_path=projects.get_build_project_output_path(
217 project=project, projects_path=args.projects_path, output_path=args.output_path
218 ),
219 )
221 return 0
223 build_ok = projects.build(
224 projects_path=args.projects_path,
225 num_parallel_builds=args.num_parallel_builds,
226 num_threads_per_build=args.num_threads_per_build,
227 output_path=args.output_path,
228 collect_artifacts=collect_artifacts_function,
229 synth_only=args.synth_only,
230 from_impl=args.from_impl,
231 )
233 if build_ok:
234 return 0
236 return 1
239def generate_register_artifacts(modules: ModuleList, output_path: Path) -> None:
240 """
241 Example of a function to generate register artifacts from the given modules.
242 Will generate pretty much all register artifacts available.
243 In your own build flow you might want to only generate a subset of these.
245 Arguments:
246 modules: Registers from these modules will be included.
247 output_path: Register artifacts will be placed here.
248 """
249 print(f"Generating register artifacts in {output_path.resolve()}...")
251 # Empty the output directory so we don't have leftover old artifacts.
252 create_directory(directory=output_path, empty=True)
254 for module in modules:
255 if module.registers is not None:
256 CHeaderGenerator(module.registers, output_path / "c").create()
258 CppInterfaceGenerator(module.registers, output_path / "cpp" / "include").create()
259 CppHeaderGenerator(module.registers, output_path / "cpp" / "include").create()
260 CppImplementationGenerator(module.registers, output_path / "cpp").create()
262 HtmlPageGenerator(module.registers, output_path / "html").create()
264 PythonPickleGenerator(module.registers, output_path / "python").create()
265 PythonAccessorGenerator(module.registers, output_path / "python").create()
268def collect_artifacts(project: VivadoProject, output_path: Path) -> bool:
269 """
270 Example of a function to collect build artifacts.
271 Will create a zip file with the bitstream, hardware definition (.xsa) and register artifacts.
273 Arguments:
274 project: Project object that has been built, and who's artifacts shall now be collected.
275 output_path: Path to the build output. Artifact zip will be placed here as well.
277 Return:
278 True if everything went well.
279 """
280 version = "0.0.0"
281 release_dir = create_directory(output_path / f"{project.name}-{version}", empty=True)
282 print(f"Creating release in {release_dir.resolve()}.zip")
284 generate_register_artifacts(modules=project.modules, output_path=release_dir / "registers")
285 copy2(output_path / f"{project.name}.bit", release_dir)
286 copy2(output_path / f"{project.name}.bin", release_dir)
287 if (output_path / f"{project.name}.xsa").exists():
288 copy2(output_path / f"{project.name}.xsa", release_dir)
290 make_archive(str(release_dir), "zip", release_dir)
292 # Remove folder so that only zip remains
293 delete(release_dir)
295 return True