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

87 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-05-31 20:00 +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://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9# Standard libraries 

10import argparse 

11import sys 

12from pathlib import Path 

13from shutil import copy2, make_archive 

14 

15# Do PYTHONPATH insert() instead of append() to prefer any local repo checkout over any pip install 

16REPO_ROOT = Path(__file__).parent.parent.parent.resolve() 

17sys.path.insert(0, str(REPO_ROOT)) 

18 

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

20import tsfpga.examples.example_pythonpath # noqa: F401 

21 

22# First party libraries 

23from tsfpga.build_project_list import BuildProjectList 

24from tsfpga.examples.example_env import TSFPGA_EXAMPLES_TEMP_DIR, get_tsfpga_example_modules 

25from tsfpga.system_utils import create_directory, delete 

26 

27 

28def arguments(default_temp_dir=TSFPGA_EXAMPLES_TEMP_DIR): 

29 """ 

30 Setup of arguments for the example build flow. 

31 

32 Arguments: 

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

34 """ 

35 parser = argparse.ArgumentParser( 

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

37 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 

38 ) 

39 

40 group = parser.add_mutually_exclusive_group() 

41 

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

43 

44 group.add_argument( 

45 "--generate-registers-only", 

46 action="store_true", 

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

48 ) 

49 

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

51 

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

53 

54 group.add_argument( 

55 "--from-impl", 

56 action="store_true", 

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

58 ) 

59 

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

61 

62 parser.add_argument( 

63 "--use-existing-project", 

64 action="store_true", 

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

66 ) 

67 

68 parser.add_argument( 

69 "--netlist-builds", 

70 action="store_true", 

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

72 ) 

73 

74 parser.add_argument( 

75 "--projects-path", 

76 type=Path, 

77 default=default_temp_dir / "projects", 

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

79 ) 

80 

81 parser.add_argument( 

82 "--ip-cache-path", 

83 type=Path, 

84 default=default_temp_dir / "vivado_ip_cache", 

85 help="location of Vivado IP cache", 

86 ) 

87 

88 parser.add_argument( 

89 "--output-path", 

90 type=Path, 

91 required=False, 

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

93 ) 

94 

95 parser.add_argument( 

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

97 ) 

98 

99 parser.add_argument( 

100 "--num-threads-per-build", 

101 type=int, 

102 default=4, 

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

104 ) 

105 

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

107 

108 parser.add_argument( 

109 "project_filters", 

110 nargs="*", 

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

112 ) 

113 

114 args = parser.parse_args() 

115 

116 assert ( 

117 args.use_existing_project or not args.from_impl 

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

119 

120 return args 

121 

122 

123def main(): 

124 """ 

125 Main function for building FPGA projects. If you are setting up a new build flow from scratch, 

126 you probably want to copy and modify this function, and reuse the others. 

127 """ 

128 args = arguments() 

129 modules = get_tsfpga_example_modules() 

130 projects = BuildProjectList( 

131 modules=modules, 

132 project_filters=args.project_filters, 

133 include_netlist_not_top_builds=args.netlist_builds, 

134 no_color=args.no_color, 

135 ) 

136 

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

138 

139 

140def collect_artifacts(project, output_path): 

141 """ 

142 Example of a method to collect build artifacts. Will create a zip file with the bitstream, 

143 hardware definition (.xsa) and register documentation. 

144 

145 Arguments: 

146 project (:class:`.VivadoProject`): Project object that has been built, 

147 and who's artifacts shall now be collected. 

148 output_path (pathlib.Path): Path to the build output. Artifact zip will be placed here 

149 as well. 

150 """ 

151 version = "0.0.0" 

152 release_dir = create_directory(output_path / f"{project.name}-{version}", empty=True) 

153 print(f"Creating release in {release_dir.resolve()}.zip") 

154 

155 generate_registers(project.modules, release_dir / "registers") 

156 copy2(output_path / f"{project.name }.bit", release_dir) 

157 copy2(output_path / f"{project.name}.bin", release_dir) 

158 if (output_path / f"{project.name}.xsa").exists(): 

159 copy2(output_path / f"{project.name}.xsa", release_dir) 

160 

161 make_archive(release_dir, "zip", release_dir) 

162 

163 # Remove folder so that only zip remains 

164 delete(release_dir) 

165 

166 return True 

167 

168 

169def setup_and_run(modules, projects, args, collect_artifacts_function=collect_artifacts): 

170 """ 

171 Setup build projects, and execute as instructed by the arguments. 

172 

173 Arguments: 

174 modules (:class:`.ModuleList`): When running a register generation, registers from these 

175 modules will be included. 

176 projects (:class:`.BuildProjectList`): These build projects will be built. 

177 args: Command line argument namespace. 

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

179 Will be run after a successful implementation build. 

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

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

182 

183 Return: 

184 int: 0 if everything passed, otherwise non-zero. Can be used for system exit code. 

185 """ 

186 if args.list_only: 

187 print(projects) 

188 return 0 

189 

190 if args.generate_registers_only: 

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

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

193 generate_registers(modules=modules, output_path=args.projects_path.parent / "registers") 

194 return 0 

195 

196 if args.open: 

197 projects.open(projects_path=args.projects_path) 

198 return 0 

199 

200 if args.use_existing_project: 

201 create_ok = projects.create_unless_exists( 

202 projects_path=args.projects_path, 

203 num_parallel_builds=args.num_parallel_builds, 

204 ip_cache_path=args.ip_cache_path, 

205 ) 

206 else: 

207 create_ok = projects.create( 

208 projects_path=args.projects_path, 

209 num_parallel_builds=args.num_parallel_builds, 

210 ip_cache_path=args.ip_cache_path, 

211 ) 

212 

213 if not create_ok: 

214 return 1 

215 

216 if args.create_only: 

217 return 0 

218 

219 # If doing only synthesis there are no artifacts to collect 

220 collect_artifacts_function = ( 

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

222 ) 

223 

224 build_ok = projects.build( 

225 projects_path=args.projects_path, 

226 collect_artifacts=collect_artifacts_function, 

227 num_parallel_builds=args.num_parallel_builds, 

228 output_path=args.output_path, 

229 synth_only=args.synth_only, 

230 from_impl=args.from_impl, 

231 num_threads_per_build=args.num_threads_per_build, 

232 ) 

233 

234 if build_ok: 

235 return 0 

236 return 1 

237 

238 

239def generate_registers(modules, output_path): 

240 """ 

241 Generate all register artifacts from the given modules. 

242 

243 Arguments: 

244 modules (:class:`.ModuleList`): Registers from these modules will be included. 

245 output_path (pathlib.Path): Register artifacts will be placed here. 

246 """ 

247 print(f"Generating registers in {output_path.resolve()}") 

248 

249 for module in modules: 

250 if module.registers is not None: 

251 vhdl_path = create_directory(output_path / "vhdl", empty=False) 

252 module.registers.create_vhdl_package(vhdl_path) 

253 

254 module.registers.copy_source_definition(output_path / "toml") 

255 

256 module.registers.create_c_header(output_path / "c") 

257 module.registers.create_cpp_interface(output_path / "cpp" / "include") 

258 module.registers.create_cpp_header(output_path / "cpp" / "include") 

259 module.registers.create_cpp_implementation(output_path / "cpp") 

260 module.registers.create_html_page(output_path / "html") 

261 module.registers.create_html_register_table(output_path / "html" / "tables") 

262 module.registers.create_html_constant_table(output_path / "html" / "tables") 

263 module.registers.create_python_class(output_path / "python") 

264 

265 

266if __name__ == "__main__": 

267 main()