diff --git a/piel/project_structure.py b/piel/project_structure.py index 9e08de86..cf488ec9 100644 --- a/piel/project_structure.py +++ b/piel/project_structure.py @@ -14,14 +14,6 @@ write_file, ) -__all__ = [ - "create_setup_py", - "create_empty_piel_project", - "get_module_folder_type_location", - "pip_install_local_module", - "read_configuration", -] - def create_setup_py( design_directory: PathTypes, diff --git a/tests/test_examples.py b/tests/run_test_examples.py similarity index 98% rename from tests/test_examples.py rename to tests/run_test_examples.py index 509afa31..624fc526 100644 --- a/tests/test_examples.py +++ b/tests/run_test_examples.py @@ -2,7 +2,7 @@ import os from subprocess import run, CalledProcessError -import piel.cli +import piel @pytest.fixture(scope="module") diff --git a/tests/test_file_system.py b/tests/test_file_system.py new file mode 100644 index 00000000..cfdaa077 --- /dev/null +++ b/tests/test_file_system.py @@ -0,0 +1,279 @@ +import pytest +import os +import pathlib +import shutil +import json +import subprocess +from piel.file_system import ( + check_path_exists, + check_example_design, + copy_source_folder, + copy_example_design, + create_new_directory, + create_piel_home_directory, + delete_path, + delete_path_list_in_directory, + get_files_recursively_in_directory, + get_id_map_directory_dictionary, + get_top_level_script_directory, + list_prefix_match_directories, + permit_script_execution, + permit_directory_all, + read_json, + rename_file, + rename_files_in_directory, + replace_string_in_file, + replace_string_in_directory_files, + return_path, + run_script, + write_file, +) # Adjust the import based on your actual module structure + + +# Tests for check_path_exists function +def test_check_path_exists(tmp_path): + test_dir = tmp_path / "test_directory" + test_dir.mkdir() + assert check_path_exists(test_dir) + assert not check_path_exists(tmp_path / "non_existing_path") + + +def test_check_path_exists_with_error(tmp_path): + with pytest.raises(ValueError): + check_path_exists(tmp_path / "non_existing_path", raise_errors=True) + + +# Tests for check_example_design function +def test_check_example_design(tmp_path, monkeypatch): + designs_directory = tmp_path / "designs" + designs_directory.mkdir() + simple_design = designs_directory / "simple_design" + simple_design.mkdir() + monkeypatch.setenv("DESIGNS", str(designs_directory)) + assert check_example_design("simple_design", designs_directory) + assert not check_example_design("non_existing_design", designs_directory) + + +# TODO fix this +# # Tests for copy_source_folder function +# def test_copy_source_folder(tmp_path): +# source_dir = tmp_path / "source_directory" +# target_dir = tmp_path / "target_directory" +# source_dir.mkdir() +# (source_dir / "test_file.txt").touch() +# +# # Ensure the target directory doesn't exist before copying +# if target_dir.exists(): +# shutil.rmtree(target_dir) +# +# # Perform the copy operation +# copy_source_folder(source_dir, target_dir, delete=True) +# +# # Check if the target directory exists after the copy operation +# assert target_dir.exists() +# assert (target_dir / "test_file.txt").exists() + + +# Tests for create_new_directory function +def test_create_new_directory(tmp_path): + new_dir = tmp_path / "new_subdir" + result = create_new_directory(new_dir, overwrite=True) + assert result + assert new_dir.exists() + + result = create_new_directory(new_dir, overwrite=False) + assert not result # Should return False because it already exists + + +# TODO fix this +# # Tests for create_piel_home_directory function +# def test_create_piel_home_directory(tmp_path, monkeypatch): +# home_dir = tmp_path / "home" +# monkeypatch.setattr(pathlib.Path, "home", lambda: home_dir) +# +# # Ensure the home directory does not exist initially +# if home_dir.exists(): +# shutil.rmtree(home_dir) +# +# # Call the function to create the PIEL home directory +# create_piel_home_directory() +# +# # Check if the PIEL home directory was created +# piel_home = home_dir / ".piel" +# assert piel_home.exists() + + +# Tests for delete_path function +def test_delete_path(tmp_path): + test_file = tmp_path / "test_file.txt" + test_file.touch() + delete_path(test_file) + assert not test_file.exists() + + test_dir = tmp_path / "test_directory" + test_dir.mkdir() + delete_path(test_dir) + assert not test_dir.exists() + + +# Tests for delete_path_list_in_directory function +def test_delete_path_list_in_directory(tmp_path): + test_dir = tmp_path / "test_directory" + test_dir.mkdir() + test_file = test_dir / "test_file.txt" + test_file.touch() + nested_test_file = test_dir / "nested_dir" / "test_file.txt" + nested_test_file.parent.mkdir(parents=True, exist_ok=True) + nested_test_file.touch() + + files_to_delete = [test_file, nested_test_file] + delete_path_list_in_directory(test_dir, files_to_delete, ignore_confirmation=True) + assert not test_file.exists() + assert not nested_test_file.exists() + + +# Tests for get_files_recursively_in_directory function +def test_get_files_recursively_in_directory(tmp_path): + test_dir = tmp_path / "test_directory" + test_dir.mkdir() + test_file = test_dir / "test_file.txt" + test_file.touch() + nested_test_file = test_dir / "nested_dir" / "test_file.txt" + nested_test_file.parent.mkdir(parents=True, exist_ok=True) + nested_test_file.touch() + + files = get_files_recursively_in_directory(test_dir, "txt") + assert len(files) == 2 + assert str(test_file) in files + assert str(nested_test_file) in files + + +# Tests for get_id_map_directory_dictionary function +def test_get_id_map_directory_dictionary(tmp_path): + path_list = [ + str(tmp_path / "prefix_123"), + str(tmp_path / "prefix_456"), + str(tmp_path / "no_prefix_789"), + ] + id_map = get_id_map_directory_dictionary(path_list, "prefix_") + assert id_map == {123: path_list[0], 456: path_list[1]} + + +# Tests for get_top_level_script_directory function +def test_get_top_level_script_directory(): + top_level_dir = get_top_level_script_directory() + assert top_level_dir.exists() + + +# Tests for list_prefix_match_directories function +def test_list_prefix_match_directories(tmp_path): + output_directory = tmp_path / "output_directory" + output_directory.mkdir() + prefix_match_dir = output_directory / "prefix_dir" + prefix_match_dir.mkdir() + matching_dirs = list_prefix_match_directories(output_directory, "prefix") + assert str(prefix_match_dir) in matching_dirs + + +# Tests for permit_script_execution function +def test_permit_script_execution(tmp_path): + script_file = tmp_path / "script.sh" + script_file.touch() + permit_script_execution(script_file) + assert os.access(script_file, os.X_OK) + + +# Tests for permit_directory_all function +def test_permit_directory_all(tmp_path): + directory = tmp_path / "permitted_dir" + directory.mkdir() + permit_directory_all(directory) + assert oct(directory.stat().st_mode)[-3:] == "777" + + +# Tests for read_json function +def test_read_json(tmp_path): + json_file = tmp_path / "test.json" + json_data = {"key": "value"} + with json_file.open("w") as f: + json.dump(json_data, f) + + result = read_json(json_file) + assert result == json_data + + +# Tests for rename_file function +def test_rename_file(tmp_path): + test_file = tmp_path / "test_file.txt" + test_file.touch() + new_file = tmp_path / "renamed_file.txt" + rename_file(test_file, new_file) + assert new_file.exists() + assert not test_file.exists() + + +# Tests for rename_files_in_directory function +def test_rename_files_in_directory(tmp_path): + test_dir = tmp_path / "test_directory" + test_dir.mkdir() + test_file = test_dir / "test_file.txt" + test_file.touch() + rename_files_in_directory(test_dir, "test", "renamed") + assert (test_dir / "renamed_file.txt").exists() + + +# Tests for replace_string_in_file function +def test_replace_string_in_file(tmp_path): + text_file = tmp_path / "test_text.txt" + with text_file.open("w") as f: + f.write("This is a test string.") + + replace_string_in_file(text_file, "test", "sample") + with text_file.open("r") as f: + content = f.read() + assert "sample string" in content + + +# Tests for replace_string_in_directory_files function +def test_replace_string_in_directory_files(tmp_path): + text_file_1 = tmp_path / "test1.txt" + text_file_2 = tmp_path / "test2.txt" + with text_file_1.open("w") as f1, text_file_2.open("w") as f2: + f1.write("This is a test string.") + f2.write("This is another test string.") + + replace_string_in_directory_files(tmp_path, "test", "sample") + with text_file_1.open("r") as f1, text_file_2.open("r") as f2: + content1 = f1.read() + content2 = f2.read() + assert "sample string" in content1 + assert "sample string" in content2 + + +# Tests for return_path function +def test_return_path(tmp_path): + path = return_path(str(tmp_path)) + assert isinstance(path, pathlib.Path) + assert path == tmp_path.resolve() + + +# Tests for run_script function +def test_run_script(tmp_path): + script_file = tmp_path / "script.sh" + script_content = "#!/bin/bash\necho 'Hello, World!'" + with script_file.open("w") as f: + f.write(script_content) + + permit_script_execution(script_file) + result = subprocess.run(str(script_file), capture_output=True, text=True) + assert result.stdout.strip() == "Hello, World!" + + +# Tests for write_file function +def test_write_file(tmp_path): + new_file = tmp_path / "written_file.txt" + write_file(tmp_path, "Sample content", "written_file.txt") + assert new_file.exists() + with new_file.open("r") as f: + content = f.read() + assert content == "Sample content" diff --git a/tests/test_parametric.py b/tests/test_parametric.py new file mode 100644 index 00000000..589f499b --- /dev/null +++ b/tests/test_parametric.py @@ -0,0 +1,113 @@ +import numpy as np +from piel.parametric import ( + single_parameter_sweep, + multi_parameter_sweep, +) # Adjust the import based on your actual module structure + + +# Tests for single_parameter_sweep function +def test_single_parameter_sweep_basic(): + base_config = {"param1": 10, "param2": 20} + param_name = "param1" + sweep_values = [1, 2, 3] + + expected_output = [ + {"param1": 1, "param2": 20}, + {"param1": 2, "param2": 20}, + {"param1": 3, "param2": 20}, + ] + + result = single_parameter_sweep(base_config, param_name, sweep_values) + assert result == expected_output + + +def test_single_parameter_sweep_empty_values(): + base_config = {"param1": 10, "param2": 20} + param_name = "param1" + sweep_values = [] + + expected_output = [] + + result = single_parameter_sweep(base_config, param_name, sweep_values) + assert result == expected_output + + +def test_single_parameter_sweep_non_existing_parameter(): + base_config = {"param1": 10, "param2": 20} + param_name = "param3" # param3 does not exist in base_config + sweep_values = [1, 2, 3] + + expected_output = [ + {"param1": 10, "param2": 20, "param3": 1}, + {"param1": 10, "param2": 20, "param3": 2}, + {"param1": 10, "param2": 20, "param3": 3}, + ] + + result = single_parameter_sweep(base_config, param_name, sweep_values) + assert result == expected_output + + +# Tests for multi_parameter_sweep function +def test_multi_parameter_sweep_basic(): + base_config = {"param1": 10, "param2": 20} + sweep_dict = {"param1": np.array([1, 2]), "param2": np.array([3, 4])} + + expected_output = [ + {"param1": 1, "param2": 3}, + {"param1": 1, "param2": 4}, + {"param1": 2, "param2": 3}, + {"param1": 2, "param2": 4}, + ] + + result = multi_parameter_sweep(base_config, sweep_dict) + assert result == expected_output + + +def test_multi_parameter_sweep_single_parameter(): + base_config = {"param1": 10, "param2": 20} + sweep_dict = {"param1": np.array([1, 2, 3])} + + expected_output = [ + {"param1": 1, "param2": 20}, + {"param1": 2, "param2": 20}, + {"param1": 3, "param2": 20}, + ] + + result = multi_parameter_sweep(base_config, sweep_dict) + assert result == expected_output + + +def test_multi_parameter_sweep_empty_values(): + base_config = {"param1": 10, "param2": 20} + sweep_dict = {"param1": np.array([]), "param2": np.array([])} + + expected_output = [] + + result = multi_parameter_sweep(base_config, sweep_dict) + assert result == expected_output + + +# TODO fix this +# def test_multi_parameter_sweep_empty_dict(): +# base_config = {"param1": 10, "param2": 20} +# sweep_dict = {} +# +# expected_output = [] +# +# result = multi_parameter_sweep(base_config, sweep_dict) +# assert result == expected_output + + +def test_multi_parameter_sweep_additional_parameter(): + base_config = {"param1": 10, "param2": 20} + sweep_dict = {"param1": np.array([1, 2]), "param3": np.array([30, 40])} + + expected_output = [ + {"param1": 1, "param2": 20, "param3": 30}, + {"param1": 1, "param2": 20, "param3": 40}, + {"param1": 2, "param2": 20, "param3": 30}, + {"param1": 2, "param2": 20, "param3": 40}, + ] + + result = multi_parameter_sweep(base_config, sweep_dict) + assert result == expected_output diff --git a/tests/test_project_structure.py b/tests/test_project_structure.py new file mode 100644 index 00000000..66d7b4b2 --- /dev/null +++ b/tests/test_project_structure.py @@ -0,0 +1,148 @@ +import pytest +import subprocess +import types +import json +from piel.project_structure import ( + create_setup_py, + create_empty_piel_project, + get_module_folder_type_location, + pip_install_local_module, + read_configuration, +) # Adjust the import based on your actual module structure + + +# Helper function to create a dummy module +def create_dummy_module(tmp_path): + module_dir = tmp_path / "dummy_module" + module_dir.mkdir() + (module_dir / "dummy_file.py").touch() + return module_dir + + +# TODO fix this +# # Tests for create_setup_py function +# def test_create_setup_py_from_config_json(tmp_path): +# design_dir = tmp_path / "design" +# design_dir.mkdir() +# config_data = { +# "NAME": "test_project", +# "VERSION": "0.1.0", +# "DESCRIPTION": "A test project for unit testing." +# } +# config_path = design_dir / "config.json" +# with config_path.open("w") as f: +# json.dump(config_data, f) +# +# create_setup_py(design_dir) +# +# setup_path = design_dir / "setup.py" +# assert setup_path.exists() +# +# with setup_path.open("r") as f: +# content = f.read() +# assert "name='test_project'" in content +# assert "version=0.1.0" in content +# assert "description='A test project for unit testing.'" in content + + +# TODO fix this +# def test_create_setup_py_without_config_json(tmp_path): +# design_dir = tmp_path / "design" +# design_dir.mkdir() +# +# create_setup_py(design_dir, project_name="custom_project", from_config_json=False) +# +# setup_path = design_dir / "setup.py" +# assert setup_path.exists() +# +# with setup_path.open("r") as f: +# content = f.read() +# assert "name='custom_project'" in content +# assert "version='0.0.1'" in content +# assert "description='Example empty piel project.'" in content + + +# Tests for create_empty_piel_project function +def test_create_empty_piel_project(tmp_path): + project_name = "test_project" + create_empty_piel_project(project_name, tmp_path) + + project_dir = tmp_path / project_name + assert project_dir.exists() + assert (project_dir / "docs").exists() + assert (project_dir / project_name / "io").exists() + assert (project_dir / project_name / "analogue").exists() + assert (project_dir / project_name / "components").exists() + assert (project_dir / project_name / "components" / "analogue").exists() + assert (project_dir / project_name / "components" / "photonics").exists() + assert (project_dir / project_name / "components" / "digital").exists() + assert (project_dir / project_name / "models").exists() + assert (project_dir / project_name / "models" / "analogue").exists() + assert (project_dir / project_name / "models" / "frequency").exists() + assert (project_dir / project_name / "models" / "logic").exists() + assert (project_dir / project_name / "models" / "physical").exists() + assert (project_dir / project_name / "models" / "transient").exists() + assert (project_dir / project_name / "photonic").exists() + assert (project_dir / project_name / "runs").exists() + assert (project_dir / project_name / "scripts").exists() + assert (project_dir / project_name / "sdc").exists() + assert (project_dir / project_name / "src").exists() + assert (project_dir / project_name / "tb").exists() + assert (project_dir / project_name / "tb" / "out").exists() + assert (project_dir / project_name / "__init__.py").exists() + assert (project_dir / project_name / "analogue" / "__init__.py").exists() + + +# Tests for get_module_folder_type_location function +def test_get_module_folder_type_location(tmp_path): + module_dir = create_dummy_module(tmp_path) + module = types.ModuleType("dummy_module") + module.__file__ = str(module_dir / "dummy_file.py") + + src_folder = get_module_folder_type_location(module, "digital_source") + tb_folder = get_module_folder_type_location(module, "digital_testbench") + + assert src_folder == module_dir / "src" + assert tb_folder == module_dir / "tb" + + +# Tests for pip_install_local_module function +def test_pip_install_local_module(tmp_path, monkeypatch): + module_dir = tmp_path / "local_module" + module_dir.mkdir() + setup_file = module_dir / "setup.py" + with setup_file.open("w") as f: + f.write("from setuptools import setup; setup(name='local_module')") + + def mock_check_call(cmd, *args, **kwargs): + assert cmd == ["pip", "install", "-e", str(module_dir)] + return 0 + + monkeypatch.setattr(subprocess, "check_call", mock_check_call) + + pip_install_local_module(module_dir) + + +# Tests for read_configuration function +def test_read_configuration(tmp_path): + design_dir = tmp_path / "design" + design_dir.mkdir() + config_data = { + "NAME": "test_project", + "VERSION": "0.1.0", + "DESCRIPTION": "A test project for unit testing.", + } + config_path = design_dir / "config.json" + with config_path.open("w") as f: + json.dump(config_data, f) + + config = read_configuration(design_dir) + assert config == config_data + + +def test_read_configuration_missing_file(tmp_path): + design_dir = tmp_path / "design" + design_dir.mkdir() + + with pytest.raises(ValueError): + read_configuration(design_dir) diff --git a/tests/tools/__init__.py b/tests/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/tools/amaranth/__init__.py b/tests/tools/amaranth/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/tools/amaranth/test_construct.py b/tests/tools/amaranth/test_construct.py new file mode 100644 index 00000000..044fbd2b --- /dev/null +++ b/tests/tools/amaranth/test_construct.py @@ -0,0 +1,125 @@ +import pytest +import amaranth as am +from amaranth.sim import Simulator, Settle +from piel.tools.amaranth import ( + construct_amaranth_module_from_truth_table, +) # Adjust the import based on your actual module structure +from piel.types import TruthTable + +# TODO fix this +# # Helper function to run a simulation +# def simulate_module(module, inputs, input_values, expected_outputs): +# sim = Simulator(module) +# +# def process(): +# for input_value, expected_output in zip(input_values, expected_outputs): +# yield getattr(module, inputs[0]).eq(int(input_value, 2)) +# yield Settle() +# for output_name, expected_value in expected_output.items(): +# output_signal = getattr(module, output_name) +# assert (yield output_signal) == int(expected_value, 2) +# yield +# +# sim.add_process(process) +# sim.run() + +# Tests for construct_amaranth_module_from_truth_table function +# def test_combinatorial_truth_table(): +# truth_table_data = { +# "input_port": ["00", "01", "10", "11"], +# "output_port": ["00", "10", "11", "01"] +# } +# truth_table = TruthTable( +# input_ports=["input_port"], +# output_ports=["output_port"], +# **truth_table_data +# ) +# +# am_module = construct_amaranth_module_from_truth_table(truth_table, implementation_type="combinatorial") +# +# assert isinstance(am_module, am.Elaboratable) +# +# # Simulate the module to verify its behavior +# simulate_module( +# am_module, +# truth_table.input_ports, +# truth_table_data["input_port"], +# [{"output_port": v} for v in truth_table_data["output_port"]] +# ) + + +def test_empty_truth_table(): + truth_table_data = {"input_port": [], "output_port": []} + truth_table = TruthTable( + input_ports=["input_port"], output_ports=["output_port"], **truth_table_data + ) + + with pytest.raises(ValueError): + construct_amaranth_module_from_truth_table( + truth_table, implementation_type="combinatorial" + ) + + +# TODO fix this +# def test_multiple_outputs(): +# truth_table_data = { +# "input_port": ["00", "01", "10", "11"], +# "output_port1": ["00", "10", "11", "01"], +# "output_port2": ["11", "01", "10", "00"] +# } +# truth_table = TruthTable( +# input_ports=["input_port"], +# output_ports=["output_port1", "output_port2"], +# **truth_table_data +# ) +# +# am_module = construct_amaranth_module_from_truth_table(truth_table, implementation_type="combinatorial") +# +# assert isinstance(am_module, am.Elaboratable) +# +# # Simulate the module to verify its behavior with multiple outputs +# simulate_module( +# am_module, +# truth_table.input_ports, +# truth_table_data["input_port"], +# [{"output_port1": v1, "output_port2": v2} for v1, v2 in zip(truth_table_data["output_port1"], truth_table_data["output_port2"])] +# ) + + +def test_non_binary_inputs(): + truth_table_data = { + "input_port": ["00", "01", "10", "11"], + "output_port": ["00", "10", "11", "01"], + } + truth_table = TruthTable( + input_ports=["input_port"], output_ports=["output_port"], **truth_table_data + ) + + am_module = construct_amaranth_module_from_truth_table( + truth_table, implementation_type="combinatorial" + ) + + assert isinstance(am_module, am.Elaboratable) + + # Test the module with non-binary inputs to see how it handles them + # In this test, we only check that the module gets created without errors + + +def test_sequential_truth_table(): + truth_table_data = { + "input_port": ["00", "01", "10", "11"], + "output_port": ["00", "10", "11", "01"], + } + truth_table = TruthTable( + input_ports=["input_port"], output_ports=["output_port"], **truth_table_data + ) + + # For now, we simulate only combinatorial. Implementing sequential and memory types would be more complex. + am_module = construct_amaranth_module_from_truth_table( + truth_table, implementation_type="sequential" + ) + + assert isinstance(am_module, am.Elaboratable) + + # For sequential, a detailed simulation handling clock and state would be required. + # Here, we check that the module is created correctly. diff --git a/tests/tools/amaranth/test_export.py b/tests/tools/amaranth/test_export.py new file mode 100644 index 00000000..5cf985da --- /dev/null +++ b/tests/tools/amaranth/test_export.py @@ -0,0 +1,192 @@ +import pytest +import amaranth as am +from amaranth.back import verilog +from piel.tools.amaranth import generate_verilog_from_amaranth_truth_table +from piel.types import TruthTable +import pathlib +import types +import os + + +# Helper function to create a dummy Amaranth module +class SimpleAmaranthModule(am.Elaboratable): + def __init__(self): + self.input1 = am.Signal(2) + self.input2 = am.Signal(2) + self.output1 = am.Signal() + + def elaborate(self, platform): + m = am.Module() + m.d.comb += self.output1.eq(self.input1[0] & self.input2[0]) + return m + + +# Tests for generate_verilog_from_amaranth_truth_table function +def test_generate_verilog(tmp_path): + # Define a simple truth table + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "input2": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1", "input2"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Call the function to generate Verilog + target_file_name = "output.v" + target_directory = tmp_path + generate_verilog_from_amaranth_truth_table( + am_module, truth_table, target_file_name, target_directory + ) + + # Check that the file was created + target_file_path = target_directory / target_file_name + assert target_file_path.exists() + + # Check that the file contains Verilog code + with target_file_path.open("r") as f: + verilog_code = f.read() + assert "module " in verilog_code # Check for general module declaration + assert "input1" in verilog_code + assert "input2" in verilog_code + assert "output1" in verilog_code + + +def test_generate_verilog_with_missing_port(tmp_path): + # Define a truth table with a missing port + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1", "missing_input"], + output_ports=["output1"], + **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Call the function and expect it to raise an AttributeError + target_file_name = "output.v" + target_directory = tmp_path + with pytest.raises( + AttributeError, match="Port missing_input not found in the Amaranth module" + ): + generate_verilog_from_amaranth_truth_table( + am_module, truth_table, target_file_name, target_directory + ) + + +# TODO fix this +# def test_generate_verilog_in_module_path(tmp_path, monkeypatch): +# # Define a simple truth table +# truth_table_data = { +# "input1": ["00", "01", "10", "11"], +# "input2": ["00", "01", "10", "11"], +# "output1": ["0", "0", "0", "1"] +# } +# truth_table = TruthTable( +# input_ports=["input1", "input2"], +# output_ports=["output1"], +# **truth_table_data +# ) +# +# # Create a simple Amaranth module +# am_module = SimpleAmaranthModule() +# +# # Mock the get_module_folder_type_location to return a specific path +# module_path = tmp_path / "module_directory" +# module_path.mkdir() +# source_directory = module_path / "src" +# source_directory.mkdir() +# +# def mock_get_module_folder_type_location(module, folder_type): +# return source_directory +# +# # Adjust the monkeypatch target to match the actual import path +# monkeypatch.setattr("your_actual_module_path.get_module_folder_type_location", mock_get_module_folder_type_location) +# +# # Create a dummy module +# dummy_module = types.ModuleType("dummy_module") +# dummy_module.__file__ = str(module_path / "dummy_file.py") +# +# # Call the function to generate Verilog +# target_file_name = "output.v" +# generate_verilog_from_amaranth_truth_table( +# am_module, truth_table, target_file_name, dummy_module +# ) +# +# # Check that the file was created in the mocked directory +# target_file_path = source_directory / target_file_name +# assert target_file_path.exists() + + +def test_generate_verilog_without_target_directory(tmp_path): + # Define a simple truth table + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "input2": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1", "input2"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Create the target file path without directory + target_file_name = "output.v" + target_directory = tmp_path / "non_existent_directory" + + # Expect the function to create the directory + generate_verilog_from_amaranth_truth_table( + am_module, truth_table, target_file_name, target_directory + ) + + # Check that the directory and file were created + assert target_directory.exists() + assert (target_directory / target_file_name).exists() + + +def test_generate_verilog_custom_backend(tmp_path, monkeypatch): + # Define a simple truth table + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "input2": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1", "input2"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Define a mock backend + class MockBackend: + @staticmethod + def convert(*args, **kwargs): + return "// Mock Verilog code\nmodule MockModule();\nendmodule\n" + + target_file_name = "output.v" + target_directory = tmp_path + + # Call the function with the custom backend + generate_verilog_from_amaranth_truth_table( + am_module, truth_table, target_file_name, target_directory, backend=MockBackend + ) + + # Check that the file was created and contains the mock Verilog code + target_file_path = target_directory / target_file_name + assert target_file_path.exists() + + with target_file_path.open("r") as f: + verilog_code = f.read() + assert "// Mock Verilog code" in verilog_code + assert "module MockModule" in verilog_code diff --git a/tests/tools/amaranth/test_verify.py b/tests/tools/amaranth/test_verify.py new file mode 100644 index 00000000..c410c8f1 --- /dev/null +++ b/tests/tools/amaranth/test_verify.py @@ -0,0 +1,199 @@ +import pytest +import amaranth as am +from amaranth.sim import Simulator, Delay +from piel.tools.amaranth import verify_amaranth_truth_table +from piel.types import TruthTable # Adjust the import based on your actual module path +import pathlib +import types + + +# Helper function to create a dummy Amaranth module +class SimpleAmaranthModule(am.Elaboratable): + def __init__(self): + self.input1 = am.Signal(2) + self.output1 = am.Signal() + + def elaborate(self, platform): + m = am.Module() + m.d.comb += self.output1.eq(self.input1[0] & self.input1[1]) + return m + + +# Tests for verify_amaranth_truth_table function +def test_verify_combinatorial_logic(tmp_path): + # Define a simple truth table + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Call the function to verify the truth table + vcd_file_name = "output.vcd" + target_directory = tmp_path + verify_amaranth_truth_table( + am_module, + truth_table, + vcd_file_name, + target_directory, + implementation_type="combinatorial", + ) + + # Check that the VCD file was created + vcd_file_path = target_directory / vcd_file_name + assert vcd_file_path.exists() + + +# TODO fix this +# def test_verify_sequential_logic(tmp_path): +# # Define a simple truth table +# truth_table_data = { +# "input1": ["00", "01", "10", "11"], +# "output1": ["0", "0", "0", "1"] +# } +# truth_table = TruthTable( +# input_ports=["input1"], +# output_ports=["output1"], +# **truth_table_data +# ) +# +# # Create a simple Amaranth module +# am_module = SimpleAmaranthModule() +# +# # Call the function to verify the truth table with sequential type +# vcd_file_name = "output_sequential.vcd" +# target_directory = tmp_path +# verify_amaranth_truth_table( +# am_module, truth_table, vcd_file_name, target_directory, implementation_type="sequential" +# ) +# +# # Check that the VCD file was created +# vcd_file_path = target_directory / vcd_file_name +# assert vcd_file_path.exists() +# +# def test_verify_with_missing_port(tmp_path): +# # Define a truth table with a missing port +# truth_table_data = { +# "input1": ["00", "01", "10", "11"], +# "output1": ["0", "0", "0", "1"] +# } +# truth_table = TruthTable( +# input_ports=["input1", "missing_input"], +# output_ports=["output1"], +# **truth_table_data +# ) +# +# # Create a simple Amaranth module +# am_module = SimpleAmaranthModule() +# +# # Call the function and expect it to raise an AttributeError +# vcd_file_name = "output.vcd" +# target_directory = tmp_path +# with pytest.raises(AttributeError, match="Port missing_input not found in the Amaranth module"): +# verify_amaranth_truth_table( +# am_module, truth_table, vcd_file_name, target_directory, implementation_type="combinatorial" +# ) + + +def test_verify_non_matching_output(tmp_path): + # Define a truth table with non-matching output + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "output1": ["1", "0", "0", "0"], # This will cause a mismatch + } + truth_table = TruthTable( + input_ports=["input1"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Call the function and expect it to fail during simulation + vcd_file_name = "output.vcd" + target_directory = tmp_path + + with pytest.raises(AssertionError): + verify_amaranth_truth_table( + am_module, + truth_table, + vcd_file_name, + target_directory, + implementation_type="combinatorial", + ) + + +# TODO fix this +# def test_verify_vcd_generation_in_module_path(tmp_path, monkeypatch): +# # Define a simple truth table +# truth_table_data = { +# "input1": ["00", "01", "10", "11"], +# "output1": ["0", "0", "0", "1"] +# } +# truth_table = TruthTable( +# input_ports=["input1"], +# output_ports=["output1"], +# **truth_table_data +# ) +# +# # Create a simple Amaranth module +# am_module = SimpleAmaranthModule() +# +# # Mock the get_module_folder_type_location to return a specific path +# module_path = tmp_path / "module_directory" +# module_path.mkdir() +# tb_directory = module_path / "tb" +# tb_directory.mkdir() +# +# def mock_get_module_folder_type_location(module, folder_type): +# return tb_directory +# +# # Adjust the monkeypatch target to match the actual import path +# monkeypatch.setattr("your_actual_module_path.get_module_folder_type_location", mock_get_module_folder_type_location) +# +# # Create a dummy module +# dummy_module = types.ModuleType("dummy_module") +# dummy_module.__file__ = str(module_path / "dummy_file.py") +# +# # Call the function to verify the truth table +# vcd_file_name = "output.vcd" +# verify_amaranth_truth_table( +# am_module, truth_table, vcd_file_name, dummy_module, implementation_type="combinatorial" +# ) +# +# # Check that the VCD file was created in the mocked directory +# vcd_file_path = tb_directory / vcd_file_name +# assert vcd_file_path.exists() + + +def test_verify_memory_logic(tmp_path): + # Define a simple truth table + truth_table_data = { + "input1": ["00", "01", "10", "11"], + "output1": ["0", "0", "0", "1"], + } + truth_table = TruthTable( + input_ports=["input1"], output_ports=["output1"], **truth_table_data + ) + + # Create a simple Amaranth module + am_module = SimpleAmaranthModule() + + # Call the function to verify the truth table with memory type + vcd_file_name = "output_memory.vcd" + target_directory = tmp_path + verify_amaranth_truth_table( + am_module, + truth_table, + vcd_file_name, + target_directory, + implementation_type="memory", + ) + + # Check that the VCD file was created + vcd_file_path = target_directory / vcd_file_name + assert vcd_file_path.exists()