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

338 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-21 23:39 +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.build_result_checker import EqualTo, MaximumLogicLevel 

21from tsfpga.vivado.common import to_tcl_path 

22from tsfpga.vivado.generics import StringGenericValue 

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

24 

25# ruff: noqa: ARG002 

26 

27 

28def test_casting_to_string(): 

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

30 assert ( 

31 str(project) 

32 == """\ 

33my_project 

34Type: VivadoProject 

35Top level: my_project_top 

36Generics: - 

37""" 

38 ) 

39 

40 project = VivadoProject( 

41 name="my_project", 

42 modules=[], 

43 part="", 

44 top="apa", 

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

46 ) 

47 assert ( 

48 str(project) 

49 == """\ 

50my_project 

51Type: VivadoProject 

52Top level: apa 

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

54""" 

55 ) 

56 

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

58 assert ( 

59 str(project) 

60 == """\ 

61my_project 

62Type: VivadoProject 

63Top level: my_project_top 

64Generics: - 

65Arguments: apa=123, hest=456 

66""" 

67 ) 

68 

69 

70def test_modules_list_should_be_copied(): 

71 modules = [1] 

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

73 

74 modules.append(2) 

75 assert len(proj.modules) == 1 

76 

77 

78def test_static_generics_dictionary_should_be_copied(): 

79 generics = {"apa": 3} 

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

81 

82 generics["apa"] = False 

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

84 

85 

86def test_constraints_list_should_be_copied(): 

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

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

89 

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

91 assert len(proj.constraints) == 1 

92 

93 

94def test_bad_constraint_type_should_raise_error(): 

95 # Correct type should not give error 

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

97 

98 # Bad type should raise exception 

99 with pytest.raises(TypeError) as exception_info: 

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

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

102 

103 

104def test_bad_tcl_sources_type_should_raise_error(): 

105 # Correct type should not give error 

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

107 

108 # Bad type should raise exception 

109 with pytest.raises(TypeError) as exception_info: 

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

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

112 

113 

114def test_bad_build_step_hooks_type_should_raise_error(): 

115 # Correct type should not give error 

116 VivadoProject( 

117 name="name", 

118 modules=[], 

119 part="part", 

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

121 ) 

122 

123 # Bad type should raise exception 

124 with pytest.raises(TypeError) as exception_info: 

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

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

127 

128 

129def test_create_should_raise_exception_if_project_path_already_exists(tmp_path): 

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

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

132 with pytest.raises(ValueError) as exception_info: 

133 proj.create(tmp_path / "project") 

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

135 

136 

137def test_build_should_raise_exception_if_project_does_not_exists(tmp_path): 

138 project_path = create_directory(tmp_path / "project") 

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

140 with pytest.raises(ValueError) as exception_info: 

141 proj.build(project_path, synth_only=True) 

142 assert ( 

143 str(exception_info.value) 

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

145 ) 

146 

147 

148def test_build_with_impl_run_should_raise_exception_if_no_output_path_is_given(): 

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

150 with pytest.raises(ValueError) as exception_info: 

151 proj.build("None") 

152 assert str(exception_info.value) == ( 

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

154 ) 

155 

156 

157def test_top_name(): 

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

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

160 

161 

162def test_project_file_name_is_same_as_project_name(): 

163 project_path = Path("projects/apa") 

164 assert ( 

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

166 == project_path / "apa.xpr" 

167 ) 

168 

169 

170def test_project_create(tmp_path): 

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

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

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

174 

175 

176def test_copy_and_combine_dict_with_both_arguments_none(): 

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

178 

179 

180def test_copy_and_combine_dict_with_first_argument_valid(): 

181 dict_first = {"first": 1} 

182 

183 result = copy_and_combine_dicts(dict_first, None) 

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

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

186 

187 dict_first["first_dummy"] = True 

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

189 

190 

191def test_copy_and_combine_dict_with_second_argument_valid(): 

192 dict_second = {"second": 2} 

193 

194 result = copy_and_combine_dicts(None, dict_second) 

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

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

197 

198 dict_second["second_dummy"] = True 

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

200 

201 

202def test_copy_and_combine_dict_with_both_arguments_valid(): 

203 dict_first = {"first": 1} 

204 dict_second = {"second": 2} 

205 

206 result = copy_and_combine_dicts(dict_first, dict_second) 

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

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

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

210 

211 dict_first["first_dummy"] = True 

212 dict_second["second_dummy"] = True 

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

214 

215 

216def test_copy_and_combine_dict_with_both_arguments_valid_and_same_key(): 

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

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

219 

220 result = copy_and_combine_dicts(dict_first, dict_second) 

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

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

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

224 

225 dict_first["first_dummy"] = True 

226 dict_second["second_dummy"] = True 

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

228 

229 

230@pytest.fixture 

231def vivado_project_test(tmp_path): 

232 class VivadoProjectTest: 

233 def __init__(self): 

234 self.modules_path = tmp_path / "modules" 

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

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

237 self.ip_cache_path = MagicMock() 

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

239 self.num_threads = 4 

240 self.run_index = 3 

241 self.synth_only = False 

242 self.from_impl = False 

243 

244 self.mocked_run_vivado_tcl = None 

245 

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

247 with patch( 

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

249 ) as self.mocked_run_vivado_tcl: 

250 return project.create( 

251 project_path=self.project_path, 

252 ip_cache_path=self.ip_cache_path, 

253 **other_arguments, 

254 ) 

255 

256 def build(self, project): 

257 with ( 

258 patch( 

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

260 ) as self.mocked_run_vivado_tcl, 

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

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

263 ): 

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

265 return project.build( 

266 project_path=self.project_path, 

267 output_path=self.output_path, 

268 run_index=self.run_index, 

269 generics=self.build_time_generics, 

270 synth_only=self.synth_only, 

271 num_threads=self.num_threads, 

272 other_parameter="hest", 

273 ) 

274 

275 return VivadoProjectTest() 

276 

277 

278def test_default_pre_create_hook_should_pass(vivado_project_test): 

279 class CustomVivadoProject(VivadoProject): 

280 pass 

281 

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

283 vivado_project_test.create(project) 

284 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

285 

286 

287def test_project_pre_create_hook_returning_false_should_fail_and_not_call_vivado_run( 

288 vivado_project_test, 

289): 

290 class CustomVivadoProject(VivadoProject): 

291 def pre_create(self, **kwargs): 

292 return False 

293 

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

295 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

296 

297 

298def test_create_should_call_pre_create_with_correct_parameters(vivado_project_test): 

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

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

301 vivado_project_test.create(project, zebra=789) 

302 mocked_pre_create.assert_called_once_with( 

303 project_path=vivado_project_test.project_path, 

304 ip_cache_path=vivado_project_test.ip_cache_path, 

305 part="", 

306 generics={"apa": 123}, 

307 hest=456, 

308 zebra=789, 

309 ) 

310 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

311 

312 

313def test_build_module_pre_build_hook_and_create_regs_are_called(vivado_project_test): 

314 project = VivadoProject( 

315 name="apa", 

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

317 part="", 

318 apa=123, 

319 ) 

320 build_result = vivado_project_test.build(project) 

321 assert build_result.success 

322 

323 for module in project.modules: 

324 module.pre_build.assert_called_once_with( 

325 project=project, 

326 other_parameter="hest", 

327 apa=123, 

328 project_path=vivado_project_test.project_path, 

329 output_path=vivado_project_test.output_path, 

330 run_index=vivado_project_test.run_index, 

331 generics=vivado_project_test.build_time_generics, 

332 synth_only=vivado_project_test.synth_only, 

333 from_impl=vivado_project_test.from_impl, 

334 num_threads=vivado_project_test.num_threads, 

335 ) 

336 module.create_register_synthesis_files.assert_called_once() 

337 module.create_register_simulation_files.assert_not_called() 

338 

339 

340def test_default_pre_and_post_build_hooks_should_pass(vivado_project_test): 

341 class CustomVivadoProject(VivadoProject): 

342 pass 

343 

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

345 assert build_result.success 

346 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

347 

348 

349def test_project_pre_build_hook_returning_false_should_fail_and_not_call_vivado_run( 

350 vivado_project_test, 

351): 

352 class CustomVivadoProject(VivadoProject): 

353 def pre_build(self, **kwargs): 

354 return False 

355 

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

357 assert not build_result.success 

358 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

359 

360 

361def test_project_post_build_hook_returning_false_should_fail(vivado_project_test): 

362 class CustomVivadoProject(VivadoProject): 

363 def post_build(self, **kwargs): 

364 return False 

365 

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

367 assert not build_result.success 

368 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

369 

370 

371def test_project_build_hooks_should_be_called_with_correct_parameters(vivado_project_test): 

372 project = VivadoProject( 

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

374 ) 

375 with ( 

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

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

378 ): 

379 vivado_project_test.build(project) 

380 

381 arguments = { 

382 "project_path": vivado_project_test.project_path, 

383 "output_path": vivado_project_test.output_path, 

384 "run_index": vivado_project_test.run_index, 

385 "generics": copy_and_combine_dicts( 

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

387 ), 

388 "synth_only": vivado_project_test.synth_only, 

389 "from_impl": vivado_project_test.from_impl, 

390 "num_threads": vivado_project_test.num_threads, 

391 "other_parameter": "hest", 

392 "apa": 123, 

393 } 

394 mocked_pre_build.assert_called_once_with(**arguments) 

395 

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

397 mocked_post_build.assert_called_once_with(**arguments) 

398 

399 

400def test_module_pre_build_hook_returning_false_should_fail_and_not_call_vivado(vivado_project_test): 

401 module = MagicMock(spec=BaseModule) 

402 module.name = "whatever" 

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

404 

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

406 build_result = vivado_project_test.build(project) 

407 assert build_result.success 

408 vivado_project_test.mocked_run_vivado_tcl.assert_called_once() 

409 

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

411 build_result = vivado_project_test.build(project) 

412 assert not build_result.success 

413 vivado_project_test.mocked_run_vivado_tcl.assert_not_called() 

414 

415 

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

417def test_different_generic_combinations(mocked_vivado_tcl, vivado_project_test): 

418 mocked_vivado_tcl.return_value.build.return_value = "" 

419 

420 # No generics 

421 vivado_project_test.build_time_generics = None 

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

423 assert build_result.success 

424 assert mocked_vivado_tcl.return_value.build.call_args.kwargs["generics"] == {} 

425 

426 # Only build time generics 

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

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

429 assert build_result.success 

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

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

432 

433 # Static and build time generics 

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

435 build_result = vivado_project_test.build( 

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

437 ) 

438 assert build_result.success 

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

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

441 

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

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

444 build_result = vivado_project_test.build( 

445 VivadoProject( 

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

447 ) 

448 ) 

449 assert build_result.success 

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

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

452 

453 # Only static generics 

454 vivado_project_test.build_time_generics = None 

455 build_result = vivado_project_test.build( 

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

457 ) 

458 assert build_result.success 

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

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

461 

462 

463def test_build_time_generics_are_copied(vivado_project_test): 

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

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

466 mocked_vivado_tcl.return_value.build.return_value = "" 

467 build_result = vivado_project_test.build( 

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

469 ) 

470 assert build_result.success 

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

472 

473 

474def test_modules_are_deep_copied_before_pre_create_hook(vivado_project_test): 

475 class CustomVivadoProject(VivadoProject): 

476 def pre_create(self, **kwargs): 

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

478 return True 

479 

480 module = MagicMock(spec=BaseModule) 

481 module.registers = "Some value" 

482 

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

484 assert vivado_project_test.create(project) 

485 

486 assert module.registers == "Some value" 

487 

488 

489def test_modules_are_deep_copied_before_pre_build_hook(vivado_project_test): 

490 class CustomVivadoProject(VivadoProject): 

491 def pre_build(self, **kwargs): 

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

493 return True 

494 

495 module = MagicMock(spec=BaseModule) 

496 module.registers = "Some value" 

497 

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

499 assert vivado_project_test.build(project).success 

500 

501 assert module.registers == "Some value" 

502 

503 

504def test_build_step_hooks_with_same_hook_step(vivado_project_test): 

505 dummy = BuildStepTclHook( 

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

507 ) 

508 files = BuildStepTclHook( 

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

510 ) 

511 

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

513 assert vivado_project_test.create(project) 

514 

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

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

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

518 

519 assert file_contains_string( 

520 vivado_project_test.project_path / "create_vivado_project.tcl", 

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

522 ) 

523 

524 

525def test_get_size_is_called_correctly(vivado_project_test): 

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

527 

528 def _build_with_size(synth_only): 

529 """ 

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

531 the _get_size() method in a different way. 

532 """ 

533 with ( 

534 patch( 

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

536 ) as vivado_project_test.mocked_run_vivado_tcl, 

537 patch( 

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

539 ) as mocked_get_size, 

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

541 ): 

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

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

544 

545 build_result = project.build( 

546 project_path=vivado_project_test.project_path, 

547 output_path=vivado_project_test.output_path, 

548 run_index=vivado_project_test.run_index, 

549 synth_only=synth_only, 

550 ) 

551 

552 assert build_result.synthesis_size == "synth_size" 

553 

554 if synth_only: 

555 mocked_get_size.assert_called_once_with("synth_file") 

556 assert build_result.implementation_size is None 

557 else: 

558 assert mocked_get_size.call_count == 2 

559 mocked_get_size.assert_any_call("synth_file") 

560 mocked_get_size.assert_any_call("impl_file") 

561 

562 assert build_result.implementation_size == "impl_size" 

563 

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

565 

566 create_file( 

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

568 contents="synth_file", 

569 ) 

570 create_file( 

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

572 contents="impl_file", 

573 ) 

574 

575 _build_with_size(synth_only=True) 

576 _build_with_size(synth_only=False) 

577 

578 

579def test_netlist_build_should_set_logic_level_distribution(vivado_project_test): 

580 def _build_with_logic_level_distribution(project): 

581 """ 

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

583 also mocks the _get_logic_level_distribution() method. 

584 """ 

585 with ( 

586 patch( 

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

588 ) as vivado_project_test.mocked_run_vivado_tcl, 

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

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

591 patch( 

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

593 ) as mocked_get_table, 

594 patch( 

595 "tsfpga.vivado.project.LogicLevelDistributionParser.get_maximum_logic_level", 

596 autospec=True, 

597 ) as mocked_get_maximum_logic_level, 

598 ): 

599 mocked_get_table.return_value = "logic_table" 

600 mocked_get_maximum_logic_level.return_value = 3 

601 

602 build_result = project.build( 

603 project_path=vivado_project_test.project_path, 

604 output_path=vivado_project_test.output_path, 

605 run_index=vivado_project_test.run_index, 

606 ) 

607 

608 if project.is_netlist_build: 

609 mocked_get_table.assert_called_once_with("logic_file") 

610 assert build_result.logic_level_distribution == "logic_table" 

611 else: 

612 mocked_get_table.assert_not_called() 

613 assert build_result.logic_level_distribution is None 

614 assert build_result.maximum_logic_level is None 

615 

616 assert build_result.success 

617 

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

619 create_file( 

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

621 contents="logic_file", 

622 ) 

623 

624 project = VivadoNetlistProject( 

625 name="apa", modules=[], part="", build_result_checkers=[MaximumLogicLevel(EqualTo(3))] 

626 ) 

627 _build_with_logic_level_distribution(project=project) 

628 

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

630 _build_with_logic_level_distribution(project=project) 

631 

632 

633def test_netlist_build_auto_detect_clocks(vivado_project_test): 

634 create_file( 

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

636 """ 

637library ieee; 

638 

639-- entity zebra is 

640-- port ( 

641-- bad_clk : in std_logic; 

642-- ) 

643-- end entity; 

644 

645-- bad_clk : in std_logic 

646entity zebra is 

647 -- bad_clk : in std_logic 

648 generic ( 

649 width : positive 

650 bad_clk : in std_logic 

651 ); 

652 -- bad_clk : in std_logic 

653 port ( 

654 clk : in std_logic; 

655 clock : in std_logic; 

656 rst : in std_logic; 

657 my_funny_clock : in std_logic; 

658 clk_my_funny : in std_logic 

659 -- bad_clk : in std_logic; 

660 badclk : in std_logic; 

661 clockbad : in std_logic 

662 ) 

663 -- bad_clk : in std_logic 

664end entity; 

665-- bad_clk : in std_logic 

666""", 

667 ) 

668 

669 project = VivadoNetlistProject( 

670 name="apa", 

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

672 part="", 

673 top="zebra", 

674 analyze_synthesis_timing=True, 

675 ) 

676 vivado_project_test.create(project, analyze_synthesis_timing=True) 

677 

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

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

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

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

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

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

684 

685 

686def test_netlist_build_auto_detect_clocks_no_file_name_matching_top_should_raise_exception( 

687 vivado_project_test, 

688): 

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

690 

691 project = VivadoNetlistProject( 

692 name="apa", 

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

694 part="", 

695 top="zebra", 

696 analyze_synthesis_timing=True, 

697 ) 

698 with pytest.raises(ValueError) as exception_info: 

699 vivado_project_test.create(project, analyze_synthesis_timing=True) 

700 assert str(exception_info.value) == ( 

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

702 ) 

703 

704 

705def test_netlist_build_auto_detect_clocks_no_entity_should_raise_exception( 

706 vivado_project_test, 

707): 

708 file_path = create_file( 

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

710 """ 

711library ieee; 

712 

713entity apa is 

714 port ( 

715 clk : in std_logic 

716 ); 

717end entity; 

718""", 

719 ) 

720 

721 project = VivadoNetlistProject( 

722 name="apa", 

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

724 part="", 

725 top="zebra", 

726 analyze_synthesis_timing=True, 

727 ) 

728 with pytest.raises(ValueError) as exception_info: 

729 vivado_project_test.create(project, analyze_synthesis_timing=True) 

730 assert str(exception_info.value) == ( 

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

732 ) 

733 

734 

735def test_netlist_build_result_maximum_frequency(vivado_project_test): 

736 def _build_with_slack(analyze_synthesis_timing: bool): 

737 """ 

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

739 also mocks the get_slack_ns() method. 

740 """ 

741 project = VivadoNetlistProject( 

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

743 ) 

744 

745 with ( 

746 patch( 

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

748 ) as vivado_project_test.mocked_run_vivado_tcl, 

749 patch( 

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

751 autospec=True, 

752 ) as _, 

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

754 patch( 

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

756 autospec=True, 

757 ) as _, 

758 patch( 

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

760 ) as mocked_get_slack_ns, 

761 ): 

762 mocked_get_slack_ns.return_value = 1 

763 

764 build_result = project.build( 

765 project_path=vivado_project_test.project_path, 

766 output_path=vivado_project_test.output_path, 

767 run_index=vivado_project_test.run_index, 

768 ) 

769 

770 if project.open_and_analyze_synthesized_design: 

771 mocked_get_slack_ns.assert_called_once_with("timing_file") 

772 assert build_result.maximum_synthesis_frequency_hz == 1e9 

773 else: 

774 mocked_get_slack_ns.assert_not_called() 

775 

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

777 create_file( 

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

779 contents="timing_file", 

780 ) 

781 

782 _build_with_slack(analyze_synthesis_timing=False) 

783 _build_with_slack(analyze_synthesis_timing=True)