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
« 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# --------------------------------------------------------------------------------------------------
9import unittest
10from pathlib import Path
11from unittest.mock import MagicMock, patch
13import pytest
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
24# ruff: noqa: ARG002
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 )
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 )
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 )
69def test_modules_list_should_be_copied():
70 modules = [1]
71 proj = VivadoProject(name="name", modules=modules, part="part")
73 modules.append(2)
74 assert len(proj.modules) == 1
77def test_static_generics_dictionary_should_be_copied():
78 generics = {"apa": 3}
79 proj = VivadoProject(name="name", modules=[], part="part", generics=generics)
81 generics["apa"] = False
82 assert proj.static_generics["apa"] == 3
85def test_constraints_list_should_be_copied():
86 constraints = [Constraint(file="1")]
87 proj = VivadoProject(name="name", modules=[], part="part", constraints=constraints)
89 constraints.append(Constraint(file="2"))
90 assert len(proj.constraints) == 1
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)])
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'
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()])
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'
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 )
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'
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}'
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 )
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 )
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"
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 )
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()
175def test_copy_and_combine_dict_with_both_arguments_none():
176 assert copy_and_combine_dicts(None, None) == {}
179def test_copy_and_combine_dict_with_first_argument_valid():
180 dict_first = {"first": 1}
182 result = copy_and_combine_dicts(dict_first, None)
183 assert result == {"first": 1}
184 assert dict_first == {"first": 1}
186 dict_first["first_dummy"] = True
187 assert result == {"first": 1}
190def test_copy_and_combine_dict_with_second_argument_valid():
191 dict_second = {"second": 2}
193 result = copy_and_combine_dicts(None, dict_second)
194 assert result == {"second": 2}
195 assert dict_second == {"second": 2}
197 dict_second["second_dummy"] = True
198 assert result == {"second": 2}
201def test_copy_and_combine_dict_with_both_arguments_valid():
202 dict_first = {"first": 1}
203 dict_second = {"second": 2}
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}
210 dict_first["first_dummy"] = True
211 dict_second["second_dummy"] = True
212 assert result == {"first": 1, "second": 2}
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}
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}
224 dict_first["first_dummy"] = True
225 dict_second["second_dummy"] = True
226 assert result == {"first": 1, "second": 2, "common": 4}
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
243 self.mocked_run_vivado_tcl = None
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 )
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 )
274 return VivadoProjectTest()
277def test_default_pre_create_hook_should_pass(vivado_project_test):
278 class CustomVivadoProject(VivadoProject):
279 pass
281 project = CustomVivadoProject(name="apa", modules=[], part="")
282 vivado_project_test.create(project)
283 vivado_project_test.mocked_run_vivado_tcl.assert_called_once()
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
293 assert not vivado_project_test.create(CustomVivadoProject(name="apa", modules=[], part=""))
294 vivado_project_test.mocked_run_vivado_tcl.assert_not_called()
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()
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
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()
339def test_default_pre_and_post_build_hooks_should_pass(vivado_project_test):
340 class CustomVivadoProject(VivadoProject):
341 pass
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()
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
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()
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
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()
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)
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)
395 arguments.update(build_result=unittest.mock.ANY)
396 mocked_post_build.assert_called_once_with(**arguments)
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="")
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()
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()
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 = ""
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"] == {}
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"}
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"}
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"}
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"}
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"}
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
481 module = MagicMock(spec=BaseModule)
482 module.registers = "Some value"
484 project = CustomVivadoProject(name="apa", modules=[module], part="")
485 assert vivado_project_test.create(project)
487 assert module.registers == "Some value"
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
496 module = MagicMock(spec=BaseModule)
497 module.registers = "Some value"
499 project = CustomVivadoProject(name="apa", modules=[module], part="")
500 assert vivado_project_test.build(project).success
502 assert module.registers == "Some value"
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 )
513 project = VivadoProject(name="apa", modules=[], part="", build_step_hooks=[dummy, files])
514 assert vivado_project_test.create(project)
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)}}}")
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 )
526def test_get_size_is_called_correctly(vivado_project_test):
527 project = VivadoProject(name="apa", modules=[], part="")
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"]
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 )
553 assert build_result.synthesis_size == "synth_size"
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")
563 assert build_result.implementation_size == "impl_size"
565 create_file(vivado_project_test.project_path / "apa.xpr")
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 )
576 _build_with_size(synth_only=True)
577 _build_with_size(synth_only=False)
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"
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 )
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
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 )
618 project = VivadoNetlistProject(name="apa", modules=[], part="")
619 _build_with_logic_level_distribution(project=project)
621 project = VivadoProject(name="apa", modules=[], part="")
622 _build_with_logic_level_distribution(project=project)
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;
631-- entity zebra is
632-- port (
633-- bad_clk : in std_logic;
634-- )
635-- end entity;
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 )
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)
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
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", "")
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 )
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;
705entity apa is
706 port (
707 clk : in std_logic
708 );
709end entity;
710""",
711 )
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 )
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 )
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
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 )
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()
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 )
774 _build_with_slack(analyze_synthesis_timing=False)
775 _build_with_slack(analyze_synthesis_timing=True)