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
« 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# --------------------------------------------------------------------------------------------------
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.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
25# ruff: noqa: ARG002
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 )
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 )
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 )
70def test_modules_list_should_be_copied():
71 modules = [1]
72 proj = VivadoProject(name="name", modules=modules, part="part")
74 modules.append(2)
75 assert len(proj.modules) == 1
78def test_static_generics_dictionary_should_be_copied():
79 generics = {"apa": 3}
80 proj = VivadoProject(name="name", modules=[], part="part", generics=generics)
82 generics["apa"] = False
83 assert proj.static_generics["apa"] == 3
86def test_constraints_list_should_be_copied():
87 constraints = [Constraint(file="1")]
88 proj = VivadoProject(name="name", modules=[], part="part", constraints=constraints)
90 constraints.append(Constraint(file="2"))
91 assert len(proj.constraints) == 1
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)])
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'
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()])
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'
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 )
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'
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}'
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 )
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 )
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"
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 )
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()
176def test_copy_and_combine_dict_with_both_arguments_none():
177 assert copy_and_combine_dicts(None, None) == {}
180def test_copy_and_combine_dict_with_first_argument_valid():
181 dict_first = {"first": 1}
183 result = copy_and_combine_dicts(dict_first, None)
184 assert result == {"first": 1}
185 assert dict_first == {"first": 1}
187 dict_first["first_dummy"] = True
188 assert result == {"first": 1}
191def test_copy_and_combine_dict_with_second_argument_valid():
192 dict_second = {"second": 2}
194 result = copy_and_combine_dicts(None, dict_second)
195 assert result == {"second": 2}
196 assert dict_second == {"second": 2}
198 dict_second["second_dummy"] = True
199 assert result == {"second": 2}
202def test_copy_and_combine_dict_with_both_arguments_valid():
203 dict_first = {"first": 1}
204 dict_second = {"second": 2}
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}
211 dict_first["first_dummy"] = True
212 dict_second["second_dummy"] = True
213 assert result == {"first": 1, "second": 2}
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}
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}
225 dict_first["first_dummy"] = True
226 dict_second["second_dummy"] = True
227 assert result == {"first": 1, "second": 2, "common": 4}
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
244 self.mocked_run_vivado_tcl = None
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 )
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 )
275 return VivadoProjectTest()
278def test_default_pre_create_hook_should_pass(vivado_project_test):
279 class CustomVivadoProject(VivadoProject):
280 pass
282 project = CustomVivadoProject(name="apa", modules=[], part="")
283 vivado_project_test.create(project)
284 vivado_project_test.mocked_run_vivado_tcl.assert_called_once()
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
294 assert not vivado_project_test.create(CustomVivadoProject(name="apa", modules=[], part=""))
295 vivado_project_test.mocked_run_vivado_tcl.assert_not_called()
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()
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
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()
340def test_default_pre_and_post_build_hooks_should_pass(vivado_project_test):
341 class CustomVivadoProject(VivadoProject):
342 pass
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()
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
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()
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
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()
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)
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)
396 arguments.update(build_result=unittest.mock.ANY)
397 mocked_post_build.assert_called_once_with(**arguments)
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="")
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()
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()
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 = ""
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"] == {}
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"}
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"}
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"}
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"}
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"}
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
480 module = MagicMock(spec=BaseModule)
481 module.registers = "Some value"
483 project = CustomVivadoProject(name="apa", modules=[module], part="")
484 assert vivado_project_test.create(project)
486 assert module.registers == "Some value"
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
495 module = MagicMock(spec=BaseModule)
496 module.registers = "Some value"
498 project = CustomVivadoProject(name="apa", modules=[module], part="")
499 assert vivado_project_test.build(project).success
501 assert module.registers == "Some value"
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 )
512 project = VivadoProject(name="apa", modules=[], part="", build_step_hooks=[dummy, files])
513 assert vivado_project_test.create(project)
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)}}}")
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 )
525def test_get_size_is_called_correctly(vivado_project_test):
526 project = VivadoProject(name="apa", modules=[], part="")
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"]
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 )
552 assert build_result.synthesis_size == "synth_size"
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")
562 assert build_result.implementation_size == "impl_size"
564 create_file(vivado_project_test.project_path / "apa.xpr")
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 )
575 _build_with_size(synth_only=True)
576 _build_with_size(synth_only=False)
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
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 )
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
616 assert build_result.success
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 )
624 project = VivadoNetlistProject(
625 name="apa", modules=[], part="", build_result_checkers=[MaximumLogicLevel(EqualTo(3))]
626 )
627 _build_with_logic_level_distribution(project=project)
629 project = VivadoProject(name="apa", modules=[], part="")
630 _build_with_logic_level_distribution(project=project)
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;
639-- entity zebra is
640-- port (
641-- bad_clk : in std_logic;
642-- )
643-- end entity;
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 )
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)
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
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", "")
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 )
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;
713entity apa is
714 port (
715 clk : in std_logic
716 );
717end entity;
718""",
719 )
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 )
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 )
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
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 )
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()
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 )
782 _build_with_slack(analyze_synthesis_timing=False)
783 _build_with_slack(analyze_synthesis_timing=True)