Coverage for tsfpga/vivado/test/test_project.py: 100%
239 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project.
5# https://tsfpga.com
6# https://gitlab.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9import unittest
10from unittest.mock import MagicMock, patch
11from pathlib import Path
13import pytest
15from tsfpga.module import BaseModule
16from tsfpga.system_utils import create_directory, create_file
17from tsfpga.vivado.project import VivadoProject, copy_and_combine_dicts
18from tsfpga.vivado.generics import StringGenericValue
20# pylint: disable=unused-import
21from tsfpga.test.conftest import fixture_tmp_path # noqa: F401
24def test_casting_to_string():
25 project = VivadoProject(name="my_project", modules=[], part="")
26 assert (
27 str(project)
28 == """\
29my_project
30Type: VivadoProject
31Top level: my_project_top
32Generics: -
33"""
34 )
36 project = VivadoProject(
37 name="my_project",
38 modules=[],
39 part="",
40 top="apa",
41 generics=dict(hest=True, zebra=3, foo=StringGenericValue("/home/test.vhd")),
42 )
43 assert (
44 str(project)
45 == """\
46my_project
47Type: VivadoProject
48Top level: apa
49Generics: hest=True, zebra=3, foo=/home/test.vhd
50"""
51 )
53 project = VivadoProject(name="my_project", modules=[], part="", apa=123, hest=456)
54 assert (
55 str(project)
56 == """\
57my_project
58Type: VivadoProject
59Top level: my_project_top
60Generics: -
61Arguments: apa=123, hest=456
62"""
63 )
66def test_modules_list_should_be_copied():
67 modules = [1]
68 proj = VivadoProject(name="name", modules=modules, part="part")
70 modules.append(2)
71 assert len(proj.modules) == 1
74def test_static_generics_dictionary_should_be_copied():
75 generics = dict(apa=3)
76 proj = VivadoProject(name="name", modules=[], part="part", generics=generics)
78 generics["apa"] = False
79 assert proj.static_generics["apa"] == 3
82def test_constraints_list_should_be_copied():
83 constraints = [1]
84 proj = VivadoProject(name="name", modules=[], part="part", constraints=constraints)
86 constraints.append(2)
87 assert len(proj.constraints) == 1
90def test_create_should_raise_exception_if_project_path_already_exists(tmp_path):
91 create_directory(tmp_path / "projects" / "name")
92 proj = VivadoProject(name="name", modules=[], part="part")
93 with pytest.raises(ValueError) as exception_info:
94 proj.create(tmp_path / "projects")
95 assert str(exception_info.value).startswith("Folder already exists")
98def test_build_should_raise_exception_if_project_does_not_exists(tmp_path):
99 create_directory(tmp_path / "projects")
100 proj = VivadoProject(name="name", modules=[], part="part")
101 with pytest.raises(ValueError) as exception_info:
102 proj.build(tmp_path / "projects", synth_only=True)
103 assert str(exception_info.value).startswith("Project file does not exist")
106def test_build_with_impl_run_should_raise_exception_if_no_output_path_is_given():
107 proj = VivadoProject(name="name", modules=[], part="part")
108 with pytest.raises(ValueError) as exception_info:
109 proj.build("None")
110 assert str(exception_info.value).startswith("Must specify output_path")
113def test_top_name():
114 assert VivadoProject(name="apa", modules=[], part="").top == "apa_top"
115 assert VivadoProject(name="apa", modules=[], part="", top="hest").top == "hest"
118def test_project_file_name_is_same_as_project_name():
119 project_path = Path("projects/apa")
120 assert (
121 VivadoProject(name="apa", modules=[], part="").project_file(project_path)
122 == project_path / "apa.xpr"
123 )
126def test_project_create(tmp_path):
127 with patch("tsfpga.vivado.project.run_vivado_tcl", autospec=True) as _:
128 assert VivadoProject(name="apa", modules=[], part="").create(tmp_path / "projects" / "apa")
129 assert (tmp_path / "projects" / "apa" / "create_vivado_project.tcl").exists()
132def test_project_create_should_raise_exception_if_project_path_already_exists(tmp_path):
133 project_path = create_directory(tmp_path / "projects" / "apa")
134 with pytest.raises(ValueError) as exception_info:
135 VivadoProject(name="apa", modules=[], part="").create(project_path)
136 assert str(exception_info.value) == f"Folder already exists: {project_path}"
139def test_copy_and_combine_dict_with_both_arguments_none():
140 assert copy_and_combine_dicts(None, None) is None
143def test_copy_and_combine_dict_with_first_argument_valid():
144 dict_first = dict(first=1)
146 result = copy_and_combine_dicts(dict_first, None)
147 assert result == dict(first=1)
148 assert dict_first == dict(first=1)
150 dict_first["first_dummy"] = True
151 assert result == dict(first=1)
154def test_copy_and_combine_dict_with_second_argument_valid():
155 dict_second = dict(second=2)
157 result = copy_and_combine_dicts(None, dict_second)
158 assert result == dict(second=2)
159 assert dict_second == dict(second=2)
161 dict_second["second_dummy"] = True
162 assert result == dict(second=2)
165def test_copy_and_combine_dict_with_both_arguments_valid():
166 dict_first = dict(first=1)
167 dict_second = dict(second=2)
169 result = copy_and_combine_dicts(dict_first, dict_second)
170 assert result == dict(first=1, second=2)
171 assert dict_first == dict(first=1)
172 assert dict_second == dict(second=2)
174 dict_first["first_dummy"] = True
175 dict_second["second_dummy"] = True
176 assert result == dict(first=1, second=2)
179def test_copy_and_combine_dict_with_both_arguments_valid_and_same_key():
180 dict_first = dict(first=1, common=3)
181 dict_second = dict(second=2, common=4)
183 result = copy_and_combine_dicts(dict_first, dict_second)
184 assert result == dict(first=1, second=2, common=4)
185 assert dict_first == dict(first=1, common=3)
186 assert dict_second == dict(second=2, common=4)
188 dict_first["first_dummy"] = True
189 dict_second["second_dummy"] = True
190 assert result == dict(first=1, second=2, common=4)
193# pylint: disable=too-many-instance-attributes
194@pytest.mark.usefixtures("fixture_tmp_path")
195class TestVivadoProject(unittest.TestCase):
197 tmp_path = None
199 def setUp(self):
200 self.project_path = self.tmp_path / "projects" / "apa" / "project"
201 self.output_path = self.tmp_path / "projects" / "apa"
202 self.ip_cache_path = MagicMock()
203 self.build_time_generics = dict(enable=True)
204 self.num_threads = 4
205 self.run_index = 3
206 self.synth_only = False
207 self.from_impl = False
209 self.mocked_run_vivado_tcl = None
211 def _create(self, project, **other_arguments):
212 with patch(
213 "tsfpga.vivado.project.run_vivado_tcl", autospec=True
214 ) as self.mocked_run_vivado_tcl:
215 return project.create(
216 project_path=self.project_path, ip_cache_path=self.ip_cache_path, **other_arguments
217 )
219 def test_default_pre_create_hook_should_pass(self):
220 class CustomVivadoProject(VivadoProject):
222 pass
224 project = CustomVivadoProject(name="apa", modules=[], part="")
225 self._create(project)
226 self.mocked_run_vivado_tcl.assert_called_once()
228 def test_project_pre_create_hook_returning_false_should_fail_and_not_call_vivado_run(self):
229 class CustomVivadoProject(VivadoProject):
230 def pre_create(self, **kwargs): # pylint: disable=no-self-use, unused-argument
231 return False
233 assert not self._create(CustomVivadoProject(name="apa", modules=[], part=""))
234 self.mocked_run_vivado_tcl.assert_not_called()
236 def test_create_should_call_pre_create_with_correct_parameters(self):
237 project = VivadoProject(name="apa", modules=[], part="", generics=dict(apa=123), hest=456)
238 with patch("tsfpga.vivado.project.VivadoProject.pre_create") as mocked_pre_create:
239 self._create(project, zebra=789)
240 mocked_pre_create.assert_called_once_with(
241 project_path=self.project_path,
242 ip_cache_path=self.ip_cache_path,
243 part="",
244 generics=dict(apa=123),
245 hest=456,
246 zebra=789,
247 )
248 self.mocked_run_vivado_tcl.assert_called_once()
250 def _build(self, project):
251 with patch(
252 "tsfpga.vivado.project.run_vivado_tcl", autospec=True
253 ) as self.mocked_run_vivado_tcl, patch(
254 "tsfpga.vivado.project.VivadoProject._get_size", autospec=True
255 ) as _, patch(
256 "tsfpga.vivado.project.shutil.copy2", autospec=True
257 ) as _:
258 create_file(self.project_path / "apa.xpr")
259 return project.build(
260 project_path=self.project_path,
261 output_path=self.output_path,
262 run_index=self.run_index,
263 generics=self.build_time_generics,
264 synth_only=self.synth_only,
265 num_threads=self.num_threads,
266 other_parameter="hest",
267 )
269 def test_build_module_pre_build_hook_and_create_regs_are_called(self):
270 project = VivadoProject(
271 name="apa",
272 modules=[MagicMock(spec=BaseModule), MagicMock(spec=BaseModule)],
273 part="",
274 apa=123,
275 )
276 build_result = self._build(project)
277 assert build_result.success
279 for module in project.modules:
280 module.pre_build.assert_called_once_with(
281 project=project,
282 other_parameter="hest",
283 apa=123,
284 project_path=self.project_path,
285 output_path=self.output_path,
286 run_index=self.run_index,
287 generics=self.build_time_generics,
288 synth_only=self.synth_only,
289 from_impl=self.from_impl,
290 num_threads=self.num_threads,
291 )
292 module.create_regs_vhdl_package.assert_called_once()
294 def test_default_pre_and_post_build_hooks_should_pass(self):
295 class CustomVivadoProject(VivadoProject):
297 pass
299 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part=""))
300 assert build_result.success
301 self.mocked_run_vivado_tcl.assert_called_once()
303 def test_project_pre_build_hook_returning_false_should_fail_and_not_call_vivado_run(self):
304 class CustomVivadoProject(VivadoProject):
305 def pre_build(self, **kwargs): # pylint: disable=no-self-use, unused-argument
306 return False
308 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part=""))
309 assert not build_result.success
310 self.mocked_run_vivado_tcl.assert_not_called()
312 def test_project_post_build_hook_returning_false_should_fail(self):
313 class CustomVivadoProject(VivadoProject):
314 def post_build(self, **kwargs): # pylint: disable=no-self-use, unused-argument
315 return False
317 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part=""))
318 assert not build_result.success
319 self.mocked_run_vivado_tcl.assert_called_once()
321 def test_project_build_hooks_should_be_called_with_correct_parameters(self):
322 project = VivadoProject(
323 name="apa", modules=[], part="", generics=dict(static_generic=2), apa=123
324 )
325 with patch("tsfpga.vivado.project.VivadoProject.pre_build") as mocked_pre_build, patch(
326 "tsfpga.vivado.project.VivadoProject.post_build"
327 ) as mocked_post_build:
328 self._build(project)
330 arguments = dict(
331 project_path=self.project_path,
332 output_path=self.output_path,
333 run_index=self.run_index,
334 generics=copy_and_combine_dicts(dict(static_generic=2), self.build_time_generics),
335 synth_only=self.synth_only,
336 from_impl=self.from_impl,
337 num_threads=self.num_threads,
338 other_parameter="hest",
339 apa=123,
340 )
341 mocked_pre_build.assert_called_once_with(**arguments)
343 # Could be improved by actually checking the build_result object.
344 # See https://gitlab.com/tsfpga/tsfpga/-/issues/39
345 arguments.update(build_result=unittest.mock.ANY)
346 mocked_post_build.assert_called_once_with(**arguments)
348 def test_module_pre_build_hook_returning_false_should_fail_and_not_call_vivado(self):
349 module = MagicMock(spec=BaseModule)
350 module.name = "whatever"
351 project = VivadoProject(name="apa", modules=[module], part="")
353 project.modules[0].pre_build.return_value = True
354 build_result = self._build(project)
355 assert build_result.success
356 self.mocked_run_vivado_tcl.assert_called_once()
358 project.modules[0].pre_build.return_value = False
359 build_result = self._build(project)
360 assert not build_result.success
361 self.mocked_run_vivado_tcl.assert_not_called()
363 @patch("tsfpga.vivado.project.VivadoTcl", autospec=True)
364 def test_different_generic_combinations(self, mocked_vivado_tcl):
365 mocked_vivado_tcl.return_value.build.return_value = ""
367 # No generics
368 self.build_time_generics = None
369 build_result = self._build(VivadoProject(name="apa", modules=[], part=""))
370 assert build_result.success
371 # Note: In python 3.8 we can use call_args.kwargs straight away
372 _, kwargs = mocked_vivado_tcl.return_value.build.call_args
373 assert kwargs["generics"] == {}
375 # Only build time generics
376 self.build_time_generics = dict(runtime="value")
377 build_result = self._build(VivadoProject(name="apa", modules=[], part=""))
378 assert build_result.success
379 _, kwargs = mocked_vivado_tcl.return_value.build.call_args
380 assert kwargs["generics"] == dict(runtime="value")
382 # Static and build time generics
383 self.build_time_generics = dict(runtime="value")
384 build_result = self._build(
385 VivadoProject(name="apa", modules=[], part="", generics=dict(static="a value"))
386 )
387 assert build_result.success
388 _, kwargs = mocked_vivado_tcl.return_value.build.call_args
389 assert kwargs["generics"] == dict(runtime="value", static="a value")
391 # Same key in both static and build time generic. Should prefer build time.
392 self.build_time_generics = dict(static_and_runtime="build value")
393 build_result = self._build(
394 VivadoProject(
395 name="apa", modules=[], part="", generics=dict(static_and_runtime="static value")
396 )
397 )
398 assert build_result.success
399 _, kwargs = mocked_vivado_tcl.return_value.build.call_args
400 assert kwargs["generics"] == dict(static_and_runtime="build value")
402 # Only static generics
403 self.build_time_generics = None
404 build_result = self._build(
405 VivadoProject(name="apa", modules=[], part="", generics=dict(runtime="a value"))
406 )
407 assert build_result.success
408 _, kwargs = mocked_vivado_tcl.return_value.build.call_args
409 assert kwargs["generics"] == dict(runtime="a value")
411 @patch("tsfpga.vivado.project.VivadoTcl", autospec=True)
412 def test_build_time_generics_are_copied(self, mocked_vivado_tcl):
413 mocked_vivado_tcl.return_value.build.return_value = ""
415 self.build_time_generics = dict(runtime="value")
416 build_result = self._build(
417 VivadoProject(name="apa", modules=[], part="", generics=dict(static="a value"))
418 )
419 assert build_result.success
420 assert self.build_time_generics == dict(runtime="value")
422 def test_modules_are_deep_copied_before_pre_create_hook(self):
423 class CustomVivadoProject(VivadoProject):
424 def pre_create(self, **kwargs):
425 self.modules[0].registers = "Some other value"
426 return True
428 module = MagicMock(spec=BaseModule)
429 module.registers = "Some value"
431 project = CustomVivadoProject(name="apa", modules=[module], part="")
432 assert self._create(project)
434 assert module.registers == "Some value"
436 def test_modules_are_deep_copied_before_pre_build_hook(self):
437 class CustomVivadoProject(VivadoProject):
438 def pre_build(self, **kwargs):
439 self.modules[0].registers = "Some other value"
440 return True
442 module = MagicMock(spec=BaseModule)
443 module.registers = "Some value"
445 project = CustomVivadoProject(name="apa", modules=[module], part="")
446 assert self._build(project).success
448 assert module.registers == "Some value"