Coverage for tsfpga/test/test_module.py: 99%
265 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10from pathlib import Path
11from unittest.mock import ANY, MagicMock, patch
13# Third party libraries
14import pytest
16# First party libraries
17from tsfpga.module import BaseModule, get_module, get_modules
18from tsfpga.system_utils import create_directory, create_file
21def test_add_vunit_config_name():
22 module = BaseModule(path=Path(), library_name="")
24 test = MagicMock()
25 pre_config = MagicMock()
26 post_check = MagicMock()
28 module.add_vunit_config(test=test, pre_config=pre_config, post_check=post_check)
29 test.add_config.assert_called_once_with(
30 name="test", generics={}, pre_config=pre_config, post_check=post_check
31 )
32 test.reset_mock()
34 module.add_vunit_config(test=test, name="apa")
35 test.add_config.assert_called_once_with(
36 name="apa", generics={}, pre_config=None, post_check=None
37 )
38 test.reset_mock()
40 module.add_vunit_config(test=test, generics=dict(apa="hest", foo="bar"))
41 test.add_config.assert_called_once_with(
42 name="apa_hest.foo_bar",
43 generics=dict(apa="hest", foo="bar"),
44 pre_config=None,
45 post_check=None,
46 )
47 test.reset_mock()
49 module.add_vunit_config(test=test, name="zebra", generics=dict(apa="hest", foo="bar"))
50 test.add_config.assert_called_once_with(
51 name="zebra.apa_hest.foo_bar",
52 generics=dict(apa="hest", foo="bar"),
53 pre_config=None,
54 post_check=None,
55 )
58def test_add_vunit_config_random_seed():
59 module = BaseModule(path=Path(), library_name="")
60 test = MagicMock()
62 # No seed at all
63 module.add_vunit_config(test=test)
64 assert "generics" not in test.add_config.call_args
66 module.add_vunit_config(test=test, set_random_seed=False)
67 assert "generics" not in test.add_config.call_args
69 # No seed, with generics set
70 module.add_vunit_config(test=test, generics={"apa": "whatever"})
71 assert "seed" not in test.add_config.call_args.kwargs["generics"]
73 # Static seed
74 module.add_vunit_config(test=test, set_random_seed=0)
75 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
76 assert test.add_config.call_args.kwargs["generics"]["seed"] == 0
78 module.add_vunit_config(test=test, set_random_seed=123)
79 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
80 assert test.add_config.call_args.kwargs["generics"]["seed"] == 123
82 # Use random seed
83 module.add_vunit_config(test=test, set_random_seed=True)
84 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
85 assert test.add_config.call_args.kwargs["generics"]["seed"] >= 0
87 # Setting explicit value should still work
88 module.add_vunit_config(test=test, generics={"seed": 711})
89 assert test.add_config.call_args.kwargs["generics"]["seed"] == 711
91 # If a value is already set it will be overwritten
92 module.add_vunit_config(test=test, generics={"seed": -5}, set_random_seed=True)
93 assert test.add_config.call_args.kwargs["generics"]["seed"] != -5
96def test_file_list_filtering(tmp_path):
97 module_name = "zebra"
98 path = tmp_path / module_name
100 create_directory(path / "folder_should_not_be_included")
101 create_file(path / "should_not_be_included.apa")
103 synth_files = {
104 create_file(path / "syn.v"),
105 create_file(path / "rtl" / "syn.v"),
106 create_file(path / "src" / "syn.vhd"),
107 create_file(path / "hdl" / "rtl" / "syn.vhdl"),
108 create_file(path / "hdl" / "package" / "syn.vhd"),
109 }
111 test_files = {
112 create_file(path / "test" / "test.v"),
113 create_file(path / "rtl" / "tb" / "test.vhd"),
114 }
116 sim_files = {create_file(path / "sim" / "sim.vhd")}
118 my_module = BaseModule(path=path, library_name="zebra")
120 files = {file.path for file in my_module.get_synthesis_files()}
121 assert files == synth_files
123 files = {file.path for file in my_module.get_simulation_files()}
124 assert files == synth_files | test_files | sim_files
126 files = {file.path for file in my_module.get_simulation_files(include_tests=False)}
127 assert files == synth_files | sim_files
129 files = {file.path for file in my_module.get_simulation_files(files_include=synth_files)}
130 assert files == synth_files
132 files = {file.path for file in my_module.get_simulation_files(files_avoid=synth_files)}
133 assert files == test_files | sim_files
136def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments():
137 module = BaseModule(path=Path(), library_name="")
138 with patch("tsfpga.module.BaseModule.get_synthesis_files") as get_synthesis_files:
139 module.get_simulation_files(
140 files_include=True,
141 files_avoid=False,
142 apa=123,
143 include_vhdl_files=1,
144 include_verilog_files=2,
145 include_systemverilog_files=3,
146 )
147 get_synthesis_files.assert_called_once_with(
148 files_include=True,
149 files_avoid=False,
150 apa=123,
151 include_vhdl_files=1,
152 include_verilog_files=2,
153 include_systemverilog_files=3,
154 )
157def test_get_vhdl_files(tmp_path):
158 paths = {
159 create_file(tmp_path / "apa.vhdl"),
160 create_file(tmp_path / "apa.vhd"),
161 }
162 create_file(tmp_path / "apa.v")
163 create_file(tmp_path / "apa.vh")
164 create_file(tmp_path / "apa.sv")
165 create_file(tmp_path / "apa.svh")
167 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_synthesis_files(
168 include_verilog_files=False, include_systemverilog_files=False
169 )
170 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
173def test_get_verilog_files(tmp_path):
174 paths = {create_file(tmp_path / "apa.v"), create_file(tmp_path / "apa.vh")}
175 create_file(tmp_path / "apa.vhdl")
176 create_file(tmp_path / "apa.vhd")
177 create_file(tmp_path / "apa.sv")
178 create_file(tmp_path / "apa.svh")
180 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_simulation_files(
181 include_vhdl_files=False, include_systemverilog_files=False
182 )
183 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
186def test_get_systemverilog_files(tmp_path):
187 paths = {create_file(tmp_path / "apa.sv"), create_file(tmp_path / "apa.svh")}
188 create_file(tmp_path / "apa.vhdl")
189 create_file(tmp_path / "apa.vhd")
190 create_file(tmp_path / "apa.v")
191 create_file(tmp_path / "apa.vh")
193 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_documentation_files(
194 include_vhdl_files=False, include_verilog_files=False
195 )
196 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
199def test_get_documentation_files(tmp_path):
200 module_name = "zebra"
201 path = tmp_path / module_name
203 synth_files = {
204 create_file(path / "rtl" / "syn.v"),
205 create_file(path / "src" / "syn.vhd"),
206 }
208 # Test files
209 create_file(path / "test" / "test.v")
210 create_file(path / "rtl" / "tb" / "test.vhd")
212 sim_files = {create_file(path / "sim" / "sim.vhd")}
214 module = BaseModule(path=path, library_name="zebra")
216 # Should include everything except test files
217 files = {file.path for file in module.get_documentation_files()}
218 assert files == synth_files | sim_files
221def test_scoped_constraints(tmp_path):
222 module_path = tmp_path / "apa"
223 create_file(module_path / "src" / "hest.vhd")
224 create_file(module_path / "scoped_constraints" / "hest.tcl")
226 my_module = BaseModule(module_path, "apa")
227 scoped_constraints = my_module.get_scoped_constraints()
228 assert len(scoped_constraints) == 1
229 assert scoped_constraints[0].ref == "hest"
232def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path):
233 module_path = tmp_path / "apa"
234 create_file(module_path / "scoped_constraints" / "hest.tcl")
236 module = BaseModule(module_path, "apa")
237 with pytest.raises(FileNotFoundError) as exception_info:
238 module.get_scoped_constraints()
239 assert str(exception_info.value).startswith("Could not find a matching entity file")
242def test_can_cast_to_string_without_error():
243 str(BaseModule(Path("dummy"), "dummy"))
246def test_test_case_name():
247 assert (
248 BaseModule.test_case_name(generics=dict(apa=3, hest_zebra="foo")) == "apa_3.hest_zebra_foo"
249 )
250 assert (
251 BaseModule.test_case_name(name="foo", generics=dict(apa=3, hest_zebra="bar"))
252 == "foo.apa_3.hest_zebra_bar"
253 )
256def test_getting_registers_calls_registers_hook(tmp_path):
257 with patch("tsfpga.module.from_toml", autospec=True) as from_toml, patch(
258 "tsfpga.module.BaseModule.registers_hook", autospec=True
259 ) as registers_hook:
260 create_file(tmp_path / "a" / "regs_a.toml")
261 module = BaseModule(path=tmp_path / "a", library_name="a")
262 registers = module.registers
264 # TOML file exists so register creation from TOML should run
265 from_toml.assert_called_once()
266 registers_hook.assert_called_once()
267 assert registers is not None
269 with patch("tsfpga.module.from_toml", autospec=True) as from_toml, patch(
270 "tsfpga.module.BaseModule.registers_hook", autospec=True
271 ) as registers_hook:
272 module = BaseModule(path=tmp_path / "b", library_name="b")
273 registers = module.registers
275 # TOML file does not exist, so register creation from TOML should not run
276 from_toml.assert_not_called()
277 # Register hook shall still run however
278 registers_hook.assert_called_once()
279 assert registers is None
282def test_creating_synthesis_files_does_not_create_simulation_files(tmp_path):
283 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'")
284 module = BaseModule(path=tmp_path / "a", library_name="a")
286 synthesis_file = module.register_synthesis_folder / "a_regs_pkg.vhd"
287 simulation_file = module.register_simulation_folder / "a_register_read_write_pkg.vhd"
289 module.get_synthesis_files()
290 assert synthesis_file.exists()
291 assert not simulation_file.exists()
292 assert not module.register_simulation_folder.exists()
294 module.get_simulation_files()
295 assert simulation_file.exists()
298def test_old_register_package_should_be_deleted(tmp_path):
299 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'")
300 regs_pkg = create_file(tmp_path / "a" / "a_regs_pkg.vhd")
302 module = BaseModule(path=tmp_path / "a", library_name="a")
303 module.get_synthesis_files()
305 assert not regs_pkg.exists()
308@pytest.fixture
309def get_modules_test(tmp_path):
310 class GetModulesTest:
311 def __init__(self):
312 create_directory(tmp_path / "a")
313 create_directory(tmp_path / "b")
314 create_directory(tmp_path / "c")
316 self.modules_folder = tmp_path
317 self.modules_folders = [self.modules_folder]
319 return GetModulesTest()
322# False positive for pytest fixtures
323# pylint: disable=redefined-outer-name
326def test_get_module(get_modules_test):
327 module = get_module(name="a", modules_folder=get_modules_test.modules_folder)
328 assert module.name == "a"
329 assert module.library_name == "a"
330 assert module.path == get_modules_test.modules_folder / "a"
332 module = get_module(
333 name="b",
334 modules_folders=[get_modules_test.modules_folder],
335 library_name_has_lib_suffix=True,
336 )
337 assert module.name == "b"
338 assert module.library_name == "b_lib"
339 assert module.path == get_modules_test.modules_folder / "b"
342def test_get_module_not_found_should_raise_exception(get_modules_test):
343 with pytest.raises(RuntimeError) as exception_info:
344 get_module(name="d", modules_folder=get_modules_test.modules_folder)
345 assert str(exception_info.value) == 'Could not find module "d".'
348def test_get_module_found_multiple_should_raise_exception(get_modules_test):
349 create_directory(get_modules_test.modules_folder / "a" / "x")
350 create_directory(get_modules_test.modules_folder / "b" / "x")
352 with pytest.raises(RuntimeError) as exception_info:
353 get_module(
354 name="x",
355 modules_folders=[
356 get_modules_test.modules_folder / "a",
357 get_modules_test.modules_folder / "b",
358 ],
359 )
360 assert str(exception_info.value) == 'Found multiple modules named "x".'
363def test_name_filtering_include(get_modules_test):
364 modules = get_modules(
365 modules_folders=get_modules_test.modules_folders, names_include=["a", "b"]
366 )
367 assert set(module.name for module in modules) == set(["a", "b"])
370def test_name_filtering_avoid(get_modules_test):
371 modules = get_modules(get_modules_test.modules_folder, names_avoid=["a", "b"])
372 assert set(module.name for module in modules) == set(["c"])
375def test_name_filtering_include_and_avoid(get_modules_test):
376 modules = get_modules(
377 get_modules_test.modules_folder, names_include=["a", "c"], names_avoid=["b", "c"]
378 )
379 assert set(module.name for module in modules) == set(["a"])
382def test_library_name_does_not_have_lib_suffix(get_modules_test):
383 modules = get_modules(get_modules_test.modules_folder)
384 assert set(module.library_name for module in modules) == set(["a", "b", "c"])
387def test_library_name_has_lib_suffix(get_modules_test):
388 modules = get_modules(get_modules_test.modules_folder, library_name_has_lib_suffix=True)
389 assert set(module.library_name for module in modules) == set(["a_lib", "b_lib", "c_lib"])
392def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test):
393 create_file(get_modules_test.modules_folder / "text_file.txt")
394 modules = get_modules(get_modules_test.modules_folder)
395 assert len(modules) == 3
398def test_local_override_of_module_type(get_modules_test):
399 module_file_content = """
400from tsfpga.module import BaseModule
402class Module(BaseModule):
403 def id(self):
404 return """
406 create_file(get_modules_test.modules_folder / "a" / "module_a.py", module_file_content + '"a"')
407 create_file(get_modules_test.modules_folder / "b" / "module_b.py", module_file_content + '"b"')
409 modules = get_modules(get_modules_test.modules_folder)
411 assert len(modules) == 3
412 for module in modules:
413 if module.name == "a":
414 assert module.id() == "a"
415 elif module.name == "b":
416 assert module.id() == "b"
417 elif module.name == "c":
418 assert isinstance(module, BaseModule)
419 else:
420 assert False
423@patch("tsfpga.module.from_toml", autospec=True)
424@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
425@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
426@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
427def test_register_toml_file_parsed_only_once_when_getting_synthesis_files(
428 create3, create2, create1, from_toml, tmp_path
429):
430 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
432 module = get_modules(tmp_path).get("a")
433 module.get_synthesis_files()
434 module.get_synthesis_files()
436 from_toml.assert_called_once_with("a", toml_file, ANY)
437 assert create3.call_count == 2
438 assert create2.call_count == 2
439 assert create1.call_count == 2
442@patch("tsfpga.module.from_toml", autospec=True)
443@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
444@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
445@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
446@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
447@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
448@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
449def test_register_toml_file_parsed_only_once_when_getting_simulation_files(
450 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
451): # pylint: disable=too-many-arguments
452 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
454 module = get_modules(tmp_path).get("a")
455 module.get_simulation_files()
456 module.get_simulation_files()
458 from_toml.assert_called_once_with("a", toml_file, ANY)
459 assert create6.call_count == 2
460 assert create5.call_count == 2
461 assert create4.call_count == 2
462 assert create3.call_count == 2
463 assert create2.call_count == 2
464 assert create1.call_count == 2
467@patch("tsfpga.module.from_toml", autospec=True)
468@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
469@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
470@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
471@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
472@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
473@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
474def test_register_toml_file_parsed_only_once_when_getting_mixed_files(
475 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
476): # pylint: disable=too-many-arguments
477 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
479 module = get_modules(tmp_path).get("a")
480 module.get_synthesis_files()
481 module.get_simulation_files()
483 from_toml.assert_called_once_with("a", toml_file, ANY)
484 assert create6.call_count == 1
485 assert create5.call_count == 1
486 assert create4.call_count == 1
487 assert create3.call_count == 2
488 assert create2.call_count == 2
489 assert create1.call_count == 2