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

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# -------------------------------------------------------------------------------------------------- 

8 

9from __future__ import annotations 

10 

11import argparse 

12from pathlib import Path 

13from shutil import copy2, make_archive 

14from typing import TYPE_CHECKING 

15 

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 

23 

24from tsfpga.system_utils import create_directory, delete 

25 

26if TYPE_CHECKING: 

27 from collections.abc import Callable 

28 

29 from tsfpga.build_project_list import BuildProjectList 

30 from tsfpga.module_list import ModuleList 

31 from tsfpga.vivado.project import VivadoProject 

32 

33 

34def arguments(default_temp_dir: Path) -> argparse.Namespace: 

35 """ 

36 Setup of arguments for the example build flow. 

37 

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 ) 

46 

47 group = parser.add_mutually_exclusive_group() 

48 

49 group.add_argument("--list-only", action="store_true", help="list the available projects") 

50 

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 ) 

56 

57 group.add_argument("--create-only", action="store_true", help="only create projects") 

58 

59 group.add_argument("--synth-only", action="store_true", help="only synthesize projects") 

60 

61 group.add_argument( 

62 "--from-impl", 

63 action="store_true", 

64 help="run impl and onwards on an existing synthesized projects", 

65 ) 

66 

67 group.add_argument("--open", action="store_true", help="open existing projects in the GUI") 

68 

69 group.add_argument( 

70 "--collect-artifacts-only", 

71 action="store_true", 

72 help="collect artifacts of previously successful builds", 

73 ) 

74 

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 ) 

80 

81 parser.add_argument( 

82 "--netlist-builds", 

83 action="store_true", 

84 help="use netlist build projects instead of top level build projects", 

85 ) 

86 

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 ) 

93 

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 ) 

100 

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 ) 

107 

108 parser.add_argument( 

109 "--num-parallel-builds", type=int, default=8, help="number of parallel builds to launch" 

110 ) 

111 

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 ) 

118 

119 parser.add_argument("--no-color", action="store_true", help="disable color in printouts") 

120 

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 ) 

126 

127 args = parser.parse_args() 

128 

129 assert args.use_existing_project or not args.from_impl, ( 

130 "Must set --use-existing-project when using --from-impl" 

131 ) 

132 

133 return args 

134 

135 

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. 

145 

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. 

155 

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. 

158 

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 

166 

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 

174 

175 if args.open: 

176 project_list.open(projects_path=args.projects_path) 

177 return 0 

178 

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 

183 

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 ) 

190 

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 ) 

197 

198 if not create_ok: 

199 return 1 

200 

201 if args.create_only: 

202 return 0 

203 

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 ) 

208 

209 if args.collect_artifacts_only: 

210 assert collect_artifacts_function is not None, "No artifact collection available" 

211 

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 ) 

222 

223 return 0 

224 

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 ) 

234 

235 if build_ok: 

236 return 0 

237 

238 return 1 

239 

240 

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. 

246 

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()}...") 

252 

253 # Empty the output directory so we don't have leftover old artifacts. 

254 create_directory(directory=output_path, empty=True) 

255 

256 for module in modules: 

257 if module.registers is not None: 

258 CHeaderGenerator(module.registers, output_path / "c").create() 

259 

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() 

263 

264 HtmlPageGenerator(module.registers, output_path / "html").create() 

265 

266 PythonPickleGenerator(module.registers, output_path / "python").create() 

267 PythonAccessorGenerator(module.registers, output_path / "python").create() 

268 

269 

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. 

274 

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. 

278 

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") 

285 

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) 

291 

292 make_archive(str(release_dir), "zip", release_dir) 

293 

294 # Remove folder so that only zip remains 

295 delete(release_dir) 

296 

297 return True