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

79 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-07 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 

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.accessor import PythonAccessorGenerator 

21from hdl_registers.generator.python.pickle import PythonPickleGenerator 

22 

23# First party libraries 

24from tsfpga.build_project_list import BuildProjectList 

25from tsfpga.system_utils import create_directory 

26 

27if TYPE_CHECKING: 

28 # First party libraries 

29 from tsfpga.module_list import ModuleList 

30 from tsfpga.vivado.project import VivadoProject 

31 

32 

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

34 """ 

35 Setup of arguments for the example build flow. 

36 

37 Arguments: 

38 default_temp_dir: Default value for output paths. 

39 """ 

40 parser = argparse.ArgumentParser( 

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

42 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 

43 ) 

44 

45 group = parser.add_mutually_exclusive_group() 

46 

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

48 

49 group.add_argument( 

50 "--generate-registers-only", 

51 action="store_true", 

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

53 ) 

54 

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

56 

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

58 

59 group.add_argument( 

60 "--from-impl", 

61 action="store_true", 

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

63 ) 

64 

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

66 

67 group.add_argument( 

68 "--collect-artifacts-only", 

69 action="store_true", 

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

71 ) 

72 

73 parser.add_argument( 

74 "--use-existing-project", 

75 action="store_true", 

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

77 ) 

78 

79 parser.add_argument( 

80 "--netlist-builds", 

81 action="store_true", 

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

83 ) 

84 

85 parser.add_argument( 

86 "--projects-path", 

87 type=Path, 

88 default=default_temp_dir / "projects", 

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

90 ) 

91 

92 parser.add_argument( 

93 "--ip-cache-path", 

94 type=Path, 

95 default=default_temp_dir / "vivado_ip_cache", 

96 help="location of Vivado IP cache", 

97 ) 

98 

99 parser.add_argument( 

100 "--output-path", 

101 type=Path, 

102 required=False, 

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

104 ) 

105 

106 parser.add_argument( 

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

108 ) 

109 

110 parser.add_argument( 

111 "--num-threads-per-build", 

112 type=int, 

113 default=4, 

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

115 ) 

116 

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

118 

119 parser.add_argument( 

120 "project_filters", 

121 nargs="*", 

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

123 ) 

124 

125 args = parser.parse_args() 

126 

127 assert ( 

128 args.use_existing_project or not args.from_impl 

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

130 

131 return args 

132 

133 

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

135 modules: "ModuleList", 

136 projects: BuildProjectList, 

137 args: argparse.Namespace, 

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

139) -> int: 

140 """ 

141 Setup and execute build projects. 

142 As instructed by the arguments. 

143 

144 Arguments: 

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

146 modules will be included. 

147 projects: These build projects will be built. 

148 args: Command line argument namespace. 

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

150 Will be run after a successful implementation build. 

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

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

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

154 

155 Return: 

156 0 if everything passed, otherwise non-zero. 

157 Can be used for system exit code. 

158 """ 

159 if args.list_only: 

160 print(projects) 

161 return 0 

162 

163 if args.generate_registers_only: 

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

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

166 generate_register_artifacts( 

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

168 ) 

169 return 0 

170 

171 if args.open: 

172 projects.open(projects_path=args.projects_path) 

173 return 0 

174 

175 if args.collect_artifacts_only: 

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

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

178 create_ok = True 

179 

180 elif args.use_existing_project: 

181 create_ok = projects.create_unless_exists( 

182 projects_path=args.projects_path, 

183 num_parallel_builds=args.num_parallel_builds, 

184 ip_cache_path=args.ip_cache_path, 

185 ) 

186 

187 else: 

188 create_ok = projects.create( 

189 projects_path=args.projects_path, 

190 num_parallel_builds=args.num_parallel_builds, 

191 ip_cache_path=args.ip_cache_path, 

192 ) 

193 

194 if not create_ok: 

195 return 1 

196 

197 if args.create_only: 

198 return 0 

199 

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

201 collect_artifacts_function = ( 

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

203 ) 

204 

205 if args.collect_artifacts_only: 

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

207 

208 for project in projects.projects: 

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

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

211 project_build_output_path = projects.get_build_project_output_path( 

212 project=project, projects_path=args.projects_path, output_path=args.output_path 

213 ) 

214 # Collect artifacts function must return True. 

215 assert collect_artifacts_function(project, project_build_output_path) 

216 

217 return 0 

218 

219 build_ok = projects.build( 

220 projects_path=args.projects_path, 

221 num_parallel_builds=args.num_parallel_builds, 

222 num_threads_per_build=args.num_threads_per_build, 

223 output_path=args.output_path, 

224 collect_artifacts=collect_artifacts_function, 

225 synth_only=args.synth_only, 

226 from_impl=args.from_impl, 

227 ) 

228 

229 if build_ok: 

230 return 0 

231 

232 return 1 

233 

234 

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

236 """ 

237 Generate register artifacts from the given modules. 

238 

239 Arguments: 

240 modules: Registers from these modules will be included. 

241 output_path: Register artifacts will be placed here. 

242 """ 

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

244 

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

246 create_directory(directory=output_path, empty=True) 

247 

248 for module in modules: 

249 if module.registers is not None: 

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

251 

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

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

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

255 

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

257 

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

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