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

107 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, 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.project import VivadoIpCoreProject 

28from tsfpga.vivado.simlib import VivadoSimlib 

29 

30if TYPE_CHECKING: 

31 # First party libraries 

32 from tsfpga.vivado.simlib_common import VivadoSimlibCommon 

33 

34 

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

36 """ 

37 Get arguments for the simulation flow. 

38 

39 Arguments: 

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

41 (both VUnit files and Vivado files). 

42 """ 

43 cli = VUnitCLI() 

44 

45 # Print default values when doing --help 

46 cli.parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter 

47 

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

49 for action in cli.parser._actions: 

50 if action.dest == "output_path": 

51 action.default = default_output_path / "vunit_out" 

52 break 

53 else: 

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

55 

56 cli.parser.add_argument( 

57 "--output-path-vivado", 

58 type=Path, 

59 default=default_output_path, 

60 help=( 

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

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

63 ), 

64 ) 

65 

66 cli.parser.add_argument( 

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

68 ) 

69 

70 cli.parser.add_argument( 

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

72 ) 

73 

74 cli.parser.add_argument( 

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

76 ) 

77 

78 cli.parser.add_argument( 

79 "--vcs-minimal", 

80 action="store_true", 

81 help="run a minimal set of tests based on version control system (e.g. git) history", 

82 ) 

83 

84 cli.parser.add_argument( 

85 "--inspect", 

86 action="store_true", 

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

88 ) 

89 

90 return cli 

91 

92 

93class SimulationProject: 

94 """ 

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

96 """ 

97 

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

99 """ 

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

101 

102 Arguments: 

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

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

105 """ 

106 self.args = args 

107 

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

109 self.vunit_proj.add_vhdl_builtins() 

110 self.vunit_proj.add_verification_components() 

111 self.vunit_proj.add_random() 

112 

113 if enable_preprocessing: 

114 self.vunit_proj.enable_location_preprocessing() 

115 self.vunit_proj.enable_check_preprocessing() 

116 

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

118 

119 def add_modules( 

120 self, 

121 modules: ModuleList, 

122 modules_no_test: Optional[ModuleList] = None, 

123 include_vhdl_files: bool = True, 

124 include_verilog_files: bool = True, 

125 include_systemverilog_files: bool = True, 

126 **setup_vunit_kwargs: Any, 

127 ) -> None: 

128 """ 

129 Add module source files to the VUnit project. 

130 

131 Arguments: 

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

133 modules_no_test: Source and simulation files from these modules will be included in the 

134 simulation project, but no testbenches. 

135 Provide your dependency modules here, if you have any. 

136 include_vhdl_files: Optionally disable inclusion of VHDL files from 

137 the modules. 

138 include_verilog_files: Optionally disable inclusion of Verilog files from 

139 the modules. 

140 include_systemverilog_files: Optionally disable inclusion of SystemVerilog files from 

141 the modules. 

142 setup_vunit_kwargs: Further arguments that will be sent to 

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

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

145 be sent. 

146 """ 

147 modules_no_test = ModuleList() if modules_no_test is None else modules_no_test 

148 

149 include_unisim = not self.args.vivado_skip 

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

151 

152 for module in modules + modules_no_test: 

153 vunit_library = self.vunit_proj.add_library( 

154 library_name=module.library_name, allow_duplicate=True 

155 ) 

156 simulate_this_module = module not in modules_no_test 

157 

158 for hdl_file in module.get_simulation_files( 

159 include_tests=simulate_this_module, 

160 include_unisim=include_unisim, 

161 include_ip_cores=include_ip_cores, 

162 include_vhdl_files=include_vhdl_files, 

163 include_verilog_files=include_verilog_files, 

164 include_systemverilog_files=include_systemverilog_files, 

165 ): 

166 vunit_library.add_source_file(hdl_file.path) 

167 

168 if simulate_this_module: 

169 module.setup_vunit( 

170 vunit_proj=self.vunit_proj, 

171 include_unisim=include_unisim, 

172 include_ip_cores=include_ip_cores, 

173 inspect=self.args.inspect, 

174 **setup_vunit_kwargs, 

175 ) 

176 

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

178 """ 

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

180 Will compile simlib if necessary. 

181 

182 Return: 

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

184 """ 

185 if self.args.vivado_skip: 

186 return None 

187 

188 return self._add_simlib( 

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

190 ) 

191 

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

193 """ 

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

195 

196 .. note:: 

197 

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

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

200 

201 Arguments: 

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

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

204 

205 Return: 

206 The simlib object. 

207 """ 

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

209 if force_compile or vivado_simlib.compile_is_needed: 

210 vivado_simlib.compile() 

211 vivado_simlib.to_archive() 

212 

213 vivado_simlib.add_to_vunit_project() 

214 

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

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

217 # using unisim. 

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

219 

220 return vivado_simlib 

221 

222 def add_vivado_ip_cores( 

223 self, 

224 modules: ModuleList, 

225 vivado_part_name: str = "xc7z020clg400-1", 

226 vivado_ip_core_project_class: Optional[Type[VivadoIpCoreProject]] = None, 

227 ) -> Optional[Path]: 

228 """ 

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

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

231 

232 Arguments: 

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

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

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

236 Vivado installation. 

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

238 Can be left at default in most cases. 

239 

240 Return: 

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

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

243 """ 

244 if self.args.vivado_skip: 

245 return None 

246 

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

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

249 ( 

250 ip_core_compile_order_file, 

251 ip_core_vivado_project_directory, 

252 ) = self._generate_ip_core_files( 

253 modules=modules, 

254 output_path=self.args.output_path_vivado, 

255 force_generate=self.args.ip_compile, 

256 part_name=vivado_part_name, 

257 vivado_project_class=vivado_ip_core_project_class, 

258 ) 

259 if self.has_commercial_simulator: 

260 add_from_compile_order_file( 

261 vunit_obj=self.vunit_proj, compile_order_file=ip_core_compile_order_file 

262 ) 

263 

264 return ip_core_vivado_project_directory 

265 

266 @staticmethod 

267 def _generate_ip_core_files( 

268 modules: ModuleList, 

269 output_path: Path, 

270 force_generate: bool, 

271 part_name: str, 

272 vivado_project_class: Optional[Type[VivadoIpCoreProject]] = None, 

273 ) -> tuple[Path, Path]: 

274 """ 

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

276 Create a new project to generate files if needed. 

277 

278 Arguments: 

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

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

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

282 part_name: Vivado part name. 

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

284 """ 

285 vivado_ip_cores = VivadoIpCores( 

286 modules=modules, 

287 output_path=output_path, 

288 part_name=part_name, 

289 vivado_project_class=vivado_project_class, 

290 ) 

291 

292 if force_generate: 

293 vivado_ip_cores.create_vivado_project() 

294 vivado_project_created = True 

295 else: 

296 vivado_project_created = vivado_ip_cores.create_vivado_project_if_needed() 

297 

298 if vivado_project_created: 

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

300 # a new compile order file 

301 create_compile_order_file( 

302 project_file=vivado_ip_cores.vivado_project_file, 

303 compile_order_file=vivado_ip_cores.compile_order_file, 

304 ) 

305 

306 return vivado_ip_cores.compile_order_file, vivado_ip_cores.project_directory 

307 

308 

309class NoVcsDiffTestsFound(Exception): 

310 """ 

311 Raised by :meth:`.find_git_test_filter` when no tests are found due to no 

312 VHDL-related git diff. 

313 """ 

314 

315 

316def find_git_test_filter( 

317 args: argparse.Namespace, 

318 repo_root: Path, 

319 modules: "ModuleList", 

320 modules_no_test: Optional["ModuleList"] = None, 

321 reference_branch: str = "origin/main", 

322 **setup_vunit_kwargs: Any, 

323) -> argparse.Namespace: 

324 """ 

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

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

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

328 

329 Arguments: 

330 args: Command line argument namespace. 

331 repo_root: Path to the repository root. 

332 Git commands will be run here. 

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

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

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

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

337 

338 Return: 

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

340 """ 

341 if args.test_patterns != "*": 

342 raise ValueError( 

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

344 f" Got {args.test_patterns}", 

345 ) 

346 

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

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

349 # in the VUnit project there are two issues: 

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

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

352 # Hence we create a new project here. 

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

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

355 simulation_project = SimulationProject(args=args) 

356 simulation_project.add_modules( 

357 args=args, 

358 modules=modules, 

359 modules_no_test=modules_no_test, 

360 include_verilog_files=False, 

361 include_systemverilog_files=False, 

362 **setup_vunit_kwargs, 

363 ) 

364 simulation_project.add_vivado_simlib() 

365 

366 testbenches_to_run = GitSimulationSubset( 

367 repo_root=repo_root, 

368 reference_branch=reference_branch, 

369 vunit_proj=simulation_project.vunit_proj, 

370 modules=modules, 

371 ).find_subset() 

372 

373 if not testbenches_to_run: 

374 raise NoVcsDiffTestsFound() 

375 

376 # Override the test pattern argument to VUnit. 

377 args.test_patterns = [] 

378 for testbench_file_name, library_name in testbenches_to_run: 

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

380 

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

382 

383 # Enable minimal compilation in VUnit to save time. 

384 args.minimal = True 

385 

386 return args 

387 

388 

389def create_vhdl_ls_configuration( 

390 output_path: Path, 

391 modules: ModuleList, 

392 vunit_proj: VUnit, 

393 ip_core_vivado_project_directory: Optional[Path] = None, 

394) -> None: 

395 """ 

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

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

398 

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

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

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

402 

403 Arguments: 

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

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

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

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

408 

409 .. warning:: 

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

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

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

413 """ 

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

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

416 additional_files = [ 

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

418 ( 

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

420 "example", 

421 ), 

422 ( 

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

424 "example", 

425 ), 

426 ] 

427 

428 try: 

429 vivado_location = get_vivado_path() 

430 except FileNotFoundError: 

431 vivado_location = None 

432 

433 tsfpga.create_vhdl_ls_config.create_configuration( 

434 output_path=output_path, 

435 modules=modules, 

436 vunit_proj=vunit_proj, 

437 files=additional_files, 

438 vivado_location=vivado_location, 

439 ip_core_vivado_project_directory=ip_core_vivado_project_directory, 

440 )