diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index 66a797a5..cbd50fb3 100644 --- a/.github/workflows/action.yaml +++ b/.github/workflows/action.yaml @@ -134,26 +134,42 @@ jobs: run: | python -m generate_and_test --RDL_source_file tests/testcases/simulator_test.rdl --root_node simulator_test + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/simulator_test.rdl --root_node simulator_test --async + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map --legacy_block_access + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map --legacy_enum_type + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map --copy_libraries + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map --hashing_mode PYTHONHASH + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/accelera-generic_example.rdl --root_node some_register_map --hashing_mode SHA256 + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp bool_property_to_include + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp bool_property_to_include enum_property_to_include + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp bool_property_to_include enum_property_to_include int_property_to_include + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp bool_property_to_include enum_property_to_include int_property_to_include str_property_to_include + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp bool_property_to_include enum_property_to_include int_property_to_include str_property_to_include struct_property_to_include double_layer_struct_property_to_include + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/user_defined_properties.rdl --root_node user_defined_properties --udp_regex "bool_property_to_include|enum_property_to_include|int_property_to_include|str_property_to_include|struct_property_to_include|double_layer_struct_property_to_include" + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/reserved_elements.rdl --root_node reserved_elements --hide_regex "(?:[\w_\[\]]+\.)+RSVD" + sleep 10 python -m generate_and_test --RDL_source_file tests/testcases/name_desc_all_levels.rdl --root_node name_desc_all_levels --skip_systemrdl_name_and_desc_properties + sleep 10 peakrdl_integration: needs: @@ -323,7 +339,6 @@ jobs: matrix: python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14"] lib_copy: [true, false] - hashing_mode: ["PYTHONHASH", "SHA256"] steps: - uses: actions/checkout@v4 @@ -349,12 +364,12 @@ jobs: - name: Generate testcases (lib_copy true) if: matrix.lib_copy == true run: | - python generate_testcases.py --copy_libraries --output testcase_output --hashing_mode ${{ matrix.hashing_mode }} + python generate_testcases.py --copy_libraries --output testcase_output - name: Generate testcases (lib_copy false) if: matrix.lib_copy == false run: | - python generate_testcases.py --output testcase_output --hashing_mode ${{ matrix.hashing_mode }} + python generate_testcases.py --output testcase_output - name: Static checks run: | diff --git a/docs/conf.py b/docs/conf.py index 9dafb576..3db1b55e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,7 +45,16 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "sphinx_rtd_theme" +html_theme = "sphinx_book_theme" + +html_theme_options = { + "repository_url": "https://github.com/krcb197/PeakRDL-python", + "path_to_docs": "docs", + "use_download_button": False, + "use_source_button": True, + "use_repository_button": True, + "use_issues_button": True, +} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/requirements.txt b/docs/requirements.txt index 16266c62..7ff1a35b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ pygments-systemrdl peakrdl-python -sphinx_rtd_theme +sphinx-book-theme diff --git a/generate_and_test.py b/generate_and_test.py index e2ce28ad..9282b8e4 100644 --- a/generate_and_test.py +++ b/generate_and_test.py @@ -36,7 +36,7 @@ #from coverage import Coverage -#from peakrdl_ipxact import IPXACTImporter +from peakrdl_ipxact import IPXACTImporter sys.path.append('src') from peakrdl_python import PythonExporter @@ -46,6 +46,8 @@ CommandLineParser = argparse.ArgumentParser(description='Test the framework') CommandLineParser.add_argument('--RDL_source_file', dest='root_RDL_file', type=pathlib.Path, required=True) +CommandLineParser.add_argument('--RDL_additional_file', dest='additional_file_RDL_file', + type=pathlib.Path) CommandLineParser.add_argument('--root_node', dest='root_node', type=str, required=True) CommandLineParser.add_argument('--output', dest='output_path', @@ -195,13 +197,15 @@ def build_logging_cong(logfilepath:str): else: raise(RuntimeError('not a list')) + if CommandLineArgs.additional_file_RDL_file is not None: + rdlc.compile_file(CommandLineArgs.additional_file_RDL_file) rdlc.compile_file(CommandLineArgs.root_RDL_file) spec = rdlc.elaborate(top_def_name=CommandLineArgs.root_node).top node_list = [] for node in spec.descendants(unroll=True): node_list.append(node) - print(node.inst_name) + # print(node.inst_name) # write out text file of all the nodes names, this can be used to debug regex issues if CommandLineArgs.full_inst_file is not None: diff --git a/generate_testcases.py b/generate_testcases.py index dcff9ae3..2fba57f8 100644 --- a/generate_testcases.py +++ b/generate_testcases.py @@ -49,16 +49,6 @@ 'and debugging as multiple copies of the libraries can cause' 'confusion. Therefore by default this script does not copy ' 'them over.') -CommandLineParser.add_argument('--hashing_mode', - dest='hashing_mode', - type=str, - choices=[item.name for item in NodeHashingMethod], - default='PYTHONHASH', - help='The method used to generate the hash of the node, in order to ' - 'deduplicate the register model. Set this to `SHA256` if ' - 'the python names need to stay consistent one export to the ' - 'next. However, this mode is slower') - def compile_rdl(infile: str, incl_search_paths: Optional[List[str]] = None, @@ -158,19 +148,22 @@ def generate(root: Node, outdir: str, options = { 'asyncoutput': [True, False], 'legacy': [True, False], - 'skip_systemrdl_name_and_desc_in_docstring': [True, False] + 'skip_systemrdl_name_and_desc_in_docstring': [True, False], + 'hashing': list(NodeHashingMethod) } - for asyncoutput, legacy, skip_name_and_desc_in_docstring in product( + for asyncoutput, legacy, skip_name_and_desc_in_docstring, hashing_method in product( options['asyncoutput'], options['legacy'], - options['skip_systemrdl_name_and_desc_in_docstring'] ): + options['skip_systemrdl_name_and_desc_in_docstring'], + options['hashing']): # test cases that use the extended widths an not be tested in the non-legacy modes if (testcase_name in ['extended_memories', 'extended_sizes_registers_array']) and \ (legacy is True): continue - folder_parts = 'raw' + folder_parts = 'raw_' + folder_parts += hashing_method.name if asyncoutput: folder_parts += '_async' if legacy: @@ -184,7 +177,7 @@ def generate(root: Node, outdir: str, legacy_enum_type=legacy, copy_library=CommandLineArgs.copy_libraries, skip_systemrdl_name_and_desc_in_docstring=skip_name_and_desc_in_docstring, - hashing_mode=NodeHashingMethod[CommandLineArgs.hashing_mode]) + hashing_mode=hashing_method) module_fqfn = output_path / folder_parts / '__init__.py' with open(module_fqfn, 'w', encoding='utf-8') as fid: diff --git a/pyproject.toml b/pyproject.toml index f36cd28b..6a8ef4ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,11 @@ dev = [ "mypy", "pylint", "coverage", - "peakrdl" + "peakrdl", + # needed for the documentation build + "sphinx-book-theme", + "pygments-systemrdl" + ] peakrdl = [ "peakrdl" diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index b19d141a..51a469d4 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -3,8 +3,8 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation, either version 3 of +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "2.2.0" +__version__ = "3.0.0rc8" diff --git a/src/peakrdl_python/_deploy_package.py b/src/peakrdl_python/_deploy_package.py index 6603c31a..24ec98a3 100644 --- a/src/peakrdl_python/_deploy_package.py +++ b/src/peakrdl_python/_deploy_package.py @@ -154,8 +154,10 @@ class GeneratedPackage(PythonPackage): Args: include_tests (bool): include the tests package """ + # pylint:disable=too-many-instance-attributes template_lib_package = PythonPackage(Path(__file__).parent / 'lib') template_sim_lib_package = PythonPackage(Path(__file__).parent / 'sim_lib') + template_lib_test_package = PythonPackage(Path(__file__).parent / 'lib_test') def __init__(self, path: str, package_name: str, include_tests: bool, include_libraries: bool): super().__init__(Path(path) / package_name) @@ -169,6 +171,9 @@ def __init__(self, path: str, package_name: str, include_tests: bool, include_li self.reg_model = _GeneratedRegModelPackage(self.child_path('reg_model')) if include_tests: + if include_libraries: + self.lib_test = self.child_ref_package('lib_test', + self.template_lib_test_package) self.tests = self.child_package('tests') if include_libraries: @@ -200,4 +205,6 @@ def create_empty_package(self, cleanup: bool) -> None: if self._include_libraries: self.lib.create_empty_package(cleanup=cleanup) self.sim_lib.create_empty_package(cleanup=cleanup) + if self._include_tests: + self.lib_test.create_empty_package(cleanup=cleanup) self.sim.create_empty_package(cleanup=cleanup) diff --git a/src/peakrdl_python/exporter.py b/src/peakrdl_python/exporter.py index e0f66bcd..74ebfb29 100644 --- a/src/peakrdl_python/exporter.py +++ b/src/peakrdl_python/exporter.py @@ -46,7 +46,8 @@ get_memory_max_entry_value_hex_string, get_memory_width_bytes, \ get_field_default_value, get_enum_values, get_properties_to_include, \ HideNodeCallback, hide_based_on_property, \ - full_slice_accessor, ShowUDPCallback + full_slice_accessor, ShowUDPCallback, \ + node_iterator_entry from .unique_component_iterator import UniqueComponents from .unique_component_iterator import PeakRDLPythonUniqueRegisterComponents from .unique_component_iterator import PeakRDLPythonUniqueMemoryComponents @@ -865,7 +866,8 @@ def is_reg_node(node: Node) -> TypeGuard[RegNode]: 'get_properties_to_include': get_properties_to_include, 'hide_node_func': hide_node_func, 'legacy_enum_type': legacy_enum_type, - 'skip_systemrdl_name_and_desc_properties': skip_systemrdl_name_and_desc_properties + 'skip_systemrdl_name_and_desc_properties': skip_systemrdl_name_and_desc_properties, + 'node_iterator_entry': node_iterator_entry, } self.__stream_jinja_template(template_name="addrmap_tb.py.jinja", diff --git a/src/peakrdl_python/lib/async_memory.py b/src/peakrdl_python/lib/async_memory.py index a03122b3..6bbb0911 100644 --- a/src/peakrdl_python/lib/async_memory.py +++ b/src/peakrdl_python/lib/async_memory.py @@ -377,6 +377,10 @@ async def _write(self, start_entry: int, data: Union[Array, list[int]]) -> None: if not isinstance(data, (list, Array)): raise TypeError(f'data should be an array.array got {type(data)}') + if (max(data) > self.max_entry_value) or (min(data) < 0): + raise ValueError('Data out of range for memory must be in the ' + f'range 0 to {self.max_entry_value}') + if len(data) not in range(0, self.entries - start_entry + 1): raise ValueError(f'data length must be in range 0 to {self.entries - start_entry:d} ' f'but got {len(data):d}') diff --git a/src/peakrdl_python/lib/async_register_and_field.py b/src/peakrdl_python/lib/async_register_and_field.py index 54cec7e9..eb4fbbc0 100644 --- a/src/peakrdl_python/lib/async_register_and_field.py +++ b/src/peakrdl_python/lib/async_register_and_field.py @@ -346,8 +346,7 @@ def __init__(self, *, # pylint: enable=too-many-arguments, duplicate-code @asynccontextmanager - async def single_read_modify_write(self, verify: bool = False, skip_write: bool = False) -> \ - AsyncGenerator[Self]: + async def single_read_modify_write(self, verify: bool = False) -> AsyncGenerator[Self]: """ Context manager to allow multiple field reads/write to be done with a single set of field operations @@ -361,11 +360,6 @@ async def single_read_modify_write(self, verify: bool = False, skip_write: bool if self.__in_read_context_manager: raise RuntimeError('using the `single_read_modify_write` context manager within the ' 'single_read` is not permitted') - - if skip_write is True: - warn('The `skip_write` argument will be removed in the future, use `single_read`' - ' instead', - DeprecationWarning, stacklevel=2) # pylint: enable=duplicate-code self.__register_state = await self.read() @@ -379,15 +373,13 @@ async def single_read_modify_write(self, verify: bool = False, skip_write: bool finally: self.__in_read_write_context_manager = False # pylint: enable=duplicate-code - if not skip_write: - await self.write(self.__register_state, verify) + await self.write(self.__register_state, verify) # clear the register states at the end of the context manager self.__register_state = None @asynccontextmanager - async def single_read(self) -> \ - AsyncGenerator[Self]: + async def single_read(self) -> AsyncGenerator[Self]: """ Context manager to allow multiple field reads with a single register read """ diff --git a/src/peakrdl_python/lib/base_field.py b/src/peakrdl_python/lib/base_field.py index df700bee..4a46740a 100644 --- a/src/peakrdl_python/lib/base_field.py +++ b/src/peakrdl_python/lib/base_field.py @@ -385,28 +385,6 @@ class _FieldReadOnlyFramework(Field[FieldType], ABC): """ __slots__ : list[str] = [] - def decode_read_value(self, value: int) -> FieldType: - """ - extracts the field value from a register value, by applying the bit - mask and shift needed - - Args: - value: value to decode, normally read from a register - - Returns: - field value - - Warning: - This method will be removed from a future version, if you have a compelling use - case for it please add a comment to the #184 ticket - - """ - # end users should not need access to the `decode_read_value` as the decoding is done - # for them, it felt like an anomaly that this was public, see #184 - warnings.warn('decode_read_value will be made private in a future version', - DeprecationWarning, stacklevel=2) - return self._decode_read_value(value=value) - def _decode_read_value(self, value: int) -> FieldType: """ extracts the field value from a register value, by applying the bit @@ -476,29 +454,6 @@ def _write_value_checks(self, value: int) -> None: raise ValueError(f'value to be written to register must be less ' f'than or equal to {self.max_value:d}') - - def encode_write_value(self, value: FieldType) -> int: - """ - Check that a value is legal for the field and then encode it in preparation to be written - to the register - - Args: - value: field value - - Returns: - value which can be applied to the register to update the field - - Warning: - This method will be removed from a future version, if you have a compelling use - case for it please add a comment to the #184 ticket - - """ - # end users should not need access to the `decode_read_value` as the decoding is done - # for them, it felt like an anomaly that this was public, see #184 - warnings.warn('encode_write_value will be made private in a future version', - DeprecationWarning, stacklevel=2) - return self._encode_write_value(value=value) - def _encode_write_value(self, value: FieldType) -> int: """ Check that a value is legal for the field and then encode it in preparation to be written diff --git a/src/peakrdl_python/lib/memory.py b/src/peakrdl_python/lib/memory.py index 2026b2fb..ee7683d2 100644 --- a/src/peakrdl_python/lib/memory.py +++ b/src/peakrdl_python/lib/memory.py @@ -99,6 +99,20 @@ def width(self) -> int: """ return self.__memwidth + @property + def max_entry_value(self) -> int: + """ + maximum unsigned integer value that can be stored in a memory entry + + For example: + + * 8-bit memory width returns 0xFF (255) + * 16-bit memory width returns 0xFFFF (65535) + * 32-bit memory width returns 0xFFFF_FFFF (4294967295) + + """ + return (2 ** self.width) - 1 + @property def width_in_bytes(self) -> int: """ @@ -505,6 +519,10 @@ def _write(self, start_entry: int, data: Union[Array, list[int]]) -> None: if not isinstance(data, (Array, list)): raise TypeError(f'data should be an List or array.array got {type(data)}') + if (max(data) > self.max_entry_value) or (min(data) < 0): + raise ValueError('Data out of range for memory must be in the ' + f'range 0 to {self.max_entry_value}') + if len(data) not in range(0, self.entries - start_entry + 1): raise ValueError(f'data length must be in range 0 to {self.entries - start_entry:d} ' f'but got {len(data):d}') diff --git a/src/peakrdl_python/lib/register_and_field.py b/src/peakrdl_python/lib/register_and_field.py index 10db0d96..50bfe898 100644 --- a/src/peakrdl_python/lib/register_and_field.py +++ b/src/peakrdl_python/lib/register_and_field.py @@ -26,7 +26,6 @@ from contextlib import contextmanager from array import array as Array import sys -from warnings import warn from .sections import AddressMap, RegFile from .utility_functions import get_array_typecode @@ -409,7 +408,7 @@ def _cached_access(self, verify: bool = False, skip_write: bool = False, field operations Args: - verify (bool): very the write with a read afterwards + verify (bool): verify the write with a read afterwards skip_write (bool): skip the write back at the end """ self.__register_address_array = \ @@ -667,26 +666,19 @@ def __init__(self, *, # pylint: enable=too-many-arguments, duplicate-code @contextmanager - def single_read_modify_write(self, verify: bool = False, skip_write: bool = False) -> \ - Generator[Self]: + def single_read_modify_write(self, verify: bool = False) -> Generator[Self]: """ Context manager to allow multiple field reads/write to be done with a single set of field operations Args: - verify (bool): very the write with a read afterwards - skip_write (bool): skip the write back at the end + verify (bool): verify the write with a read afterwards """ if self.__in_read_context_manager: raise RuntimeError('using the `single_read_modify_write` context manager within the ' 'single_read` is not permitted') - if skip_write is True: - warn('The `skip_write` argument will be removed in the future, use `single_read`' - ' instead', - DeprecationWarning, stacklevel=2) - self.__register_state = self.read() self.__in_read_write_context_manager = True try: @@ -695,16 +687,13 @@ def single_read_modify_write(self, verify: bool = False, skip_write: bool = Fals # need to make sure the state flag is cleared even if an exception occurs within # the context self.__in_read_write_context_manager = False - - if not skip_write: - self.write(self.__register_state, verify) + self.write(self.__register_state, verify) # clear the register states at the end of the context manager self.__register_state = None @contextmanager - def single_read(self) -> \ - Generator[Self]: + def single_read(self) -> Generator[Self]: """ Context manager to allow multiple field reads with a single register read """ @@ -837,8 +826,7 @@ def __init__(self, *, # pylint: enable=too-many-arguments,duplicate-code @contextmanager - def single_read(self) -> \ - Generator[Self]: + def single_read(self) -> Generator[Self]: """ Context manager to allow multiple field reads/write to be done with a single set of field operations @@ -889,8 +877,7 @@ def __init__(self, *, # pylint: enable=too-many-arguments,duplicate-code @contextmanager - def single_write(self) -> \ - Generator[Self]: + def single_write(self) -> Generator[Self]: """ Context manager to allow multiple field reads/write to be done with a single set of field operations diff --git a/src/peakrdl_python/lib/utility_functions.py b/src/peakrdl_python/lib/utility_functions.py index b1554f51..00071ac0 100644 --- a/src/peakrdl_python/lib/utility_functions.py +++ b/src/peakrdl_python/lib/utility_functions.py @@ -59,7 +59,7 @@ def get_array_typecode(width: int) -> str: return 'Q' if width == 16: - return 'I' + return 'H' if width == 8: return 'B' diff --git a/src/peakrdl_python/lib_test/__init__.py b/src/peakrdl_python/lib_test/__init__.py new file mode 100644 index 00000000..33bb43da --- /dev/null +++ b/src/peakrdl_python/lib_test/__init__.py @@ -0,0 +1,26 @@ +""" +peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL +Copyright (C) 2021 - 2025 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +This package is intended to distributed as part of automatically generated code by the PeakRDL +Python tool. It provides a set of base test classes to reduce the size of the auto generated code +""" +from .base_reg_test_class import LibTestBase +from .async_reg_base_test_class import AsyncLibTestBase + +from .utilities import reverse_bits + +from ._common_base_test_class import NodeIterators diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py new file mode 100644 index 00000000..0ceb5ed1 --- /dev/null +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -0,0 +1,552 @@ +""" +peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL +Copyright (C) 2021 - 2025 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +This package is intended to distributed as part of automatically generated code by the PeakRDL +Python tool. It provide the base class common to both the async and non-async versions +""" +import unittest +from abc import ABC, abstractmethod +from typing import Union, Optional +from itertools import product + +from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly +from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite +from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly +from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly +from ..lib import AddressMap, AsyncAddressMap +from ..lib import RegFile, AsyncRegFile +from ..lib.memory import BaseMemory +from ..lib import MemoryReadOnly, MemoryReadOnlyLegacy +from ..lib import MemoryWriteOnly, MemoryWriteOnlyLegacy +from ..lib import MemoryReadWrite, MemoryReadWriteLegacy +from ..lib import MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy +from ..lib import MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy +from ..lib import MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy +from ..lib.base_register import BaseReg +from ..lib import Node +from ..lib import Base +from ..lib import SystemRDLEnum +from .utilities import get_field_bitmask_int, get_field_inv_bitmask +from ..sim_lib.simulator import BaseSimulator + + +class NodeIterators: + """ + The Node Iterator class is intended to an efficient way to define the iterators of particular + type that are present on a node + """ + __slots__ = ['__node_descriptions'] + + def __init__(self, *args: Union[str, tuple[str, list[int]]]): + self.__node_descriptions = args + + @staticmethod + def __rolled_item(item: Union[str, tuple[str, list[int]]]) -> str: + if isinstance(item, tuple): + return item[0] + return item + + @property + def rolled(self) -> set[str]: + """ + name of all the rolled nodes in a set + """ + return {self.__rolled_item(item) for item in self.__node_descriptions} + + @property + def unrolled(self) -> set[str]: + """ + name of all the unrolled nodes in a set + """ + return_list = [] + for item in self.__node_descriptions: + if isinstance(item, tuple): + dim_set = list(product(*[range(dim) for dim in item[1]])) + for dim in dim_set: + # to match the systemrdl compiler dimension put into the inst name of + # the array, the name must be item[x][y] + dim_str = ''.join([f'[{str(i)}]' for i in dim]) + return_list.append(f'{item[0]}{dim_str}') + else: + return_list.append(item) + return set(return_list) + + +class CommonTestBase(unittest.TestCase, ABC): + """ + Base Test class for the autogenerated register test to be used for the async and + non-async cases + """ + + @property + @abstractmethod + def simulator_instance(self) -> BaseSimulator: + """ + Simulator configured for the DUT + """ + + @property + @abstractmethod + def legacy_block_access(self) -> bool: + """ + Whether the register model has been configured for legacy block access or not + """ + + # pylint:disable-next=too-many-arguments + def _single_field_property_test(self, *, + fut: Union[FieldReadWrite, + FieldReadOnly, + FieldWriteOnly, + FieldEnumReadWrite, + FieldEnumReadOnly, + FieldEnumWriteOnly, + FieldAsyncReadOnly, + FieldAsyncWriteOnly, + FieldAsyncReadWrite, + FieldEnumAsyncReadOnly, + FieldEnumAsyncWriteOnly, + FieldEnumAsyncReadWrite], + lsb: int, + msb: int, + low: int, + high: int, + is_volatile: bool, + default: Optional[int], + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: str, + inst_name: str + ) -> None: + self.assertEqual(fut.lsb, lsb) + self.assertEqual(fut.msb, msb) + self.assertEqual(fut.low, low) + self.assertEqual(fut.high, high) + self.assertEqual(fut.bitmask, get_field_bitmask_int(fut)) + self.assertEqual(fut.inverse_bitmask, get_field_inv_bitmask(fut)) + width = (fut.high - fut.low) + 1 + self.assertEqual(fut.width, width) + self.assertEqual(fut.max_value, (2 ** width) - 1) + self.assertEqual(fut.is_volatile, is_volatile) + + if default is None: + self.assertIsNone(fut.default) + else: + if isinstance(fut, (FieldEnumReadWrite, + FieldEnumReadOnly, + FieldEnumWriteOnly, + FieldEnumAsyncReadOnly, + FieldEnumAsyncWriteOnly, + FieldEnumAsyncReadWrite)): + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + # pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + if default in [item.value for item in fut.enum_cls]: + self.assertEqual(fut.default, EnumCls(default)) + else: + # this is a special case if the default value for the field does not map + # to a legal value of the encoding + self.assertIsNone(fut.default) + else: + self.assertEqual(fut.default, default) + + self.__single_node_rdl_name_and_desc_test(dut=fut, + rdl_name=rdl_name, + rdl_desc=rdl_desc) + + self.__test_node_inst_name(dut=fut, + parent_full_inst_name=parent_full_inst_name, + inst_name=inst_name) + + self.__bad_attribute_test(dut=fut) + + # pylint:disable-next=too-many-arguments + def _single_register_property_test(self, *, + rut: BaseReg, + address: int, + width: int, + accesswidth: Optional[int], + size: int, + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: str, + inst_name: str + ) -> None: + self.assertEqual(rut.address, address) + self.assertEqual(rut.width, width) + if accesswidth is not None: + self.assertEqual(rut.accesswidth, accesswidth) + else: + self.assertEqual(rut.accesswidth, width) + self.assertEqual(rut.size, size) + + self.__single_node_rdl_name_and_desc_test(dut=rut, + rdl_name=rdl_name, + rdl_desc=rdl_desc) + + self.__test_node_inst_name(dut=rut, + parent_full_inst_name=parent_full_inst_name, + inst_name=inst_name) + + self.__bad_attribute_test(dut=rut) + + # pylint:disable-next=too-many-arguments + def _single_memory_property_test(self, *, + mut: BaseMemory, + address: int, + width: int, + entries: int, + accesswidth: Optional[int], + array_typecode: Optional[str], + size: int, + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: str, + inst_name: str + ) -> None: + self.assertEqual(mut.address, address) + self.assertEqual(mut.width, width) + self.assertEqual(mut.entries, entries) + if accesswidth is not None: + self.assertEqual(mut.accesswidth, accesswidth) + else: + self.assertEqual(mut.accesswidth, width) + if self.legacy_block_access: + self.assertEqual(mut.array_typecode, array_typecode) + else: + self.assertIsNone(array_typecode) + self.assertEqual(mut.size, size) + + self.__single_node_rdl_name_and_desc_test(dut=mut, + rdl_name=rdl_name, + rdl_desc=rdl_desc) + + self.__test_node_inst_name(dut=mut, + parent_full_inst_name=parent_full_inst_name, + inst_name=inst_name) + + self.__bad_attribute_test(dut=mut) + + # pylint:disable-next=too-many-arguments + def _single_addrmap_property_test(self, *, + dut: Union[AddressMap, AsyncAddressMap], + size: int, + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: Optional[str], + inst_name: str + ) -> None: + + self.assertEqual(dut.size, size) + + self.__single_node_rdl_name_and_desc_test(dut=dut, + rdl_name=rdl_name, + rdl_desc=rdl_desc) + + self.__test_node_inst_name(dut=dut, + parent_full_inst_name=parent_full_inst_name, + inst_name=inst_name) + + self.__bad_attribute_test(dut=dut) + + # pylint:disable-next=too-many-arguments + def _single_regfile_property_test(self, *, + dut: Union[RegFile, AsyncRegFile], + size: int, + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: str, + inst_name: str + ) -> None: + + self.assertEqual(dut.size, size) + + self.__single_node_rdl_name_and_desc_test(dut=dut, + rdl_name=rdl_name, + rdl_desc=rdl_desc) + + self.__test_node_inst_name(dut=dut, + parent_full_inst_name=parent_full_inst_name, + inst_name=inst_name) + + self.__bad_attribute_test(dut=dut) + + def __single_node_rdl_name_and_desc_test(self, + dut: Base, + rdl_name: Optional[str], + rdl_desc: Optional[str]) -> None: + """ + Check the SystemRDL Name and Desc properties for a node + """ + if rdl_name is None: + self.assertIsNone(dut.rdl_name) + else: + self.assertEqual(dut.rdl_name, rdl_name) + + if rdl_desc is None: + self.assertIsNone(dut.rdl_desc) + else: + self.assertEqual(dut.rdl_desc, rdl_desc) + + def __test_node_inst_name(self, + dut: Base, + parent_full_inst_name: Optional[str], + inst_name: str) -> None: + """ + Test the `inst_name` and `full_inst_name` attributes of a node + """ + self.assertEqual(dut.inst_name, inst_name) + if parent_full_inst_name is None: + # root node (which has no parent) + self.assertEqual(dut.full_inst_name, inst_name) + else: + full_inst_name = parent_full_inst_name + '.' + inst_name + self.assertEqual(dut.full_inst_name, full_inst_name) + + def __bad_attribute_test(self, dut: Base) -> None: + """ + Check that adding an attribute fails, the __slots__ should prevent this + + The attribute name: cppkbrgmgeloagvfgjjeiiushygirh was randomly generated to be unlikely to + every be a attribute name + """ + with self.assertRaises(AttributeError): + dut.cppkbrgmgeloagvfgjjeiiushygirh = 1 # type: ignore[attr-defined,union-attr] + + def __test_name_map(self, dut: Node, child_names: set[str]) -> None: + """ + Test that the get_child_by_system_rdl_name and systemrdl_python_child_name_map are + populated correctly + """ + self.assertCountEqual(dut.systemrdl_python_child_name_map, child_names) + self.assertEqual(set(dut.systemrdl_python_child_name_map.keys()), child_names) + for child_name in child_names: + self.assertEqual(dut.get_child_by_system_rdl_name(child_name).inst_name, child_name) + + def _test_field_iterators(self, *, + rut: Union[RegReadOnly, + RegReadWrite, + RegWriteOnly, + RegAsyncReadOnly, + RegAsyncReadWrite, + RegAsyncWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool, + readable_fields: set[str], + writeable_fields: set[str]) -> None: + if has_sw_readable: + if not isinstance(rut, (RegReadOnly, + RegReadWrite, + RegAsyncReadOnly, + RegAsyncReadWrite, + )): + raise TypeError(f'Register was expected to readable, got {type(rut)}') + + child_readable_field_names = {field.inst_name for field in rut.readable_fields} + + self.assertEqual(readable_fields, child_readable_field_names) + else: + self.assertFalse(hasattr(rut, 'readable_fields')) + # check the readable_fields is empty + self.assertFalse(readable_fields) + + if has_sw_writable: + if not isinstance(rut, (RegWriteOnly, + RegReadWrite, + RegAsyncWriteOnly, + RegAsyncReadWrite, + )): + raise TypeError(f'Register was expected to writable, got {type(rut)}') + + child_writeable_fields_names = {field.inst_name for field in rut.writable_fields} + + self.assertEqual(writeable_fields, child_writeable_fields_names) + else: + self.assertFalse(hasattr(rut, 'writeable_fields')) + # check the writeable_fields is empty + self.assertFalse(writeable_fields) + + child_field_names = {field.inst_name for field in rut.fields} + self.assertEqual(readable_fields | writeable_fields, child_field_names) + + # Check the child name map + self.__test_name_map(dut=rut, child_names=readable_fields | writeable_fields) + + def _test_register_iterators(self, + dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile, + MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: + + if isinstance(dut, (AddressMap, AsyncAddressMap, RegFile, AsyncRegFile, + MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + child_readable_reg_names = {reg.inst_name for reg in + dut.get_readable_registers(unroll=True)} + self.assertEqual(readable_registers.unrolled, child_readable_reg_names) + child_readable_reg_names = {reg.inst_name for reg in + dut.get_readable_registers(unroll=False)} + self.assertEqual(readable_registers.rolled, child_readable_reg_names) + else: + self.assertFalse(hasattr(dut, 'get_readable_registers')) + + if isinstance(dut, (AddressMap, AsyncAddressMap, RegFile, AsyncRegFile, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + child_writable_reg_names = {reg.inst_name for reg in + dut.get_writable_registers(unroll=True)} + self.assertEqual(writeable_registers.unrolled, child_writable_reg_names) + child_writable_reg_names = {reg.inst_name for reg in + dut.get_writable_registers(unroll=False)} + self.assertEqual(writeable_registers.rolled, child_writable_reg_names) + else: + self.assertFalse(hasattr(dut, 'get_writable_registers')) + + child_reg_names = {field.inst_name for field in dut.get_registers(unroll=True)} + self.assertEqual(readable_registers.unrolled | writeable_registers.unrolled, + child_reg_names) + child_reg_names = {field.inst_name for field in dut.get_registers(unroll=False)} + self.assertEqual(readable_registers.rolled | writeable_registers.rolled, + child_reg_names) + + # The register file and addrmap have other items in their child map so it has to be + # tested at the next level up, however, a memory only has child registers + if isinstance(dut, (MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + # Check the child name map + self.__test_name_map(dut=dut, + child_names=readable_registers.rolled | + writeable_registers.rolled) + + def _test_memory_iterators(self, + dut: Union[AddressMap, AsyncAddressMap], + memories: NodeIterators) -> None: + child_mem_names = {reg.inst_name for reg in dut.get_memories(unroll=True)} + self.assertEqual(memories.unrolled, child_mem_names) + child_mem_names = {reg.inst_name for reg in dut.get_memories(unroll=False)} + self.assertEqual(memories.rolled, child_mem_names) + + def __test_section_iterators(self, + dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile], + sections: NodeIterators) -> None: + child_section_names = {reg.inst_name for reg in dut.get_sections(unroll=True)} + self.assertEqual(sections.unrolled, child_section_names) + child_section_names = {reg.inst_name for reg in dut.get_sections(unroll=False)} + self.assertEqual(sections.rolled, child_section_names) + + def _test_addrmap_iterators(self, *, + dut: Union[AddressMap, AsyncAddressMap], + memories: NodeIterators, + sections: NodeIterators, + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: + self._test_register_iterators(dut=dut, + readable_registers=readable_registers, + writeable_registers=writeable_registers) + self._test_memory_iterators(dut=dut, + memories=memories) + self.__test_section_iterators(dut=dut, + sections=sections) + + # Check the child name map + self.__test_name_map(dut=dut, child_names=memories.rolled | readable_registers.rolled | + writeable_registers.rolled | sections.rolled) + + def _test_regfile_iterators(self, + dut: Union[RegFile, AsyncRegFile], + sections: NodeIterators, + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: + self._test_register_iterators(dut=dut, + readable_registers=readable_registers, + writeable_registers=writeable_registers) + self.__test_section_iterators(dut=dut, + sections=sections) + self.assertFalse(hasattr(dut, 'get_memories')) + + # Check the child name map + self.__test_name_map(dut=dut, child_names=readable_registers.rolled | + writeable_registers.rolled | + sections.rolled) + + def _full_to_reduced_enum_conversion( + self, + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]]) -> dict[str, int]: + return {key:value[0] for key,value in full_enum_def.items() } + + def _test_enum_def_rdl_name_desc_( + self, + fut: Union[FieldEnumReadOnly, FieldEnumReadOnly, FieldEnumWriteOnly, + FieldEnumAsyncReadOnly, FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly], + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]]) -> None: + """ + Check that the enumeration in the field matches the enumeration specifed in the + systemRDL + + Args: + fut: field node + full_enum_def: definition of the enumeration a dictionary, with the of the + entry as a key and the value a tuple that has: + 1. int value encoding the enumeration + 2. system RDL name (or None) + 3. system RDL name (or None) + + Returns: None + + """ + + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + # pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + for name, value in full_enum_def.items(): + enum_inst = EnumCls[name] + self.assertEqual(enum_inst.value, value[0]) + + if issubclass(EnumCls, SystemRDLEnum): + if value[1] is None: + self.assertIsNone(enum_inst.rdl_name) + else: + self.assertEqual(enum_inst.rdl_name, value[1]) + + if value[2] is None: + self.assertIsNone(enum_inst.rdl_desc) + else: + self.assertEqual(enum_inst.rdl_desc, value[2]) + + else: + # if using a legacy enumeration, then the systemRDL name and desc must be None + # as the legacy enum did not support these + self.assertIsNone(value[1]) + self.assertIsNone(value[2]) + self.assertFalse(hasattr(enum_inst, 'rdl_name')) + self.assertFalse(hasattr(enum_inst, 'rdl_desc')) diff --git a/src/peakrdl_python/lib_test/async_reg_base_test_class.py b/src/peakrdl_python/lib_test/async_reg_base_test_class.py new file mode 100644 index 00000000..2a1e6a55 --- /dev/null +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -0,0 +1,1185 @@ +""" +peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL +Copyright (C) 2021 - 2025 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +This package is intended to distributed as part of automatically generated code by the PeakRDL +Python tool. It provide the base class for the autogenerated tests +""" +# this module is very similar to the non-async version, a lot of code has been put into common +# methods but it did not make sense to do everything as it would destroy readability +# pylint:disable=duplicate-code + +import unittest +from abc import ABC, abstractmethod +from typing import Union, Optional +from unittest.mock import patch, Mock, call +from itertools import product, chain, combinations +from collections.abc import Iterable +from array import array as Array + +from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite +from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly +from ..lib import MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy +from ..lib import MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy +from ..lib import MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy +from ..lib import RegisterWriteVerifyError +from ..sim_lib.register import Register as SimRegister +from ..sim_lib.register import MemoryRegister as SimMemoryRegister +from ..sim_lib.register import Field as SimField +from ..sim_lib.memory import Memory as SimMemory + +from .utilities import reverse_bits, expected_reg_write_data +from .utilities import reg_value_for_field_read_with_random_base +from .utilities import random_int_field_value, random_field_parent_reg_value +from .utilities import random_encoded_field_value,reg_value_for_field_read +from .utilities import random_reg_value, RandomReg +from .utilities import RegWriteTestSequence,RegWriteZeroStartTestSequence +from .utilities import random_memory_entry,random_memory_entry_value + +from ._common_base_test_class import CommonTestBase, NodeIterators + +# This module is planned to be split, see #272, for now the length is supressed +# pylint:disable=too-many-lines + +class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, CommonTestBase, ABC): + """ + Base Test class for the autogenerated register test when in async mode + """ + + # The following may look odd by a second layer of indirection is required to effectively patch + # the read and write within tests + + # pylint:disable=missing-function-docstring + + async def outer_read_callback(self, addr: int, width: int, accesswidth: int) -> int: + return await self.read_callback(addr=addr, + width=width, + accesswidth=accesswidth) + + @abstractmethod + async def read_callback(self, addr: int, width: int, accesswidth: int) -> int: + ... + + async def outer_write_callback(self, addr: int, + width: int, accesswidth: int, + data: int) -> None: + return await self.write_callback(addr=addr, + width=width, + accesswidth=accesswidth, + data=data) + + @abstractmethod + async def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + ... + + # pylint:enable=missing-function-docstring + + + async def _single_int_field_read_and_write_test( + self, + fut: Union[FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + + # the lsb, msb, high, low, bitmask, inv_bitmask and max_value are all checked as part of + # `test_field_properties` so do no need to checked here. Similarly, the properties of + # parent register are checked as part of `test_register_properties` + + await self.__single_int_field_simulator_read_and_write_test(fut=fut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + if is_sw_readable: + if not isinstance(fut, (FieldAsyncReadOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + await self.__single_field_read_test(fut=fut) + + if is_sw_writable: + if not isinstance(fut, (FieldAsyncWriteOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + await self.__single_field_write_test(fut=fut) + + async def __single_field_read_test( + self, + fut: Union[FieldAsyncReadOnly, FieldAsyncReadWrite]) -> None: + + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', return_value=0) as read_callback_mock: + + # read back - zero, this is achieved by setting the register to inverse bitmask + read_callback_mock.return_value = fut.inverse_bitmask + self.assertEqual(await fut.read(),0) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # read back - max_value, this is achieved by setting the register to bitmask + read_callback_mock.reset_mock() + read_callback_mock.return_value = fut.bitmask + self.assertEqual(await fut.read(), fut.max_value) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # read back - random value + read_callback_mock.reset_mock() + random_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = random_value + field_value = (random_value & fut.bitmask) >> fut.low + if fut.msb == fut.high: + self.assertEqual(await fut.read(), field_value) + else: + self.assertEqual(await fut.read(), + reverse_bits(value=field_value, number_bits=fut.width)) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # at the end of the read tests the write should not have been called + read_callback_mock.reset_mock() + write_callback_mock.assert_not_called() + + async def __single_field_write_test( + self, + fut: Union[FieldAsyncWriteOnly, FieldAsyncReadWrite]) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + for reg_base_value, field_value in product( + [0, fut.parent_register.max_value, random_field_parent_reg_value(fut)], + [0, fut.max_value, random_int_field_value(fut)]): + read_callback_mock.reset_mock() + write_callback_mock.reset_mock() + read_callback_mock.return_value = reg_base_value + + await fut.write(field_value) + + if (fut.width < fut.parent_register.width) and readable_reg: + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + else: + read_callback_mock.assert_not_called() + + write_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth, + data=expected_reg_write_data(fut=fut, + reg_base_value=reg_base_value, + readable_reg=readable_reg, + field_value=field_value)) + + + # check invalid write values bounce + with self.assertRaises(ValueError): + await fut.write(fut.max_value + 1) + + with self.assertRaises(ValueError): + await fut.write(-1) + + async def __single_enum_field_read_test(self, + fut: Union[FieldEnumAsyncReadOnly, FieldEnumAsyncReadOnly], + enum_definition: dict[str, int], + ) -> None: + + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + #pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # read back - each legal enum value + for enum_name, enum_value in enum_definition.items(): + read_callback_mock.reset_mock() + reg_value = reg_value_for_field_read_with_random_base(fut=fut, + field_value= enum_value) + read_callback_mock.return_value = reg_value + self.assertEqual(await fut.read(), EnumCls[enum_name]) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # check register values that don't map to a legal enum value create an exception + # this check is only relevant if there are potential field values that do not map to + # the enum + if len(enum_definition) < (2**fut.width): + # there are two versions of this: + # 1) for small fields (up to 8 bit wide) every value is tested + # 2) for large fields (typically occurring with a sparse enum 100 values are + # checked + legal_enum_values_set = set(enum_definition.values()) + if fut.width <= 8: + bad_field_value_iter = set(range(fut.max_value+1)) + else: + bad_field_value_iter = {random_int_field_value(fut) for _ in range(100)} + + for bad_field_value in bad_field_value_iter - legal_enum_values_set: + read_callback_mock.reset_mock() + reg_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=bad_field_value) + read_callback_mock.return_value = reg_value + with self.assertRaises(ValueError): + _ = await fut.read() + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # at the end of the read tests the write should not have been called + write_callback_mock.assert_not_called() + + async def __single_enum_field_write_test(self, + fut: Union[FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite], + enum_definition: dict[str, int]) -> None: + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + # pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + for enum_name, enum_value in enum_definition.items(): + reg_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = reg_value + await fut.write(EnumCls[enum_name]) + + # the read is skipped if the register is not readable or has the same width + # as the field + if (fut.width < fut.parent_register.width) and readable_reg: + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + else: + read_callback_mock.assert_not_called() + + write_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth, + data=expected_reg_write_data(fut=fut, + reg_base_value=reg_value, + readable_reg=readable_reg, + field_value=enum_value)) + + read_callback_mock.reset_mock() + write_callback_mock.reset_mock() + + async def _single_enum_field_read_and_write_test( + self, + fut: Union[FieldEnumAsyncReadOnly, FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly], + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + """ + Check the ability to read and write to integer (non-encoded) field + """ + + # the lsb, msb, high, low, bitmask, inv_bitmask and max_value are all checked as part of + # `test_field_properties` so do not need to checked here. Similarly, the properties of + # parent register are checked as part of `test_register_properties` + + await self.__single_enum_field_simulator_read_and_write_test(fut=fut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + # split the enum definition from the full enum definition + self._test_enum_def_rdl_name_desc_(fut=fut, full_enum_def=full_enum_def) + enum_definition = self._full_to_reduced_enum_conversion(full_enum_def) + + if is_sw_readable: + if not isinstance(fut, (FieldEnumAsyncReadOnly, FieldEnumAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + await self.__single_enum_field_read_test(fut=fut, + enum_definition=enum_definition) + + if is_sw_writable: + if not isinstance(fut, (FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + await self.__single_enum_field_write_test(fut=fut, + enum_definition=enum_definition) + + async def _single_register_read_and_write_test( + self, *, + rut: Union[RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool, + readable_fields: set[str], + writeable_fields: set[str]) -> None: + + # the register properties are tested separately so are available to be used here + + self._test_field_iterators(rut=rut, + has_sw_readable=has_sw_readable, + has_sw_writable=has_sw_writable, + readable_fields=readable_fields, + writeable_fields=writeable_fields) + + await self.__single_register_simulator_read_and_write_test( + rut=rut, + has_sw_readable=has_sw_readable, + has_sw_writable=has_sw_writable) + + if has_sw_readable: + if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): + raise TypeError('Test can not proceed as the rut is not a readable register') + await self.__single_reg_read_test(rut=rut) + await self.__single_reg_read_fields_and_context_test(rut=rut) + else: + # test that a non-readable register has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= rut.read(0) # type: ignore[union-attr,call-arg] + + if has_sw_writable: + if not isinstance(rut, (RegAsyncWriteOnly, RegAsyncReadWrite)): + raise TypeError('Test can not proceed as the rut is not a writable register') + await self.__single_reg_write_test(rut=rut) + if has_sw_readable: + if not isinstance(rut, RegAsyncReadWrite): + raise TypeError('Test can not proceed as the rut is not a read ' + 'and writable register') + await self.__single_reg_write_fields_and_context_test(rut) + else: + if not isinstance(rut, RegAsyncWriteOnly): + raise TypeError('Test can not proceed as the rut is not a writable register') + await self.__single_reg_full_write_fields_test(rut) + else: + # test that a non-writable register has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + await rut.write(0) # type: ignore[union-attr,call-arg] + + async def __single_reg_read_test(self, rut: Union[RegAsyncReadOnly, RegAsyncReadWrite]) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=1) as read_callback_mock: + for reg_value in [0, 1, rut.max_value, random_reg_value(rut)]: + read_callback_mock.reset_mock() + read_callback_mock.return_value = reg_value + self.assertEqual(await rut.read(), reg_value) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + write_callback_mock.assert_not_called() + + async def __single_reg_write_test(self, + rut: Union[RegAsyncWriteOnly, RegAsyncReadWrite]) -> None: + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + for reg_value in [0, 1, rut.max_value, random_reg_value(rut)]: + write_callback_mock.reset_mock() + read_callback_mock.return_value = reg_value + await rut.write(reg_value) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_value) + read_callback_mock.assert_not_called() + + # test writing a value beyond the register range is blocked with an exception + # being raised + with self.assertRaises(ValueError): + await rut.write(-1) + + with self.assertRaises(ValueError): + await rut.write(rut.max_value + 1) + + async def __single_reg_read_fields_and_context_test( + self, + rut: Union[RegAsyncReadOnly, RegAsyncReadWrite]) -> None: + + # build up a register value, starting with a random register value + reg_value = RandomReg(rut).value + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=reg_value) as read_callback_mock: + + # build the expected return structure + ref_read_fields = { field.inst_name: await field.read() + for field in rut.readable_fields } + read_callback_mock.reset_mock() + + self.assertDictEqual(await rut.read_fields(), ref_read_fields) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + read_callback_mock.reset_mock() + + async with rut.single_read() as rut_context_inst: + context_ref_read_fields = {field.inst_name: await field.read() + for field in rut_context_inst.readable_fields} + self.assertDictEqual(ref_read_fields, context_ref_read_fields) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + + write_callback_mock.assert_not_called() + + async def __single_reg_write_fields_and_context_test(self, rut: RegAsyncReadWrite) -> None: + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + # fix for #196 (excessive test time) if the number of fields is greater than 4 + # the combinations are reduced to only tests combinations of three plus the full + # set + num_writable_fields = len(list(rut.writable_fields)) + if num_writable_fields > 4: + perms_iterator: Iterable[int] = chain(range(1, 4), [num_writable_fields]) + else: + perms_iterator = range(1, num_writable_fields + 1) + for fields_to_write in chain.from_iterable( + (combinations(rut.writable_fields, perms) for perms in perms_iterator)): + + reg_sequence = RegWriteTestSequence(rut, fields=fields_to_write) + + # read/write without verify + read_callback_mock.return_value = reg_sequence.start_value + async with rut.single_read_modify_write(verify=False) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + await field.write(field_value) + + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_called_once() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # read/write/verify pass + async with rut.single_read_modify_write(verify=True) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + await field.write(field_value) + read_callback_mock.return_value = reg_sequence.value + + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + self.assertEqual(read_callback_mock.call_count, 2) + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # read/write/verify pass + with self.assertRaises(RegisterWriteVerifyError): + async with rut.single_read_modify_write(verify=True) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + await field.write(field_value) + # changing the readback value to the inverse of the expected value + # causes an error on the exit from the context manager + read_callback_mock.return_value = reg_sequence.value ^ reg_session.max_value + + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # check the write_fields + read_callback_mock.return_value = reg_sequence.start_value + # make the kwargs by replacing the field names with the safe versions + kwargs = { rut.systemrdl_python_child_name_map[unsafe_name] : value + for unsafe_name, value in reg_sequence.write_sequence.items() } + await rut.write_fields(**kwargs) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_called_once() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + async def __single_reg_full_write_fields_test(self, rut: RegAsyncWriteOnly) -> None: + """ + Test the `write_fields` method of a Write Only Register + """ + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback') as read_callback_mock: + # in the case of a write only register the only legal case is a full field write in + # one go + reg_sequence = RegWriteZeroStartTestSequence(rut, fields=rut.writable_fields) + + # make the kwargs by replacing the field names with the safe versions + kwargs = { rut.systemrdl_python_child_name_map[unsafe_name] : value + for unsafe_name, value in reg_sequence.write_sequence.items() } + await rut.write_fields(**kwargs) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_not_called() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + async def __single_register_simulator_read_and_write_test( + self, + rut: Union[RegAsyncReadOnly,RegAsyncReadWrite,RegAsyncWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + sim_register = self.simulator_instance.register_by_full_name(rut.full_inst_name) + + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + register_read_callback = Mock() + register_write_callback = Mock() + + if has_sw_readable: + if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): + raise TypeError('Test can not proceed as the rut is not a readable register') + # register read checks + # update the value via the backdoor in the simulator + random_value = random_reg_value(rut) + sim_register.value = random_value + self.assertEqual(await rut.read(), random_value) + # up to now the callback should not have been called + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + random_value = random_reg_value(rut) + sim_register.value = random_value + self.assertEqual(await rut.read(), random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + register_write_callback.reset_mock() + register_read_callback.reset_mock() + sim_register.value = random_value + sim_register.read_callback = None + sim_register.write_callback = None + self.assertEqual(await rut.read(), random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + + if has_sw_writable: + if not isinstance(rut, (RegAsyncWriteOnly, RegAsyncReadWrite)): + raise TypeError('Test can not proceed as the rut is not a writable register') + # register write checks + random_value = random_reg_value(rut) + await rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + # up to now the callback should not have been called + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + random_value = random_reg_value(rut) + await rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with(value=random_value) + register_read_callback.assert_not_called() + register_write_callback.reset_mock() + register_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + random_value = random_reg_value(rut) + await rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + if has_sw_readable: + if not isinstance(rut, RegAsyncReadWrite): + raise TypeError('Test can not proceed as the rut is not a read ' + 'and writable register') + self.assertEqual(await rut.read(), random_value) + + async def __single_int_field_simulator_read_and_write_test( + self, + fut: Union[FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + #pylint:disable=too-many-statements + + sim_register = self.simulator_instance.register_by_full_name( + fut.parent_register.full_inst_name) + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + sim_field = self.simulator_instance.field_by_full_name(fut.full_inst_name) + self.assertIsInstance(sim_field, SimField) + register_read_callback = Mock() + register_write_callback = Mock() + field_read_callback = Mock() + field_write_callback = Mock() + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + if is_sw_readable: + # register read checks + # update the register value via the backdoor in the simulator + if not isinstance(fut, (FieldAsyncReadOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + self.assertEqual(await fut.read(), random_field_value) + # update the field value via the backdoor in the simulator + previous_register_value = random_value + + random_field_value = random_int_field_value(fut) + sim_field.value = random_field_value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=previous_register_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + self.assertEqual(await fut.read(), random_field_value) + # hook up the callbacks to check they work correctly + random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + sim_field.read_callback = field_read_callback + sim_field.write_callback = field_write_callback + self.assertEqual(await fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + field_write_callback.assert_not_called() + field_read_callback.assert_called_once_with(value=random_field_value) + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + sim_field.read_callback = None + sim_field.write_callback = None + #random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + self.assertEqual(await fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + if is_sw_writable: + # register write checks + # update the register value via the backdoor in the simulator, then perform a field + # write and make sure it is updated + + if not isinstance(fut, (FieldAsyncWriteOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + if readable_reg: + initial_reg_random_value = random_field_parent_reg_value(fut) + sim_register.value = initial_reg_random_value + else: + # if the register is not readable the write assumes the rest of the register is 0 + initial_reg_random_value = 0 + + random_field_value = random_int_field_value(fut) + sim_field.value = random_field_value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + await fut.write(random_field_value) + self.assertEqual(sim_register.value, random_value) + + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + # hook up the call backs + sim_register.read_callback = None + sim_register.write_callback = register_write_callback + sim_field.read_callback = None + sim_field.write_callback = field_write_callback + random_field_value = random_int_field_value(fut) + await fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with( + value=random_value) + field_write_callback.assert_called_once_with( + value=random_field_value) + register_read_callback.assert_not_called() + field_read_callback.assert_not_called() + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.write_callback = None + sim_field.write_callback = None + random_field_value = random_int_field_value(fut) + await fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + async def __single_enum_field_simulator_read_and_write_test( + self, + fut: Union[FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + # pylint:disable=too-many-statements + + sim_register = self.simulator_instance.register_by_full_name( + fut.parent_register.full_inst_name) + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + sim_field = self.simulator_instance.field_by_full_name(fut.full_inst_name) + self.assertIsInstance(sim_field, SimField) + register_read_callback = Mock() + register_write_callback = Mock() + field_read_callback = Mock() + field_write_callback = Mock() + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + if is_sw_readable: + # register read checks + # update the register value via the backdoor in the simulator + + if not isinstance(fut, (FieldEnumAsyncReadOnly, FieldEnumAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + self.assertEqual(await fut.read(), random_field_value) + # update the field value via the backdoor in the simulator + previous_register_value = random_value + random_field_value = random_encoded_field_value(fut) + sim_field.value = random_field_value.value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=previous_register_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + self.assertEqual(await fut.read(), random_field_value) + + + # hook up the callbacks to check they work correctly + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + sim_field.read_callback = field_read_callback + sim_field.write_callback = field_write_callback + self.assertEqual(await fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + field_write_callback.assert_not_called() + field_read_callback.assert_called_once_with(value=random_field_value.value) + + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + sim_field.read_callback = None + sim_field.write_callback = None + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + self.assertEqual(await fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + + if is_sw_writable: + # register write checks + # update the register value via the backdoor in the simulator, then perform a field + # write and make sure it is updated + + if not isinstance(fut, (FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + if readable_reg: + initial_reg_random_value = random_field_parent_reg_value(fut) + sim_register.value = initial_reg_random_value + else: + # if the register is not readable the write assumes the rest of the register is 0 + initial_reg_random_value = 0 + + + random_field_value = random_encoded_field_value(fut) + sim_field.value = random_field_value.value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + await fut.write(random_field_value) + + + self.assertEqual(sim_register.value, random_value) + + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + # hook up the call backs + sim_register.read_callback = None + sim_register.write_callback = register_write_callback + sim_field.read_callback = None + sim_field.write_callback = field_write_callback + random_field_value = random_encoded_field_value(fut) + await fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with( + value=random_value) + field_write_callback.assert_called_once_with( + value=random_field_value.value) + register_read_callback.assert_not_called() + field_read_callback.assert_not_called() + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.write_callback = None + sim_field.write_callback = None + random_field_value = random_encoded_field_value(fut) + await fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + + async def _single_memory_read_and_write_test(self, *, + mut: Union[MemoryAsyncReadOnly, + MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, + MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, + MemoryAsyncReadWriteLegacy], + is_sw_readable: bool, + is_sw_writable: bool, + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: + + # the register memory are tested separately so are available to be used here + + self._test_register_iterators(dut=mut, + readable_registers=readable_registers, + writeable_registers=writeable_registers) + + if is_sw_readable: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a readable memory') + await self.__single_memory_read_test(mut=mut) + await self.__single_memory_simulator_read_test(mut=mut) + else: + # test that a non-readable memory has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= await mut.read(0) # type: ignore[union-attr,call-arg] + + if is_sw_writable: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a writable memory') + await self.__single_memory_write_test(mut=mut) + await self.__single_memory_simulator_write_test(mut=mut) + else: + # test that a non-writable memory has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + await mut.write(0) # type: ignore[union-attr,call-arg] + + async def __single_memory_read_test( + self, + mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry read test + for entry, value in product( + [0, mut.entries-1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + read_callback_mock.return_value = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1),[value]) + + read_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth) + read_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + # the following needs to have the same parameters as the callback so has some unused + # args + # pylint:disable-next=unused-argument + def read_data_mock(addr: int, width: int, accesswidth: int) -> int: + mem_entry = (addr - mut.address) // mut.width_in_bytes + return rand_data_list[mem_entry] + read_callback_mock.side_effect = read_data_mock + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + write_callback_mock.assert_not_called() + + async def __single_memory_write_test( + self, + mut: Union[MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + # this function will simplify once all the legacy modes are removed later so we have + # allowed more branches for now + # pylint:disable=too-many-branches + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data = Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data = [value]) + + write_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=value) + write_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=rand_data_list) + + calls = [call(addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=rand_data_list[entry]) for entry in range(entries_to_test)] + write_callback_mock.assert_has_calls(calls, any_order=False) + + # check invalid write values bounce + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + # depending the hardware array sizes the error may be at the point the array is + # constructed on internal + dummy_array = Array(mut.array_typecode,[0]) + if dummy_array.itemsize > mut.width_in_bytes: + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + else: + with self.assertRaises(OverflowError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + with self.assertRaises(OverflowError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [-1])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=[mut.max_entry_value + 1]) + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=[-1]) + + read_callback_mock.assert_not_called() + + async def __single_memory_simulator_read_test( + self, + mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry read test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + sim_memory.value[entry] = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), [value]) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + for entry in range(entries_to_test): + sim_memory.value[entry] = rand_data_list[entry] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + async def __single_memory_simulator_write_test( + self, + mut: Union[MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data=Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError( + f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data=[value]) + + self.assertEqual(sim_memory.value[entry], value) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=rand_data_list) + + for entry in range(entries_to_test): + self.assertEqual(sim_memory.value[entry], rand_data_list[entry]) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py new file mode 100644 index 00000000..97d9171c --- /dev/null +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -0,0 +1,1175 @@ +""" +peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL +Copyright (C) 2021 - 2025 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +This package is intended to distributed as part of automatically generated code by the PeakRDL +Python tool. It provide the base class for the autogenerated tests +""" +from abc import ABC, abstractmethod +from typing import Union, Optional +from unittest.mock import patch, Mock, call +from itertools import product, chain, combinations +from collections.abc import Iterable +from array import array as Array + +from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly +from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly +from ..lib import MemoryReadOnly, MemoryReadOnlyLegacy +from ..lib import MemoryWriteOnly, MemoryWriteOnlyLegacy +from ..lib import MemoryReadWrite, MemoryReadWriteLegacy +from ..lib import RegisterWriteVerifyError +from ..sim_lib.register import Register as SimRegister +from ..sim_lib.register import MemoryRegister as SimMemoryRegister +from ..sim_lib.register import Field as SimField +from ..sim_lib.memory import Memory as SimMemory + +from .utilities import reverse_bits, expected_reg_write_data +from .utilities import reg_value_for_field_read_with_random_base +from .utilities import random_int_field_value, random_field_parent_reg_value +from .utilities import random_encoded_field_value, reg_value_for_field_read +from .utilities import random_reg_value, RandomReg +from .utilities import RegWriteTestSequence,RegWriteZeroStartTestSequence +from .utilities import random_memory_entry,random_memory_entry_value + +from ._common_base_test_class import CommonTestBase, NodeIterators + +# This module is planned to be split, see #272, for now the length is supressed +# pylint:disable=too-many-lines + + +class LibTestBase(CommonTestBase, ABC): + """ + Base Test class for the autogenerated register test when in async mode + """ + + # The following may look odd by a second layer of indirection is required to effectively patch + # the read and write within tests + + # pylint:disable=missing-function-docstring + + def outer_read_callback(self, addr: int, width: int, accesswidth: int) -> int: + return self.read_callback(addr=addr, width=width, accesswidth=accesswidth) + + @abstractmethod + def read_callback(self, addr: int, width: int, accesswidth: int) -> int: + ... + + def outer_write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + return self.write_callback(addr=addr, width=width, accesswidth=accesswidth, data=data) + + @abstractmethod + def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + ... + + # pylint:enable=missing-function-docstring + + def __single_int_field_read_test(self, fut: Union[FieldReadOnly, FieldReadOnly]) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # read back - zero, this is achieved by setting the register to inverse bitmask + read_callback_mock.return_value = fut.inverse_bitmask + self.assertEqual(fut.read(), 0) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # read back - max_value, this is achieved by setting the register to bitmask + read_callback_mock.reset_mock() + read_callback_mock.return_value = fut.bitmask + self.assertEqual(fut.read(), fut.max_value) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # read back - random value + read_callback_mock.reset_mock() + random_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = random_value + field_value = (random_value & fut.bitmask) >> fut.low + if fut.msb == fut.high: + self.assertEqual(fut.read(), field_value) + else: + self.assertEqual(fut.read(), + reverse_bits(value=field_value, number_bits=fut.width)) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # at the end of the read tests the write should not have been called + write_callback_mock.assert_not_called() + + def __single_int_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly]) -> None: + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + for reg_base_value,field_value in product( + [0, fut.parent_register.max_value, random_field_parent_reg_value(fut)], + [0, fut.max_value, random_int_field_value(fut)]): + read_callback_mock.reset_mock() + write_callback_mock.reset_mock() + read_callback_mock.return_value = reg_base_value + + fut.write(field_value) + + # special case if the field is the full size of the register, the read is skipped + # similarly, if the register is not readable it is skipped + if (fut.width < fut.parent_register.width) and readable_reg: + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + else: + read_callback_mock.assert_not_called() + + write_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth, + data=expected_reg_write_data(fut=fut, + reg_base_value=reg_base_value, + readable_reg=readable_reg, + field_value=field_value)) + + # check invalid write values bounce + with self.assertRaises(ValueError): + fut.write(fut.max_value + 1) + + with self.assertRaises(ValueError): + fut.write(-1) + + def _single_int_field_read_and_write_test( + self, + fut: Union[FieldReadOnly, FieldReadOnly, FieldWriteOnly], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + """ + Check the ability to read and write to integer (non-encoded) field + """ + + # the lsb, msb, high, low, bitmask, inv_bitmask and max_value are all checked as part of + # `test_field_properties` so do not need to checked here. Similarly, the properties of + # parent register are checked as part of `test_register_properties` + + self.__single_int_field_simulator_read_and_write_test(fut=fut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + if is_sw_readable: + if not isinstance(fut, (FieldReadOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + self.__single_int_field_read_test(fut=fut) + + if is_sw_writable: + if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + self.__single_int_field_write_test(fut=fut) + + def __single_enum_field_read_test(self, + fut: Union[FieldEnumReadOnly, FieldEnumReadOnly], + enum_definition: dict[str, int], + ) -> None: + + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + # pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # read back - each legal enum value + for enum_name, enum_value in enum_definition.items(): + read_callback_mock.reset_mock() + reg_value = reg_value_for_field_read_with_random_base(fut=fut, + field_value=enum_value) + read_callback_mock.return_value = reg_value + self.assertEqual(fut.read(), EnumCls[enum_name]) + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # check register values that don't map to a legal enum value create an exception + # this check is only relevant if there are potential field values that do not map to + # the enum + if len(enum_definition) < (2**fut.width): + # there are two versions of this: + # 1) for small fields (up to 8 bit wide) every value is tested + # 2) for large fields (typically occurring with a sparse enum 100 values are + # checked + legal_enum_values_set = set(enum_definition.values()) + if fut.width <= 8: + bad_field_value_iter = set(range(fut.max_value+1)) + else: + bad_field_value_iter = {random_int_field_value(fut) for _ in range(100)} + + for bad_field_value in bad_field_value_iter - legal_enum_values_set: + read_callback_mock.reset_mock() + reg_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=bad_field_value) + read_callback_mock.return_value = reg_value + with self.assertRaises(ValueError): + _ = fut.read() + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + + # at the end of the read tests the write should not have been called + write_callback_mock.assert_not_called() + + def __single_enum_field_write_test(self, + fut: Union[FieldEnumWriteOnly, FieldEnumReadWrite], + enum_definition: dict[str, int]) -> None: + + # pylint does not realise this is a class being returned rather than an object, so + # is unhappy with the name + # pylint:disable-next=invalid-name + EnumCls = fut.enum_cls + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + for enum_name, enum_value in enum_definition.items(): + reg_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = reg_value + fut.write(EnumCls[enum_name]) + + # the read is skipped if the register is not readable or has the same width + # as the field + if (fut.width < fut.parent_register.width) and readable_reg: + read_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth) + else: + read_callback_mock.assert_not_called() + + write_callback_mock.assert_called_once_with( + addr=fut.parent_register.address, + width=fut.parent_register.width, + accesswidth=fut.parent_register.accesswidth, + data=expected_reg_write_data(fut=fut, + reg_base_value=reg_value, + readable_reg=readable_reg, + field_value=enum_value)) + + read_callback_mock.reset_mock() + write_callback_mock.reset_mock() + + def _single_enum_field_read_and_write_test( + self, + fut: Union[FieldEnumReadOnly, FieldEnumReadOnly, FieldEnumWriteOnly], + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + """ + Check the ability to read and write to integer (non-encoded) field + """ + + # the lsb, msb, high, low, bitmask, inv_bitmask and max_value are all checked as part of + # `test_field_properties` so do not need to checked here. Similarly, the properties of + # parent register are checked as part of `test_register_properties` + + self.__single_enum_field_simulator_read_and_write_test(fut=fut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + # split the enum definition from the full enum definition + self._test_enum_def_rdl_name_desc_(fut=fut, full_enum_def=full_enum_def) + enum_definition = self._full_to_reduced_enum_conversion(full_enum_def) + + if is_sw_readable: + if not isinstance(fut, (FieldEnumReadOnly, FieldEnumReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + self.__single_enum_field_read_test(fut=fut, + enum_definition=enum_definition) + + if is_sw_writable: + if not isinstance(fut, (FieldEnumWriteOnly, FieldEnumReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + self.__single_enum_field_write_test(fut=fut, + enum_definition=enum_definition) + + def _single_register_read_and_write_test(self, *, + rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool, + readable_fields: set[str], + writeable_fields: set[str]) -> None: + + # the register properties are tested separately so are available to be used here + + self._test_field_iterators(rut=rut, + has_sw_readable=has_sw_readable, + has_sw_writable=has_sw_writable, + readable_fields=readable_fields, + writeable_fields=writeable_fields) + + self.__single_register_simulator_read_and_write_test(rut=rut, + has_sw_readable=has_sw_readable, + has_sw_writable=has_sw_writable) + + if has_sw_readable: + if not isinstance(rut, (RegReadOnly, RegReadWrite)): + raise TypeError('Test can not proceed as the rut is not a readable register') + self.__single_reg_read_test(rut=rut) + # check the read fields and read context manager + self.__single_reg_read_fields_and_context_test(rut=rut) + else: + # test that a non-readable register has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= rut.read(0) # type: ignore[union-attr,call-arg] + + if has_sw_writable: + if not isinstance(rut, (RegWriteOnly, RegReadWrite)): + raise TypeError('Test can not proceed as the rut is not a writable register') + self.__single_reg_write_test(rut=rut) + if has_sw_readable: + if not isinstance(rut, RegReadWrite): + raise TypeError('Test can not proceed as the rut is not a read ' + 'and writable register') + self.__single_reg_write_fields_and_context_test(rut) + else: + if not isinstance(rut, RegWriteOnly): + raise TypeError('Test can not proceed as the rut is not a writable register') + self.__single_reg_full_write_fields_test(rut) + + else: + # test that a non-writable register has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + rut.write(0) # type: ignore[union-attr,call-arg] + + def __single_reg_read_test(self, rut: Union[RegReadOnly, RegReadWrite]) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=1) as read_callback_mock: + for reg_value in [0, 1, rut.max_value, random_reg_value(rut)]: + read_callback_mock.reset_mock() + read_callback_mock.return_value = reg_value + self.assertEqual(rut.read(), reg_value) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + write_callback_mock.assert_not_called() + + def __single_reg_write_test(self, rut: Union[RegWriteOnly, RegReadWrite]) -> None: + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + for reg_value in [0, 1, rut.max_value, random_reg_value(rut)]: + write_callback_mock.reset_mock() + read_callback_mock.return_value = reg_value + rut.write(reg_value) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_value) + read_callback_mock.assert_not_called() + + # test writing a value beyond the register range is blocked with an exception + # being raised + with self.assertRaises(ValueError): + rut.write(-1) + + with self.assertRaises(ValueError): + rut.write(rut.max_value + 1) + + def __single_reg_read_fields_and_context_test(self, + rut: Union[RegReadOnly, RegReadWrite]) -> None: + """ + Check the `read_fields` and `single_read` methods + """ + + # build up a register value, starting with a random register value + reg_value = RandomReg(rut).value + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=reg_value) as read_callback_mock: + + # build the expected return structure + ref_read_fields = { field.inst_name: field.read() for field in rut.readable_fields } + read_callback_mock.reset_mock() + + self.assertDictEqual(rut.read_fields(), ref_read_fields) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + read_callback_mock.reset_mock() + + with rut.single_read() as rut_context_inst: + context_ref_read_fields = {field.inst_name: field.read() + for field in rut_context_inst.readable_fields} + self.assertDictEqual(ref_read_fields, context_ref_read_fields) + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + + write_callback_mock.assert_not_called() + + def __single_reg_write_fields_and_context_test(self, rut: RegReadWrite) -> None: + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + # fix for #196 (excessive test time) if the number of fields is greater than 4 + # the combinations are reduced to only tests combinations of three plus the full + # set + num_writable_fields = len(list(rut.writable_fields)) + if num_writable_fields > 4: + perms_iterator: Iterable[int] = chain(range(1, 4), [num_writable_fields]) + else: + perms_iterator = range(1, num_writable_fields + 1) + for fields_to_write in chain.from_iterable( + (combinations(rut.writable_fields, perms) for perms in perms_iterator)): + + reg_sequence = RegWriteTestSequence(rut, fields=fields_to_write) + + # read/write without verify + read_callback_mock.return_value = reg_sequence.start_value + with rut.single_read_modify_write(verify=False) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + field.write(field_value) + + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_called_once() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # read/write/verify pass + with rut.single_read_modify_write(verify=True) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + field.write(field_value) + read_callback_mock.return_value = reg_sequence.value + + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + self.assertEqual(read_callback_mock.call_count, 2) + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # read/write/verify pass + with self.assertRaises(RegisterWriteVerifyError): + with rut.single_read_modify_write(verify=True) as reg_session: + for field_name, field_value in reg_sequence.write_sequence.items(): + field = reg_session.get_child_by_system_rdl_name(field_name) + field.write(field_value) + read_callback_mock.return_value = reg_sequence.value ^ reg_session.max_value + + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + # check the write_fields + read_callback_mock.return_value = reg_sequence.start_value + # make the kwargs by replacing the field names with the safe versions + kwargs = { rut.systemrdl_python_child_name_map[unsafe_name] : value + for unsafe_name, value in reg_sequence.write_sequence.items() } + rut.write_fields(**kwargs) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_called_once() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + def __single_reg_full_write_fields_test(self, rut: RegWriteOnly) -> None: + """ + Test the `write_fields` method of a Write Only Register + """ + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback') as read_callback_mock: + # in the case of a write only register the only legal case is a full field write in + # one go + reg_sequence = RegWriteZeroStartTestSequence(rut, fields=rut.writable_fields) + + # make the kwargs by replacing the field names with the safe versions + kwargs = { rut.systemrdl_python_child_name_map[unsafe_name] : value + for unsafe_name, value in reg_sequence.write_sequence.items() } + rut.write_fields(**kwargs) + write_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth, + data=reg_sequence.value) + read_callback_mock.assert_not_called() + write_callback_mock.reset_mock() + read_callback_mock.reset_mock() + + def __single_register_simulator_read_and_write_test( + self, + rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + sim_register = self.simulator_instance.register_by_full_name(rut.full_inst_name) + + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + register_read_callback = Mock() + register_write_callback = Mock() + + if has_sw_readable: + if not isinstance(rut, (RegReadOnly, RegReadWrite)): + raise TypeError('Test can not proceed as the rut is not a readable register') + # register read checks + # update the value via the backdoor in the simulator + random_value = random_reg_value(rut) + sim_register.value = random_value + self.assertEqual(rut.read(), random_value) + # up to now the callback should not have been called + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + random_value = random_reg_value(rut) + sim_register.value = random_value + self.assertEqual(rut.read(), random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + register_write_callback.reset_mock() + register_read_callback.reset_mock() + sim_register.value = random_value + sim_register.read_callback = None + sim_register.write_callback = None + self.assertEqual(rut.read(), random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + + if has_sw_writable: + if not isinstance(rut, (RegWriteOnly, RegReadWrite)): + raise TypeError('Test can not proceed as the rut is not a writable register') + # register write checks + random_value = random_reg_value(rut) + rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + # up to now the callback should not have been called + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + random_value = random_reg_value(rut) + rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with(value=random_value) + register_read_callback.assert_not_called() + register_write_callback.reset_mock() + register_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + random_value = random_reg_value(rut) + rut.write(random_value) + self.assertEqual(sim_register.value, random_value) + if has_sw_readable: + if not isinstance(rut, RegReadWrite): + raise TypeError('Test can not proceed as the rut is not a read ' + 'and writable register') + self.assertEqual(rut.read(), random_value) + + def __single_int_field_simulator_read_and_write_test( + self, + fut: Union[FieldReadOnly, FieldWriteOnly, FieldReadWrite], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + #pylint:disable=too-many-statements + + sim_register = self.simulator_instance.register_by_full_name( + fut.parent_register.full_inst_name) + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + sim_field = self.simulator_instance.field_by_full_name(fut.full_inst_name) + self.assertIsInstance(sim_field, SimField) + register_read_callback = Mock() + register_write_callback = Mock() + field_read_callback = Mock() + field_write_callback = Mock() + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + if is_sw_readable: + # register read checks + # update the register value via the backdoor in the simulator + if not isinstance(fut, (FieldReadOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + self.assertEqual(fut.read(), random_field_value) + # update the field value via the backdoor in the simulator + previous_register_value = random_value + + random_field_value = random_int_field_value(fut) + sim_field.value = random_field_value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=previous_register_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + self.assertEqual(fut.read(), random_field_value) + # hook up the callbacks to check they work correctly + random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + sim_field.read_callback = field_read_callback + sim_field.write_callback = field_write_callback + self.assertEqual(fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + field_write_callback.assert_not_called() + field_read_callback.assert_called_once_with(value=random_field_value) + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + sim_field.read_callback = None + sim_field.write_callback = None + #random_field_value = random_int_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value) + sim_register.value = random_value + self.assertEqual(fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + if is_sw_writable: + # register write checks + # update the register value via the backdoor in the simulator, then perform a field + # write and make sure it is updated + + if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + if readable_reg: + initial_reg_random_value = random_field_parent_reg_value(fut) + sim_register.value = initial_reg_random_value + else: + # if the register is not readable the write assumes the rest of the register is 0 + initial_reg_random_value = 0 + + random_field_value = random_int_field_value(fut) + sim_field.value = random_field_value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + fut.write(random_field_value) + self.assertEqual(sim_register.value, random_value) + + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + # hook up the call backs + sim_register.read_callback = None + sim_register.write_callback = register_write_callback + sim_field.read_callback = None + sim_field.write_callback = field_write_callback + random_field_value = random_int_field_value(fut) + fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with( + value=random_value) + field_write_callback.assert_called_once_with( + value=random_field_value) + register_read_callback.assert_not_called() + field_read_callback.assert_not_called() + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.write_callback = None + sim_field.write_callback = None + random_field_value = random_int_field_value(fut) + fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + def __single_enum_field_simulator_read_and_write_test( + self, + fut: Union[FieldEnumReadOnly, FieldEnumWriteOnly, FieldEnumReadWrite], + is_sw_readable: bool, + is_sw_writable: bool) -> None: + # pylint:disable=too-many-statements + + sim_register = self.simulator_instance.register_by_full_name( + fut.parent_register.full_inst_name) + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + sim_field = self.simulator_instance.field_by_full_name(fut.full_inst_name) + self.assertIsInstance(sim_field, SimField) + register_read_callback = Mock() + register_write_callback = Mock() + field_read_callback = Mock() + field_write_callback = Mock() + + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + if is_sw_readable: + # register read checks + # update the register value via the backdoor in the simulator + + if not isinstance(fut, (FieldEnumReadOnly, FieldEnumReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + self.assertEqual(fut.read(), random_field_value) + # update the field value via the backdoor in the simulator + previous_register_value = random_value + random_field_value = random_encoded_field_value(fut) + sim_field.value = random_field_value.value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=previous_register_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + self.assertEqual(fut.read(), random_field_value) + + + # hook up the callbacks to check they work correctly + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + sim_register.read_callback = register_read_callback + sim_register.write_callback = register_write_callback + sim_field.read_callback = field_read_callback + sim_field.write_callback = field_write_callback + self.assertEqual(fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_called_once_with(value=random_value) + field_write_callback.assert_not_called() + field_read_callback.assert_called_once_with(value=random_field_value.value) + + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.read_callback = None + sim_register.write_callback = None + sim_field.read_callback = None + sim_field.write_callback = None + random_field_value = random_encoded_field_value(fut) + random_value = reg_value_for_field_read_with_random_base( + fut=fut, + field_value=random_field_value.value) + + sim_register.value = random_value + self.assertEqual(fut.read(), random_field_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + + if is_sw_writable: + # register write checks + # update the register value via the backdoor in the simulator, then perform a field + # write and make sure it is updated + + if not isinstance(fut, (FieldEnumWriteOnly, FieldEnumReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + if readable_reg: + initial_reg_random_value = random_field_parent_reg_value(fut) + sim_register.value = initial_reg_random_value + else: + # if the register is not readable the write assumes the rest of the register is 0 + initial_reg_random_value = 0 + + + random_field_value = random_encoded_field_value(fut) + sim_field.value = random_field_value.value + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + fut.write(random_field_value) + + + self.assertEqual(sim_register.value, random_value) + + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + # hook up the call backs + sim_register.read_callback = None + sim_register.write_callback = register_write_callback + sim_field.read_callback = None + sim_field.write_callback = field_write_callback + random_field_value = random_encoded_field_value(fut) + fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_called_once_with( + value=random_value) + field_write_callback.assert_called_once_with( + value=random_field_value.value) + register_read_callback.assert_not_called() + field_read_callback.assert_not_called() + # revert the callbacks and check again + register_write_callback.reset_mock() + register_read_callback.reset_mock() + field_write_callback.reset_mock() + field_read_callback.reset_mock() + sim_register.write_callback = None + sim_field.write_callback = None + random_field_value = random_encoded_field_value(fut) + fut.write(random_field_value) + random_value = reg_value_for_field_read( + fut=fut, + reg_base_value=initial_reg_random_value, + field_value=random_field_value.value) + self.assertEqual(sim_register.value, random_value) + register_write_callback.assert_not_called() + register_read_callback.assert_not_called() + field_write_callback.assert_not_called() + field_read_callback.assert_not_called() + + def _single_memory_read_and_write_test(self, *, + mut: Union[MemoryReadOnly, + MemoryReadOnlyLegacy, + MemoryWriteOnly, + MemoryWriteOnlyLegacy, + MemoryReadWrite, + MemoryReadWriteLegacy], + is_sw_readable: bool, + is_sw_writable: bool, + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: + + # the register memory are tested separately so are available to be used here + + self._test_register_iterators(dut=mut, + readable_registers=readable_registers, + writeable_registers=writeable_registers) + + + + if is_sw_readable: + if not isinstance(mut, (MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a readable memory') + self.__single_memory_read_test(mut=mut) + self.__single_memory_simulator_read_test(mut=mut) + else: + # test that a non-readable memory has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= mut.read(0) # type: ignore[union-attr,call-arg] + + if is_sw_writable: + if not isinstance(mut, (MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a writable memory') + self.__single_memory_write_test(mut=mut) + self.__single_memory_simulator_write_test(mut=mut) + else: + # test that a non-writable memory has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + mut.write(0) # type: ignore[union-attr,call-arg] + + def __single_memory_read_test( + self, + mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry read test + for entry, value in product( + [0, mut.entries-1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + read_callback_mock.return_value = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1),[value]) + + read_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth) + read_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + # the following needs to have the same parameters as the callback so has some unused + # args + # pylint:disable-next=unused-argument + def read_data_mock(addr: int, width: int, accesswidth: int) -> int: + mem_entry = (addr - mut.address) // mut.width_in_bytes + return rand_data_list[mem_entry] + read_callback_mock.side_effect = read_data_mock + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + write_callback_mock.assert_not_called() + + def __single_memory_write_test( + self, + mut: Union[MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + # this function will simplify once all the legacy modes are removed later so we have + # allowed more branches for now + # pylint:disable=too-many-branches + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=entry, data = Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=entry, data = [value]) + + write_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=value) + write_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=0, data=rand_data_list) + + calls = [call(addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=rand_data_list[entry]) for entry in range(entries_to_test)] + write_callback_mock.assert_has_calls(calls, any_order=False) + + # check invalid write values bounce + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + # depending the hardware array sizes the error may be at the point the array is + # constructed on internal + dummy_array = Array(mut.array_typecode, [0]) + if dummy_array.itemsize > mut.width_in_bytes: + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + else: + with self.assertRaises(OverflowError): + mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + with self.assertRaises(OverflowError): + mut.write(start_entry=0, data=Array(mut.array_typecode,[-1])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=[mut.max_entry_value + 1]) + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=[-1]) + + read_callback_mock.assert_not_called() + + def __single_memory_simulator_read_test( + self, + mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy], + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry read test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + sim_memory.value[entry] = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), [value]) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + for entry in range(entries_to_test): + sim_memory.value[entry] = rand_data_list[entry] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + def __single_memory_simulator_write_test( + self, + mut: Union[MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=entry, data=Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError( + f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=entry, data=[value]) + + self.assertEqual(sim_memory.value[entry], value) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=0, data=rand_data_list) + + for entry in range(entries_to_test): + self.assertEqual(sim_memory.value[entry], rand_data_list[entry]) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py new file mode 100644 index 00000000..f23a83f4 --- /dev/null +++ b/src/peakrdl_python/lib_test/utilities.py @@ -0,0 +1,248 @@ +""" +peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL +Copyright (C) 2021 - 2025 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +This package is intended to distributed as part of automatically generated code by the PeakRDL +Python tool. It provide methods used by the tests +""" +import random +from typing import Union, TypeVar, TYPE_CHECKING +from enum import Enum, EnumMeta +from collections.abc import Iterable +from dataclasses import dataclass +from dataclasses import field as dataclass_field + + +from ..lib import Field +from ..lib import AsyncReg, Reg +from ..lib.base_register import BaseReg +from ..lib.utility_functions import calculate_bitmask +from ..lib import FieldEnum +from ..lib.memory import BaseMemory + +def reverse_bits(value: int, number_bits: int) -> int: + """ + reverse the order of bits in an integer + + Args: + value: value to reverse + number_bits: number of bits used in the value + + Returns: + reversed valued + """ + result = 0 + for i in range(number_bits): + if (value >> i) & 1: + result |= 1 << (number_bits - 1 - i) + return result + +def expected_reg_write_data(fut: Field, + reg_base_value: int, + field_value: int, + readable_reg: bool) -> int: + """ + Test utility function to generate the expected value to be write to a register, given a field + update in the register + + Args: + fut: Field being updated + reg_base_value: register value before the write + field_value: new field value + readable_reg: is register readable + + Returns: + + """ + if readable_reg: + return reg_value_for_field_read(fut=fut, + reg_base_value=reg_base_value, + field_value=field_value) + + # if the register is not readable, the value is simply written + if fut.msb == fut.high: + return field_value << fut.low + return reverse_bits(value=field_value, number_bits=fut.width) << fut.low + +def reg_value_for_field_read(fut: Field,reg_base_value: int, field_value: int) -> int: + """ + Return the register value that when a field read occurs will result in the field + read providing a value of field_value + """ + reg_value = reg_base_value & fut.inverse_bitmask + if fut.msb == fut.high: + return reg_value | fut.bitmask & (field_value << fut.low) + + return reg_value | fut.bitmask & (reverse_bits(value=field_value, + number_bits=fut.width) << fut.low) + +def reg_value_for_field_read_with_random_base(fut: Field, field_value: int) -> int: + """ + Return the register value that when a field read occurs will result in the field + read providing a value of field_value. With all other register bits being in a random + state + """ + return reg_value_for_field_read( + fut=fut, + field_value=field_value, + reg_base_value=random_field_parent_reg_value(fut)) + +def random_int_field_value(fut: Field) -> int: + """ + Return a random integer value within the legal range for a field + """ + return random.randint(0, fut.max_value) + +# The following line should be: +# FieldType = TypeVar('FieldType', bound=int|IntEnum|SystemRDLEnum) +# However, python 3.9 does not support the combination so the binding was removed +# pylint: disable-next=invalid-name +FieldType = TypeVar('FieldType') +def random_encoded_field_value(fut: FieldEnum[FieldType]) -> FieldType: + """ + Return a random encoded values within the legal range for a field + """ + # pylint:disable-next=invalid-name + FieldEnumType = fut.enum_cls + if TYPE_CHECKING: + assert isinstance(FieldEnumType, EnumMeta) + return random.choice(list(FieldEnumType)) + +def random_field_parent_reg_value(fut: Field) -> int: + """ + Return a random integer values within the legal range for a field's register parent + """ + # this needs a mypy ignore because the parent type of the register is not defined at the + # field level, as it can be sync or async + return random.randint(0, fut.parent_register.max_value) # type: ignore[attr-defined] + +def random_reg_value(rut: BaseReg) -> int: + """ + Returns a random register value (note that this value may not have legal field decodes) + """ + return random.randint(0, rut.max_value) + +@dataclass() +class RandomReg: + """ + Instance for testing a sequence of operations that occur when a register is written too, + starting with a random register value. + """ + rut: Union[AsyncReg, Reg] + value: int = dataclass_field(init=False) + + def __post_init__(self) -> None: + end_value, _ = self._random_legal_values(initial_value=random_reg_value(rut=self.rut), + field_iter=self.rut.fields) + self.value = end_value + + def _random_legal_values(self, initial_value:int ,field_iter: Iterable[Field]) -> tuple[ + int, dict[str, Union[int, Enum]]]: + """ + Returns a random register value, based on legal values for the fields within the register + """ + + # build up a register value, starting with a random register value + reg_value = initial_value + reg_field_content: dict[str, Union[int, Enum]] = {} + for field in field_iter: + if isinstance(field, FieldEnum): + field_value_enum = random_encoded_field_value(field) + reg_value = reg_value_for_field_read( + fut=field, + reg_base_value=reg_value, + field_value=field_value_enum.value) + reg_field_content[field.inst_name] = field_value_enum + else: + field_value_int = random_int_field_value(field) + reg_value = reg_value_for_field_read( + fut=field, + reg_base_value=reg_value, + field_value=field_value_int) + reg_field_content[field.inst_name] = field_value_int + + return reg_value, reg_field_content + +@dataclass() +class RegWriteTestSequence(RandomReg): + """ + Instance for testing a sequence of operations that occur when a register is written too, + starting with a random register value. + """ + rut: Union[AsyncReg, Reg] + value: int = dataclass_field(init=False) + fields: Iterable[Field] + start_value: int = dataclass_field(init=False) + write_sequence: dict[str, Union[int, Enum]] = dataclass_field(init=False) + + def __post_init__(self) -> None: + super().__post_init__() + self.start_value = self.value + self.value, self.write_sequence = self._random_legal_values(initial_value=self.start_value, + field_iter=self.fields) + +@dataclass() +class RegWriteZeroStartTestSequence(RandomReg): + """ + Instance for testing a sequence of operations that occur when a register is written too, + starting with a random register value. + """ + fields: Iterable[Field] + write_sequence: dict[str, Union[int, Enum]] = dataclass_field(init=False) + + def __post_init__(self) -> None: + self.value, self.write_sequence = self._random_legal_values(initial_value=0, + field_iter=self.fields) + + +def get_field_bitmask_int(field: Field) -> int: + """ + Integer bitmask for a field + + Args: + field: node to be analysed + + Returns: + bitmask as a string prefixed by 0x + + """ + return calculate_bitmask(high=field.high, low=field.low) + +def get_field_inv_bitmask(field: Field) -> str: + """ + Integer inverse bitmask for a field + + Args: + field: node to be analysed + + Returns: + inverse bitmask as a string prefixed by 0x + + """ + reg_max_value = field.parent_register.max_value # type: ignore[attr-defined] + return reg_max_value ^ get_field_bitmask_int(field) + +def random_memory_entry(mut: BaseMemory) -> int: + """ + Returns a random memory entry + """ + return random.randint(0, mut.entries-1) + +def random_memory_entry_value(mut: BaseMemory) -> int: + """ + Returns a random memory entry value (note that this value may not have legal field decodes) + """ + return random.randint(0, mut.max_entry_value) diff --git a/src/peakrdl_python/sim_lib/simulator.py b/src/peakrdl_python/sim_lib/simulator.py index 64f5a115..5267966a 100644 --- a/src/peakrdl_python/sim_lib/simulator.py +++ b/src/peakrdl_python/sim_lib/simulator.py @@ -293,6 +293,22 @@ def field_by_full_name(self, name: str) -> Field: raise ValueError(f'field name not matched: {name}') + def memory_by_full_name(self, name: str) -> Memory: + """ + Find a memory in the simulator by its fully qualified name + + Args: + name: fully qualified field name + + Returns: Field + + """ + for mem in self._memories: + if mem.memory.full_inst_name == name: + return mem.memory + + raise ValueError(f'Memory name not matched: {name}') + def node_by_full_name(self, name: str) -> Union[Memory, MemoryRegister, Register, Field]: """ Find a node in the simulator by its fully qualified name diff --git a/src/peakrdl_python/systemrdl_node_utility_functions.py b/src/peakrdl_python/systemrdl_node_utility_functions.py index acde819b..268b1c38 100644 --- a/src/peakrdl_python/systemrdl_node_utility_functions.py +++ b/src/peakrdl_python/systemrdl_node_utility_functions.py @@ -420,7 +420,7 @@ def get_field_default_value(node: FieldNode) -> Optional[int]: return value if isinstance(value, (FieldNode, SignalNode)): - # if the node resets to an external external signal or value of another field, there is no + # if the node resets to an external signal or value of another field, there is no # knowledge the code can have of this state and it gets treated as None return None @@ -480,3 +480,18 @@ def full_slice_accessor(node: AddressableNode) -> str: if dimensions is None: raise RuntimeError('array node should have dimensions') return '[' + ','.join([':' for _ in range(len(dimensions))]) + ']' + +def node_iterator_entry(node: AddressableNode) -> str: + """ + The NodeIterators in the lib_test requires all the children of a node to be presented in the + following format: + - non-array node: str of the name inst name from the system RDL + - array node: a tuple with the inst name and the length + """ + if node.is_array: + dimensions = node.array_dimensions + if dimensions is None: + raise RuntimeError('array node should have dimensions') + return f"('{node.inst_name}', {str(dimensions)})" + + return "'" + node.inst_name + "'" diff --git a/src/peakrdl_python/templates/addrmap_register.py.jinja b/src/peakrdl_python/templates/addrmap_register.py.jinja index fe430b93..06747872 100644 --- a/src/peakrdl_python/templates/addrmap_register.py.jinja +++ b/src/peakrdl_python/templates/addrmap_register.py.jinja @@ -93,7 +93,7 @@ class {{node.python_class_name}}({{node.base_class(asyncoutput)}}): Memory{% if asyncoutput %}Async{% endif -%}ReadWrite{% if legacy_block_access %}Legacy{% endif %} {%- elif node.read_only -%} Readable{% if asyncoutput %}Async{% endif -%}Memory{% if legacy_block_access %}Legacy{% endif %} - {%- elif node.read_only -%} + {%- elif node.write_only -%} Writable{% if asyncoutput %}Async{% endif -%}Memory{% if legacy_block_access %}Legacy{% endif %} {%- endif -%} ]): diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index c44c5179..ca184676 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -20,13 +20,13 @@ along with this program. If not, see . {% from 'template_ultilities.py.jinja' import peakrdl_python_sim_lib with context %} {% from 'template_ultilities.py.jinja' import peakrdl_python_lib with context %} +{% from 'template_ultilities.py.jinja' import peakrdl_python_lib_test with context %} {# the following defining the number relative steps up to the lib and sim_lib packages from the current file #} {% set lib_depth = 1 %} {% if legacy_block_access %}from array import array as Array{% endif %} from typing import Union, cast {% if asyncoutput %} -import sys import asyncio import unittest from unittest.mock import Mock @@ -42,7 +42,7 @@ from enum import IntEnum from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.register import Register,MemoryRegister from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.field import Field -from ._{{top_node.inst_name}}_sim_test_base import {{top_node.inst_name}}_SimTestCase, {{top_node.inst_name}}_SimTestCase_BlockAccess +from ._{{top_node.inst_name}}_sim_test_base import {{top_node.inst_name}}_SimTestCase_BlockAccess from ._{{top_node.inst_name}}_sim_test_base import __name__ as base_name from ._{{top_node.inst_name}}_test_base import random_enum_reg_value @@ -50,306 +50,7 @@ from ._{{top_node.inst_name}}_test_base import random_enum_reg_value from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum {% endif %} -class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # type: ignore[valid-type,misc] - - {% if asyncoutput %}async {% endif %}def test_register_read_and_write(self) -> None: - """ - Walk the register map and check every register can be read and written to correctly - """ - {% for node in owned_elements.registers -%} - # test access operations (read and/or write) to register: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - sim_register = self.sim.register_by_full_name('{{'.'.join(node.get_path_segments())}}') - self.assertIsInstance(sim_register, (Register,MemoryRegister)) - register_read_callback = Mock() - register_write_callback = Mock() - - {% if node.has_sw_readable -%} - # register read checks - # update the value via the backdoor in the simulator - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - sim_register.value = random_value - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_value) - # up to now the callback should not have been called - sim_register.read_callback = register_read_callback - sim_register.write_callback = register_write_callback - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - sim_register.value = random_value - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_value) - register_write_callback.assert_not_called() - register_read_callback.assert_called_once_with(value=random_value) - register_write_callback.reset_mock() - register_read_callback.reset_mock() - sim_register.value = random_value - sim_register.read_callback = None - sim_register.write_callback = None - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_value) - register_write_callback.assert_not_called() - register_read_callback.assert_not_called() - - {% endif %} - - {% if node.has_sw_writable -%} - # register write checks - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_value) # type: ignore[union-attr] - self.assertEqual(sim_register.value, random_value) - # up to now the callback should not have been called - sim_register.read_callback = register_read_callback - sim_register.write_callback = register_write_callback - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_value) # type: ignore[union-attr] - self.assertEqual(sim_register.value, random_value) - register_write_callback.assert_called_once_with(value=random_value) - register_read_callback.assert_not_called() - register_write_callback.reset_mock() - register_read_callback.reset_mock() - sim_register.read_callback = None - sim_register.write_callback = None - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_value) # type: ignore[union-attr] - self.assertEqual(sim_register.value, random_value) - {% if node.has_sw_readable -%} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_value) - {% endif %} - {% endif %} - - {% endfor %} - - {% if asyncoutput %}async {% endif %}def test_field_read_and_write(self) -> None: - """ - Walk the register map and check every field can be read and written to correctly - """ - random_field_value: Union[int, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}] - {% for node in owned_elements.fields -%} - # test access operations (read and/or write) to register: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): - sim_register = self.sim.register_by_full_name('{{'.'.join(node.parent.get_path_segments())}}') - self.assertIsInstance(sim_register, (Register,MemoryRegister)) - sim_field = self.sim.field_by_full_name('{{'.'.join(node.get_path_segments())}}') - self.assertIsInstance(sim_field, Field) - register_read_callback = Mock() - register_write_callback = Mock() - field_read_callback = Mock() - field_write_callback = Mock() - - {% if node.is_sw_readable -%} - # register read checks - # update the register value via the backdoor in the simulator - - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - random_value = (random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}) - {% else %} - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) - random_field_value = (random_value & {{get_field_bitmask_hex_string(node)}}) >> {{node.low}} - {% if node.msb == node.low %} - random_field_value = self._reverse_bits(value=random_field_value, number_bits={{node.width}}) - {% endif %} - {% endif %} - sim_register.value = random_value - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_field_value) - # update the field value via the backdoor in the simulator - previous_register_value = random_value - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - sim_field.value = random_field_value.value - self.assertEqual(sim_register.value, (previous_register_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}})) - {% else %} - random_field_value = random.randrange(0, {{get_field_max_value_hex_string(node)}}+1) - sim_field.value = random_field_value - self.assertEqual(sim_register.value, (previous_register_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value{% else %}self._reverse_bits(value=random_field_value, number_bits={{node.width}}){% endif %} << {{node.low}})) - {% endif %} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_field_value) - # hook up the call backs to check they work correctly - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - random_value = (random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}) - {% else %} - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) - random_field_value = (random_value & {{get_field_bitmask_hex_string(node)}}) >> {{node.low}} - {% if node.msb == node.low %} - random_field_value = self._reverse_bits(value=random_field_value, number_bits={{node.width}}) - {% endif %} - {% endif %} - sim_register.value = random_value - sim_register.read_callback = register_read_callback - sim_register.write_callback = register_write_callback - sim_field.read_callback = field_read_callback - sim_field.write_callback = field_write_callback - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_field_value) - register_write_callback.assert_not_called() - register_read_callback.assert_called_once_with(value=random_value) - field_write_callback.assert_not_called() - {%- if 'encode' in node.list_properties() %} - field_read_callback.assert_called_once_with(value=random_field_value.value) - {% else %} - field_read_callback.assert_called_once_with(value=random_field_value) - {% endif %} - # revert the callbacks and check again - register_write_callback.reset_mock() - register_read_callback.reset_mock() - field_write_callback.reset_mock() - field_read_callback.reset_mock() - sim_register.read_callback = None - sim_register.write_callback = None - sim_field.read_callback = None - sim_field.write_callback = None - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - random_value = (random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}) - {% else %} - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) - random_field_value = (random_value & {{get_field_bitmask_hex_string(node)}}) >> {{node.low}} - {% if node.msb == node.low %} - random_field_value = self._reverse_bits(value=random_field_value, number_bits={{node.width}}) - {% endif %} - {% endif %} - sim_register.value = random_value - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), random_field_value) - register_write_callback.assert_not_called() - register_read_callback.assert_not_called() - field_write_callback.assert_not_called() - field_read_callback.assert_not_called() - {% endif %} - - {% if node.is_sw_writable -%} - # register write checks - # update the register value via the backdoor in the simulator, then perform a field - # write and make sure it is updated - {% if node.parent.has_sw_readable -%} - inital_reg_random_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) - sim_register.value = inital_reg_random_value - {% else -%} - {# if the register is not readable the write assumes the rest of the register is 0 #} - inital_reg_random_value = 0 - {% endif %} - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - {% else %} - random_field_value = random.randrange(0, {{get_field_max_value_hex_string(node)}}+1) - {% endif %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_field_value) # type: ignore[arg-type] - {%- if 'encode' in node.list_properties() %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - {% else %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value{% else %}self._reverse_bits(value=random_field_value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - {% endif %} - register_write_callback.assert_not_called() - register_read_callback.assert_not_called() - field_write_callback.assert_not_called() - field_read_callback.assert_not_called() - reg_random_value = sim_register.value - # hook up the call backs - sim_register.read_callback = None - sim_register.write_callback = register_write_callback - sim_field.read_callback = None - sim_field.write_callback = field_write_callback - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - {% else %} - random_field_value = random.randrange(0, {{get_field_max_value_hex_string(node)}}+1) - {% endif %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_field_value) # type: ignore[arg-type] - {%- if 'encode' in node.list_properties() %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - register_write_callback.assert_called_once_with(value=(reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - field_write_callback.assert_called_once_with(value=random_field_value.value) # type: ignore[attr-defined] - {% else %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value{% else %}self._reverse_bits(value=random_field_value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - register_write_callback.assert_called_once_with(value=(reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value{% else %}self._reverse_bits(value=random_field_value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - field_write_callback.assert_called_once_with(value=random_field_value) - {% endif %} - register_read_callback.assert_not_called() - field_read_callback.assert_not_called() - reg_random_value = sim_register.value - # revert the callbacks and check again - register_write_callback.reset_mock() - register_read_callback.reset_mock() - field_write_callback.reset_mock() - field_read_callback.reset_mock() - sim_register.write_callback = None - sim_field.write_callback = None - {%- if 'encode' in node.list_properties() %} - random_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls) - {% else %} - random_field_value = random.randrange(0, {{get_field_max_value_hex_string(node)}}+1) - {% endif %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(random_field_value) # type: ignore[arg-type] - {%- if 'encode' in node.list_properties() %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value.value{% else %}self._reverse_bits(value=random_field_value.value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - {% else %} - self.assertEqual(sim_register.value, (inital_reg_random_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({{get_field_bitmask_hex_string(node)}} & ({% if node.msb == node.high %}random_field_value{% else %}self._reverse_bits(value=random_field_value, number_bits={{node.width}}){% endif %} << {{node.low}}))) - {% endif %} - register_write_callback.assert_not_called() - register_read_callback.assert_not_called() - field_write_callback.assert_not_called() - field_read_callback.assert_not_called() - {% endif %} - - {% endfor %} - - - {% if uses_memory %} - {% if asyncoutput %}async {% endif %}def test_memory_read_and_write(self) -> None: - """ - Walk the register map and check every memory can be read and written to correctly - """ - {% for node in owned_elements.memories -%} - - # test access operations (read and/or write) to register: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): - - {% if node.is_sw_readable -%} - - # checks single unit accesses at the first entry, the last entry and a random entry in - # in each case check a 0, max value and random value being read - for entry in [0, random.randint(0,{{node.get_property('mementries')-1}}), {{node.get_property('mementries')-1}}]: - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=entry, number_entries=1), # type: ignore[union-attr] - {% if legacy_block_access %}Array('{{get_array_typecode(node.get_property('memwidth'))}}', [0]){% else %}[0]{% endif %}) - - # check a multi-entry read, if the memory is small do the entire memory, however, if - # it is large limit the number of entries to 10 - entries_to_test = {% if node.get_property('mementries') > 10 %}10{% else %}{{node.get_property('mementries')}}{% endif %} - - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=0, number_entries=entries_to_test), # type: ignore[union-attr] - {% if legacy_block_access %}Array('{{get_array_typecode(node.get_property('memwidth'))}}', [0 for _ in range(entries_to_test)]){% else %}[0 for _ in range(entries_to_test)]{% endif %}) - {% endif %} - - {% if node.is_sw_writable -%} - # checks single unit accesses at the first entry, the last entry and a random entry in - # in each case check a 0, max value and random value being read - for entry in [0, random.randint(0,{{node.get_property('mementries')-1}}), {{node.get_property('mementries')-1}}]: - for value in [0, random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}), {{get_memory_max_entry_value_hex_string(node)}}]: - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(start_entry=entry, data={% if legacy_block_access %}Array('{{get_array_typecode(node.get_property('memwidth'))}}', [value]){% else %}[value]{% endif %}) # type: ignore[union-attr] - {% if node.is_sw_readable -%} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=entry, number_entries=1), # type: ignore[union-attr] - {% if legacy_block_access %}Array('{{get_array_typecode(node.get_property('memwidth'))}}', [value]){% else %}[value]{% endif %}) - {% endif %} - - # check a multi-entry read, if the memory is small do the entire memory, however, if - # it is large limit the number of entries to 10 - entries_to_test = {% if node.get_property('mementries') > 10 %}10{% else %}{{node.get_property('mementries')}}{% endif %} - {% if legacy_block_access %} - random_data = Array('{{get_array_typecode(node.get_property('memwidth'))}}', - [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)]) - {% else %} - random_data = [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)] - {% endif %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(start_entry=0, data=random_data) # type: ignore[union-attr] - {% if node.is_sw_readable -%} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=0, number_entries=entries_to_test), # type: ignore[union-attr] - random_data) - {% endif %} - {%- endif %} - - {%- endfor %} - {%- endif %} - - +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits class {{fq_block_name}}_block_access({{top_node.inst_name}}_SimTestCase_BlockAccess): # type: ignore[valid-type,misc] """ @@ -420,14 +121,9 @@ class {{fq_block_name}}_block_access({{top_node.inst_name}}_SimTestCase_BlockAcc {%- endif %} if __name__ == '__main__': -{% if asyncoutput %} - if sys.version_info < (3, 8): - asynctest.main() - else: - unittest.main() -{% else %} + unittest.main() -{% endif %} + diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index c4520e8d..426cd1d7 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -19,13 +19,13 @@ along with this program. If not, see . {% include "header_tb.py.jinja" with context %} {% from 'template_ultilities.py.jinja' import peakrdl_python_lib with context %} +{% from 'template_ultilities.py.jinja' import peakrdl_python_lib_test with context %} {# the following defining the number relative steps up to the lib and sim_lib packages from the current file #} {% set lib_depth = 1 %} from typing import Union,Iterable from array import array as Array {% if asyncoutput %} -import sys import asyncio import unittest from unittest.mock import patch, call @@ -40,14 +40,13 @@ import math from enum import IntEnum {% endif %} -from {{ peakrdl_python_lib(depth=lib_depth) }} import RegisterWriteVerifyError, UnsupportedWidthError +from {{ peakrdl_python_lib(depth=lib_depth) }} import UnsupportedWidthError from ..reg_model import RegModel from ..reg_model.{{top_node.inst_name}}_property_enums import * {% if asyncoutput %} from {{ peakrdl_python_lib(depth=lib_depth) }} import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite -from {{ peakrdl_python_lib(depth=lib_depth) }} import WritableAsyncRegister, ReadableAsyncRegister from {{ peakrdl_python_lib(depth=lib_depth) }} import RegAsyncReadWrite, RegAsyncReadOnly, RegAsyncWriteOnly from {{ peakrdl_python_lib(depth=lib_depth) }} import RegAsyncReadWriteArray, RegAsyncReadOnlyArray, RegAsyncWriteOnlyArray from {{ peakrdl_python_lib(depth=lib_depth) }} import MemoryAsyncReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncReadWrite{% if legacy_block_access %}Legacy{% endif %} @@ -57,7 +56,6 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import AsyncAddressMapArray, Asyn from {{ peakrdl_python_lib(depth=lib_depth) }} import AsyncMemory {% else %} from {{ peakrdl_python_lib(depth=lib_depth) }} import FieldReadOnly, FieldWriteOnly, FieldReadWrite -from {{ peakrdl_python_lib(depth=lib_depth) }} import WritableRegister, ReadableRegister from {{ peakrdl_python_lib(depth=lib_depth) }} import RegReadWrite, RegReadOnly, RegWriteOnly from {{ peakrdl_python_lib(depth=lib_depth) }} import RegReadWriteArray, RegReadOnlyArray, RegWriteOnlyArray from {{ peakrdl_python_lib(depth=lib_depth) }} import MemoryReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryReadWrite{% if legacy_block_access %}Legacy{% endif %} @@ -73,6 +71,8 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import Reg {% if not legacy_enum_type %} from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum, SystemRDLEnumEntry {% endif %} +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import NodeIterators from ._{{top_node.inst_name}}_test_base import {{top_node.inst_name}}_TestCase, {{top_node.inst_name}}_TestCase_BlockAccess, {{top_node.inst_name}}_TestCase_AltBlockAccess from ._{{top_node.inst_name}}_test_base import __name__ as base_name @@ -82,162 +82,7 @@ from ._{{top_node.inst_name}}_test_base import random_enum_reg_value class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: ignore[valid-type,misc] - def test_inst_name(self) -> None: - """ - Walk the address map and check the inst name has been correctly populated - """ - {% for node in owned_elements.nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.inst_name, '{{node.get_path_segments()[-1]}}') # type: ignore[union-attr] - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.full_inst_name, '{{'.'.join(node.get_path_segments())}}') # type: ignore[union-attr] - {% endfor %} - - def test_name_property(self) -> None: - """ - Walk the address map and check the name property has been correctly populated - """ - {% for node in owned_elements.nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - {% if skip_systemrdl_name_and_desc_properties %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_name) # type: ignore[union-attr] - {% else %} - {% if node.get_property('name', default=None) is none %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_name) # type: ignore[union-attr] - {% else %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_name, {{node.get_property('name') | tojson}}) # type: ignore[union-attr] - {% endif %} - {% endif %} - {% endfor %} - - def test_desc(self) -> None: - """ - Walk the address map and check the desc property has been correctly populated - """ - {% for node in owned_elements.nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - {% if skip_systemrdl_name_and_desc_properties %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_desc) # type: ignore[union-attr] - {% else %} - {% if node.get_property('desc', default=None) is none %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_desc) # type: ignore[union-attr] - {% else %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.rdl_desc, {{node.get_property('desc') | tojson}}) # type: ignore[union-attr] - {% endif %} - {% endif %} - - {% endfor %} - - def test_sizes(self) -> None: - """ - Check that the sizes all match - """ - {% for node in owned_elements.addressable_nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.size, {{node.size}}) # type: ignore[union-attr] - {% endfor %} - - # check the size of the address map itself - {% if block == top_node %} - with self.subTest(msg='node: {{'.'.join(block.get_path_segments())}}'): - self.assertEqual(self.dut.size, {{block.size}}) # type: ignore[union-attr] - {% else %} - with self.subTest(msg='node: {{'.'.join(block.get_path_segments())}}'): - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(block))}}.size, {{block.size}}) # type: ignore[union-attr] - {% endif %} - - - def test_register_properties(self) -> None: - """ - Walk the address map and check the address, size and accesswidth of every register is - correct - """ - {% for node in owned_elements.registers -%} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.address, {{node.absolute_address}}) # type: ignore[union-attr] - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.width, {{node.size * 8}}) # type: ignore[union-attr] - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.size, {{node.size}}) # type: ignore[union-attr] - {% if 'accesswidth' in node.list_properties() -%}self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.accesswidth, {{node.get_property('accesswidth')}}){%- else -%} self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.accesswidth, self.dut.{{'.'.join(get_python_path_segments(node))}}.accesswidth){%- endif %} # type: ignore[union-attr] - {% endfor %} - - def test_memory_properties(self) -> None: - """ - Walk the address map and check the address, size and accesswidth of every memory is - correct - """ - mut: {% if asyncoutput %}Async{% endif %}Memory - {% for node in owned_elements.memories -%} - with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): - mut = self.dut.{{'.'.join(get_python_path_segments(node))}} # type: ignore[union-attr,assignment] - - self.assertIsInstance(mut, (Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %})) - self.assertEqual(mut.address, {{node.absolute_address}}) - self.assertEqual(mut.width, {{node.get_property('memwidth')}}) - self.assertEqual(mut.entries, {{node.get_property('mementries')}}) - {% if 'accesswidth' in node.list_properties() -%}self.assertEqual(mut.accesswidth, {{node.get_property('accesswidth')}}){%- else -%}self.assertEqual(mut.accesswidth, mut.accesswidth){%- endif %} - {% endfor %} - - def test_field_properties(self) -> None: - """ - walk the address map and check: - - that the lsb and msb of every field is correct - - that where default values are provided they are applied correctly - """ - fut:Field - {% for node in owned_elements.fields -%} - with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): - # test properties of field: {{'.'.join(node.get_path_segments())}} - fut = self.dut.{{'.'.join(get_python_path_segments(node))}} # type: ignore[union-attr] - if not isinstance(fut, Field): - raise TypeError('This test relies on node being of type Field') - self.assertEqual(fut.lsb,{{node.lsb}}) - self.assertEqual(fut.msb,{{node.msb}}) - self.assertEqual(fut.low,{{node.low}}) - self.assertEqual(fut.high,{{node.high}}) - self.assertEqual(fut.bitmask,{{get_field_bitmask_hex_string(node)}}) - self.assertEqual(fut.inverse_bitmask,{{get_field_inv_bitmask_hex_string(node)}}) - self.assertEqual(fut.max_value,{{get_field_max_value_hex_string(node)}}) - {% if 'encode' in node.list_properties() %} - {# only attempt to test enum fields if the reset value is legal for the field #} - {% if get_field_default_value(node) in get_enum_values(node.get_property('encode')) %} - if fut.default is None: - # This is needed to ensure type checking with mypy is happy - raise TypeError('The default should be set') - self.assertEqual(fut.default.value,{{get_field_default_value(node)}}) - {% else %} - self.assertIsNone(fut.default) - {% endif %} - {% else %} - self.assertEqual(fut.default,{{get_field_default_value(node)}}) - {% endif %} - self.assertEqual(fut.is_volatile,{{node.is_hw_writable}}) - {% endfor %} - - def test_field_encoding_properties(self) -> None: - """ - Check that enumeration has the name and desc meta data from the systemRDL - """ - {% for node in owned_elements.fields -%} - {% if 'encode' in node.list_properties() %} - with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): - # test properties of field: {{'.'.join(node.get_path_segments())}} - {% for encoding_entry in node.get_property('encode') %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{encoding_entry.name | upper}}.value, {{encoding_entry.value}}) - {% if not legacy_enum_type %} - {% if encoding_entry.rdl_name is none %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{encoding_entry.name | upper}}.rdl_name) - {% else %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{encoding_entry.name | upper}}.rdl_name, {{encoding_entry.rdl_name | tojson}}) - {% endif %} - {% if encoding_entry.rdl_desc is none %} - self.assertIsNone(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{encoding_entry.name | upper}}.rdl_desc) - {% else %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{encoding_entry.name | upper}}.rdl_desc, {{encoding_entry.rdl_desc | tojson}}) - {% endif %} - {% endif %} - {% endfor %} - {% endif %} - {% endfor %} def test_user_defined_properties(self) -> None: """ @@ -266,1123 +111,118 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% endif %} {% endfor %} - {% if asyncoutput %}async {% endif %}def test_register_read_and_write(self) -> None: + {% if uses_memory %} + {% if asyncoutput %}async {% endif %}def test_memory(self) -> None: """ - Walk the register map and check every register can be read and written to correctly + Walk the memory instances in the register map and check: + - the properties + - it can be read and written to correctly """ - rut: Reg - {% for node in owned_elements.registers -%} - # test access operations (read and/or write) to register: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - rut=self.dut.{{'.'.join(get_python_path_segments(node))}} # type: ignore[union-attr,assignment] - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=1) as read_callback_mock: - - {% if node.has_sw_readable -%} - {% if asyncoutput %} - if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): - raise TypeError('Register is not a Readable Async Type') - {% else %} - if not isinstance(rut, (RegReadOnly, RegReadWrite)): - raise TypeError('Register is not a Readable Type') - {% endif %} - # test reading back 1 (the unpatched version returns 0 so this confirms the patch works) - self.assertEqual({% if asyncoutput %}await {%endif %}rut.read(), 1) - read_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth) - - # test the read check with high value - read_callback_mock.reset_mock() - read_callback_mock.return_value = {{get_reg_max_value_hex_string(node)}} - self.assertEqual({% if asyncoutput %}await {%endif %}rut.read(), {{get_reg_max_value_hex_string(node)}}) - read_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth) - - # test the read of the low value - read_callback_mock.reset_mock() - read_callback_mock.return_value = 0 - self.assertEqual({% if asyncoutput %}await {%endif %}rut.read(), 0x0) - read_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth) - - # test the read of a random value - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - read_callback_mock.reset_mock() - read_callback_mock.return_value = random_value - self.assertEqual({% if asyncoutput %}await {%endif %}rut.read(), random_value) - read_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth) - - # at the end of the read tests the write should not have been called - read_callback_mock.reset_mock() - write_callback_mock.assert_not_called() - {% endif %} - - {% if node.has_sw_writable -%} - {% if asyncoutput %} - if not isinstance(rut, (RegAsyncWriteOnly, RegAsyncReadWrite)): - raise TypeError('Register is not a Writeable Async Type') - {% else %} - if not isinstance(rut, (RegWriteOnly, RegReadWrite)): - raise TypeError('Register is not a Writeable Type') - {% endif %} - # test the write with high value - {% if asyncoutput %}await {%endif %}rut.write({{get_reg_max_value_hex_string(node)}}) - write_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth, - data={{get_reg_max_value_hex_string(node)}}) - write_callback_mock.reset_mock() - - # test the write of a low value - {% if asyncoutput %}await {%endif %}rut.write(0) - write_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth, - data=0) - write_callback_mock.reset_mock() - - # test the write of a random - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}}+1) - {% if asyncoutput %}await {%endif %}rut.write(random_value) # type: ignore[union-attr] - write_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=rut.accesswidth, - data=random_value) - write_callback_mock.reset_mock() - - # test writing a value beyond the register range is blocked with an exception being raised - with self.assertRaises(ValueError): - {% if asyncoutput %}await {%endif %}rut.write(-1) - - with self.assertRaises(ValueError): - {% if asyncoutput %}await {%endif %}rut.write({{get_reg_max_value_hex_string(node)}}+1) - - {%- else %} - # test that a non-writable register has no write method and attempting one generates and error - with self.assertRaises(AttributeError): - {% if asyncoutput %}await {%endif %}rut.write(0) # type: ignore[attr-defined] - {%- endif %} - - # check the read has not been called in the write test - read_callback_mock.assert_not_called() + {% for node in owned_elements.memories -%} + with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): + self._single_memory_property_test(mut=self.dut.{{'.'.join(get_python_path_segments(node))}}, address={{node.absolute_address}}, width={{node.get_property('memwidth')}}, entries={{node.get_property('mementries')}}, accesswidth={% if 'accesswidth' in node.list_properties() -%}{{node.get_property('accesswidth')}}{% else %}None{%- endif %}, array_typecode={% if legacy_block_access %}'{{get_array_typecode(node.get_property('memwidth'))}}'{% else %}None{% endif %}, size={{node.size}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('name', default=None) is none %}None{% else %}{{node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('desc', default=None) is none %}None{% else %}{{node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') + {% if asyncoutput %}await {%endif %}self._single_memory_read_and_write_test(mut=self.dut.{{'.'.join(get_python_path_segments(node))}}, is_sw_readable={{node.is_sw_readable}}, is_sw_writable={{node.is_sw_writable}}, + writeable_registers=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + readable_registers=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %})) {% endfor %} + {% endif %} {# end if memory #} - {% if asyncoutput %}async {% endif %}def test_int_field_read_and_write(self) -> None: - """ - Check the ability to read and write to integer (non-eumn) fields - """ - fut:Field - {% for node in owned_elements.fields -%} - {%- if 'encode' not in node.list_properties() %} - - # test access operations (read and/or write) to field: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): - fut = self.dut.{{'.'.join(get_python_path_segments(node))}} # type: ignore[union-attr] - with patch(base_name + '.write_addr_space') as write_callback_mock,\ - patch(base_name + '.read_addr_space', return_value=0) as read_callback_mock: - - {% if node.is_sw_readable %} - {% if asyncoutput %} - if not isinstance(fut, (FieldAsyncReadOnly, FieldAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable async field') - {% else %} - if not isinstance(fut, (FieldReadOnly, FieldReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable field') - {% endif %} - - # read back - zero, this is achieved by setting the register to inverse bitmask - read_callback_mock.return_value = {{get_field_inv_bitmask_hex_string(node)}} - self.assertEqual({% if asyncoutput %}await {%endif %}fut.read(), - 0) - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth) - - # read back - max_value, this is achieved by setting the register to bitmask - read_callback_mock.reset_mock() - read_callback_mock.return_value = {{get_field_bitmask_hex_string(node)}} - self.assertEqual({% if asyncoutput %}await {%endif %}fut.read(), - {{ get_field_max_value_hex_string(node) }}) - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth) - - # read back - random value - read_callback_mock.reset_mock() - random_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}}+1) - read_callback_mock.return_value = random_value - random_field_value = (random_value & {{get_field_bitmask_hex_string(node)}}) >> {{node.low}} - {% if node.msb == node.high -%} - self.assertEqual({% if asyncoutput %}await {%endif %}fut.read(), - random_field_value) - {% else -%} - self.assertEqual({% if asyncoutput %}await {%endif %}fut.read(), - self._reverse_bits(value=random_field_value, number_bits={{node.width}})) - {% endif -%} - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth) - - # at the end of the read tests the write should not have been called - read_callback_mock.reset_mock() - write_callback_mock.assert_not_called() - {%- endif %} - - {%- if node.is_sw_writable %} - # check the write - {% if asyncoutput %} - if not isinstance(fut, (FieldAsyncWriteOnly, FieldAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a writable async field') - {% else %} - if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): - raise TypeError('Test can not proceed as the fut is not a writable field') - {% endif %} - - random_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}} + 1) - random_field_value = random.randrange(0, {{ get_field_max_value_hex_string(node) }} + 1) - for reg_base_value in [0, {{get_reg_max_value_hex_string(node.parent)}}, random_reg_value]: - for field_value in [0, {{ get_field_max_value_hex_string(node) }}, random_field_value]: - read_callback_mock.reset_mock() - write_callback_mock.reset_mock() - read_callback_mock.return_value = reg_base_value - - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(field_value) # type: ignore[union-attr] - - {% if (node.width < (node.parent.size*8)) and (node.parent.has_sw_readable) %} - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth) - {% else %} - read_callback_mock.assert_not_called() - {%- endif %} - {% if node.parent.has_sw_readable -%} - {% if node.msb == node.high -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth, # type: ignore[union-attr] - data=(reg_base_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (field_value << {{node.low}}))) - {% else -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth, - data=(reg_base_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (self._reverse_bits(value=field_value, number_bits={{node.width}}) << {{node.low}}))) - {% endif -%} - {% else -%} - # if the register is not readable, the value is simply written - {% if node.msb == node.high -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth, - data=field_value << {{node.low}}) - {% else -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=fut.parent_register.accesswidth, - data=self._reverse_bits(value=field_value, number_bits={{node.width}}) << {{node.low}}) - {% endif -%} - {% endif %} - - # check invalid write values bounce - with self.assertRaises(ValueError): - {% if asyncoutput %}await {%endif %}fut.write({{ get_field_max_value_hex_string(node) }} + 1) - - with self.assertRaises(ValueError): - {% if asyncoutput %}await {%endif %}fut.write(-1) - {%- endif %} - - {%- endif %} - {%- endfor %} - - {% if uses_enum %} - {% if asyncoutput %}async {% endif %}def test_enum_field_read_and_write(self) -> None: - """ - Check the ability to read and write to enum fields - """ - {% for node in owned_elements.fields -%} - {%- if 'encode' in node.list_properties() %} - - # test access operations (read and/or write) to field: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): - with patch(base_name + '.write_addr_space') as write_callback_mock,\ - patch(base_name + '.read_addr_space', return_value=0) as read_callback_mock: - - {% if node.is_sw_readable %} - # read back test - for enum_name, enum_value in ({% for enum_entry in node.get_property('encode') %}('{{enum_entry.name.upper()}}',{{enum_entry.value}}),{% endfor %}): - random_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}} + 1) - read_callback_mock.reset_mock() - {% if node.msb == node.high -%} - read_callback_mock.return_value = (random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (enum_value << {{node.low}} )) - {% else -%} - read_callback_mock.return_value = (random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (self._reverse_bits(value=enum_value, number_bits={{node.width}}) << {{node.low}} )) - {% endif -%} - - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(), # type: ignore[union-attr] - self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls[enum_name] # type: ignore[attr-defined] - ) - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth) # type: ignore[union-attr] - - - {# only run this if there are field values that are not part of the enum #} - {% if node.get_property('encode').__len__() < (2**node.width) %} - # check that other values of the field int that don't appear in the enum generate - # an error - {% if node.width <= 8 %} - {# for enumerated fields of up to 8 bit all combinations can be exhaustively tested #} - for field_value in set(range({{get_field_max_value_hex_string(node)}}+1)) - { {%- for value_of_enum_needed in node.get_property('encode') -%}{{value_of_enum_needed.value}}{% if not loop.last %}, {% endif %}{%- endfor %} }: - {% else %} - {# for enumerated fields of with larger sizes look for up to 100 illegal values #} - for field_value in { random.randint(0, {{get_field_max_value_hex_string(node)}}+1) for _ in range(100) } - { {%- for value_of_enum_needed in node.get_property('encode') -%}{{value_of_enum_needed.value}}{% if not loop.last %}, {% endif %}{%- endfor %} }: - {% endif %} - read_callback_mock.reset_mock() - random_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}} + 1) - {% if node.msb == node.high -%} - read_callback_mock.return_value = (random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (field_value << {{node.low}})) - {% else -%} - read_callback_mock.return_value = (random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (self._reverse_bits(value=field_value, number_bits={{node.width}}) << {{node.low}})) - {% endif -%} - with self.assertRaises(ValueError): - _ = {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read() # type: ignore[union-attr] - read_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth) # type: ignore[union-attr] - {% endif %} - write_callback_mock.assert_not_called() - {% endif %} - - {% if node.is_sw_writable %} - {% for value_of_enum_needed in node.get_property('encode') %} - random_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node.parent)}} + 1) - read_callback_mock.reset_mock() - write_callback_mock.reset_mock() - read_callback_mock.return_value = random_reg_value - - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(self.dut.{{'.'.join(get_python_path_segments(node))}}.enum_cls.{{value_of_enum_needed.name.upper()}}) - - {% if ( node.width < (node.parent.size*8)) and (node.parent.has_sw_readable) %} - read_callback_mock.assert_called_once() - {% else %} - read_callback_mock.assert_not_called() - {%- endif %} - - {% if node.parent.has_sw_readable -%} - {% if node.msb == node.high -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth, # type: ignore[union-attr] - data=(random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & ({{value_of_enum_needed.value}} << {{node.low}}))) - {% else -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth, # type: ignore[union-attr] - data=(random_reg_value & {{get_field_inv_bitmask_hex_string(node)}}) | \ - ({{get_field_bitmask_hex_string(node)}} & (self._reverse_bits(value={{value_of_enum_needed.value}}, number_bits={{node.width}}) << {{node.low}}))) - {% endif -%} - {% else -%} - # if the register is not readable, the value is simply written - {% if node.msb == node.high -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth, # type: ignore[union-attr] - data={{value_of_enum_needed.value}} << {{node.low}}) - {% else -%} - write_callback_mock.assert_called_once_with( - addr={{node.parent.absolute_address}}, - width={{node.parent.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.parent_register.accesswidth, # type: ignore[union-attr] - data=self._reverse_bits(value={{value_of_enum_needed.value}}, number_bits={{node.width}}) << {{node.low}}) - {% endif -%} - {% endif %} - - {% endfor %} - {% endif %} - - {%- endif %} - {%- endfor %} - {%- endif %} - - {% if asyncoutput %}async {% endif %}def test_register_read_fields(self) -> None: - """ - Walk the register map and check every register read_fields method - """ - reference_read_fields: dict[str, Union[bool, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}, int]] - {% for node in owned_elements.registers -%} - {% if node.has_sw_readable %} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - # test read_fields to register: - # {{'.'.join(node.get_path_segments())}} - # build up the register value with a random base value, overlaid with - # a random value for each field - rand_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}} + 1) - {%- for child_node in node.children() %} - {% if isinstance(child_node, systemrdlSignalNode) %} - # do nothing with signal {{ child_node.inst_name }} - {% elif isinstance(child_node, systemrdlFieldNode) %} - {% if child_node.is_sw_readable %} - {% if 'encode' in child_node.list_properties() %} - rand_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(child_node))}}.enum_cls).value - {%- else %} - rand_field_value = random.randrange(0, {{ get_field_max_value_hex_string(child_node) }} + 1) - {% endif %} - {% if child_node.msb == child_node.high %} - rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(child_node)}}) | (rand_field_value << {{ child_node.low }}) - {% else %} - rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(child_node)}}) | (self._reverse_bits(value=rand_field_value, number_bits={{child_node.width}}) << {{ child_node.low }}) - {% endif %} - {% endif %} - {% else %} - {{ raise_template_error('unexpected type') }} - {% endif %} - {% endfor %} - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=rand_reg_value) as read_callback_mock: - # the read_fields method gets a dictionary back - # from the object with all the read back field - # values - reference_read_fields = { {% for child_node in node.children() -%} - {% if isinstance(child_node, systemrdlFieldNode) %} - {%- if child_node.is_sw_readable %} - {%- if not hide_node_func(child_node) %} - '{{ child_node.inst_name }}' : {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}.read(){%- if not loop.last -%}, {%- endif %} - {%- endif -%} - {%- endif -%} - {%- endif -%} - {%- endfor %} - } - - read_callback_mock.reset_mock() - - self.assertDictEqual({% if asyncoutput %}await {% endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read_fields(), - reference_read_fields) - read_callback_mock.assert_called_once() - write_callback_mock.assert_not_called() - - {%- endif %} - {%- endfor %} - - {% if asyncoutput %}async {% endif %}def test_register_read_context_manager(self) -> None: + {% if asyncoutput %}async {% endif %}def test_register(self) -> None: """ - Walk the register map and check every register read_fields method + Walk the registers in the register map and check: + - the properties + - it can be read and written to correctly """ - reference_read_fields: dict[str, Union[bool, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}, int]] {% for node in owned_elements.registers -%} - {% if node.has_sw_readable %} - # test context manager to register: - # {{'.'.join(node.get_path_segments())}} - # build up the register value with a random base value, overlaid with - # a random value for each field with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - rand_reg_value = random.randrange(0, {{get_reg_max_value_hex_string(node)}} + 1) - {%- for field in node.children() %} - {% if isinstance(field, systemrdlFieldNode) %} - {% if field.is_sw_readable %} - {% if 'encode' in field.list_properties() %} - rand_field_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(field))}}.enum_cls).value - {% else %} - rand_field_value = random.randrange(0, {{ get_field_max_value_hex_string(field) }} + 1) - {% endif %} - {% if field.msb == field.high %} - rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(field)}}) | (rand_field_value << {{ field.low }}) - {% else %} - rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(field)}}) | (self._reverse_bits(value=rand_field_value, number_bits={{field.width}}) << {{ field.low }}) - {% endif %} - {% endif %} - {% else %} - # skipping {{field.inst_name}} - {% endif %} - {% endfor %} - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=rand_reg_value) as read_callback_mock: - - # first read the fields using the "normal" method, then compare the result to reading - # via the context manager - reference_read_fields = { {% for field in node.children() -%} - {%- if isinstance(field, systemrdlFieldNode) -%} - {%- if field.is_sw_readable %} - {%- if not hide_node_func(field) %} - '{{ field.inst_name }}' : {%if asyncoutput %}await {% endif %}self.dut.{{'.'.join(get_python_path_segments(field))}}.read(){%- if not loop.last -%}, {%- endif %} # type: ignore[union-attr] - {%- endif -%} - {%- endif -%} - {%- endif -%} - {%- endfor %} - } - read_callback_mock.reset_mock() - - {% if node.has_sw_writable %} - {% if asyncoutput %}async {% endif %}with self.dut.{{'.'.join(get_python_path_segments(node))}}.single_read_modify_write(skip_write=True) as reg_context: # type: ignore[union-attr] - {% else %} - {% if asyncoutput %}async {% endif %}with self.dut.{{'.'.join(get_python_path_segments(node))}}.single_read() as reg_context: # type: ignore[union-attr] - {%- endif -%} - {% for field in node.children() -%} - {%- if isinstance(field, systemrdlFieldNode) -%} - {%- if field.is_sw_readable %} - {%- if not hide_node_func(field) %} - self.assertEqual(reference_read_fields['{{ field.inst_name }}'], - {% if asyncoutput %} await {% endif %}reg_context.get_child_by_system_rdl_name('{{ field.inst_name }}').read() - ) - {%- endif -%} - {%- endif -%} - {%- endif -%} - {%- endfor %} - pass - - read_callback_mock.assert_called_once() - write_callback_mock.assert_not_called() - - {%- endif %} - {%- endfor %} - - {% if asyncoutput %}async {% endif %}def test_register_write_context_manager(self) -> None: - """ - Test the read modify write context manager - """ - {% if asyncoutput %}async {% endif %}def write_field_combinations(reg: Reg{% if asyncoutput %}Async{% endif %}ReadWrite, writable_fields:list[str]) -> None: - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=0) as read_callback_mock: - # fix for #196 (excessive test time) if the number of fields is greater than 4 - # the combinations are reduced to only tests combinations of three plus the full - # set - if len(writable_fields) > 4: - perms_iterator: Iterable[int] = chain(range(1,4), [len(writable_fields)]) - else: - perms_iterator = range(1, len(writable_fields) + 1) - for fields_to_write in chain.from_iterable((combinations(writable_fields, perms) for perms in perms_iterator)): - field_values: dict[str, Union[bool, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}, int]] = {} - expected_value = 0 - for field_str in fields_to_write: - field = getattr(reg, field_str) - if hasattr(field, 'enum_cls'): - rand_enum_value = random_enum_reg_value(field.enum_cls) - rand_field_value = rand_enum_value.value - field_values[field_str] = rand_enum_value - else: - rand_field_value = random.randrange(0, field.max_value + 1) - field_values[field_str] = rand_field_value - - if field.msb == field.high: - expected_value = ( expected_value & field.inverse_bitmask ) | (rand_field_value << field.low) - elif field.msb == field.low: - expected_value = ( expected_value & field.inverse_bitmask ) | (self._reverse_bits(value=rand_field_value, number_bits=field.width) << field.low) - else: - raise RuntimeError('invalid msb/lsb high/low combination') - - # read/write without verify - read_callback_mock.return_value = 0 - {% if asyncoutput %}async {% endif %}with reg.single_read_modify_write(verify=False) as reg_session: - for field_name, field_value in field_values.items(): - field = getattr(reg_session, field_name) - {% if asyncoutput %}await {% endif %}field.write(field_value) - - write_callback_mock.assert_called_once_with( - addr=reg.address, - width=reg.width, - accesswidth=reg.accesswidth, - data=expected_value) - read_callback_mock.assert_called_once() - write_callback_mock.reset_mock() - read_callback_mock.reset_mock() - - # read/write/verify pass - {% if asyncoutput %}async {% endif %}with reg.single_read_modify_write(verify=True) as reg_session: - for field_name, field_value in field_values.items(): - field = getattr(reg_session, field_name) - {% if asyncoutput %}await {% endif %}field.write(field_value) - read_callback_mock.return_value = expected_value - - write_callback_mock.assert_called_once_with( - addr=reg.address, - width=reg.width, - accesswidth=reg.accesswidth, - data=expected_value) - self.assertEqual(read_callback_mock.call_count, 2) - write_callback_mock.reset_mock() - read_callback_mock.reset_mock() - - # read/write/verify pass - with self.assertRaises(RegisterWriteVerifyError) as context: - {% if asyncoutput %}async {% endif %}with reg.single_read_modify_write(verify=True) as reg_session: - for field_name, field_value in field_values.items(): - field = getattr(reg_session, field_name) - {% if asyncoutput %}await {% endif %}field.write(field_value) - read_callback_mock.return_value = expected_value ^ reg_session.max_value - - write_callback_mock.reset_mock() - read_callback_mock.reset_mock() - - {% for node in owned_elements.registers -%} - {% if node.has_sw_writable and node.has_sw_readable %} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - {% if asyncoutput %}await {% endif %}write_field_combinations(reg=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writable_fields = [ {% for field in get_reg_writable_fields(node) -%} - '{{ safe_node_name(field) }}' {%- if not loop.last -%},{%- endif %} - {% endfor -%} ]) - {%- endif %} - {%- endfor %} + self._single_register_property_test(rut=self.dut.{{'.'.join(get_python_path_segments(node))}}, address={{node.absolute_address}}, width={{node.size * 8}}, accesswidth={% if 'accesswidth' in node.list_properties() -%}{{node.get_property('accesswidth')}}{% else %}None{%- endif %}, size={{node.size}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('name', default=None) is none %}None{% else %}{{node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('desc', default=None) is none %}None{% else %}{{node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') + {% if asyncoutput %}await {%endif %}self._single_register_read_and_write_test(rut=self.dut.{{'.'.join(get_python_path_segments(node))}}, has_sw_readable={{node.has_sw_readable}}, has_sw_writable={{node.has_sw_writable}}, + readable_fields=set([ {%- for child_node in node.children(unroll=True) -%}{%- if not hide_node_func(child_node) -%}{% if isinstance(child_node, systemrdlFieldNode) %}{% if child_node.is_sw_readable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + writeable_fields=set([ {%- for child_node in node.children(unroll=True) -%}{%- if not hide_node_func(child_node) -%}{% if isinstance(child_node, systemrdlFieldNode) %}{% if child_node.is_sw_writable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]) ) + {% endfor %} - {% if asyncoutput %}async {% endif %}def test_register_write_fields(self) -> None: + {% if asyncoutput %}async {% endif %}def test_field(self) -> None: """ - Walk the register map and check every register write_fields method + Check the properties and function (read and write) on the fields both integer and enum """ - rand_enum_value:{% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %} - {% if asyncoutput %}async {% endif %}def write_field_combinations(reg: Reg{% if asyncoutput %}Async{% endif %}ReadWrite, writable_fields:list[str]) -> None: - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=0) as read_callback_mock: - # fix for #196 (excessive test time) if the number of fields is greater than 4 - # the combinations are reduced to only tests combinations of three plus the full - # set - if len(writable_fields) > 4: - perms_iterator: Iterable[int] = chain(range(1,4), [len(writable_fields)]) - else: - perms_iterator = range(1, len(writable_fields) + 1) - for fields_to_write in chain.from_iterable((combinations(writable_fields, perms) for perms in perms_iterator)): - kwargs: dict[str, Union[bool, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}, int]] = {} - expected_value = 0 - for field_str in fields_to_write: - field = getattr(reg, field_str) - if hasattr(field, 'enum_cls'): - rand_enum_value = random_enum_reg_value(field.enum_cls) - rand_field_value = rand_enum_value.value - kwargs[field_str] = rand_enum_value - else: - rand_field_value = random.randrange(0, field.max_value + 1) - kwargs[field_str] = rand_field_value - - if field.msb == field.high: - expected_value = ( expected_value & field.inverse_bitmask ) | (rand_field_value << field.low) - elif field.msb == field.low: - expected_value = ( expected_value & field.inverse_bitmask ) | (self._reverse_bits(value=rand_field_value, number_bits=field.width) << field.low) - else: - raise RuntimeError('invalid msb/lsb high/low combination') - - {% if asyncoutput %}await {% endif %}reg.write_fields(**kwargs) - write_callback_mock.assert_called_once_with( - addr=reg.address, - width=reg.width, - accesswidth=reg.accesswidth, - data=expected_value) - read_callback_mock.assert_called_once() - write_callback_mock.reset_mock() - read_callback_mock.reset_mock() - - kwargs : dict[str, Union[bool, {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}, int]] - - {% for node in owned_elements.registers -%} - {% if node.has_sw_writable %} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - # test read_fields to register: - # {{'.'.join(node.get_path_segments())}} - {% if node.has_sw_readable -%} - {% if asyncoutput %}await {% endif %}write_field_combinations(reg=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writable_fields = [ {% for field in get_reg_writable_fields(node) -%} - '{{ safe_node_name(field) }}' {%- if not loop.last -%},{%- endif %} - {% endfor -%} ]) - {% else -%} - kwargs = {} - expected_value = 0 - {% for field in get_reg_writable_fields(node) %} - {%- if 'encode' in field.list_properties() %} - rand_enum_value = random_enum_reg_value(self.dut.{{'.'.join(get_python_path_segments(field))}}.enum_cls) - rand_field_value = rand_enum_value.value - kwargs['{{ safe_node_name(field)}}'] = rand_enum_value + {% for node in owned_elements.fields %} + with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): + self._single_field_property_test(fut=self.dut.{{'.'.join(get_python_path_segments(node))}}, lsb={{node.lsb}}, msb={{node.msb}}, low={{node.low}}, high={{node.high}}, is_volatile={{node.is_hw_writable}}, default={{get_field_default_value(node)}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('name', default=None) is none %}None{% else %}{{node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('desc', default=None) is none %}None{% else %}{{node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') + {%- if 'encode' not in node.list_properties() %} + {% if asyncoutput %}await {%endif %}self._single_int_field_read_and_write_test(fut=self.dut.{{'.'.join(get_python_path_segments(node))}}, is_sw_readable={{node.is_sw_readable}}, is_sw_writable={{node.is_sw_writable}}) {%- else %} - rand_field_value = random.randrange(0, {{ get_field_max_value_hex_string(field) }} + 1) - kwargs['{{ safe_node_name(field)}}'] = rand_field_value - {%- endif %} - expected_value = ( expected_value & {{get_field_inv_bitmask_hex_string(field)}} ) | (rand_field_value << {{ field.low }}) - {% endfor %} - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=0) as read_callback_mock: - {% if asyncoutput %}await {% endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write_fields(**kwargs) # type: ignore[arg-type] - write_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}, - width={{node.size * 8}}, - accesswidth=self.dut.{{'.'.join(get_python_path_segments(node))}}.accesswidth, # type: ignore[union-attr] - data=expected_value) - read_callback_mock.assert_not_called() - write_callback_mock.reset_mock() - read_callback_mock.reset_mock() - - {% endif %} - - - {%- endif %} + {% if asyncoutput %}await {%endif %}self._single_enum_field_read_and_write_test(fut=self.dut.{{'.'.join(get_python_path_segments(node))}}, # type: ignore[arg-type] + is_sw_readable={{node.is_sw_readable}}, is_sw_writable={{node.is_sw_writable}}, full_enum_def={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':({{enum_entry.value}}, {% if enum_entry.rdl_name is none or legacy_enum_type or skip_systemrdl_name_and_desc_properties %}None{% else %}{{enum_entry.rdl_name | tojson}}{% endif %}, {% if enum_entry.rdl_desc is none or legacy_enum_type or skip_systemrdl_name_and_desc_properties %}None{% else %}{{enum_entry.rdl_desc | tojson}}{% endif %}),{% endfor %} }) + {%- endif %} {%- endfor %} - {% if uses_memory %} - {% if asyncoutput %}async {% endif %}def test_memory_read_and_write(self) -> None: + def test_addrmap(self) -> None: """ - Walk the register map and check every register can be read and written to correctly + Check the properties on the addrmaps files """ - {% for node in owned_elements.memories -%} - - # test access operations (read and/or write) to register: - # {{'.'.join(node.get_path_segments())}} - with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): - with patch(base_name + '.write_addr_space') as write_callback_mock, \ - patch(base_name + '.read_addr_space', return_value=1) as read_callback_mock: - - {% if node.is_sw_readable -%} - # checks single unit accesses at the first entry, the last entry and a random entry in - # in each case check a 0, max value and random value being read - for entry in [0, random.randint(0,{{node.get_property('mementries')-1}}), {{node.get_property('mementries')-1}}]: - for value in [0, random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}), {{get_memory_max_entry_value_hex_string(node)}}]: - read_callback_mock.reset_mock() - read_callback_mock.return_value = value - {% if legacy_block_access %} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=entry, number_entries=1), # type: ignore[union-attr] - Array('{{get_array_typecode(node.get_property('memwidth'))}}', [value])) - {% else %} - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=entry, number_entries=1), # type: ignore[union-attr] - [value]) - {% endif %} - - read_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}+(entry * {{get_memory_width_bytes(node)}}), - width={{node.get_property('memwidth')}}, - accesswidth={{node.get_property('memwidth')}}) - - # check a multi-entry read, if the memory is small do the entire memory, however, if - # it is large limit the number of entries to 10 - entries_to_test = {% if node.get_property('mementries') > 10 %}10{% else %}{{node.get_property('mementries')}}{% endif %} - {% if legacy_block_access %} - random_data = Array('{{get_array_typecode(node.get_property('memwidth'))}}', - [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)]) - {% else %} - random_data = [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)] - {% endif %} - - - def read_data_mock(addr:int, width:int, accesswidth:int) -> int: - mem_entry = (addr - {{node.absolute_address}}) // {{get_memory_width_bytes(node)}} - return random_data[mem_entry] - - read_callback_mock.reset_mock() - read_callback_mock.side_effect=read_data_mock - - self.assertEqual({% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.read(start_entry=0, number_entries=entries_to_test), # type: ignore[union-attr] - random_data) - - write_callback_mock.assert_not_called() - read_callback_mock.reset_mock() - {% endif %} - - {% if node.is_sw_writable -%} - # checks single unit accesses at the first entry, the last entry and a random entry in - # in each case check a 0, max value and random value being read - for entry in [0, random.randint(0,{{node.get_property('mementries')-1}}), {{node.get_property('mementries')-1}}]: - for value in [0, random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}), {{get_memory_max_entry_value_hex_string(node)}}]: - write_callback_mock.reset_mock() - {% if legacy_block_access %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(start_entry=entry, data=Array('{{get_array_typecode(node.get_property('memwidth'))}}', [value])) # type: ignore[union-attr] - {% else %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(start_entry=entry, data=[value]) # type: ignore[union-attr] - {% endif %} - write_callback_mock.assert_called_once_with( - addr={{node.absolute_address}}+(entry * {{get_memory_width_bytes(node)}}), - width={{node.get_property('memwidth')}}, - accesswidth={{node.get_property('memwidth')}}, - data=value) - - read_callback_mock.assert_not_called() - write_callback_mock.reset_mock() - # check a multi-entry read, if the memory is small do the entire memory, however, if - # it is large limit the number of entries to 10 - entries_to_test = {% if node.get_property('mementries') > 10 %}10{% else %}{{node.get_property('mementries')}}{% endif %} - {% if legacy_block_access %} - random_data = Array('{{get_array_typecode(node.get_property('memwidth'))}}', - [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)]) - {% else %} - random_data = [random.randint(0,{{get_memory_max_entry_value_hex_string(node)}}) for x in range(entries_to_test)] - {% endif %} - {% if asyncoutput %}await {%endif %}self.dut.{{'.'.join(get_python_path_segments(node))}}.write(start_entry=0, data=random_data) # type: ignore[union-attr] - - for x in range(entries_to_test): - self.assertEqual(write_callback_mock.call_args_list[x], - call(addr={{node.absolute_address}} + (x * {{get_memory_width_bytes(node)}}), - width={{node.get_property('memwidth')}}, - accesswidth={% if 'accesswidth' in node.list_properties() -%}{{node.get_property('accesswidth')}}{%- else -%}{{node.get_property('memwidth')}}{%- endif -%}, - data=random_data[x])) - - read_callback_mock.assert_not_called() - write_callback_mock.reset_mock() - - {%- endif %} - - {%- endfor %} - {%- endif %} - - def test_adding_attributes(self) -> None: - """ - Walk the address map and attempt to set a new value on each node + {% if block == top_node %} + with self.subTest(msg='addrmap: top_node'): + self._single_addrmap_property_test(dut=self.dut, + size={{block.size}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif top_node.get_property('name', default=None) is none %}None{% else %}{{top_node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif top_node.get_property('desc', default=None) is none %}None{% else %}{{top_node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{top_node.get_path_segments()[-1]}}', + parent_full_inst_name=None) + self._test_addrmap_iterators(dut=self.dut, + writeable_registers=NodeIterators({%- for child_node in top_node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + readable_registers=NodeIterators({%- for child_node in top_node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + sections=NodeIterators({%- for child_node in top_node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endfor %}), + memories=NodeIterators({%- for child_node in top_node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endfor %})) + {% endif %}{# if block == top_block #} - The attribute name: cppkbrgmgeloagvfgjjeiiushygirh was randomly generated to be unlikely to - every be a attribute name - """ - {% for node in owned_elements.nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - with self.assertRaises(AttributeError): - # this line is trying to set an illegal value so by definition should fail the type - # checks - self.dut.{{'.'.join(get_python_path_segments(node))}}.cppkbrgmgeloagvfgjjeiiushygirh = 1 # type: ignore[attr-defined,union-attr] + # test all the address maps + {% for node in owned_elements.addr_maps -%} + with self.subTest(msg='addrmap: {{'.'.join(node.get_path_segments())}}'): + self._single_addrmap_property_test(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + size={{node.size}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('name', default=None) is none %}None{% else %}{{node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('desc', default=None) is none %}None{% else %}{{node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') + self._test_addrmap_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + writeable_registers=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + readable_registers=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + sections=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endfor %}), + memories=NodeIterators( {%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endfor %})) {% endfor %} - {% if block == top_node %} - def test_top_traversal_iterators(self) -> None: - {% if asyncoutput %} - expected_writable_regs: list[WritableAsyncRegister] - expected_readable_regs: list[ReadableAsyncRegister] - expected_memories:list[Union[MemoryAsyncReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncReadWrite{% if legacy_block_access %}Legacy{% endif %}]] - - {% else %} - expected_writable_regs: list[WritableRegister] - expected_readable_regs: list[ReadableRegister] - expected_memories:list[Union[MemoryReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryReadWrite{% if legacy_block_access %}Legacy{% endif %}]] - {% endif %} - expected_sections : list[Union[{% if asyncoutput %}Async{% endif %}AddressMap, {% if asyncoutput %}Async{% endif %}RegFile]] - - # check the readable registers - expected_readable_regs = [ {%- for child_node in top_node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - readable_regs = [] - for readable_reg in self.dut.get_readable_registers(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(readable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}ReadOnly)) - readable_regs.append(readable_reg) - self.assertCountEqual(expected_readable_regs, readable_regs) - {# check the rolled case second #} - expected_readable_regs = [ {%- for child_node in top_node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - readable_regs = [] - for readable_reg in self.dut.get_readable_registers(unroll=False): # type: ignore[union-attr] - readable_regs.append(readable_reg) - self.assertCountEqual(expected_readable_regs, readable_regs) - - # check the writable registers - expected_writable_regs = [ {%- for child_node in top_node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - writable_regs = [] - for writable_reg in self.dut.get_writable_registers(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(writable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}WriteOnly)) - writable_regs.append(writable_reg) - self.assertCountEqual(expected_writable_regs, writable_regs) - {# check the rolled case second #} - expected_writable_regs = [ {%- for child_node in top_node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - writable_regs = [] - for writable_reg in self.dut.get_writable_registers(unroll=False): # type: ignore[union-attr] - writable_regs.append(writable_reg) - self.assertCountEqual(expected_writable_regs, writable_regs) - - # check the sections - expected_sections = [ {%- for child_node in top_node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, (systemrdlAddrmapNode, systemrdlRegfileNode)) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - sections = [] - for section in self.dut.get_sections(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(section, ({% if asyncoutput %}Async{% endif %}AddressMap, {% if asyncoutput %}Async{% endif %}RegFile)) - sections.append(section) - self.assertCountEqual(expected_sections, sections) - {# check the rolled case second #} - expected_sections = [ {%- for child_node in top_node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, (systemrdlAddrmapNode, systemrdlRegfileNode)) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - sections = [] - for section in self.dut.get_sections(unroll=False): # type: ignore[union-attr] - sections.append(section) - self.assertCountEqual(expected_sections, sections) - - # check the memories - expected_memories = [ {%- for child_node in top_node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlMemNode) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - memories = [] - for memory in self.dut.get_memories(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(memory, (Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %})) - memories.append(memory) - self.assertCountEqual(expected_memories, memories) - {# check the rolled case second #} - expected_memories = [ {%- for child_node in top_node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlMemNode) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - memories = [] - for memory in self.dut.get_memories(unroll=False): # type: ignore[union-attr] - self.assertIsInstance(memory, (Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}Array, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}Array, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %}Array)) - memories.append(memory) - self.assertCountEqual(expected_memories, memories) - - {% endif %}{# if block == top_block #} - - {% macro check_readable_register_iterators(node) %} - {# check the unrolled case first #} - expected_readable_regs = [ {%- for child_node in node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - readable_regs = [] - for readable_reg in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_readable_registers(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(readable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}ReadOnly)) - readable_regs.append(readable_reg) - self.assertCountEqual(expected_readable_regs, readable_regs) - {# check the rolled case second #} - expected_readable_regs = [ {%- for child_node in node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - readable_regs = [] - for readable_reg in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_readable_registers(unroll=False): # type: ignore[union-attr] - self.assertIsInstance(readable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}ReadOnly, Reg{% if asyncoutput %}Async{% endif %}ReadWriteArray, Reg{% if asyncoutput %}Async{% endif %}ReadOnlyArray)) - readable_regs.append(readable_reg) - self.assertCountEqual(expected_readable_regs, readable_regs) - {% endmacro %} - - {% macro check_writable_register_iterators(node) %} - {# check the unrolled case first #} - expected_writable_regs = [ {%- for child_node in node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - writable_regs = [] - for writable_reg in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_writable_registers(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(writable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}WriteOnly)) - writable_regs.append(writable_reg) - self.assertCountEqual(expected_writable_regs, writable_regs) - {# check the rolled case second #} - expected_writable_regs = [ {%- for child_node in node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} ] - writable_regs = [] - for writable_reg in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_writable_registers(unroll=False): # type: ignore[union-attr] - self.assertIsInstance(writable_reg, (Reg{% if asyncoutput %}Async{% endif %}ReadWrite, Reg{% if asyncoutput %}Async{% endif %}WriteOnly, Reg{% if asyncoutput %}Async{% endif %}ReadWriteArray, Reg{% if asyncoutput %}Async{% endif %}WriteOnlyArray)) - writable_regs.append(writable_reg) - self.assertCountEqual(expected_writable_regs, writable_regs) - {% endmacro %} - - {% macro check_section_iterators(node) %} - {# check the unrolled case first #} - expected_sections = [ {%- for child_node in node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, (systemrdlAddrmapNode, systemrdlRegfileNode)) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - sections = [] - for section in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_sections(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(section, ({% if asyncoutput %}Async{% endif %}AddressMap, {% if asyncoutput %}Async{% endif %}RegFile)) - sections.append(section) - self.assertCountEqual(expected_sections, sections) - {# check the rolled case second #} - expected_sections = [ {%- for child_node in node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, (systemrdlAddrmapNode, systemrdlRegfileNode)) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - sections = [] - for section in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_sections(unroll=False): # type: ignore[union-attr] - self.assertIsInstance(section, ({% if asyncoutput %}Async{% endif %}AddressMap, {% if asyncoutput %}Async{% endif %}RegFile, {% if asyncoutput %}Async{% endif %}AddressMapArray, {% if asyncoutput %}Async{% endif %}RegFileArray)) - sections.append(section) - self.assertCountEqual(expected_sections, sections) - {% endmacro %} - - {% macro check_memory_iterators(node) %} - {# check the unrolled case first #} - expected_memories = [ {%- for child_node in node.children(unroll=True) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlMemNode) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - memories = [] - for memory in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_memories(unroll=True): # type: ignore[union-attr] - self.assertIsInstance(memory, (Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %})) - memories.append(memory) - self.assertCountEqual(expected_memories, memories) - {# check the rolled case second #} - expected_memories = [ {%- for child_node in node.children(unroll=False) %} - {%- if not hide_node_func(child_node) %} - {%- if isinstance(child_node, systemrdlMemNode) %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %} - {% endif %} - {% endfor %} ] - memories = [] - for memory in self.dut.{{'.'.join(get_python_path_segments(node))}}.get_memories(unroll=False): # type: ignore[union-attr] - self.assertIsInstance(memory, (Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %}, Memory{% if asyncoutput %}Async{% endif %}ReadOnly{% if legacy_block_access %}Legacy{% endif %}Array, Memory{% if asyncoutput %}Async{% endif %}WriteOnly{% if legacy_block_access %}Legacy{% endif %}Array, Memory{% if asyncoutput %}Async{% endif %}ReadWrite{% if legacy_block_access %}Legacy{% endif %}Array)) - memories.append(memory) - self.assertCountEqual(expected_memories, memories) - {% endmacro %} - - def test_traversal_iterators(self) -> None: + def test_regfile(self) -> None: """ - Walk the address map and check that the iterators for each node as as expected + Check the properties on the register files """ - {% if asyncoutput %} - expected_writable_fields: list[Union[FieldAsyncWriteOnly, FieldAsyncReadWrite]] - expected_readable_fields: list[Union[FieldAsyncReadOnly, FieldAsyncReadWrite]] - expected_fields: list[Union[FieldAsyncWriteOnly, FieldAsyncReadOnly, FieldAsyncReadWrite]] - expected_writable_regs: list[WritableAsyncRegister] - expected_readable_regs: list[ReadableAsyncRegister] - expected_memories:list[Union[MemoryAsyncReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryAsyncReadWrite{% if legacy_block_access %}Legacy{% endif %}]] - - {% else %} - expected_writable_fields: list[Union[FieldWriteOnly, FieldReadWrite]] - expected_readable_fields: list[Union[FieldReadOnly, FieldReadWrite]] - expected_fields: list[Union[FieldWriteOnly, FieldReadOnly, FieldReadWrite]] - expected_writable_regs: list[WritableRegister] - expected_readable_regs: list[ReadableRegister] - expected_memories:list[Union[MemoryReadOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryWriteOnly{% if legacy_block_access %}Legacy{% endif %}, MemoryReadWrite{% if legacy_block_access %}Legacy{% endif %}]] - {% endif %} - expected_sections : list[Union[{% if asyncoutput %}Async{% endif %}AddressMap, {% if asyncoutput %}Async{% endif %}RegFile]] - - # test all the registers - {% for node in owned_elements.registers -%} - with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - {# a register can only have fields beneath it #} - expected_fields = [ {%- for child_node in node.children(unroll=True) -%} - {% if isinstance(child_node, systemrdlFieldNode) %} - {%- if not hide_node_func(child_node) -%} - self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] - {% endif %} - {% endif %} - {% endfor %} - ] - fields = [] - for field in self.dut.{{'.'.join(get_python_path_segments(node))}}.fields: # type: ignore[union-attr] - fields.append(field) - self.assertCountEqual(expected_fields, fields) - {% if node.has_sw_writable %} - expected_writable_fields = [ {%- for child_node in node.children(unroll=True) -%} - {%- if not hide_node_func(child_node) -%} - {% if isinstance(child_node, systemrdlFieldNode) %}{% if child_node.is_sw_writable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} - ] - writable_fields = [] - for writable_field in self.dut.{{'.'.join(get_python_path_segments(node))}}.writable_fields: # type: ignore[union-attr] - writable_fields.append(writable_field) - self.assertCountEqual(expected_writable_fields, writable_fields) - {% else %} - # register should not have writable_fields attribute - self.assertFalse(hasattr(self.dut.{{'.'.join(get_python_path_segments(node))}}, 'writable_fields')) # type: ignore[union-attr] - {% endif %} - {% if node.has_sw_readable %} - expected_readable_fields = [ {%- for child_node in node.children(unroll=True) -%} - {%- if not hide_node_func(child_node) -%} - {% if isinstance(child_node, systemrdlFieldNode) %}{% if child_node.is_sw_readable %}self.dut.{{'.'.join(get_python_path_segments(child_node))}}, # type: ignore[union-attr,list-item] {% endif %}{% endif %} - {% endif %} - {% endfor %} - ] - readable_fields = [] - for readable_field in self.dut.{{'.'.join(get_python_path_segments(node))}}.readable_fields: # type: ignore[union-attr] - readable_fields.append(readable_field) - self.assertCountEqual(expected_readable_fields, readable_fields) - {% else %} - # register should not have readable_fields attribute - self.assertFalse(hasattr(self.dut.{{'.'.join(get_python_path_segments(node))}}, 'readable_fields')) # type: ignore[union-attr] - {% endif %} - {% endfor %} - # test all the memories - {% for node in owned_elements.memories -%} - with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): - {% if node.is_sw_writable %} - {{ check_writable_register_iterators(node) | indent }} - {% else %} - self.assertFalse(hasattr(self.dut.{{'.'.join(get_python_path_segments(node))}}, 'get_writeable_registers')) # type: ignore[union-attr] - {% endif %} - {% if node.is_sw_readable %} - {{ check_readable_register_iterators(node) | indent}} - {% else %} - self.assertFalse(hasattr(self.dut.{{'.'.join(get_python_path_segments(node))}}, 'get_readable_registers')) # type: ignore[union-attr] - {% endif %} - {% endfor %} - # test all the address maps - {% for node in owned_elements.addr_maps -%} - with self.subTest(msg='addrmap: {{'.'.join(node.get_path_segments())}}'): - {{ check_readable_register_iterators(node) | indent }} - {{ check_writable_register_iterators(node) | indent}} - {{ check_section_iterators(node) | indent }} - {{ check_memory_iterators(node) | indent}} - {% endfor %} # test all the register files {% for node in owned_elements.reg_files -%} with self.subTest(msg='regfile: {{'.'.join(node.get_path_segments())}}'): - {{ check_readable_register_iterators(node) | indent }} - {{ check_writable_register_iterators(node) | indent}} - {{ check_section_iterators(node) | indent }} - self.assertFalse(hasattr(self.dut.{{'.'.join(get_python_path_segments(node))}}, 'get_memories')) # type: ignore[union-attr] - {% endfor %} - - def test_name_map(self) -> None: - """ - Check that the function for getting a node by its original systemRDL name works - """ - {% for node in owned_elements.nodes -%} - {% for child_node in node.children(unroll=False) %} - {% if not isinstance(child_node, systemrdlSignalNode) %} - {%- if not hide_node_func(child_node) %} - self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(node))}}.get_child_by_system_rdl_name('{{child_node.inst_name}}').inst_name, '{{child_node.inst_name}}') - {% endif %} - {% endif %} - {% endfor %} + self._single_regfile_property_test(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + size={{node.size}}, + rdl_name={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('name', default=None) is none %}None{% else %}{{node.get_property('name') | tojson}}{% endif %}, + rdl_desc={% if skip_systemrdl_name_and_desc_properties %}None{% elif node.get_property('desc', default=None) is none %}None{% else %}{{node.get_property('desc') | tojson}}{% endif %}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') + self._test_regfile_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + writeable_registers=NodeIterators({%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_writable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + readable_registers=NodeIterators({%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlRegNode) %}{% if child_node.has_sw_readable %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endif %}{% endfor %}), + sections=NodeIterators({%- for child_node in node.children(unroll=False) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}{{node_iterator_entry(child_node)}},{% endif %}{% endif %}{% endfor %})) {% endfor %} {% if owned_elements.has_hidden_nodes %} @@ -1706,15 +546,5 @@ class {{fq_block_name}}_alt_block_access({{top_node.inst_name}}_TestCase_AltBloc if __name__ == '__main__': -{% if asyncoutput %} - if sys.version_info < (3, 8): - asynctest.main() - else: - unittest.main() -{% else %} - unittest.main() -{% endif %} - - - + unittest.main() diff --git a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja index c6a064aa..7d18a516 100644 --- a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja @@ -24,15 +24,9 @@ along with this program. If not, see . {% if legacy_block_access %}from array import array as Array{% endif %} {% if asyncoutput %} -import sys import asyncio -if sys.version_info < (3, 8): - import asynctest # type: ignore[import] -else: - import unittest -{% else %} -import unittest {% endif %} +import unittest from {{ peakrdl_python_lib(depth=lib_depth) }} import RegisterWriteVerifyError @@ -47,14 +41,13 @@ from ._{{top_node.inst_name}}_test_base import {{top_node.inst_name}}_TestCase, from ..reg_model import RegModel from ..sim import Simulator -class {{top_node.inst_name}}_SimTestCase({{top_node.inst_name}}_TestCase): # type: ignore[valid-type,misc] - - def setUp(self) -> None: - self.sim = Simulator(address=0) - self.dut = RegModel(callbacks={% if asyncoutput %}AsyncCallbackSet{% else %}NormalCallbackSet{% endif %}{% if legacy_block_access %}Legacy{% endif %}(read_callback=self.sim.read, - write_callback=self.sim.write)) +{% if asyncoutput %} +TestCaseBase = unittest.IsolatedAsyncioTestCase +{% else %} +TestCaseBase = unittest.TestCase +{% endif %} -class {{top_node.inst_name}}_SimTestCase_BlockAccess({{top_node.inst_name}}_TestCase_BlockAccess): # type: ignore[valid-type,misc] +class {{top_node.inst_name}}_SimTestCase_BlockAccess(TestCaseBase): # type: ignore[valid-type,misc] def setUp(self) -> None: self.sim = Simulator(address=0) diff --git a/src/peakrdl_python/templates/baseclass_tb.py.jinja b/src/peakrdl_python/templates/baseclass_tb.py.jinja index 2192d0da..672e2b40 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -20,20 +20,15 @@ along with this program. If not, see . {% from 'template_ultilities.py.jinja' import peakrdl_python_sim_lib with context %} {% from 'template_ultilities.py.jinja' import peakrdl_python_lib with context %} +{% from 'template_ultilities.py.jinja' import peakrdl_python_lib_test with context %} {# the following defining the number relative steps up to the lib and sim_lib packages from the current file #} {% set lib_depth = 1 %} from array import array as Array {% if asyncoutput %} -import sys import asyncio -if sys.version_info < (3, 8): - import asynctest # type: ignore[import] -else: - import unittest -{% else %} -import unittest {% endif %} +import unittest import random {% if legacy_enum_type %} from enum import IntEnum @@ -49,6 +44,8 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import NormalCallbackSet, NormalC from ..reg_model import RegModel +from ..sim import Simulator +from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.simulator import BaseSimulator {% if asyncoutput %} from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_dummy_read as read_addr_space from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_dummy_write as write_addr_space @@ -56,6 +53,8 @@ from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_ from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_dummy_write_block{% if legacy_block_access %}_legacy{% endif %} as write_block_addr_space from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_dummy_read_block{% if not legacy_block_access %}_legacy{% endif %} as read_block_addr_space_alt from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import async_dummy_write_block{% if not legacy_block_access %}_legacy{% endif %} as write_block_addr_space_alt +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import AsyncLibTestBase as TestCaseBase +BlockTestBase = unittest.IsolatedAsyncioTestCase {% else %} from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_read as read_addr_space from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_write as write_addr_space @@ -63,6 +62,8 @@ from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_ from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_write_block{% if legacy_block_access %}_legacy{% endif %} as write_block_addr_space from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_read_block{% if not legacy_block_access %}_legacy{% endif %} as read_block_addr_space_alt from {{ peakrdl_python_sim_lib(depth=lib_depth) }}.dummy_callbacks import dummy_write_block{% if not legacy_block_access %}_legacy{% endif %} as write_block_addr_space_alt +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import LibTestBase as TestCaseBase +BlockTestBase = unittest.TestCase {% endif %} {% if not legacy_enum_type %} @@ -90,39 +91,29 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum, SystemRDLEn def random_enum_reg_value(enum_class: type[{% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}]) -> {% if legacy_enum_type %}IntEnum{% else %}SystemRDLEnum{% endif %}: return random.choice(list(enum_class)) -{% if asyncoutput %} -if sys.version_info < (3, 8): - TestCaseBase = asynctest.TestCase -else: - TestCaseBase = unittest.IsolatedAsyncioTestCase -{% else %} -TestCaseBase = unittest.TestCase -{% endif %} class {{top_node.inst_name}}_TestCase(TestCaseBase): # type: ignore[valid-type,misc] - def setUp(self) -> None: - self.dut = RegModel(callbacks={% if asyncoutput %}AsyncCallbackSet{% else %}NormalCallbackSet{% endif %}{% if legacy_block_access %}Legacy{% endif %}(read_callback=read_callback, - write_callback=write_callback)) + {% if asyncoutput %}async {% endif %}def read_callback(self, addr: int, width: int, accesswidth: int) -> int: + return {% if asyncoutput %}await {% endif %}self.sim.read(addr=addr, width=width, accesswidth=accesswidth) - @staticmethod - def _reverse_bits(value: int, number_bits: int) -> int: - """ + {% if asyncoutput %}async {% endif %}def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + return {% if asyncoutput %}await {% endif %}self.sim.write(addr=addr, width=width, accesswidth=accesswidth, data=data) - Args: - value: value to reverse - number_bits: number of bits used in the value + @property + def simulator_instance(self) -> BaseSimulator: + return self.sim - Returns: - reversed valued - """ - result = 0 - for i in range(number_bits): - if (value >> i) & 1: - result |= 1 << (number_bits - 1 - i) - return result + @property + def legacy_block_access(self) -> bool: + return {{ legacy_block_access }} -class {{top_node.inst_name}}_TestCase_BlockAccess(TestCaseBase): # type: ignore[valid-type,misc] + def setUp(self) -> None: + self.sim = Simulator(address=0) + self.dut = RegModel(callbacks={% if asyncoutput %}AsyncCallbackSet{% else %}NormalCallbackSet{% endif %}{% if legacy_block_access %}Legacy{% endif %}(read_callback=self.outer_read_callback, + write_callback=self.outer_write_callback)) + +class {{top_node.inst_name}}_TestCase_BlockAccess(BlockTestBase): # type: ignore[valid-type,misc] def setUp(self) -> None: self.dut = RegModel(callbacks={% if asyncoutput %}AsyncCallbackSet{% else %}NormalCallbackSet{% endif %}{% if legacy_block_access %}Legacy{% endif %}(read_callback=read_callback, @@ -130,12 +121,13 @@ class {{top_node.inst_name}}_TestCase_BlockAccess(TestCaseBase): # type: ignore[ read_block_callback=read_block_callback, write_block_callback=write_block_callback)) -class {{top_node.inst_name}}_TestCase_AltBlockAccess(TestCaseBase): # type: ignore[valid-type,misc] +class {{top_node.inst_name}}_TestCase_AltBlockAccess(BlockTestBase): # type: ignore[valid-type,misc] """ Based test to use with the alternative call backs, this allow the legacy output API to be tested with the new callbacks and visa versa. """ + def setUp(self) -> None: self.dut = RegModel(callbacks={% if asyncoutput %}AsyncCallbackSet{% else %}NormalCallbackSet{% endif %}{% if not legacy_block_access %}Legacy{% endif %}( read_callback=read_callback, diff --git a/src/peakrdl_python/templates/template_ultilities.py.jinja b/src/peakrdl_python/templates/template_ultilities.py.jinja index f8158184..4568ea3a 100644 --- a/src/peakrdl_python/templates/template_ultilities.py.jinja +++ b/src/peakrdl_python/templates/template_ultilities.py.jinja @@ -23,6 +23,10 @@ along with this program. If not, see . {% if skip_lib_copy %}peakrdl_python.{% else %}.{{ '.' * depth }}{% endif %}sim_lib {%- endmacro -%} +{%- macro peakrdl_python_lib_test(depth) -%} +{% if skip_lib_copy %}peakrdl_python.{% else %}.{{ '.' * depth }}{% endif %}lib_test +{%- endmacro -%} + {%- macro peakrld_version_check() %} {% if skip_lib_copy %} from peakrdl_python.__about__ import __version__ diff --git a/tests/testcases/memories_with_registers.rdl b/tests/testcases/memories_with_registers.rdl index b51bc118..91ad59f9 100644 --- a/tests/testcases/memories_with_registers.rdl +++ b/tests/testcases/memories_with_registers.rdl @@ -6,11 +6,39 @@ addrmap memories_with_registers { sw=rw; reg mem_entry_definition { - field {} lower_entry[15:0]; + field { sw=rw;} lower_entry[15:0]; }; mem_entry_definition mem_entry_set1; mem_entry_definition mem_entry_set2[4]; } mem_with_internal_registers; + + external mem { + mementries = 16; + memwidth = 32; + sw=r; + + reg mem_entry_definition { + field {sw=r;} lower_entry[15:0]; + }; + + mem_entry_definition mem_entry_set1; + mem_entry_definition mem_entry_set2[4]; + + } mem_with_internal_registers_read_only; + + external mem { + mementries = 16; + memwidth = 32; + sw=w; + + reg mem_entry_definition { + field {sw=w;} lower_entry[15:0]; + }; + + mem_entry_definition mem_entry_set1; + mem_entry_definition mem_entry_set2[4]; + + } mem_with_internal_registers_write_only; }; \ No newline at end of file diff --git a/tests/unit_tests/test_export.py b/tests/unit_tests/test_export.py index ee3f235d..2a819dc0 100644 --- a/tests/unit_tests/test_export.py +++ b/tests/unit_tests/test_export.py @@ -25,7 +25,7 @@ import tempfile import sys import re -from itertools import chain, permutations, product +from itertools import chain, combinations, product from pathlib import Path from array import array as Array import inspect @@ -272,7 +272,7 @@ def check_udp_present(dut, udp_to_include:list[str]) -> None: for udp_to_include in chain.from_iterable( - [permutations(full_property_list, r) for r in range(len(full_property_list))]): + [combinations(full_property_list, r) for r in range(4)]): # check the list method with self.subTest(udp_to_include=udp_to_include), \ self.build_wrappers_and_import(udp_list=list(udp_to_include)) as dut: diff --git a/tests/unit_tests/test_reg.py b/tests/unit_tests/test_reg.py index 45973509..7c5920be 100644 --- a/tests/unit_tests/test_reg.py +++ b/tests/unit_tests/test_reg.py @@ -326,15 +326,17 @@ def test_context_manager_read_modify_write_check_writeback(self) -> None: side_effect=self.write_addr_space) as write_patch: with self.dut.single_read_modify_write() as reg: - _ = reg.field.read() - _ = reg.field.read() + self.assertEqual(reg.field.read(), False) + self.assertEqual(reg.field.read(), False) + reg.field.write(True) + self.assertEqual(reg.field.read(), True) read_patch.assert_called_once_with(addr=0, width=self.dut.width, accesswidth=self.dut.accesswidth) write_patch.assert_called_once_with(addr=0, width=self.dut.width, - accesswidth=self.dut.accesswidth, data=0) + accesswidth=self.dut.accesswidth, data=1) # check the `skip_write` works as expected, this will however raise an deprecation warning # and the feature will be removed at some point in the future @@ -343,7 +345,7 @@ def test_context_manager_read_modify_write_check_writeback(self) -> None: patch.object(self.callbacks, 'write_callback', side_effect=self.write_addr_space) as write_patch: - with self.dut.single_read_modify_write(skip_write=True) as reg: + with self.dut.single_read() as reg: _ = reg.field.read() _ = reg.field.read() @@ -358,11 +360,10 @@ def test_context_manager_read_modify_write_check_writeback(self) -> None: patch.object(self.callbacks, 'write_callback', side_effect=self.write_addr_space) as write_patch: - with self.dut.single_read_modify_write(skip_write=True) as reg: - self.assertEqual(reg.field.read(), False) + with self.dut.single_read() as reg: self.assertEqual(reg.field.read(), False) - reg.field.write(True) - self.assertEqual(reg.field.read(), True) + with self.assertRaises(RuntimeError): + reg.field.write(True) read_patch.assert_called_once_with(addr=0, width=self.dut.width,