Coverage for tsfpga/examples/build_fpga_utils.py: 0%
80 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 20:51 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 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 typing import TYPE_CHECKING, Callable
15from hdl_registers.generator.c.header import CHeaderGenerator
16from hdl_registers.generator.cpp.header import CppHeaderGenerator
17from hdl_registers.generator.cpp.implementation import CppImplementationGenerator
18from hdl_registers.generator.cpp.interface import CppInterfaceGenerator
19from hdl_registers.generator.html.page import HtmlPageGenerator
20from hdl_registers.generator.python.accessor import PythonAccessorGenerator
21from hdl_registers.generator.python.pickle import PythonPickleGenerator
23from tsfpga.system_utils import create_directory
25if TYPE_CHECKING:
26 from tsfpga.build_project_list import BuildProjectList
27 from tsfpga.module_list import ModuleList
28 from tsfpga.vivado.project import VivadoProject
31def arguments(default_temp_dir: Path) -> argparse.Namespace:
32 """
33 Setup of arguments for the example build flow.
35 Arguments:
36 default_temp_dir: Default value for output paths.
37 """
38 parser = argparse.ArgumentParser(
39 "Create, synth and build an FPGA project",
40 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
41 )
43 group = parser.add_mutually_exclusive_group()
45 group.add_argument("--list-only", action="store_true", help="list the available projects")
47 group.add_argument(
48 "--generate-registers-only",
49 action="store_true",
50 help="only generate the register artifacts (C/C++ code, HTML, ...) for inspection",
51 )
53 group.add_argument("--create-only", action="store_true", help="only create projects")
55 group.add_argument("--synth-only", action="store_true", help="only synthesize projects")
57 group.add_argument(
58 "--from-impl",
59 action="store_true",
60 help="run impl and onwards on an existing synthesized projects",
61 )
63 group.add_argument("--open", action="store_true", help="open existing projects in the GUI")
65 group.add_argument(
66 "--collect-artifacts-only",
67 action="store_true",
68 help="collect artifacts of previously successful builds",
69 )
71 parser.add_argument(
72 "--use-existing-project",
73 action="store_true",
74 help="build existing projects, or create first if they do not exist",
75 )
77 parser.add_argument(
78 "--netlist-builds",
79 action="store_true",
80 help="use netlist build projects instead of top level build projects",
81 )
83 parser.add_argument(
84 "--projects-path",
85 type=Path,
86 default=default_temp_dir / "projects",
87 help="the FPGA build projects will be placed here",
88 )
90 parser.add_argument(
91 "--ip-cache-path",
92 type=Path,
93 default=default_temp_dir / "vivado_ip_cache",
94 help="location of Vivado IP cache",
95 )
97 parser.add_argument(
98 "--output-path",
99 type=Path,
100 required=False,
101 help="the output products (bit file, ...) will be placed here",
102 )
104 parser.add_argument(
105 "--num-parallel-builds", type=int, default=8, help="Number of parallel builds to launch"
106 )
108 parser.add_argument(
109 "--num-threads-per-build",
110 type=int,
111 default=4,
112 help="number of threads for each build process",
113 )
115 parser.add_argument("--no-color", action="store_true", help="disable color in printouts")
117 parser.add_argument(
118 "project_filters",
119 nargs="*",
120 help="filter for which projects to build. Can use wildcards. Leave empty for all.",
121 )
123 args = parser.parse_args()
125 assert args.use_existing_project or not args.from_impl, (
126 "Must set --use-existing-project when using --from-impl"
127 )
129 return args
132def setup_and_run( # noqa: C901, PLR0911
133 modules: ModuleList,
134 projects: BuildProjectList,
135 args: argparse.Namespace,
136 collect_artifacts_function: Callable[[VivadoProject, Path], bool] | None,
137) -> int:
138 """
139 Setup and execute build projects.
140 As instructed by the arguments.
142 Arguments:
143 modules: When running a register generation, registers from these
144 modules will be included.
145 projects: These build projects will be built.
146 args: Command line argument namespace.
147 collect_artifacts_function: Function pointer to a function that collects build artifacts.
148 Will be run after a successful implementation build.
149 The function must return ``True`` if successful and ``False`` otherwise.
150 It will receive the ``project`` and ``output_path`` as arguments.
151 Can be ``None`` if no special artifact collection operation shall be run.
153 Return:
154 0 if everything passed, otherwise non-zero.
155 Can be used for system exit code.
156 """
157 if args.list_only:
158 print(projects)
159 return 0
161 if args.generate_registers_only:
162 # Generate register output from all modules. Note that this is not used by the
163 # build flow or simulation flow, it is only for the user to inspect the artifacts.
164 generate_register_artifacts(
165 modules=modules, output_path=args.projects_path.parent / "registers"
166 )
167 return 0
169 if args.open:
170 projects.open(projects_path=args.projects_path)
171 return 0
173 if args.collect_artifacts_only:
174 # We have to assume that the project exists if the user sent this argument.
175 # The 'collect_artifacts_function' call below will probably fail if it does not.
176 create_ok = True
178 elif args.use_existing_project:
179 create_ok = projects.create_unless_exists(
180 projects_path=args.projects_path,
181 num_parallel_builds=args.num_parallel_builds,
182 ip_cache_path=args.ip_cache_path,
183 )
185 else:
186 create_ok = projects.create(
187 projects_path=args.projects_path,
188 num_parallel_builds=args.num_parallel_builds,
189 ip_cache_path=args.ip_cache_path,
190 )
192 if not create_ok:
193 return 1
195 if args.create_only:
196 return 0
198 # If doing only synthesis, there are no artifacts to collect.
199 collect_artifacts_function = (
200 None if (args.synth_only or args.netlist_builds) else collect_artifacts_function
201 )
203 if args.collect_artifacts_only:
204 assert collect_artifacts_function is not None, "No artifact collection available"
206 for project in projects.projects:
207 # Assign the arguments in the exact same way as the call to 'projects.build()' below.
208 # Ensures that the correct output path is used in all scenarios.
209 project_build_output_path = projects.get_build_project_output_path(
210 project=project, projects_path=args.projects_path, output_path=args.output_path
211 )
212 # Collect artifacts function must return True.
213 assert collect_artifacts_function(project, project_build_output_path)
215 return 0
217 build_ok = projects.build(
218 projects_path=args.projects_path,
219 num_parallel_builds=args.num_parallel_builds,
220 num_threads_per_build=args.num_threads_per_build,
221 output_path=args.output_path,
222 collect_artifacts=collect_artifacts_function,
223 synth_only=args.synth_only,
224 from_impl=args.from_impl,
225 )
227 if build_ok:
228 return 0
230 return 1
233def generate_register_artifacts(modules: ModuleList, output_path: Path) -> None:
234 """
235 Generate register artifacts from the given modules.
237 Arguments:
238 modules: Registers from these modules will be included.
239 output_path: Register artifacts will be placed here.
240 """
241 print(f"Generating register artifacts in {output_path.resolve()}...")
243 # Empty the output directory so we don't have leftover old artifacts.
244 create_directory(directory=output_path, empty=True)
246 for module in modules:
247 if module.registers is not None:
248 CHeaderGenerator(module.registers, output_path / "c").create()
250 CppInterfaceGenerator(module.registers, output_path / "cpp" / "include").create()
251 CppHeaderGenerator(module.registers, output_path / "cpp" / "include").create()
252 CppImplementationGenerator(module.registers, output_path / "cpp").create()
254 HtmlPageGenerator(module.registers, output_path / "html").create()
256 PythonPickleGenerator(module.registers, output_path / "python").create()
257 PythonAccessorGenerator(module.registers, output_path / "python").create()