Coverage for tsfpga/test/test_module.py: 99%
286 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 08:30 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 08:30 +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# --------------------------------------------------------------------------------------------------
9from pathlib import Path
10from unittest.mock import ANY, MagicMock, patch
12import pytest
14from tsfpga.module import BaseModule, get_module, get_modules
15from tsfpga.system_utils import create_directory, create_file
18def test_add_vunit_config_name():
19 module = BaseModule(path=Path(), library_name="")
21 test = MagicMock()
22 pre_config = MagicMock()
23 post_check = MagicMock()
25 module.add_vunit_config(test=test, pre_config=pre_config, post_check=post_check)
26 test.add_config.assert_called_once_with(
27 name="0", generics={}, pre_config=pre_config, post_check=post_check
28 )
29 test.reset_mock()
31 module.add_vunit_config(test=test, name="apa")
32 test.add_config.assert_called_once_with(
33 name="apa", generics={}, pre_config=None, post_check=None
34 )
35 test.reset_mock()
37 module.add_vunit_config(test=test, generics={"apa": "hest", "foo": "bar"})
38 test.add_config.assert_called_once_with(
39 name="apa_hest.foo_bar",
40 generics={"apa": "hest", "foo": "bar"},
41 pre_config=None,
42 post_check=None,
43 )
44 test.reset_mock()
46 module.add_vunit_config(test=test, name="zebra", generics={"apa": "hest", "foo": "bar"})
47 test.add_config.assert_called_once_with(
48 name="zebra.apa_hest.foo_bar",
49 generics={"apa": "hest", "foo": "bar"},
50 pre_config=None,
51 post_check=None,
52 )
55def test_add_vunit_config_random_seed():
56 module = BaseModule(path=Path(), library_name="")
57 test = MagicMock()
59 # No seed at all
60 module.add_vunit_config(test=test)
61 assert not test.add_config.call_args.kwargs["generics"]
63 module.add_vunit_config(test=test, set_random_seed=False)
64 assert not test.add_config.call_args.kwargs["generics"]
66 # No seed, with generics set
67 module.add_vunit_config(test=test, generics={"apa": "whatever"})
68 assert "seed" not in test.add_config.call_args.kwargs["generics"]
70 # Static seed
71 module.add_vunit_config(test=test, set_random_seed=0)
72 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
73 assert test.add_config.call_args.kwargs["generics"]["seed"] == 0
75 module.add_vunit_config(test=test, set_random_seed=123)
76 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
77 assert test.add_config.call_args.kwargs["generics"]["seed"] == 123
79 # Use random seed
80 module.add_vunit_config(test=test, set_random_seed=True)
81 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int)
82 assert test.add_config.call_args.kwargs["generics"]["seed"] >= 0
84 # Setting explicit value should still work
85 module.add_vunit_config(test=test, generics={"seed": 711})
86 assert test.add_config.call_args.kwargs["generics"]["seed"] == 711
88 # If a value is already set it will be overwritten
89 module.add_vunit_config(test=test, generics={"seed": -5}, set_random_seed=True)
90 assert test.add_config.call_args.kwargs["generics"]["seed"] != -5
93def test_add_vunit_config_count_1():
94 module = BaseModule(path=Path(), library_name="")
95 test = MagicMock()
97 module.add_vunit_config(test=test, count=1)
98 assert test.add_config.call_count == 1
99 assert test.add_config.call_args.kwargs["name"] == "0"
101 module.add_vunit_config(test=test, name="apa")
102 assert test.add_config.call_args.kwargs["name"] == "apa"
105def test_add_vunit_config_count_2():
106 module = BaseModule(path=Path(), library_name="")
107 test = MagicMock()
109 module.add_vunit_config(test=test, count=2)
110 assert test.add_config.call_count == 2
111 assert test.add_config.call_args_list[0].kwargs["name"] == "0"
112 assert test.add_config.call_args_list[1].kwargs["name"] == "1"
114 module.add_vunit_config(test=test, name="apa", count=2)
115 assert test.add_config.call_args_list[2].kwargs["name"] == "apa.0"
116 assert test.add_config.call_args_list[3].kwargs["name"] == "apa.1"
118 module.add_vunit_config(test=test, generics={"apa": True}, count=2)
119 assert test.add_config.call_args_list[4].kwargs["name"] == "apa_True.0"
120 assert test.add_config.call_args_list[5].kwargs["name"] == "apa_True.1"
123def test_file_list_filtering(tmp_path):
124 module_name = "zebra"
125 path = tmp_path / module_name
127 create_directory(path / "folder_should_not_be_included")
128 create_file(path / "should_not_be_included.apa")
130 synth_files = {
131 create_file(path / "syn.v"),
132 create_file(path / "rtl" / "syn.v"),
133 create_file(path / "src" / "syn.vhd"),
134 create_file(path / "hdl" / "rtl" / "syn.vhdl"),
135 create_file(path / "hdl" / "package" / "syn.vhd"),
136 }
138 test_files = {
139 create_file(path / "test" / "test.v"),
140 create_file(path / "rtl" / "tb" / "test.vhd"),
141 }
143 sim_files = {create_file(path / "sim" / "sim.vhd")}
145 my_module = BaseModule(path=path, library_name="zebra")
147 files = {file.path for file in my_module.get_synthesis_files()}
148 assert files == synth_files
150 files = {file.path for file in my_module.get_simulation_files()}
151 assert files == synth_files | test_files | sim_files
153 files = {file.path for file in my_module.get_simulation_files(include_tests=False)}
154 assert files == synth_files | sim_files
156 files = {file.path for file in my_module.get_simulation_files(files_include=synth_files)}
157 assert files == synth_files
159 files = {file.path for file in my_module.get_simulation_files(files_avoid=synth_files)}
160 assert files == test_files | sim_files
163def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments():
164 module = BaseModule(path=Path(), library_name="")
165 with patch("tsfpga.module.BaseModule.get_synthesis_files") as get_synthesis_files:
166 module.get_simulation_files(
167 files_include=True,
168 files_avoid=False,
169 apa=123,
170 include_vhdl_files=1,
171 include_verilog_files=2,
172 include_systemverilog_files=3,
173 )
174 get_synthesis_files.assert_called_once_with(
175 files_include=True,
176 files_avoid=False,
177 apa=123,
178 include_vhdl_files=1,
179 include_verilog_files=2,
180 include_systemverilog_files=3,
181 )
184def test_get_vhdl_files(tmp_path):
185 paths = {
186 create_file(tmp_path / "apa.vhdl"),
187 create_file(tmp_path / "apa.vhd"),
188 }
189 create_file(tmp_path / "apa.v")
190 create_file(tmp_path / "apa.vh")
191 create_file(tmp_path / "apa.sv")
192 create_file(tmp_path / "apa.svh")
194 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_synthesis_files(
195 include_verilog_files=False, include_systemverilog_files=False
196 )
197 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
200def test_get_verilog_files(tmp_path):
201 paths = {create_file(tmp_path / "apa.v"), create_file(tmp_path / "apa.vh")}
202 create_file(tmp_path / "apa.vhdl")
203 create_file(tmp_path / "apa.vhd")
204 create_file(tmp_path / "apa.sv")
205 create_file(tmp_path / "apa.svh")
207 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_simulation_files(
208 include_vhdl_files=False, include_systemverilog_files=False
209 )
210 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
213def test_get_systemverilog_files(tmp_path):
214 paths = {create_file(tmp_path / "apa.sv"), create_file(tmp_path / "apa.svh")}
215 create_file(tmp_path / "apa.vhdl")
216 create_file(tmp_path / "apa.vhd")
217 create_file(tmp_path / "apa.v")
218 create_file(tmp_path / "apa.vh")
220 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_documentation_files(
221 include_vhdl_files=False, include_verilog_files=False
222 )
223 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
226def test_get_documentation_files(tmp_path):
227 module_name = "zebra"
228 path = tmp_path / module_name
230 synth_files = {
231 create_file(path / "rtl" / "syn.v"),
232 create_file(path / "src" / "syn.vhd"),
233 }
235 # Test files
236 create_file(path / "test" / "test.v")
237 create_file(path / "rtl" / "tb" / "test.vhd")
239 sim_files = {create_file(path / "sim" / "sim.vhd")}
241 module = BaseModule(path=path, library_name="zebra")
243 # Should include everything except test files
244 files = {file.path for file in module.get_documentation_files()}
245 assert files == synth_files | sim_files
248def test_scoped_constraints(tmp_path):
249 module_path = tmp_path / "apa"
250 create_file(module_path / "src" / "hest.vhd")
251 create_file(module_path / "scoped_constraints" / "hest.tcl")
253 my_module = BaseModule(module_path, "apa")
254 scoped_constraints = my_module.get_scoped_constraints()
255 assert len(scoped_constraints) == 1
256 assert scoped_constraints[0].ref == "hest"
259def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path):
260 module_path = tmp_path / "apa"
261 create_file(module_path / "scoped_constraints" / "hest.tcl")
263 module = BaseModule(module_path, "apa")
264 with pytest.raises(FileNotFoundError) as exception_info:
265 module.get_scoped_constraints()
266 assert str(exception_info.value).startswith("Could not find a matching entity file")
269def test_can_cast_to_string_without_error():
270 str(BaseModule(Path("dummy"), "dummy"))
273def test_test_case_name():
274 assert (
275 BaseModule.test_case_name(generics={"apa": 3, "hest_zebra": "foo"})
276 == "apa_3.hest_zebra_foo"
277 )
278 assert (
279 BaseModule.test_case_name(name="foo", generics={"apa": 3, "hest_zebra": "bar"})
280 == "foo.apa_3.hest_zebra_bar"
281 )
284def test_getting_registers_calls_registers_hook(tmp_path):
285 with (
286 patch("tsfpga.module.from_toml", autospec=True) as from_toml,
287 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook,
288 ):
289 create_file(tmp_path / "a" / "regs_a.toml")
290 module = BaseModule(path=tmp_path / "a", library_name="a")
291 registers = module.registers
293 # TOML file exists so register creation from TOML should run
294 from_toml.assert_called_once()
295 registers_hook.assert_called_once()
296 assert registers is not None
298 with (
299 patch("tsfpga.module.from_toml", autospec=True) as from_toml,
300 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook,
301 ):
302 module = BaseModule(path=tmp_path / "b", library_name="b")
303 registers = module.registers
305 # TOML file does not exist, so register creation from TOML should not run
306 from_toml.assert_not_called()
307 # Register hook shall still run however
308 registers_hook.assert_called_once()
309 assert registers is None
312def test_creating_synthesis_files_does_not_create_simulation_files(tmp_path):
313 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'")
314 module = BaseModule(path=tmp_path / "a", library_name="a")
316 synthesis_file = module.register_synthesis_folder / "a_regs_pkg.vhd"
317 simulation_file = module.register_simulation_folder / "a_register_read_write_pkg.vhd"
319 module.get_synthesis_files()
320 assert synthesis_file.exists()
321 assert not simulation_file.exists()
322 assert not module.register_simulation_folder.exists()
324 module.get_simulation_files()
325 assert simulation_file.exists()
328def test_old_register_package_should_be_deleted(tmp_path):
329 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'")
330 regs_pkg = create_file(tmp_path / "a" / "a_regs_pkg.vhd")
332 module = BaseModule(path=tmp_path / "a", library_name="a")
333 module.get_synthesis_files()
335 assert not regs_pkg.exists()
338@pytest.fixture
339def get_modules_test(tmp_path):
340 class GetModulesTest:
341 def __init__(self):
342 create_directory(tmp_path / "a")
343 create_directory(tmp_path / "b")
344 create_directory(tmp_path / "c")
346 self.modules_folder = tmp_path
347 self.modules_folders = [self.modules_folder]
349 return GetModulesTest()
352def test_get_module(get_modules_test):
353 module = get_module(name="a", modules_folder=get_modules_test.modules_folder)
354 assert module.name == "a"
355 assert module.library_name == "a"
356 assert module.path == get_modules_test.modules_folder / "a"
358 module = get_module(
359 name="b",
360 modules_folders=[get_modules_test.modules_folder],
361 library_name_has_lib_suffix=True,
362 )
363 assert module.name == "b"
364 assert module.library_name == "b_lib"
365 assert module.path == get_modules_test.modules_folder / "b"
368def test_get_module_not_found_should_raise_exception(get_modules_test):
369 with pytest.raises(RuntimeError) as exception_info:
370 get_module(name="d", modules_folder=get_modules_test.modules_folder)
371 assert str(exception_info.value) == 'Could not find module "d".'
374def test_get_module_found_multiple_should_raise_exception(get_modules_test):
375 create_directory(get_modules_test.modules_folder / "a" / "x")
376 create_directory(get_modules_test.modules_folder / "b" / "x")
378 with pytest.raises(RuntimeError) as exception_info:
379 get_module(
380 name="x",
381 modules_folders=[
382 get_modules_test.modules_folder / "a",
383 get_modules_test.modules_folder / "b",
384 ],
385 )
386 assert str(exception_info.value) == 'Found multiple modules named "x".'
389def test_name_filtering_include(get_modules_test):
390 modules = get_modules(
391 modules_folders=get_modules_test.modules_folders, names_include=["a", "b"]
392 )
393 assert {module.name for module in modules} == {"a", "b"}
396def test_name_filtering_avoid(get_modules_test):
397 modules = get_modules(get_modules_test.modules_folder, names_avoid=["a", "b"])
398 assert {module.name for module in modules} == {"c"}
401def test_name_filtering_include_and_avoid(get_modules_test):
402 modules = get_modules(
403 get_modules_test.modules_folder, names_include=["a", "c"], names_avoid=["b", "c"]
404 )
405 assert {module.name for module in modules} == {"a"}
408def test_library_name_does_not_have_lib_suffix(get_modules_test):
409 modules = get_modules(get_modules_test.modules_folder)
410 assert {module.library_name for module in modules} == {"a", "b", "c"}
413def test_library_name_has_lib_suffix(get_modules_test):
414 modules = get_modules(get_modules_test.modules_folder, library_name_has_lib_suffix=True)
415 assert {module.library_name for module in modules} == {"a_lib", "b_lib", "c_lib"}
418def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test):
419 create_file(get_modules_test.modules_folder / "text_file.txt")
420 modules = get_modules(get_modules_test.modules_folder)
421 assert len(modules) == 3
424def test_local_override_of_module_type(get_modules_test):
425 module_file_content = """
426from tsfpga.module import BaseModule
428class Module(BaseModule):
429 def id(self):
430 return """
432 create_file(get_modules_test.modules_folder / "a" / "module_a.py", module_file_content + '"a"')
433 create_file(get_modules_test.modules_folder / "b" / "module_b.py", module_file_content + '"b"')
435 modules = get_modules(get_modules_test.modules_folder)
437 assert len(modules) == 3
438 for module in modules:
439 if module.name == "a":
440 assert module.id() == "a"
441 elif module.name == "b":
442 assert module.id() == "b"
443 elif module.name == "c":
444 assert isinstance(module, BaseModule)
445 else:
446 raise AssertionError
449@patch("tsfpga.module.from_toml", autospec=True)
450@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
451@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
452@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
453def test_register_toml_file_parsed_only_once_when_getting_synthesis_files(
454 create3, create2, create1, from_toml, tmp_path
455):
456 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
458 module = get_modules(tmp_path).get("a")
459 module.get_synthesis_files()
460 module.get_synthesis_files()
462 from_toml.assert_called_once_with("a", toml_file, ANY)
463 assert create3.call_count == 2
464 assert create2.call_count == 2
465 assert create1.call_count == 2
468@patch("tsfpga.module.from_toml", autospec=True)
469@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
470@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
471@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
472@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
473@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
474@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
475def test_register_toml_file_parsed_only_once_when_getting_simulation_files(
476 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
477):
478 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
480 module = get_modules(tmp_path).get("a")
481 module.get_simulation_files()
482 module.get_simulation_files()
484 from_toml.assert_called_once_with("a", toml_file, ANY)
485 assert create6.call_count == 2
486 assert create5.call_count == 2
487 assert create4.call_count == 2
488 assert create3.call_count == 2
489 assert create2.call_count == 2
490 assert create1.call_count == 2
493@patch("tsfpga.module.from_toml", autospec=True)
494@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
495@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
496@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
497@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
498@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
499@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
500def test_register_toml_file_parsed_only_once_when_getting_mixed_files(
501 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
502):
503 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
505 module = get_modules(tmp_path).get("a")
506 module.get_synthesis_files()
507 module.get_simulation_files()
509 from_toml.assert_called_once_with("a", toml_file, ANY)
510 assert create6.call_count == 1
511 assert create5.call_count == 1
512 assert create4.call_count == 1
513 assert create3.call_count == 2
514 assert create2.call_count == 2
515 assert create1.call_count == 2