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

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 typing import TYPE_CHECKING, Callable 

14 

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 

22 

23from tsfpga.system_utils import create_directory 

24 

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 

29 

30 

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

32 """ 

33 Setup of arguments for the example build flow. 

34 

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 ) 

42 

43 group = parser.add_mutually_exclusive_group() 

44 

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

46 

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 ) 

52 

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

54 

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

56 

57 group.add_argument( 

58 "--from-impl", 

59 action="store_true", 

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

61 ) 

62 

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

64 

65 group.add_argument( 

66 "--collect-artifacts-only", 

67 action="store_true", 

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

69 ) 

70 

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 ) 

76 

77 parser.add_argument( 

78 "--netlist-builds", 

79 action="store_true", 

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

81 ) 

82 

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 ) 

89 

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 ) 

96 

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 ) 

103 

104 parser.add_argument( 

105 "--num-parallel-builds", type=int, default=8, help="Number of parallel builds to launch" 

106 ) 

107 

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 ) 

114 

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

116 

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 ) 

122 

123 args = parser.parse_args() 

124 

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

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

127 ) 

128 

129 return args 

130 

131 

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. 

141 

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. 

152 

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 

160 

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 

168 

169 if args.open: 

170 projects.open(projects_path=args.projects_path) 

171 return 0 

172 

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 

177 

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 ) 

184 

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 ) 

191 

192 if not create_ok: 

193 return 1 

194 

195 if args.create_only: 

196 return 0 

197 

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 ) 

202 

203 if args.collect_artifacts_only: 

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

205 

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) 

214 

215 return 0 

216 

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 ) 

226 

227 if build_ok: 

228 return 0 

229 

230 return 1 

231 

232 

233def generate_register_artifacts(modules: ModuleList, output_path: Path) -> None: 

234 """ 

235 Generate register artifacts from the given modules. 

236 

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

242 

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

244 create_directory(directory=output_path, empty=True) 

245 

246 for module in modules: 

247 if module.registers is not None: 

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

249 

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

253 

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

255 

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

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