Coverage for tsfpga/vivado/test/test_project.py: 100%

336 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-06 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 

9import unittest 

10from pathlib import Path 

11from unittest.mock import MagicMock, patch 

12 

13import pytest 

14 

15from tsfpga.build_step_tcl_hook import BuildStepTclHook 

16from tsfpga.constraint import Constraint 

17from tsfpga.module import BaseModule, get_modules 

18from tsfpga.system_utils import create_directory, create_file, read_file 

19from tsfpga.test.test_utils import file_contains_string 

20from tsfpga.vivado.common import to_tcl_path 

21from tsfpga.vivado.generics import StringGenericValue 

22from tsfpga.vivado.project import VivadoNetlistProject, VivadoProject, copy_and_combine_dicts 

23 

24# ruff: noqa: ARG002 

25 

26 

27def test_casting_to_string(): 

28 project = VivadoProject(name="my_project", modules=[], part="") 

29 assert ( 

30 str(project) 

31 == """\ 

32my_project 

33Type: VivadoProject 

34Top level: my_project_top 

35Generics: - 

36""" 

37 ) 

38 

39 project = VivadoProject( 

40 name="my_project", 

41 modules=[], 

42 part="", 

43 top="apa", 

44 generics={"hest": True, "zebra": 3, "foo": StringGenericValue("/home/test.vhd")}, 

45 ) 

46 assert ( 

47 str(project) 

48 == """\ 

49my_project 

50Type: VivadoProject 

51Top level: apa 

52Generics: hest=True, zebra=3, foo=/home/test.vhd 

53""" 

54 ) 

55 

56 project = VivadoProject(name="my_project", modules=[], part="", apa=123, hest=456) 

57 assert ( 

58 str(project) 

59 == """\ 

60my_project 

61Type: VivadoProject 

62Top level: my_project_top 

63Generics: - 

64Arguments: apa=123, hest=456 

65""" 

66 ) 

67 

68 

69def test_modules_list_should_be_copied(): 

70 modules = [1] 

71 proj = VivadoProject(name="name", modules=modules, part="part") 

72 

73 modules.append(2) 

74 assert len(proj.modules) == 1 

75 

76 

77def test_static_generics_dictionary_should_be_copied(): 

78 generics = {"apa": 3} 

79 proj = VivadoProject(name="name", modules=[], part="part", generics=generics) 

80 

81 generics["apa"] = False 

82 assert proj.static_generics["apa"] == 3 

83 

84 

85def test_constraints_list_should_be_copied(): 

86 constraints = [Constraint(file="1")] 

87 proj = VivadoProject(name="name", modules=[], part="part", constraints=constraints) 

88 

89 constraints.append(Constraint(file="2")) 

90 assert len(proj.constraints) == 1 

91 

92 

93def test_bad_constraint_type_should_raise_error(): 

94 # Correct type should not give error 

95 VivadoProject(name="name", modules=[], part="part", constraints=[Constraint(file=None)]) 

96 

97 # Bad type should raise exception 

98 with pytest.raises(TypeError) as exception_info: 

99 VivadoProject(name="name", modules=[], part="part", constraints=["file.vhd"]) 

100 assert str(exception_info.value) == 'Got bad type for "constraints" element: file.vhd' 

101 

102 

103def test_bad_tcl_sources_type_should_raise_error(): 

104 # Correct type should not give error 

105 VivadoProject(name="name", modules=[], part="part", tcl_sources=[Path()]) 

106 

107 # Bad type should raise exception 

108 with pytest.raises(TypeError) as exception_info: 

109 VivadoProject(name="name", modules=[], part="part", tcl_sources=["file.tcl"]) 

110 assert str(exception_info.value) == 'Got bad type for "tcl_sources" element: file.tcl' 

111 

112 

113def test_bad_build_step_hooks_type_should_raise_error(): 

114 # Correct type should not give error 

115 VivadoProject( 

116 name="name", 

117 modules=[], 

118 part="part", 

119 build_step_hooks=[BuildStepTclHook(tcl_file="", hook_step="")], 

120 ) 

121 

122 # Bad type should raise exception 

123 with pytest.raises(TypeError) as exception_info: 

124 VivadoProject(name="name", modules=[], part="part", build_step_hooks=["file.tcl"]) 

125 assert str(exception_info.value) == 'Got bad type for "build_step_hooks" element: file.tcl' 

126 

127 

128def test_create_should_raise_exception_if_project_path_already_exists(tmp_path): 

129 xpr_path = create_file(tmp_path / "project" / "name.xpr") 

130 proj = VivadoProject(name="name", modules=[], part="part") 

131 with pytest.raises(ValueError) as exception_info: 

132 proj.create(tmp_path / "project") 

133 assert str(exception_info.value) == f'Project "name" already exists: {xpr_path}' 

134 

135 

136def test_build_should_raise_exception_if_project_does_not_exists(tmp_path): 

137 project_path = create_directory(tmp_path / "project") 

138 proj = VivadoProject(name="name", modules=[], part="part") 

139 with pytest.raises(ValueError) as exception_info: 

140 proj.build(project_path, synth_only=True) 

141 assert ( 

142 str(exception_info.value) 

143 == f'Project "name" does not exist in the specified location: {project_path / "name.xpr"}' 

144 ) 

145 

146 

147def test_build_with_impl_run_should_raise_exception_if_no_output_path_is_given(): 

148 proj = VivadoProject(name="name", modules=[], part="part") 

149 with pytest.raises(ValueError) as exception_info: 

150 proj.build("None") 

151 assert str(exception_info.value) == ( 

152 "Must specify 'output_path' when doing an implementation build." 

153 ) 

154 

155 

156def test_top_name(): 

157 assert VivadoProject(name="apa", modules=[], part="").top == "apa_top" 

158 assert VivadoProject(name="apa", modules=[], part="", top="hest").top == "hest" 

159 

160 

161def test_project_file_name_is_same_as_project_name(): 

162 project_path = Path("projects/apa") 

163 assert ( 

164 VivadoProject(name="apa", modules=[], part="").project_file(project_path) 

165 == project_path / "apa.xpr" 

166 ) 

167 

168 

169def test_project_create(tmp_path): 

170 with patch("tsfpga.vivado.project.run_vivado_tcl", autospec=True) as _: 

171 assert VivadoProject(name="apa", modules=[], part="").create(tmp_path / "projects" / "apa") 

172 assert (tmp_path / "projects" / "apa" / "create_vivado_project.tcl").exists() 

173 

174 

175def test_copy_and_combine_dict_with_both_arguments_none(): 

176 assert copy_and_combine_dicts(None, None) == {} 

177 

178 

179def test_copy_and_combine_dict_with_first_argument_valid(): 

180 dict_first = {"first": 1} 

181 

182 result = copy_and_combine_dicts(dict_first, None) 

183 assert result == {"first": 1} 

184 assert dict_first == {"first": 1} 

185 

186 dict_first["first_dummy"] = True 

187 assert result == {"first": 1} 

188 

189 

190def test_copy_and_combine_dict_with_second_argument_valid(): 

191 dict_second = {"second": 2} 

192 

193 result = copy_and_combine_dicts(None, dict_second) 

194 assert result == {"second": 2} 

195 assert dict_second == {"second": 2} 

196 

197 dict_second["second_dummy"] = True 

198 assert result == {"second": 2} 

199 

200 

201def test_copy_and_combine_dict_with_both_arguments_valid(): 

202 dict_first = {"first": 1} 

203 dict_second = {"second": 2} 

204 

205 result = copy_and_combine_dicts(dict_first, dict_second) 

206 assert result == {"first": 1, "second": 2} 

207 assert dict_first == {"first": 1} 

208 assert dict_second == {"second": 2} 

209 

210 dict_first["first_dummy"] = True 

211 dict_second["second_dummy"] = True 

212 assert result == {"first": 1, "second": 2} 

213 

214 

215def test_copy_and_combine_dict_with_both_arguments_valid_and_same_key(): 

216 dict_first = {"first": 1, "common": 3} 

217 dict_second = {"second": 2, "common": 4} 

218 

219 result = copy_and_combine_dicts(dict_first, dict_second) 

220 assert result == {"first": 1, "second": 2, "common": 4} 

221 assert dict_first == {"first": 1, "common": 3} 

222 assert dict_second == {"second": 2, "common": 4} 

223 

224 dict_first["first_dummy"] = True 

225 dict_second["second_dummy"] = True 

226 assert result == {"first": 1, "second": 2, "common": 4} 

227 

228 

229@pytest.fixture 

230def vivado_project_test(tmp_path): 

231 class VivadoProjectTest: 

232 def __init__(self): 

233 self.modules_path = tmp_path / "modules" 

234 self.project_path = tmp_path / "projects" / "apa" / "project" 

235 self.output_path = tmp_path / "projects" / "apa" 

236 self.ip_cache_path = MagicMock() 

237 self.build_time_generics = {"enable": True} 

238 self.num_threads = 4 

239 self.run_index = 3 

240 self.synth_only = False 

241 self.from_impl = False 

242 

243 self.mocked_run_vivado_tcl = None 

244 

245 def create(self, project, **other_arguments): 

246 with patch( 

247 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

248 ) as self.mocked_run_vivado_tcl: 

249 return project.create( 

250 project_path=self.project_path, 

251 ip_cache_path=self.ip_cache_path, 

252 **other_arguments, 

253 ) 

254 

255 def build(self, project): 

256 with ( 

257 patch( 

258 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

259 ) as self.mocked_run_vivado_tcl, 

260 patch("tsfpga.vivado.project.VivadoProject._get_size", autospec=True) as _, 

261 patch("tsfpga.vivado.project.shutil.copy2", autospec=True) as _, 

262 ): 

263 create_file(self.project_path / "apa.xpr") 

264 return project.build( 

265 project_path=self.project_path, 

266 output_path=self.output_path, 

267 run_index=self.run_index, 

268 generics=self.build_time_generics, 

269 synth_only=self.synth_only, 

270 num_threads=self.num_threads, 

271 other_parameter="hest", 

272 ) 

273 

274 return VivadoProjectTest() 

275 

276 

277def test_default_pre_create_hook_should_pass(vivado_project_test): 

278 class CustomVivadoProject(VivadoProject): 

279 pass 

280 

281 project = CustomVivadoProject(name="apa", modules=[], part="") 

282 vivado_project_test.create(project) 

283 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

284 

285 

286def test_project_pre_create_hook_returning_false_should_fail_and_not_call_vivado_run( 

287 vivado_project_test, 

288): 

289 class CustomVivadoProject(VivadoProject): 

290 def pre_create(self, **kwargs): 

291 return False 

292 

293 assert not vivado_project_test.create(CustomVivadoProject(name="apa", modules=[], part="")) 

294 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

295 

296 

297def test_create_should_call_pre_create_with_correct_parameters(vivado_project_test): 

298 project = VivadoProject(name="apa", modules=[], part="", generics={"apa": 123}, hest=456) 

299 with patch("tsfpga.vivado.project.VivadoProject.pre_create") as mocked_pre_create: 

300 vivado_project_test.create(project, zebra=789) 

301 mocked_pre_create.assert_called_once_with( 

302 project_path=vivado_project_test.project_path, 

303 ip_cache_path=vivado_project_test.ip_cache_path, 

304 part="", 

305 generics={"apa": 123}, 

306 hest=456, 

307 zebra=789, 

308 ) 

309 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

310 

311 

312def test_build_module_pre_build_hook_and_create_regs_are_called(vivado_project_test): 

313 project = VivadoProject( 

314 name="apa", 

315 modules=[MagicMock(spec=BaseModule), MagicMock(spec=BaseModule)], 

316 part="", 

317 apa=123, 

318 ) 

319 build_result = vivado_project_test.build(project) 

320 assert build_result.success 

321 

322 for module in project.modules: 

323 module.pre_build.assert_called_once_with( 

324 project=project, 

325 other_parameter="hest", 

326 apa=123, 

327 project_path=vivado_project_test.project_path, 

328 output_path=vivado_project_test.output_path, 

329 run_index=vivado_project_test.run_index, 

330 generics=vivado_project_test.build_time_generics, 

331 synth_only=vivado_project_test.synth_only, 

332 from_impl=vivado_project_test.from_impl, 

333 num_threads=vivado_project_test.num_threads, 

334 ) 

335 module.create_register_synthesis_files.assert_called_once() 

336 module.create_register_simulation_files.assert_not_called() 

337 

338 

339def test_default_pre_and_post_build_hooks_should_pass(vivado_project_test): 

340 class CustomVivadoProject(VivadoProject): 

341 pass 

342 

343 build_result = vivado_project_test.build(CustomVivadoProject(name="apa", modules=[], part="")) 

344 assert build_result.success 

345 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

346 

347 

348def test_project_pre_build_hook_returning_false_should_fail_and_not_call_vivado_run( 

349 vivado_project_test, 

350): 

351 class CustomVivadoProject(VivadoProject): 

352 def pre_build(self, **kwargs): 

353 return False 

354 

355 build_result = vivado_project_test.build(CustomVivadoProject(name="apa", modules=[], part="")) 

356 assert not build_result.success 

357 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

358 

359 

360def test_project_post_build_hook_returning_false_should_fail(vivado_project_test): 

361 class CustomVivadoProject(VivadoProject): 

362 def post_build(self, **kwargs): 

363 return False 

364 

365 build_result = vivado_project_test.build(CustomVivadoProject(name="apa", modules=[], part="")) 

366 assert not build_result.success 

367 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

368 

369 

370def test_project_build_hooks_should_be_called_with_correct_parameters(vivado_project_test): 

371 project = VivadoProject( 

372 name="apa", modules=[], part="", generics={"static_generic": 2}, apa=123 

373 ) 

374 with ( 

375 patch("tsfpga.vivado.project.VivadoProject.pre_build") as mocked_pre_build, 

376 patch("tsfpga.vivado.project.VivadoProject.post_build") as mocked_post_build, 

377 ): 

378 vivado_project_test.build(project) 

379 

380 arguments = { 

381 "project_path": vivado_project_test.project_path, 

382 "output_path": vivado_project_test.output_path, 

383 "run_index": vivado_project_test.run_index, 

384 "generics": copy_and_combine_dicts( 

385 {"static_generic": 2}, vivado_project_test.build_time_generics 

386 ), 

387 "synth_only": vivado_project_test.synth_only, 

388 "from_impl": vivado_project_test.from_impl, 

389 "num_threads": vivado_project_test.num_threads, 

390 "other_parameter": "hest", 

391 "apa": 123, 

392 } 

393 mocked_pre_build.assert_called_once_with(**arguments) 

394 

395 arguments.update(build_result=unittest.mock.ANY) 

396 mocked_post_build.assert_called_once_with(**arguments) 

397 

398 

399def test_module_pre_build_hook_returning_false_should_fail_and_not_call_vivado(vivado_project_test): 

400 module = MagicMock(spec=BaseModule) 

401 module.name = "whatever" 

402 project = VivadoProject(name="apa", modules=[module], part="") 

403 

404 project.modules[0].pre_build.return_value = True 

405 build_result = vivado_project_test.build(project) 

406 assert build_result.success 

407 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

408 

409 project.modules[0].pre_build.return_value = False 

410 build_result = vivado_project_test.build(project) 

411 assert not build_result.success 

412 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

413 

414 

415@patch("tsfpga.vivado.project.VivadoTcl", autospec=True) 

416def test_different_generic_combinations(mocked_vivado_tcl, vivado_project_test): 

417 mocked_vivado_tcl.return_value.build.return_value = "" 

418 

419 # No generics 

420 vivado_project_test.build_time_generics = None 

421 build_result = vivado_project_test.build(VivadoProject(name="apa", modules=[], part="")) 

422 assert build_result.success 

423 # Note: In python 3.8 we can use call_args.kwargs straight away 

424 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

425 assert kwargs["generics"] == {} 

426 

427 # Only build time generics 

428 vivado_project_test.build_time_generics = {"runtime": "value"} 

429 build_result = vivado_project_test.build(VivadoProject(name="apa", modules=[], part="")) 

430 assert build_result.success 

431 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

432 assert kwargs["generics"] == {"runtime": "value"} 

433 

434 # Static and build time generics 

435 vivado_project_test.build_time_generics = {"runtime": "value"} 

436 build_result = vivado_project_test.build( 

437 VivadoProject(name="apa", modules=[], part="", generics={"static": "a value"}) 

438 ) 

439 assert build_result.success 

440 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

441 assert kwargs["generics"] == {"runtime": "value", "static": "a value"} 

442 

443 # Same key in both static and build time generic. Should prefer build time. 

444 vivado_project_test.build_time_generics = {"static_and_runtime": "build value"} 

445 build_result = vivado_project_test.build( 

446 VivadoProject( 

447 name="apa", modules=[], part="", generics={"static_and_runtime": "static value"} 

448 ) 

449 ) 

450 assert build_result.success 

451 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

452 assert kwargs["generics"] == {"static_and_runtime": "build value"} 

453 

454 # Only static generics 

455 vivado_project_test.build_time_generics = None 

456 build_result = vivado_project_test.build( 

457 VivadoProject(name="apa", modules=[], part="", generics={"runtime": "a value"}) 

458 ) 

459 assert build_result.success 

460 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

461 assert kwargs["generics"] == {"runtime": "a value"} 

462 

463 

464def test_build_time_generics_are_copied(vivado_project_test): 

465 vivado_project_test.build_time_generics = {"runtime": "value"} 

466 with patch("tsfpga.vivado.project.VivadoTcl", autospec=True) as mocked_vivado_tcl: 

467 mocked_vivado_tcl.return_value.build.return_value = "" 

468 build_result = vivado_project_test.build( 

469 VivadoProject(name="apa", modules=[], part="", generics={"static": "a value"}) 

470 ) 

471 assert build_result.success 

472 assert vivado_project_test.build_time_generics == {"runtime": "value"} 

473 

474 

475def test_modules_are_deep_copied_before_pre_create_hook(vivado_project_test): 

476 class CustomVivadoProject(VivadoProject): 

477 def pre_create(self, **kwargs): 

478 self.modules[0].registers = "Some other value" 

479 return True 

480 

481 module = MagicMock(spec=BaseModule) 

482 module.registers = "Some value" 

483 

484 project = CustomVivadoProject(name="apa", modules=[module], part="") 

485 assert vivado_project_test.create(project) 

486 

487 assert module.registers == "Some value" 

488 

489 

490def test_modules_are_deep_copied_before_pre_build_hook(vivado_project_test): 

491 class CustomVivadoProject(VivadoProject): 

492 def pre_build(self, **kwargs): 

493 self.modules[0].registers = "Some other value" 

494 return True 

495 

496 module = MagicMock(spec=BaseModule) 

497 module.registers = "Some value" 

498 

499 project = CustomVivadoProject(name="apa", modules=[module], part="") 

500 assert vivado_project_test.build(project).success 

501 

502 assert module.registers == "Some value" 

503 

504 

505def test_build_step_hooks_with_same_hook_step(vivado_project_test): 

506 dummy = BuildStepTclHook( 

507 vivado_project_test.modules_path / "dummy.tcl", "STEPS.SYNTH_DESIGN.TCL.PRE" 

508 ) 

509 files = BuildStepTclHook( 

510 vivado_project_test.modules_path / "files.tcl", "STEPS.SYNTH_DESIGN.TCL.PRE" 

511 ) 

512 

513 project = VivadoProject(name="apa", modules=[], part="", build_step_hooks=[dummy, files]) 

514 assert vivado_project_test.create(project) 

515 

516 tcl_path = vivado_project_test.project_path / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl" 

517 assert file_contains_string(tcl_path, f"source {{{to_tcl_path(dummy.tcl_file)}}}") 

518 assert file_contains_string(tcl_path, f"source {{{to_tcl_path(files.tcl_file)}}}") 

519 

520 assert file_contains_string( 

521 vivado_project_test.project_path / "create_vivado_project.tcl", 

522 f'\n set_property "STEPS.SYNTH_DESIGN.TCL.PRE" {{{to_tcl_path(tcl_path)}}} ${{run}}\n', 

523 ) 

524 

525 

526def test_get_size_is_called_correctly(vivado_project_test): 

527 project = VivadoProject(name="apa", modules=[], part="") 

528 

529 def _build_with_size(synth_only): 

530 """ 

531 The project.build() call is very similar to _build() method in this class, but it mocks 

532 the _get_size() method in a different way. 

533 """ 

534 with ( 

535 patch( 

536 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

537 ) as vivado_project_test.mocked_run_vivado_tcl, 

538 patch( 

539 "tsfpga.vivado.project.HierarchicalUtilizationParser.get_size", autospec=True 

540 ) as mocked_get_size, 

541 patch("tsfpga.vivado.project.shutil.copy2", autospec=True) as _, 

542 ): 

543 # Only the first return value will be used if we are in synth_only 

544 mocked_get_size.side_effect = ["synth_size", "impl_size"] 

545 

546 build_result = project.build( 

547 project_path=vivado_project_test.project_path, 

548 output_path=vivado_project_test.output_path, 

549 run_index=vivado_project_test.run_index, 

550 synth_only=synth_only, 

551 ) 

552 

553 assert build_result.synthesis_size == "synth_size" 

554 

555 if synth_only: 

556 mocked_get_size.assert_called_once_with("synth_file") 

557 assert build_result.implementation_size is None 

558 else: 

559 assert mocked_get_size.call_count == 2 

560 mocked_get_size.assert_any_call("synth_file") 

561 mocked_get_size.assert_any_call("impl_file") 

562 

563 assert build_result.implementation_size == "impl_size" 

564 

565 create_file(vivado_project_test.project_path / "apa.xpr") 

566 

567 create_file( 

568 vivado_project_test.project_path / "apa.runs" / "synth_3" / "hierarchical_utilization.rpt", 

569 contents="synth_file", 

570 ) 

571 create_file( 

572 vivado_project_test.project_path / "apa.runs" / "impl_3" / "hierarchical_utilization.rpt", 

573 contents="impl_file", 

574 ) 

575 

576 _build_with_size(synth_only=True) 

577 _build_with_size(synth_only=False) 

578 

579 

580def test_netlist_build_should_set_logic_level_distribution(vivado_project_test): 

581 def _build_with_logic_level_distribution(project): 

582 """ 

583 The project.build() call is very similar to _build() method in this class, except this one 

584 also mocks the _get_logic_level_distribution() method. 

585 """ 

586 with ( 

587 patch( 

588 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

589 ) as vivado_project_test.mocked_run_vivado_tcl, 

590 patch("tsfpga.vivado.project.VivadoProject._get_size", autospec=True) as _, 

591 patch("tsfpga.vivado.project.shutil.copy2", autospec=True) as _, 

592 patch( 

593 "tsfpga.vivado.project.LogicLevelDistributionParser.get_table", autospec=True 

594 ) as mocked_get_table, 

595 ): 

596 mocked_get_table.return_value = "logic_table" 

597 

598 build_result = project.build( 

599 project_path=vivado_project_test.project_path, 

600 output_path=vivado_project_test.output_path, 

601 run_index=vivado_project_test.run_index, 

602 ) 

603 

604 if project.is_netlist_build: 

605 mocked_get_table.assert_called_once_with("logic_file") 

606 assert build_result.logic_level_distribution == "logic_table" 

607 else: 

608 mocked_get_table.assert_not_called() 

609 assert build_result.logic_level_distribution is None 

610 assert build_result.maximum_logic_level is None 

611 

612 create_file(vivado_project_test.project_path / "apa.xpr") 

613 create_file( 

614 vivado_project_test.project_path / "apa.runs" / "synth_3" / "logic_level_distribution.rpt", 

615 contents="logic_file", 

616 ) 

617 

618 project = VivadoNetlistProject(name="apa", modules=[], part="") 

619 _build_with_logic_level_distribution(project=project) 

620 

621 project = VivadoProject(name="apa", modules=[], part="") 

622 _build_with_logic_level_distribution(project=project) 

623 

624 

625def test_netlist_build_auto_detect_clocks(vivado_project_test): 

626 create_file( 

627 vivado_project_test.modules_path / "hest" / "zebra.vhd", 

628 """ 

629library ieee; 

630 

631-- entity zebra is 

632-- port ( 

633-- bad_clk : in std_logic; 

634-- ) 

635-- end entity; 

636 

637-- bad_clk : in std_logic 

638entity zebra is 

639 -- bad_clk : in std_logic 

640 generic ( 

641 width : positive 

642 bad_clk : in std_logic 

643 ); 

644 -- bad_clk : in std_logic 

645 port ( 

646 clk : in std_logic; 

647 clock : in std_logic; 

648 rst : in std_logic; 

649 my_funny_clock : in std_logic; 

650 clk_my_funny : in std_logic 

651 -- bad_clk : in std_logic; 

652 badclk : in std_logic; 

653 clockbad : in std_logic 

654 ) 

655 -- bad_clk : in std_logic 

656end entity; 

657-- bad_clk : in std_logic 

658""", 

659 ) 

660 

661 project = VivadoNetlistProject( 

662 name="apa", 

663 modules=get_modules(modules_folder=vivado_project_test.modules_path), 

664 part="", 

665 top="zebra", 

666 analyze_synthesis_timing=True, 

667 ) 

668 vivado_project_test.create(project, analyze_synthesis_timing=True) 

669 

670 tcl = read_file(vivado_project_test.project_path / "auto_create_zebra_clocks.tcl") 

671 assert 'create_clock -name "clk"' in tcl 

672 assert 'create_clock -name "clock"' in tcl 

673 assert 'create_clock -name "my_funny_clock"' in tcl 

674 assert 'create_clock -name "clk_my_funny"' in tcl 

675 assert tcl.count("create_clock") == 4 

676 

677 

678def test_netlist_build_auto_detect_clocks_no_file_name_matching_top_should_raise_exception( 

679 vivado_project_test, 

680): 

681 create_file(vivado_project_test.modules_path / "hest" / "apa.vhd", "") 

682 

683 project = VivadoNetlistProject( 

684 name="apa", 

685 modules=get_modules(modules_folder=vivado_project_test.modules_path), 

686 part="", 

687 top="zebra", 

688 analyze_synthesis_timing=True, 

689 ) 

690 with pytest.raises(ValueError) as exception_info: 

691 vivado_project_test.create(project, analyze_synthesis_timing=True) 

692 assert str(exception_info.value) == ( 

693 'Could not find HDL source file corresponding to top-level "zebra".' 

694 ) 

695 

696 

697def test_netlist_build_auto_detect_clocks_no_entity_should_raise_exception( 

698 vivado_project_test, 

699): 

700 file_path = create_file( 

701 vivado_project_test.modules_path / "hest" / "zebra.vhd", 

702 """ 

703library ieee; 

704 

705entity apa is 

706 port ( 

707 clk : in std_logic 

708 ); 

709end entity; 

710""", 

711 ) 

712 

713 project = VivadoNetlistProject( 

714 name="apa", 

715 modules=get_modules(modules_folder=vivado_project_test.modules_path), 

716 part="", 

717 top="zebra", 

718 analyze_synthesis_timing=True, 

719 ) 

720 with pytest.raises(ValueError) as exception_info: 

721 vivado_project_test.create(project, analyze_synthesis_timing=True) 

722 assert str(exception_info.value) == ( 

723 f'Could not find "zebra" entity declaration in "{file_path}".' 

724 ) 

725 

726 

727def test_netlist_build_result_maximum_frequency(vivado_project_test): 

728 def _build_with_slack(analyze_synthesis_timing: bool): 

729 """ 

730 The project.build() call is very similar to _build() method in this class, except this one 

731 also mocks the get_slack_ns() method. 

732 """ 

733 project = VivadoNetlistProject( 

734 name="apa", modules=[], part="", analyze_synthesis_timing=analyze_synthesis_timing 

735 ) 

736 

737 with ( 

738 patch( 

739 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

740 ) as vivado_project_test.mocked_run_vivado_tcl, 

741 patch( 

742 "tsfpga.vivado.project.VivadoNetlistProject._set_auto_clock_constraint", 

743 autospec=True, 

744 ) as _, 

745 patch("tsfpga.vivado.project.VivadoProject._get_size", autospec=True) as _, 

746 patch( 

747 "tsfpga.vivado.project.VivadoNetlistProject._get_logic_level_distribution", 

748 autospec=True, 

749 ) as _, 

750 patch( 

751 "tsfpga.vivado.project.TimingParser.get_slack_ns", autospec=True 

752 ) as mocked_get_slack_ns, 

753 ): 

754 mocked_get_slack_ns.return_value = 1 

755 

756 build_result = project.build( 

757 project_path=vivado_project_test.project_path, 

758 output_path=vivado_project_test.output_path, 

759 run_index=vivado_project_test.run_index, 

760 ) 

761 

762 if project.open_and_analyze_synthesized_design: 

763 mocked_get_slack_ns.assert_called_once_with("timing_file") 

764 assert build_result.maximum_synthesis_frequency_hz == 1e9 

765 else: 

766 mocked_get_slack_ns.assert_not_called() 

767 

768 create_file(vivado_project_test.project_path / "apa.xpr") 

769 create_file( 

770 vivado_project_test.project_path / "apa.runs" / "synth_3" / "timing.rpt", 

771 contents="timing_file", 

772 ) 

773 

774 _build_with_slack(analyze_synthesis_timing=False) 

775 _build_with_slack(analyze_synthesis_timing=True)