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

106 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-20 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, Any, Optional, Type 

13 

14# Third party libraries 

15import hdl_registers 

16from vunit.ui import VUnit 

17from vunit.vivado.vivado import add_from_compile_order_file, create_compile_order_file 

18from vunit.vunit_cli import VUnitCLI 

19 

20# First party libraries 

21import tsfpga 

22import tsfpga.create_vhdl_ls_config 

23from tsfpga.git_simulation_subset import GitSimulationSubset 

24from tsfpga.module_list import ModuleList 

25from tsfpga.vivado.common import get_vivado_path 

26from tsfpga.vivado.ip_cores import VivadoIpCores 

27from tsfpga.vivado.simlib import VivadoSimlib 

28 

29if TYPE_CHECKING: 

30 # First party libraries 

31 from tsfpga.vivado.simlib_common import VivadoSimlibCommon 

32 

33 

34def get_arguments_cli(default_output_path: Path) -> VUnitCLI: 

35 """ 

36 Get arguments for the simulation flow. 

37 

38 Arguments: 

39 default_output_path: Will be set as default for output path arguments 

40 (both VUnit files and Vivado files). 

41 """ 

42 cli = VUnitCLI() 

43 

44 # Print default values when doing --help 

45 cli.parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter 

46 

47 # Set the supplied default value for VUnit output path. pylint: disable=protected-access 

48 for action in cli.parser._actions: 

49 if action.dest == "output_path": 

50 action.default = default_output_path / "vunit_out" 

51 break 

52 else: 

53 raise AssertionError("VUnit --output-path argument not found") 

54 

55 cli.parser.add_argument( 

56 "--output-path-vivado", 

57 type=Path, 

58 default=default_output_path, 

59 help=( 

60 "where to place Vivado IP core and simlib files. " 

61 "Note that --output-path is for VUnit files" 

62 ), 

63 ) 

64 

65 cli.parser.add_argument( 

66 "--vivado-skip", action="store_true", help="skip all steps that require Vivado" 

67 ) 

68 

69 cli.parser.add_argument( 

70 "--ip-compile", action="store_true", help="force (re)compile of IP cores" 

71 ) 

72 

73 cli.parser.add_argument( 

74 "--simlib-compile", action="store_true", help="force (re)compile of Vivado simlib" 

75 ) 

76 

77 cli.parser.add_argument( 

78 "--vcs-minimal", 

79 action="store_true", 

80 help="compile and run only a minimal set of tests based on Version Control System history", 

81 ) 

82 

83 cli.parser.add_argument( 

84 "--inspect", 

85 action="store_true", 

86 help="optionally inspect some simulation result. Is only available for some modules", 

87 ) 

88 

89 return cli 

90 

91 

92class SimulationProject: 

93 """ 

94 Class for setting up and handling a VUnit simulation project. Should be reusable in most cases. 

95 """ 

96 

97 def __init__(self, args: argparse.Namespace, enable_preprocessing: bool = False) -> None: 

98 """ 

99 Create a VUnit project, configured according to the given arguments. 

100 

101 Arguments: 

102 args: Command line argument namespace from ``simulate.py``. 

103 enable_preprocessing: If ``True``, VUnit location/check preprocessing will be enabled. 

104 """ 

105 self.args = args 

106 

107 self.vunit_proj = VUnit.from_args(args=args) 

108 self.vunit_proj.add_vhdl_builtins() 

109 self.vunit_proj.add_verification_components() 

110 self.vunit_proj.add_random() 

111 

112 if enable_preprocessing: 

113 self.vunit_proj.enable_location_preprocessing() 

114 self.vunit_proj.enable_check_preprocessing() 

115 

116 self.has_commercial_simulator = self.vunit_proj.get_simulator_name() != "ghdl" 

117 

118 def add_modules( 

119 self, 

120 modules: ModuleList, 

121 modules_no_sim: Optional[ModuleList] = None, 

122 include_vhdl_files: bool = True, 

123 include_verilog_files: bool = True, 

124 include_systemverilog_files: bool = True, 

125 **setup_vunit_kwargs: Any, 

126 ) -> None: 

127 """ 

128 Add module source files to the VUnit project. 

129 

130 Arguments: 

131 modules: These modules will be included in the simulation project. 

132 modules_no_sim: These modules will be included in the simulation project, 

133 but their test files will not be added. 

134 include_vhdl_files: Optionally disable inclusion of VHDL files from 

135 the modules. 

136 include_verilog_files: Optionally disable inclusion of Verilog files from 

137 the modules. 

138 include_systemverilog_files: Optionally disable inclusion of SystemVerilog files from 

139 the modules. 

140 setup_vunit_kwargs: Further arguments that will be sent to 

141 :meth:`.BaseModule.setup_vunit` for each module. 

142 Note that this is a "kwargs" style argument; any number of named arguments can 

143 be sent. 

144 """ 

145 modules_no_sim = ModuleList() if modules_no_sim is None else modules_no_sim 

146 

147 include_unisim = not self.args.vivado_skip 

148 include_ip_cores = self.has_commercial_simulator and not self.args.vivado_skip 

149 

150 for module in modules + modules_no_sim: 

151 vunit_library = self.vunit_proj.add_library( 

152 library_name=module.library_name, allow_duplicate=True 

153 ) 

154 simulate_this_module = module not in modules_no_sim 

155 

156 for hdl_file in module.get_simulation_files( 

157 include_tests=simulate_this_module, 

158 include_unisim=include_unisim, 

159 include_ip_cores=include_ip_cores, 

160 include_vhdl_files=include_vhdl_files, 

161 include_verilog_files=include_verilog_files, 

162 include_systemverilog_files=include_systemverilog_files, 

163 ): 

164 vunit_library.add_source_file(hdl_file.path) 

165 

166 if simulate_this_module: 

167 module.setup_vunit( 

168 vunit_proj=self.vunit_proj, 

169 include_unisim=include_unisim, 

170 include_ip_cores=include_ip_cores, 

171 inspect=self.args.inspect, 

172 **setup_vunit_kwargs, 

173 ) 

174 

175 def add_vivado_simlib(self) -> Optional["VivadoSimlibCommon"]: 

176 """ 

177 Add Vivado simlib to the VUnit project, unless instructed not to by ``args``. 

178 Will compile simlib if necessary. 

179 

180 Return: 

181 The simlib object, ``None`` if simlib was not added due to command line argument. 

182 """ 

183 if self.args.vivado_skip: 

184 return None 

185 

186 return self._add_simlib( 

187 output_path=self.args.output_path_vivado, force_compile=self.args.simlib_compile 

188 ) 

189 

190 def _add_simlib(self, output_path: Path, force_compile: bool) -> "VivadoSimlibCommon": 

191 """ 

192 Add Vivado simlib to the VUnit project. Compile if needed. 

193 

194 .. note:: 

195 

196 This method can be overloaded in a subclass if you want to do something more 

197 advanced, e.g. fetching compiled simlib from Artifactory. 

198 

199 Arguments: 

200 output_path: Compiled simlib will be placed in sub-directory of this path. 

201 force_compile: Will (re)-compile simlib even if compiled artifacts exist. 

202 

203 Return: 

204 The simlib object. 

205 """ 

206 vivado_simlib = VivadoSimlib.init(output_path=output_path, vunit_proj=self.vunit_proj) 

207 if force_compile or vivado_simlib.compile_is_needed: 

208 vivado_simlib.compile() 

209 vivado_simlib.to_archive() 

210 

211 vivado_simlib.add_to_vunit_project() 

212 

213 # Code in the "vital2000" package gives GHDL errors such as "result subtype of a pure 

214 # function cannot have access sub-elements". Hence, relaxed rules need to be enabled when 

215 # using unisim. 

216 self.vunit_proj.set_sim_option("ghdl.elab_flags", ["-frelaxed-rules"]) 

217 

218 return vivado_simlib 

219 

220 def add_vivado_ip_cores( 

221 self, 

222 modules: ModuleList, 

223 vivado_part_name: str = "xc7z020clg400-1", 

224 vivado_ip_core_project_class: Optional[Type[Any]] = None, 

225 ) -> Optional[Path]: 

226 """ 

227 Generate IP cores from the modules, unless instructed not to by ``args``. 

228 When running with a commercial simulator they will be added to the VUnit project. 

229 

230 Arguments: 

231 modules: IP cores from these modules will be included in the simulation project. 

232 vivado_part_name: Part name to be used for Vivado IP core project. 

233 Might have to change from default depending on what parts you have available in your 

234 Vivado installation. 

235 vivado_ip_core_project_class: Class to be used for Vivado IP core project. 

236 Can be left at default in most cases. 

237 

238 Return: 

239 Path to the Vivado IP core project's ``project`` directory. 

240 ``None`` if Vivado IP cores were not added due to command line argument. 

241 """ 

242 if self.args.vivado_skip: 

243 return None 

244 

245 # Generate IP core simulation files. Might be used for the vhdl_ls config, 

246 # even if they are not added to the simulation project. 

247 ( 

248 ip_core_compile_order_file, 

249 ip_core_vivado_project_directory, 

250 ) = self._generate_ip_core_files( 

251 modules=modules, 

252 output_path=self.args.output_path_vivado, 

253 force_generate=self.args.ip_compile, 

254 part_name=vivado_part_name, 

255 vivado_project_class=vivado_ip_core_project_class, 

256 ) 

257 if self.has_commercial_simulator: 

258 add_from_compile_order_file( 

259 vunit_obj=self.vunit_proj, compile_order_file=ip_core_compile_order_file 

260 ) 

261 

262 return ip_core_vivado_project_directory 

263 

264 @staticmethod 

265 def _generate_ip_core_files( 

266 modules: ModuleList, 

267 output_path: Path, 

268 force_generate: bool, 

269 part_name: str, 

270 vivado_project_class: Optional[Type[Any]] = None, 

271 ) -> tuple[Path, Path]: 

272 """ 

273 Generate Vivado IP core files that are to be added to the VUnit project. 

274 Create a new project to generate files if needed. 

275 

276 Arguments: 

277 modules: IP cores from these modules will be included. 

278 output_path: IP core files will be placed in sub-directory of this path. 

279 force_generate: Will (re)-generate files even if they exist. 

280 part_name: Vivado part name. 

281 vivado_project_class: Class to be used for Vivado IP core project. 

282 """ 

283 vivado_ip_cores = VivadoIpCores( 

284 modules=modules, 

285 output_path=output_path, 

286 part_name=part_name, 

287 vivado_project_class=vivado_project_class, 

288 ) 

289 

290 if force_generate: 

291 vivado_ip_cores.create_vivado_project() 

292 vivado_project_created = True 

293 else: 

294 vivado_project_created = vivado_ip_cores.create_vivado_project_if_needed() 

295 

296 if vivado_project_created: 

297 # If the IP core Vivado project has been (re)created we need to create 

298 # a new compile order file 

299 create_compile_order_file( 

300 project_file=vivado_ip_cores.vivado_project_file, 

301 compile_order_file=vivado_ip_cores.compile_order_file, 

302 ) 

303 

304 return vivado_ip_cores.compile_order_file, vivado_ip_cores.project_directory 

305 

306 

307class NoGitDiffTestsFound(Exception): 

308 """ 

309 Raised by :meth:`.find_git_test_filters` when no tests are found due to no 

310 VHDL-related git diff. 

311 """ 

312 

313 

314def find_git_test_filters( 

315 args: argparse.Namespace, 

316 repo_root: Path, 

317 modules: "ModuleList", 

318 modules_no_sim: Optional["ModuleList"] = None, 

319 reference_branch: str = "origin/main", 

320 **setup_vunit_kwargs: Any, 

321) -> argparse.Namespace: 

322 """ 

323 Construct a VUnit test filter that will run all test cases that are affected by git changes. 

324 The current git state is compared to a reference branch, and differences are derived. 

325 See :class:`.GitSimulationSubset` for details. 

326 

327 Arguments: 

328 args: Command line argument namespace. 

329 repo_root: Path to the repository root. 

330 Git commands will be run here. 

331 modules: Will be passed on to :meth:`.SimulationProject.add_modules`. 

332 modules_no_sim: Will be passed on to :meth:`.SimulationProject.add_modules`. 

333 reference_branch: The name of the reference branch that is used to collect a diff. 

334 setup_vunit_kwargs : Will be passed on to :meth:`.SimulationProject.add_modules`. 

335 

336 Return: 

337 An updated argument namespace from which a VUnit project can be created. 

338 """ 

339 if args.test_patterns != "*": 

340 raise ValueError( 

341 "Can not specify a test pattern when using the --vcs-minimal flag." 

342 f" Got {args.test_patterns}", 

343 ) 

344 

345 # Set up a dummy VUnit project that will be used for dependency scanning. 

346 # We could use the "real" simulation project, which the user has no doubt created, but 

347 # in the VUnit project there are two issues: 

348 # 1. It is impossible to change the test filter after the project has been created. 

349 # 2. We would have to access the _minimal private member. 

350 # Hence we create a new project here. 

351 # We add the 'modules_no_sim' as well as simlib, not because we need them, but to avoid 

352 # excessive terminal printouts about missing files in dependency scanning. 

353 simulation_project = SimulationProject(args=args) 

354 simulation_project.add_modules( 

355 args=args, 

356 modules=modules, 

357 modules_no_sim=modules_no_sim, 

358 include_verilog_files=False, 

359 include_systemverilog_files=False, 

360 **setup_vunit_kwargs, 

361 ) 

362 simulation_project.add_vivado_simlib() 

363 

364 testbenches_to_run = GitSimulationSubset( 

365 repo_root=repo_root, 

366 reference_branch=reference_branch, 

367 vunit_proj=simulation_project.vunit_proj, 

368 modules=modules, 

369 ).find_subset() 

370 

371 if not testbenches_to_run: 

372 raise NoGitDiffTestsFound() 

373 

374 # Override the test pattern argument to VUnit. 

375 args.test_patterns = [] 

376 for testbench_file_name, library_name in testbenches_to_run: 

377 args.test_patterns.append(f"{library_name}.{testbench_file_name}.*") 

378 

379 print(f"Running VUnit with test pattern {args.test_patterns}") 

380 

381 # Enable minimal compilation in VUnit to save time. 

382 args.minimal = True 

383 

384 return args 

385 

386 

387def create_vhdl_ls_configuration( 

388 output_path: Path, 

389 modules: ModuleList, 

390 vunit_proj: VUnit, 

391 ip_core_vivado_project_directory: Optional[Path] = None, 

392) -> None: 

393 """ 

394 Create a configuration file (``vhdl_ls.toml``) for the rust_hdl VHDL Language Server 

395 (https://github.com/VHDL-LS/rust_hdl). 

396 

397 The call is very quick (10-15 ms). 

398 Running it from ``simulate.py``, a script that is run "often", might be a good idea 

399 in order to always have an up-to-date vhdl_ls config. 

400 

401 Arguments: 

402 output_path: Config file will be placed in this directory. 

403 modules: All files from these modules will be added. 

404 vunit_proj: All files in this VUnit project will be added. 

405 This includes the files from VUnit itself, and any user files. 

406 

407 .. warning:: 

408 Using a VUnit project with location/check preprocessing enabled might be 

409 dangerous, since it introduces the risk of editing a generated file. 

410 ip_core_vivado_project_directory: Vivado IP core files in this location will be added. 

411 """ 

412 # Add some files needed when doing hdl-registers development. 

413 hdl_register_repo_root = Path(hdl_registers.__file__).parent.parent 

414 additional_files = [ 

415 (hdl_register_repo_root / "tests" / "functional" / "simulation" / "*.vhd", "example"), 

416 ( 

417 hdl_register_repo_root / "generated" / "vunit_out" / "generated_register" / "*.vhd", 

418 "example", 

419 ), 

420 ( 

421 hdl_register_repo_root / "doc" / "sphinx" / "rst" / "generator" / "sim" / "*.vhd", 

422 "example", 

423 ), 

424 ] 

425 

426 try: 

427 vivado_location = get_vivado_path() 

428 except FileNotFoundError: 

429 vivado_location = None 

430 

431 tsfpga.create_vhdl_ls_config.create_configuration( 

432 output_path=output_path, 

433 modules=modules, 

434 vunit_proj=vunit_proj, 

435 files=additional_files, 

436 vivado_location=vivado_location, 

437 ip_core_vivado_project_directory=ip_core_vivado_project_directory, 

438 )