Coverage for tsfpga/examples/build.py: 0%
92 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 20:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import argparse
11import sys
12from pathlib import Path
13from shutil import copy2, make_archive
14from typing import TYPE_CHECKING, Callable, Optional
16if TYPE_CHECKING:
17 # First party libraries
18 from tsfpga.module_list import ModuleList
19 from tsfpga.vivado.project import VivadoProject
21# Do PYTHONPATH insert() instead of append() to prefer any local repo checkout over any pip install
22REPO_ROOT = Path(__file__).parent.parent.parent.resolve()
23sys.path.insert(0, str(REPO_ROOT))
25# Import before others since it modifies PYTHONPATH. pylint: disable=unused-import
26import tsfpga.examples.example_pythonpath # noqa: F401
28# Third party libraries
29from hdl_registers.generator.c.header import CHeaderGenerator
30from hdl_registers.generator.cpp.header import CppHeaderGenerator
31from hdl_registers.generator.cpp.implementation import CppImplementationGenerator
32from hdl_registers.generator.cpp.interface import CppInterfaceGenerator
33from hdl_registers.generator.html.page import HtmlPageGenerator
34from hdl_registers.generator.python.python_class_generator import PythonClassGenerator
36# First party libraries
37from tsfpga.build_project_list import BuildProjectList
38from tsfpga.examples.example_env import TSFPGA_EXAMPLES_TEMP_DIR, get_tsfpga_example_modules
39from tsfpga.system_utils import create_directory, delete
42def arguments(default_temp_dir: Path = TSFPGA_EXAMPLES_TEMP_DIR) -> argparse.Namespace:
43 """
44 Setup of arguments for the example build flow.
46 Arguments:
47 default_temp_dir (pathlib.Path): Default value for output paths.
48 """
49 parser = argparse.ArgumentParser(
50 "Create, synth and build an FPGA project",
51 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
52 )
54 group = parser.add_mutually_exclusive_group()
56 group.add_argument("--list-only", action="store_true", help="list the available projects")
58 group.add_argument(
59 "--generate-registers-only",
60 action="store_true",
61 help="only generate the register artifacts (C/C++ code, HTML, ...) for inspection",
62 )
64 group.add_argument("--create-only", action="store_true", help="only create projects")
66 group.add_argument("--synth-only", action="store_true", help="only synthesize projects")
68 group.add_argument(
69 "--from-impl",
70 action="store_true",
71 help="run impl and onwards on an existing synthesized projects",
72 )
74 group.add_argument("--open", action="store_true", help="open existing projects in the GUI")
76 parser.add_argument(
77 "--use-existing-project",
78 action="store_true",
79 help="build existing projects, or create first if they do not exist",
80 )
82 parser.add_argument(
83 "--netlist-builds",
84 action="store_true",
85 help="use netlist build projects instead of top level build projects",
86 )
88 parser.add_argument(
89 "--projects-path",
90 type=Path,
91 default=default_temp_dir / "projects",
92 help="the FPGA build projects will be placed here",
93 )
95 parser.add_argument(
96 "--ip-cache-path",
97 type=Path,
98 default=default_temp_dir / "vivado_ip_cache",
99 help="location of Vivado IP cache",
100 )
102 parser.add_argument(
103 "--output-path",
104 type=Path,
105 required=False,
106 help="the output products (bit file, ...) will be placed here",
107 )
109 parser.add_argument(
110 "--num-parallel-builds", type=int, default=8, help="Number of parallel builds to launch"
111 )
113 parser.add_argument(
114 "--num-threads-per-build",
115 type=int,
116 default=4,
117 help="number of threads for each build process",
118 )
120 parser.add_argument("--no-color", action="store_true", help="disable color in printouts")
122 parser.add_argument(
123 "project_filters",
124 nargs="*",
125 help="filter for which projects to build. Can use wildcards. Leave empty for all.",
126 )
128 args = parser.parse_args()
130 assert (
131 args.use_existing_project or not args.from_impl
132 ), "Must set --use-existing-project when using --from-impl"
134 return args
137def main() -> None:
138 """
139 Main function for building FPGA projects. If you are setting up a new build flow from scratch,
140 you probably want to copy and modify this function, and reuse the others.
141 """
142 args = arguments()
143 modules = get_tsfpga_example_modules()
144 projects = BuildProjectList(
145 modules=modules,
146 project_filters=args.project_filters,
147 include_netlist_not_top_builds=args.netlist_builds,
148 no_color=args.no_color,
149 )
151 sys.exit(setup_and_run(modules, projects, args))
154def collect_artifacts(project: "VivadoProject", output_path: Path) -> bool:
155 """
156 Example of a method to collect build artifacts. Will create a zip file with the bitstream,
157 hardware definition (.xsa) and register documentation.
159 Arguments:
160 project: Project object that has been built, and who's artifacts shall now be collected.
161 output_path: Path to the build output. Artifact zip will be placed here as well.
163 Return:
164 True if everything went well.
165 """
166 version = "0.0.0"
167 release_dir = create_directory(output_path / f"{project.name}-{version}", empty=True)
168 print(f"Creating release in {release_dir.resolve()}.zip")
170 generate_registers(project.modules, release_dir / "registers")
171 copy2(output_path / f"{project.name }.bit", release_dir)
172 copy2(output_path / f"{project.name}.bin", release_dir)
173 if (output_path / f"{project.name}.xsa").exists():
174 copy2(output_path / f"{project.name}.xsa", release_dir)
176 make_archive(str(release_dir), "zip", release_dir)
178 # Remove folder so that only zip remains
179 delete(release_dir)
181 return True
184def setup_and_run(
185 modules: "ModuleList",
186 projects: BuildProjectList,
187 args: argparse.Namespace,
188 collect_artifacts_function: Optional[
189 Callable[["VivadoProject", Path], bool]
190 ] = collect_artifacts,
191) -> int:
192 """
193 Setup build projects, and execute as instructed by the arguments.
195 Arguments:
196 modules: When running a register generation, registers from these
197 modules will be included.
198 projects: These build projects will be built.
199 args: Command line argument namespace.
200 collect_artifacts_function: Function pointer to a function that collects build artifacts.
201 Will be run after a successful implementation build.
202 The function must return ``True`` if successful and ``False`` otherwise.
203 It will receive the ``project`` and ``output_path`` as arguments.
205 Return:
206 0 if everything passed, otherwise non-zero. Can be used for system exit code.
207 """
208 if args.list_only:
209 print(projects)
210 return 0
212 if args.generate_registers_only:
213 # Generate register output from all modules. Note that this is not used by the
214 # build flow or simulation flow, it is only for the user to inspect the artifacts.
215 generate_registers(modules=modules, output_path=args.projects_path.parent / "registers")
216 return 0
218 if args.open:
219 projects.open(projects_path=args.projects_path)
220 return 0
222 if args.use_existing_project:
223 create_ok = projects.create_unless_exists(
224 projects_path=args.projects_path,
225 num_parallel_builds=args.num_parallel_builds,
226 ip_cache_path=args.ip_cache_path,
227 )
228 else:
229 create_ok = projects.create(
230 projects_path=args.projects_path,
231 num_parallel_builds=args.num_parallel_builds,
232 ip_cache_path=args.ip_cache_path,
233 )
235 if not create_ok:
236 return 1
238 if args.create_only:
239 return 0
241 # If doing only synthesis there are no artifacts to collect
242 collect_artifacts_function = (
243 None if (args.synth_only or args.netlist_builds) else collect_artifacts_function
244 )
246 build_ok = projects.build(
247 projects_path=args.projects_path,
248 collect_artifacts=collect_artifacts_function,
249 num_parallel_builds=args.num_parallel_builds,
250 output_path=args.output_path,
251 synth_only=args.synth_only,
252 from_impl=args.from_impl,
253 num_threads_per_build=args.num_threads_per_build,
254 )
256 if build_ok:
257 return 0
259 return 1
262def generate_registers(modules: "ModuleList", output_path: Path) -> None:
263 """
264 Generate register artifacts from the given modules.
266 Arguments:
267 modules: Registers from these modules will be included.
268 output_path: Register artifacts will be placed here.
269 """
270 print(f"Generating registers in {output_path.resolve()}")
272 for module in modules:
273 if module.registers is not None:
274 CHeaderGenerator(module.registers, output_path / "c").create()
276 CppInterfaceGenerator(module.registers, output_path / "cpp" / "include").create()
277 CppHeaderGenerator(module.registers, output_path / "cpp" / "include").create()
278 CppImplementationGenerator(module.registers, output_path / "cpp").create()
280 HtmlPageGenerator(module.registers, output_path / "html").create()
282 PythonClassGenerator(module.registers, output_path / "python").create()
285if __name__ == "__main__":
286 main()