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

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 

11import sys 

12from pathlib import Path 

13from shutil import copy2, make_archive 

14from typing import TYPE_CHECKING, Callable, Optional 

15 

16if TYPE_CHECKING: 

17 # First party libraries 

18 from tsfpga.module_list import ModuleList 

19 from tsfpga.vivado.project import VivadoProject 

20 

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

24 

25# Import before others since it modifies PYTHONPATH. pylint: disable=unused-import 

26import tsfpga.examples.example_pythonpath # noqa: F401 

27 

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 

35 

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 

40 

41 

42def arguments(default_temp_dir: Path = TSFPGA_EXAMPLES_TEMP_DIR) -> argparse.Namespace: 

43 """ 

44 Setup of arguments for the example build flow. 

45 

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 ) 

53 

54 group = parser.add_mutually_exclusive_group() 

55 

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

57 

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 ) 

63 

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

65 

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

67 

68 group.add_argument( 

69 "--from-impl", 

70 action="store_true", 

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

72 ) 

73 

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

75 

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 ) 

81 

82 parser.add_argument( 

83 "--netlist-builds", 

84 action="store_true", 

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

86 ) 

87 

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 ) 

94 

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 ) 

101 

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 ) 

108 

109 parser.add_argument( 

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

111 ) 

112 

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 ) 

119 

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

121 

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 ) 

127 

128 args = parser.parse_args() 

129 

130 assert ( 

131 args.use_existing_project or not args.from_impl 

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

133 

134 return args 

135 

136 

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 ) 

150 

151 sys.exit(setup_and_run(modules, projects, args)) 

152 

153 

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. 

158 

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. 

162 

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

169 

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) 

175 

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

177 

178 # Remove folder so that only zip remains 

179 delete(release_dir) 

180 

181 return True 

182 

183 

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. 

194 

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. 

204 

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 

211 

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 

217 

218 if args.open: 

219 projects.open(projects_path=args.projects_path) 

220 return 0 

221 

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 ) 

234 

235 if not create_ok: 

236 return 1 

237 

238 if args.create_only: 

239 return 0 

240 

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 ) 

245 

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 ) 

255 

256 if build_ok: 

257 return 0 

258 

259 return 1 

260 

261 

262def generate_registers(modules: "ModuleList", output_path: Path) -> None: 

263 """ 

264 Generate register artifacts from the given modules. 

265 

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

271 

272 for module in modules: 

273 if module.registers is not None: 

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

275 

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

279 

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

281 

282 PythonClassGenerator(module.registers, output_path / "python").create() 

283 

284 

285if __name__ == "__main__": 

286 main()