Coverage for tsfpga/examples/build_fpga_utils.py: 0%

77 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-07 11:31 +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 

9# Standard libraries 

10import argparse 

11from pathlib import Path 

12from typing import TYPE_CHECKING, Callable, Optional 

13 

14# Third party libraries 

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.pickle import PythonPickleGenerator 

21 

22# First party libraries 

23from tsfpga.build_project_list import BuildProjectList 

24from tsfpga.system_utils import create_directory 

25 

26if TYPE_CHECKING: 

27 # First party libraries 

28 from tsfpga.module_list import ModuleList 

29 from tsfpga.vivado.project import VivadoProject 

30 

31 

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

33 """ 

34 Setup of arguments for the example build flow. 

35 

36 Arguments: 

37 default_temp_dir (pathlib.Path): Default value for output paths. 

38 """ 

39 parser = argparse.ArgumentParser( 

40 "Create, synth and build an FPGA project", 

41 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 

42 ) 

43 

44 group = parser.add_mutually_exclusive_group() 

45 

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

47 

48 group.add_argument( 

49 "--generate-registers-only", 

50 action="store_true", 

51 help="only generate the register artifacts (C/C++ code, HTML, ...) for inspection", 

52 ) 

53 

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

55 

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

57 

58 group.add_argument( 

59 "--from-impl", 

60 action="store_true", 

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

62 ) 

63 

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

65 

66 group.add_argument( 

67 "--collect-artifacts-only", 

68 action="store_true", 

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

70 ) 

71 

72 parser.add_argument( 

73 "--use-existing-project", 

74 action="store_true", 

75 help="build existing projects, or create first if they do not exist", 

76 ) 

77 

78 parser.add_argument( 

79 "--netlist-builds", 

80 action="store_true", 

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

82 ) 

83 

84 parser.add_argument( 

85 "--projects-path", 

86 type=Path, 

87 default=default_temp_dir / "projects", 

88 help="the FPGA build projects will be placed here", 

89 ) 

90 

91 parser.add_argument( 

92 "--ip-cache-path", 

93 type=Path, 

94 default=default_temp_dir / "vivado_ip_cache", 

95 help="location of Vivado IP cache", 

96 ) 

97 

98 parser.add_argument( 

99 "--output-path", 

100 type=Path, 

101 required=False, 

102 help="the output products (bit file, ...) will be placed here", 

103 ) 

104 

105 parser.add_argument( 

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

107 ) 

108 

109 parser.add_argument( 

110 "--num-threads-per-build", 

111 type=int, 

112 default=4, 

113 help="number of threads for each build process", 

114 ) 

115 

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

117 

118 parser.add_argument( 

119 "project_filters", 

120 nargs="*", 

121 help="filter for which projects to build. Can use wildcards. Leave empty for all.", 

122 ) 

123 

124 args = parser.parse_args() 

125 

126 assert ( 

127 args.use_existing_project or not args.from_impl 

128 ), "Must set --use-existing-project when using --from-impl" 

129 

130 return args 

131 

132 

133def setup_and_run( # pylint: disable=too-many-return-statements 

134 modules: "ModuleList", 

135 projects: BuildProjectList, 

136 args: argparse.Namespace, 

137 collect_artifacts_function: Optional[Callable[["VivadoProject", Path], bool]], 

138) -> int: 

139 """ 

140 Setup and execute build projects. 

141 As instructed by the arguments. 

142 

143 Arguments: 

144 modules: When running a register generation, registers from these 

145 modules will be included. 

146 projects: These build projects will be built. 

147 args: Command line argument namespace. 

148 collect_artifacts_function: Function pointer to a function that collects build artifacts. 

149 Will be run after a successful implementation build. 

150 The function must return ``True`` if successful and ``False`` otherwise. 

151 It will receive the ``project`` and ``output_path`` as arguments. 

152 Can be ``None`` if no special artifact collection operation shall be run. 

153 

154 Return: 

155 0 if everything passed, otherwise non-zero. 

156 Can be used for system exit code. 

157 """ 

158 if args.list_only: 

159 print(projects) 

160 return 0 

161 

162 if args.generate_registers_only: 

163 # Generate register output from all modules. Note that this is not used by the 

164 # build flow or simulation flow, it is only for the user to inspect the artifacts. 

165 generate_register_artifacts( 

166 modules=modules, output_path=args.projects_path.parent / "registers" 

167 ) 

168 return 0 

169 

170 if args.open: 

171 projects.open(projects_path=args.projects_path) 

172 return 0 

173 

174 if args.collect_artifacts_only: 

175 # We have to assume that the project exists if the user sent this argument. 

176 # The 'collect_artifacts_function' call below will probably fail if it does not. 

177 create_ok = True 

178 

179 elif args.use_existing_project: 

180 create_ok = projects.create_unless_exists( 

181 projects_path=args.projects_path, 

182 num_parallel_builds=args.num_parallel_builds, 

183 ip_cache_path=args.ip_cache_path, 

184 ) 

185 

186 else: 

187 create_ok = projects.create( 

188 projects_path=args.projects_path, 

189 num_parallel_builds=args.num_parallel_builds, 

190 ip_cache_path=args.ip_cache_path, 

191 ) 

192 

193 if not create_ok: 

194 return 1 

195 

196 if args.create_only: 

197 return 0 

198 

199 # If doing only synthesis, there are no artifacts to collect. 

200 collect_artifacts_function = ( 

201 None if (args.synth_only or args.netlist_builds) else collect_artifacts_function 

202 ) 

203 

204 if args.collect_artifacts_only: 

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

206 

207 for project in projects.projects: 

208 # Assign the arguments in the exact same way as the call to 'projects.build()' below. 

209 # Ensures that the correct output path is used in all scenarios. 

210 project_build_output_path = projects.get_build_project_output_path( 

211 project=project, projects_path=args.projects_path, output_path=args.output_path 

212 ) 

213 # Collect artifacts function must return True. 

214 assert collect_artifacts_function(project, project_build_output_path) 

215 

216 return 0 

217 

218 build_ok = projects.build( 

219 projects_path=args.projects_path, 

220 num_parallel_builds=args.num_parallel_builds, 

221 num_threads_per_build=args.num_threads_per_build, 

222 output_path=args.output_path, 

223 collect_artifacts=collect_artifacts_function, 

224 synth_only=args.synth_only, 

225 from_impl=args.from_impl, 

226 ) 

227 

228 if build_ok: 

229 return 0 

230 

231 return 1 

232 

233 

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

235 """ 

236 Generate register artifacts from the given modules. 

237 

238 Arguments: 

239 modules: Registers from these modules will be included. 

240 output_path: Register artifacts will be placed here. 

241 """ 

242 print(f"Generating register artifacts in {output_path.resolve()}...") 

243 

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

245 create_directory(directory=output_path, empty=True) 

246 

247 for module in modules: 

248 if module.registers is not None: 

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

250 

251 CppInterfaceGenerator(module.registers, output_path / "cpp" / "include").create() 

252 CppHeaderGenerator(module.registers, output_path / "cpp" / "include").create() 

253 CppImplementationGenerator(module.registers, output_path / "cpp").create() 

254 

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

256 

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