Coverage for tsfpga/test/test_module.py: 99%
265 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 20:51 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 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# --------------------------------------------------------------------------------------------------
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="test", 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 "generics" not in test.add_config.call_args
63 module.add_vunit_config(test=test, set_random_seed=False)
64 assert "generics" not in test.add_config.call_args
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_file_list_filtering(tmp_path):
94 module_name = "zebra"
95 path = tmp_path / module_name
97 create_directory(path / "folder_should_not_be_included")
98 create_file(path / "should_not_be_included.apa")
100 synth_files = {
101 create_file(path / "syn.v"),
102 create_file(path / "rtl" / "syn.v"),
103 create_file(path / "src" / "syn.vhd"),
104 create_file(path / "hdl" / "rtl" / "syn.vhdl"),
105 create_file(path / "hdl" / "package" / "syn.vhd"),
106 }
108 test_files = {
109 create_file(path / "test" / "test.v"),
110 create_file(path / "rtl" / "tb" / "test.vhd"),
111 }
113 sim_files = {create_file(path / "sim" / "sim.vhd")}
115 my_module = BaseModule(path=path, library_name="zebra")
117 files = {file.path for file in my_module.get_synthesis_files()}
118 assert files == synth_files
120 files = {file.path for file in my_module.get_simulation_files()}
121 assert files == synth_files | test_files | sim_files
123 files = {file.path for file in my_module.get_simulation_files(include_tests=False)}
124 assert files == synth_files | sim_files
126 files = {file.path for file in my_module.get_simulation_files(files_include=synth_files)}
127 assert files == synth_files
129 files = {file.path for file in my_module.get_simulation_files(files_avoid=synth_files)}
130 assert files == test_files | sim_files
133def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments():
134 module = BaseModule(path=Path(), library_name="")
135 with patch("tsfpga.module.BaseModule.get_synthesis_files") as get_synthesis_files:
136 module.get_simulation_files(
137 files_include=True,
138 files_avoid=False,
139 apa=123,
140 include_vhdl_files=1,
141 include_verilog_files=2,
142 include_systemverilog_files=3,
143 )
144 get_synthesis_files.assert_called_once_with(
145 files_include=True,
146 files_avoid=False,
147 apa=123,
148 include_vhdl_files=1,
149 include_verilog_files=2,
150 include_systemverilog_files=3,
151 )
154def test_get_vhdl_files(tmp_path):
155 paths = {
156 create_file(tmp_path / "apa.vhdl"),
157 create_file(tmp_path / "apa.vhd"),
158 }
159 create_file(tmp_path / "apa.v")
160 create_file(tmp_path / "apa.vh")
161 create_file(tmp_path / "apa.sv")
162 create_file(tmp_path / "apa.svh")
164 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_synthesis_files(
165 include_verilog_files=False, include_systemverilog_files=False
166 )
167 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
170def test_get_verilog_files(tmp_path):
171 paths = {create_file(tmp_path / "apa.v"), create_file(tmp_path / "apa.vh")}
172 create_file(tmp_path / "apa.vhdl")
173 create_file(tmp_path / "apa.vhd")
174 create_file(tmp_path / "apa.sv")
175 create_file(tmp_path / "apa.svh")
177 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_simulation_files(
178 include_vhdl_files=False, include_systemverilog_files=False
179 )
180 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
183def test_get_systemverilog_files(tmp_path):
184 paths = {create_file(tmp_path / "apa.sv"), create_file(tmp_path / "apa.svh")}
185 create_file(tmp_path / "apa.vhdl")
186 create_file(tmp_path / "apa.vhd")
187 create_file(tmp_path / "apa.v")
188 create_file(tmp_path / "apa.vh")
190 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_documentation_files(
191 include_vhdl_files=False, include_verilog_files=False
192 )
193 assert {hdl_file.path for hdl_file in got_hdl_files} == paths
196def test_get_documentation_files(tmp_path):
197 module_name = "zebra"
198 path = tmp_path / module_name
200 synth_files = {
201 create_file(path / "rtl" / "syn.v"),
202 create_file(path / "src" / "syn.vhd"),
203 }
205 # Test files
206 create_file(path / "test" / "test.v")
207 create_file(path / "rtl" / "tb" / "test.vhd")
209 sim_files = {create_file(path / "sim" / "sim.vhd")}
211 module = BaseModule(path=path, library_name="zebra")
213 # Should include everything except test files
214 files = {file.path for file in module.get_documentation_files()}
215 assert files == synth_files | sim_files
218def test_scoped_constraints(tmp_path):
219 module_path = tmp_path / "apa"
220 create_file(module_path / "src" / "hest.vhd")
221 create_file(module_path / "scoped_constraints" / "hest.tcl")
223 my_module = BaseModule(module_path, "apa")
224 scoped_constraints = my_module.get_scoped_constraints()
225 assert len(scoped_constraints) == 1
226 assert scoped_constraints[0].ref == "hest"
229def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path):
230 module_path = tmp_path / "apa"
231 create_file(module_path / "scoped_constraints" / "hest.tcl")
233 module = BaseModule(module_path, "apa")
234 with pytest.raises(FileNotFoundError) as exception_info:
235 module.get_scoped_constraints()
236 assert str(exception_info.value).startswith("Could not find a matching entity file")
239def test_can_cast_to_string_without_error():
240 str(BaseModule(Path("dummy"), "dummy"))
243def test_test_case_name():
244 assert (
245 BaseModule.test_case_name(generics={"apa": 3, "hest_zebra": "foo"})
246 == "apa_3.hest_zebra_foo"
247 )
248 assert (
249 BaseModule.test_case_name(name="foo", generics={"apa": 3, "hest_zebra": "bar"})
250 == "foo.apa_3.hest_zebra_bar"
251 )
254def test_getting_registers_calls_registers_hook(tmp_path):
255 with (
256 patch("tsfpga.module.from_toml", autospec=True) as from_toml,
257 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook,
258 ):
259 create_file(tmp_path / "a" / "regs_a.toml")
260 module = BaseModule(path=tmp_path / "a", library_name="a")
261 registers = module.registers
263 # TOML file exists so register creation from TOML should run
264 from_toml.assert_called_once()
265 registers_hook.assert_called_once()
266 assert registers is not None
268 with (
269 patch("tsfpga.module.from_toml", autospec=True) as from_toml,
270 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook,
271 ):
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()
322def test_get_module(get_modules_test):
323 module = get_module(name="a", modules_folder=get_modules_test.modules_folder)
324 assert module.name == "a"
325 assert module.library_name == "a"
326 assert module.path == get_modules_test.modules_folder / "a"
328 module = get_module(
329 name="b",
330 modules_folders=[get_modules_test.modules_folder],
331 library_name_has_lib_suffix=True,
332 )
333 assert module.name == "b"
334 assert module.library_name == "b_lib"
335 assert module.path == get_modules_test.modules_folder / "b"
338def test_get_module_not_found_should_raise_exception(get_modules_test):
339 with pytest.raises(RuntimeError) as exception_info:
340 get_module(name="d", modules_folder=get_modules_test.modules_folder)
341 assert str(exception_info.value) == 'Could not find module "d".'
344def test_get_module_found_multiple_should_raise_exception(get_modules_test):
345 create_directory(get_modules_test.modules_folder / "a" / "x")
346 create_directory(get_modules_test.modules_folder / "b" / "x")
348 with pytest.raises(RuntimeError) as exception_info:
349 get_module(
350 name="x",
351 modules_folders=[
352 get_modules_test.modules_folder / "a",
353 get_modules_test.modules_folder / "b",
354 ],
355 )
356 assert str(exception_info.value) == 'Found multiple modules named "x".'
359def test_name_filtering_include(get_modules_test):
360 modules = get_modules(
361 modules_folders=get_modules_test.modules_folders, names_include=["a", "b"]
362 )
363 assert {module.name for module in modules} == {"a", "b"}
366def test_name_filtering_avoid(get_modules_test):
367 modules = get_modules(get_modules_test.modules_folder, names_avoid=["a", "b"])
368 assert {module.name for module in modules} == {"c"}
371def test_name_filtering_include_and_avoid(get_modules_test):
372 modules = get_modules(
373 get_modules_test.modules_folder, names_include=["a", "c"], names_avoid=["b", "c"]
374 )
375 assert {module.name for module in modules} == {"a"}
378def test_library_name_does_not_have_lib_suffix(get_modules_test):
379 modules = get_modules(get_modules_test.modules_folder)
380 assert {module.library_name for module in modules} == {"a", "b", "c"}
383def test_library_name_has_lib_suffix(get_modules_test):
384 modules = get_modules(get_modules_test.modules_folder, library_name_has_lib_suffix=True)
385 assert {module.library_name for module in modules} == {"a_lib", "b_lib", "c_lib"}
388def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test):
389 create_file(get_modules_test.modules_folder / "text_file.txt")
390 modules = get_modules(get_modules_test.modules_folder)
391 assert len(modules) == 3
394def test_local_override_of_module_type(get_modules_test):
395 module_file_content = """
396from tsfpga.module import BaseModule
398class Module(BaseModule):
399 def id(self):
400 return """
402 create_file(get_modules_test.modules_folder / "a" / "module_a.py", module_file_content + '"a"')
403 create_file(get_modules_test.modules_folder / "b" / "module_b.py", module_file_content + '"b"')
405 modules = get_modules(get_modules_test.modules_folder)
407 assert len(modules) == 3
408 for module in modules:
409 if module.name == "a":
410 assert module.id() == "a"
411 elif module.name == "b":
412 assert module.id() == "b"
413 elif module.name == "c":
414 assert isinstance(module, BaseModule)
415 else:
416 raise AssertionError
419@patch("tsfpga.module.from_toml", autospec=True)
420@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
421@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
422@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
423def test_register_toml_file_parsed_only_once_when_getting_synthesis_files(
424 create3, create2, create1, from_toml, tmp_path
425):
426 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
428 module = get_modules(tmp_path).get("a")
429 module.get_synthesis_files()
430 module.get_synthesis_files()
432 from_toml.assert_called_once_with("a", toml_file, ANY)
433 assert create3.call_count == 2
434 assert create2.call_count == 2
435 assert create1.call_count == 2
438@patch("tsfpga.module.from_toml", autospec=True)
439@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
440@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
441@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
442@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
443@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
444@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
445def test_register_toml_file_parsed_only_once_when_getting_simulation_files(
446 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
447):
448 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
450 module = get_modules(tmp_path).get("a")
451 module.get_simulation_files()
452 module.get_simulation_files()
454 from_toml.assert_called_once_with("a", toml_file, ANY)
455 assert create6.call_count == 2
456 assert create5.call_count == 2
457 assert create4.call_count == 2
458 assert create3.call_count == 2
459 assert create2.call_count == 2
460 assert create1.call_count == 2
463@patch("tsfpga.module.from_toml", autospec=True)
464@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True)
465@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True)
466@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True)
467@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True)
468@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True)
469@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True)
470def test_register_toml_file_parsed_only_once_when_getting_mixed_files(
471 create6, create5, create4, create3, create2, create1, from_toml, tmp_path
472):
473 toml_file = create_file(tmp_path / "a" / "regs_a.toml")
475 module = get_modules(tmp_path).get("a")
476 module.get_synthesis_files()
477 module.get_simulation_files()
479 from_toml.assert_called_once_with("a", toml_file, ANY)
480 assert create6.call_count == 1
481 assert create5.call_count == 1
482 assert create4.call_count == 1
483 assert create3.call_count == 2
484 assert create2.call_count == 2
485 assert create1.call_count == 2