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,