From 162bfd46511cd8b754717d57a1c14bd5a879ea32 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:08:04 +0000 Subject: [PATCH 01/80] Remove unnecessary python 3.8 bakward compatibility support --- .../templates/baseclass_simulation_tb.py.jinja | 5 ----- src/peakrdl_python/templates/baseclass_tb.py.jinja | 12 +----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja index 89d145f6..53d6d303 100644 --- a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja @@ -24,12 +24,7 @@ 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 %} diff --git a/src/peakrdl_python/templates/baseclass_tb.py.jinja b/src/peakrdl_python/templates/baseclass_tb.py.jinja index 793a5470..13bb3a5a 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -25,14 +25,7 @@ along with this program. If not, see . 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 random {% if legacy_enum_type %} @@ -91,10 +84,7 @@ def random_enum_reg_value(enum_class: type[{% if legacy_enum_type %}IntEnum{% el return random.choice(list(enum_class)) {% if asyncoutput %} -if sys.version_info < (3, 8): - TestCaseBase = asynctest.TestCase -else: - TestCaseBase = unittest.IsolatedAsyncioTestCase +TestCaseBase = unittest.IsolatedAsyncioTestCase {% else %} TestCaseBase = unittest.TestCase {% endif %} From a2ff8f3a452dfaa4838e034bf89dd3874031d4a6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:39:20 +0000 Subject: [PATCH 02/80] move int field test to a function to reduced code closes #234 --- src/peakrdl_python/_deploy_package.py | 7 + src/peakrdl_python/lib_test/__init__.py | 24 +++ .../lib_test/async_reg_base_test_class.py | 184 +++++++++++++++++ .../lib_test/base_reg_test_class.py | 163 +++++++++++++++ src/peakrdl_python/lib_test/utilities.py | 70 +++++++ .../templates/addrmap_simulation_tb.py.jinja | 35 ++-- .../templates/addrmap_tb.py.jinja | 186 +++--------------- .../baseclass_simulation_tb.py.jinja | 13 +- .../templates/baseclass_tb.py.jinja | 36 +--- .../templates/template_ultilities.py.jinja | 4 + 10 files changed, 521 insertions(+), 201 deletions(-) create mode 100644 src/peakrdl_python/lib_test/__init__.py create mode 100644 src/peakrdl_python/lib_test/async_reg_base_test_class.py create mode 100644 src/peakrdl_python/lib_test/base_reg_test_class.py create mode 100644 src/peakrdl_python/lib_test/utilities.py diff --git a/src/peakrdl_python/_deploy_package.py b/src/peakrdl_python/_deploy_package.py index 474005ef..c1a9adba 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/lib_test/__init__.py b/src/peakrdl_python/lib_test/__init__.py new file mode 100644 index 00000000..c3fcc9d3 --- /dev/null +++ b/src/peakrdl_python/lib_test/__init__.py @@ -0,0 +1,24 @@ +""" +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 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 General Public License for more details. + +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 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 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..555a5a2b --- /dev/null +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -0,0 +1,184 @@ +""" +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 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 General Public License for more details. + +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 +""" +# 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 +from typing import Union +import random +from unittest.mock import patch +from itertools import product + +from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..sim_lib.dummy_callbacks import async_dummy_read +from ..sim_lib.dummy_callbacks import async_dummy_write + + +from .utilities import reverse_bits, expected_reg_write_data + +class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, 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) + + async def read_callback(self, addr: int, width: int, accesswidth: int) -> int: + return await async_dummy_read(addr=addr, + width=width, + accesswidth=accesswidth) + + 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) + + async def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + return await async_dummy_write(addr=addr, + width=width, + accesswidth=accesswidth, + data=data) + + # 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` + + if is_sw_readable: + await self.__single_field_read_test(fut=fut) + + if is_sw_writable: + 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: + + if not isinstance(fut, (FieldAsyncReadOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + # 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.randrange(0, fut.parent_register.max_value + 1) + read_callback_mock.return_value = random_value + random_field_value = (random_value & fut.bitmask) >> fut.low + if fut.msb == fut.high: + self.assertEqual(await fut.read(), random_field_value) + else: + self.assertEqual(await fut.read(), + reverse_bits(value=random_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 + + if not isinstance(fut, (FieldAsyncWriteOnly, FieldAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a writable field') + + random_reg_value = random.randrange(0, fut.parent_register.max_value + 1) + random_field_value = random.randrange(0, fut.max_value + 1) + for reg_base_value, field_value in product( + [0, fut.parent_register.max_value, random_reg_value], + [0, fut.max_value, random_field_value]): + 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, + 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) 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..c65186fc --- /dev/null +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -0,0 +1,163 @@ +""" +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 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 General Public License for more details. + +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 +""" +import unittest +from abc import ABC +from typing import Union +import random +from unittest.mock import patch +from itertools import product + +from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..sim_lib.dummy_callbacks import dummy_read +from ..sim_lib.dummy_callbacks import dummy_write + +from .utilities import reverse_bits, expected_reg_write_data + +class LibTestBase(unittest.TestCase, 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) + + def read_callback(self, addr: int, width: int, accesswidth: int) -> int: + return dummy_read(addr=addr, width=width, accesswidth=accesswidth) + + 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) + + def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: + return dummy_write(addr=addr, width=width, accesswidth=accesswidth, data=data) + + # pylint:enable=missing-function-docstring + + def __single_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: + if not isinstance(fut, (FieldReadOnly, FieldReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + + # 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.randrange(0, fut.parent_register.max_value + 1) + read_callback_mock.return_value = random_value + random_field_value = (random_value & fut.bitmask) >> fut.low + if fut.msb == fut.high: + self.assertEqual(fut.read(), random_field_value) + else: + self.assertEqual(fut.read(), + reverse_bits(value=random_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_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') + + random_reg_value = random.randrange(0, fut.parent_register.max_value + 1) + random_field_value = random.randrange(0, fut.max_value + 1) + for reg_base_value,field_value in product( + [0, fut.parent_register.max_value, random_reg_value], + [0, fut.max_value, random_field_value]): + 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, + 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` + + if is_sw_readable: + self.__single_field_read_test(fut=fut) + + if is_sw_writable: + self.__single_field_write_test(fut=fut) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py new file mode 100644 index 00000000..68dc1b94 --- /dev/null +++ b/src/peakrdl_python/lib_test/utilities.py @@ -0,0 +1,70 @@ +""" +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 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +This package It provide methods used by the tests +""" +from ..lib import Field + +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) -> 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 + + Returns: + + """ + # pylint:disable-next=protected-access + readable_reg = fut.parent_register._is_readable + + if readable_reg: + expected_data = reg_base_value & fut.inverse_bitmask + if fut.msb == fut.high: + expected_data |= (fut.bitmask & (field_value << fut.low)) + else: + expected_data |= (fut.bitmask & (reverse_bits(value=field_value, + number_bits=fut.width) << fut.low)) + + return expected_data + + # 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 diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index cf5a1a8e..ebb5bd13 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -20,6 +20,7 @@ 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 %} @@ -50,6 +51,8 @@ from ._{{top_node.inst_name}}_test_base import random_enum_reg_value from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum {% endif %} +from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits + 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: @@ -141,12 +144,12 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ {%- 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}}) + 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 %}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}}) + random_field_value = reverse_bits(value=random_field_value, number_bits={{node.width}}) {% endif %} {% endif %} sim_register.value = random_value @@ -156,22 +159,22 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ {%- 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}})) + 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 %}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}})) + self.assertEqual(sim_register.value, (previous_register_value & {{get_field_inv_bitmask_hex_string(node)}}) | ({% if node.msb == node.high %}random_field_value{% else %}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}}) + 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 %}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}}) + random_field_value = reverse_bits(value=random_field_value, number_bits={{node.width}}) {% endif %} {% endif %} sim_register.value = random_value @@ -199,12 +202,12 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ 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}}) + 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 %}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}}) + random_field_value = reverse_bits(value=random_field_value, number_bits={{node.width}}) {% endif %} {% endif %} sim_register.value = random_value @@ -233,9 +236,9 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ {% 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}}))) + 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 %}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}}))) + 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 %}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() @@ -254,12 +257,12 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ {% 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}}))) + 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 %}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 %}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}}))) + 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 %}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 %}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() @@ -279,9 +282,9 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # typ {% 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}}))) + 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 %}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}}))) + 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 %}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() diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index f3742702..bc38dfff 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -19,6 +19,7 @@ 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 %} @@ -73,6 +74,7 @@ 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 ._{{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 @@ -276,8 +278,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: # {{'.'.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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', return_value=1) as read_callback_mock: {% if node.has_sw_readable -%} {% if asyncoutput %} @@ -382,140 +384,14 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 + Check the ability to read and write to integer (non-encoded) 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())}} + {%- if 'encode' not in node.list_properties() %} 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 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}}) + {%- endif %} + {%- endfor %} {% if uses_enum %} {% if asyncoutput %}async {% endif %}def test_enum_field_read_and_write(self) -> None: @@ -528,8 +404,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: # 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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', return_value=0) as read_callback_mock: {% if node.is_sw_readable %} # read back test @@ -541,7 +417,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: ({{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}} )) + ({{get_field_bitmask_hex_string(node)}} & (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] @@ -571,7 +447,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: ({{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}})) + ({{get_field_bitmask_hex_string(node)}} & (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] @@ -612,7 +488,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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}}))) + ({{get_field_bitmask_hex_string(node)}} & (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 @@ -627,7 +503,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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}}) + data=reverse_bits(value={{value_of_enum_needed.value}}, number_bits={{node.width}}) << {{node.low}}) {% endif -%} {% endif %} @@ -664,15 +540,15 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 }}) + rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(child_node)}}) | (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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', 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 @@ -721,15 +597,15 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 }}) + rand_reg_value = (rand_reg_value & {{get_field_inv_bitmask_hex_string(field)}}) | (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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', 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 @@ -774,8 +650,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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: + 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 @@ -799,7 +675,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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) + expected_value = ( expected_value & field.inverse_bitmask ) | (reverse_bits(value=rand_field_value, number_bits=field.width) << field.low) else: raise RuntimeError('invalid msb/lsb high/low combination') @@ -862,8 +738,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ 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: + 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 @@ -887,7 +763,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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) + expected_value = ( expected_value & field.inverse_bitmask ) | (reverse_bits(value=rand_field_value, number_bits=field.width) << field.low) else: raise RuntimeError('invalid msb/lsb high/low combination') @@ -927,8 +803,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- 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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', 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}}, @@ -955,8 +831,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: # 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: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', 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 diff --git a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja index 53d6d303..16c8e4f3 100644 --- a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja @@ -25,9 +25,8 @@ along with this program. If not, see . {% if legacy_block_access %}from array import array as Array{% endif %} {% if asyncoutput %} import asyncio -{% else %} -import unittest {% endif %} +import unittest from {{ peakrdl_python_lib(depth=lib_depth) }} import RegisterWriteVerifyError @@ -42,14 +41,20 @@ 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] +{% if asyncoutput %} +TestCaseBase = unittest.IsolatedAsyncioTestCase +{% else %} +TestCaseBase = unittest.TestCase +{% endif %} + +class {{top_node.inst_name}}_SimTestCase(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.sim.read, write_callback=self.sim.write)) -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 13bb3a5a..5c03a211 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -20,6 +20,7 @@ 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 %} @@ -27,6 +28,7 @@ from array import array as Array {% if asyncoutput %} import asyncio {% endif %} +import unittest import random {% if legacy_enum_type %} from enum import IntEnum @@ -49,6 +51,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 @@ -56,6 +60,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 %} @@ -83,36 +89,14 @@ 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 %} -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)) - - @staticmethod - def _reverse_bits(value: int, number_bits: int) -> int: - """ - - 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 + 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(TestCaseBase): # type: ignore[valid-type,misc] +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, @@ -120,7 +104,7 @@ 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. diff --git a/src/peakrdl_python/templates/template_ultilities.py.jinja b/src/peakrdl_python/templates/template_ultilities.py.jinja index 310682bd..07ea276b 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__ From 5f8e7ea048a10af079d808da0fbb9b6aa6242d47 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:39:50 +0000 Subject: [PATCH 03/80] Update the version number --- src/peakrdl_python/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 7b4d7529..68e62bac 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "2.1.0" +__version__ = "3.0.0rc1" From 70c8d0a59e74ec70ea3fcb83b7b96db80abc9691 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:58:28 +0000 Subject: [PATCH 04/80] Resolve typing issues --- .../lib_test/async_reg_base_test_class.py | 11 ++++++----- src/peakrdl_python/lib_test/base_reg_test_class.py | 7 +++++-- src/peakrdl_python/lib_test/utilities.py | 7 +++---- 3 files changed, 14 insertions(+), 11 deletions(-) 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 index 555a5a2b..880bbcfc 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -84,9 +84,13 @@ async def _single_int_field_read_and_write_test( # parent register are checked as part of `test_register_properties` 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( @@ -96,9 +100,6 @@ async def __single_field_read_test( 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, (FieldAsyncReadOnly, FieldAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable field') - # 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) @@ -145,8 +146,7 @@ async def __single_field_write_test( # pylint:disable-next=protected-access readable_reg = fut.parent_register._is_readable - if not isinstance(fut, (FieldAsyncWriteOnly, FieldAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a writable field') + random_reg_value = random.randrange(0, fut.parent_register.max_value + 1) random_field_value = random.randrange(0, fut.max_value + 1) @@ -173,6 +173,7 @@ async def __single_field_write_test( 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)) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index c65186fc..d8ea16c1 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -59,8 +59,6 @@ def __single_field_read_test(self, fut: Union[FieldReadOnly, FieldReadOnly]) -> 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, (FieldReadOnly, FieldReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable field') # read back - zero, this is achieved by setting the register to inverse bitmask read_callback_mock.return_value = fut.inverse_bitmask @@ -134,6 +132,7 @@ def __single_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly]) - 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 @@ -157,7 +156,11 @@ def _single_int_field_read_and_write_test( # parent register are checked as part of `test_register_properties` 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_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_field_write_test(fut=fut) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 68dc1b94..f5cddf06 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -38,7 +38,8 @@ def reverse_bits(value: int, number_bits: int) -> int: def expected_reg_write_data(fut: Field, reg_base_value: int, - field_value: int) -> 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 @@ -47,13 +48,11 @@ def expected_reg_write_data(fut: Field, fut: Field being updated reg_base_value: register value before the write field_value: new field value + readable_reg: is register readable Returns: """ - # pylint:disable-next=protected-access - readable_reg = fut.parent_register._is_readable - if readable_reg: expected_data = reg_base_value & fut.inverse_bitmask if fut.msb == fut.high: From 6679b86fca0a82d532e414b95c81b7b2062cbc9a Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:15:13 +0000 Subject: [PATCH 05/80] Fix missing tab in template --- src/peakrdl_python/templates/addrmap_tb.py.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index bc38dfff..dbb683e8 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -404,8 +404,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: # 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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', return_value=0) as read_callback_mock: + with patch.object(self,'write_callback') as write_callback_mock, \ + patch.object(self,'read_callback', return_value=0) as read_callback_mock: {% if node.is_sw_readable %} # read back test From 3ace243e2ebed1bda68da68bc198b4cbceee3c9e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 13:32:16 +0000 Subject: [PATCH 06/80] Optimise the `test_enum_field_read_and_write` closes #238 --- generate_and_test.py | 6 +- .../lib_test/async_reg_base_test_class.py | 140 ++++++++++++++-- .../lib_test/base_reg_test_class.py | 149 ++++++++++++++++-- src/peakrdl_python/lib_test/utilities.py | 39 +++++ .../templates/addrmap_tb.py.jinja | 131 +-------------- 5 files changed, 316 insertions(+), 149 deletions(-) diff --git a/generate_and_test.py b/generate_and_test.py index e8fa970f..884adf18 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,6 +197,8 @@ 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 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 index 880bbcfc..19d6aefb 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -25,16 +25,18 @@ import unittest from abc import ABC from typing import Union -import random from unittest.mock import patch from itertools import product from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..sim_lib.dummy_callbacks import async_dummy_read from ..sim_lib.dummy_callbacks import async_dummy_write from .utilities import reverse_bits, expected_reg_write_data +from .utilities import reg_value_for_field_read_with_random_base +from .utilities import random_field_value, random_field_parent_reg_value class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, ABC): """ @@ -119,14 +121,14 @@ async def __single_field_read_test( # read back - random value read_callback_mock.reset_mock() - random_value = random.randrange(0, fut.parent_register.max_value + 1) + random_value = random_field_parent_reg_value(fut) read_callback_mock.return_value = random_value - random_field_value = (random_value & fut.bitmask) >> fut.low + field_value = (random_value & fut.bitmask) >> fut.low if fut.msb == fut.high: self.assertEqual(await fut.read(), random_field_value) else: self.assertEqual(await fut.read(), - reverse_bits(value=random_field_value, number_bits=fut.width)) + 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, @@ -146,13 +148,9 @@ async def __single_field_write_test( # pylint:disable-next=protected-access readable_reg = fut.parent_register._is_readable - - - random_reg_value = random.randrange(0, fut.parent_register.max_value + 1) - random_field_value = random.randrange(0, fut.max_value + 1) for reg_base_value, field_value in product( - [0, fut.parent_register.max_value, random_reg_value], - [0, fut.max_value, random_field_value]): + [0, fut.parent_register.max_value, random_field_parent_reg_value(fut)], + [0, fut.max_value, random_field_value(fut)]): read_callback_mock.reset_mock() write_callback_mock.reset_mock() read_callback_mock.return_value = reg_base_value @@ -183,3 +181,125 @@ async def __single_field_write_test( 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_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(): + random_reg_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = random_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=random_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], + enum_definition: dict[str, int], + 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` + + 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) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index d8ea16c1..169ac34b 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -21,15 +21,17 @@ import unittest from abc import ABC from typing import Union -import random from unittest.mock import patch from itertools import product from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..sim_lib.dummy_callbacks import dummy_read from ..sim_lib.dummy_callbacks import dummy_write from .utilities import reverse_bits, expected_reg_write_data +from .utilities import reg_value_for_field_read_with_random_base +from .utilities import random_field_value, random_field_parent_reg_value class LibTestBase(unittest.TestCase, ABC): """ @@ -55,7 +57,7 @@ def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> # pylint:enable=missing-function-docstring - def __single_field_read_test(self, fut: Union[FieldReadOnly, FieldReadOnly]) -> None: + 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: @@ -79,14 +81,14 @@ def __single_field_read_test(self, fut: Union[FieldReadOnly, FieldReadOnly]) -> # read back - random value read_callback_mock.reset_mock() - random_value = random.randrange(0, fut.parent_register.max_value + 1) + random_value = random_field_parent_reg_value(fut) read_callback_mock.return_value = random_value - random_field_value = (random_value & fut.bitmask) >> fut.low + field_value = (random_value & fut.bitmask) >> fut.low if fut.msb == fut.high: - self.assertEqual(fut.read(), random_field_value) + self.assertEqual(fut.read(), field_value) else: self.assertEqual(fut.read(), - reverse_bits(value=random_field_value, number_bits=fut.width)) + 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, @@ -95,7 +97,7 @@ def __single_field_read_test(self, fut: Union[FieldReadOnly, FieldReadOnly]) -> # at the end of the read tests the write should not have been called write_callback_mock.assert_not_called() - def __single_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly]) -> None: + def __single_int_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly]) -> None: # pylint:disable-next=protected-access readable_reg = fut.parent_register._is_readable @@ -105,11 +107,9 @@ def __single_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly]) - if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): raise TypeError('Test can not proceed as the fut is not a writable field') - random_reg_value = random.randrange(0, fut.parent_register.max_value + 1) - random_field_value = random.randrange(0, fut.max_value + 1) for reg_base_value,field_value in product( - [0, fut.parent_register.max_value, random_reg_value], - [0, fut.max_value, random_field_value]): + [0, fut.parent_register.max_value, random_field_parent_reg_value(fut)], + [0, fut.max_value, random_field_value(fut)]): read_callback_mock.reset_mock() write_callback_mock.reset_mock() read_callback_mock.return_value = reg_base_value @@ -158,9 +158,132 @@ def _single_int_field_read_and_write_test( 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_field_read_test(fut=fut) + 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_field_write_test(fut=fut) + 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_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(): + random_reg_value = random_field_parent_reg_value(fut) + read_callback_mock.return_value = random_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=random_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], + enum_definition: dict[str, int], + 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` + + 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) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index f5cddf06..8d99d931 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -17,6 +17,8 @@ This package It provide methods used by the tests """ +import random + from ..lib import Field def reverse_bits(value: int, number_bits: int) -> int: @@ -67,3 +69,40 @@ def expected_reg_write_data(fut: Field, 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_field_value(fut: Field) -> int: + """ + Return a random integer values within the legal range for a field + """ + return random.randint(0, fut.max_value) + +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] diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index dbb683e8..4a55460f 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -382,138 +382,19 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: read_callback_mock.assert_not_called() {% endfor %} - {% if asyncoutput %}async {% endif %}def test_int_field_read_and_write(self) -> None: + {% if asyncoutput %}async {% endif %}def test_field_read_and_write(self) -> None: """ - Check the ability to read and write to integer (non-encoded) fields + Check the ability to read and write to fields integer and enum """ - {% for node in owned_elements.fields -%} - {%- if 'encode' not in node.list_properties() %} + {% for node in owned_elements.fields %} with self.subTest(msg='field: {{'.'.join(node.get_path_segments())}}'): + {%- 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 %} + {% if asyncoutput %}await {%endif %}self._single_enum_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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) {%- 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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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)}} & (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)}} & (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)}} & (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=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 From c81851a4f6e0155694f916cede2e464eef9e3ec1 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:00:11 +0000 Subject: [PATCH 07/80] Resttucture the test library with a shared base class between async and non-async --- .../lib_test/_common_base_test_class.py | 37 +++++++++++++++++++ .../lib_test/async_reg_base_test_class.py | 4 +- .../lib_test/base_reg_test_class.py | 4 +- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/peakrdl_python/lib_test/_common_base_test_class.py 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..b2dba13f --- /dev/null +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -0,0 +1,37 @@ +""" +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 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 General Public License for more details. + +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 common to both the async and non-async versions +""" +import unittest +from abc import ABC +from typing import Union +from unittest.mock import patch +from itertools import product + +from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly +from ..sim_lib.dummy_callbacks import dummy_read +from ..sim_lib.dummy_callbacks import dummy_write + +from .utilities import reverse_bits, expected_reg_write_data +from .utilities import reg_value_for_field_read_with_random_base +from .utilities import random_field_value, random_field_parent_reg_value + +class CommonTestBase(unittest.TestCase, ABC): + ... 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 index 19d6aefb..5bbe9790 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -38,7 +38,9 @@ from .utilities import reg_value_for_field_read_with_random_base from .utilities import random_field_value, random_field_parent_reg_value -class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, ABC): +from ._common_base_test_class import CommonTestBase + +class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, CommonTestBase, ABC): """ Base Test class for the autogenerated register test when in async mode """ diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 169ac34b..f004832f 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -33,7 +33,9 @@ from .utilities import reg_value_for_field_read_with_random_base from .utilities import random_field_value, random_field_parent_reg_value -class LibTestBase(unittest.TestCase, ABC): +from ._common_base_test_class import CommonTestBase + +class LibTestBase(CommonTestBase, ABC): """ Base Test class for the autogenerated register test when in async mode """ From b40d01cecd65ac6d0fe62e76b4c48c530109f58f Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:03:22 +0000 Subject: [PATCH 08/80] Fixed a bug with the async __single_int_field_read_test --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 19d6aefb..ad5d2a4a 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -125,7 +125,7 @@ async def __single_field_read_test( 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(), random_field_value) + self.assertEqual(await fut.read(), field_value) else: self.assertEqual(await fut.read(), reverse_bits(value=field_value, number_bits=fut.width)) From b4b5e1e69360f8184a1975eace37b51d69ca4a67 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:03:22 +0000 Subject: [PATCH 09/80] Fixed a bug with the async __single_int_field_read_test --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 5bbe9790..1cb2d919 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -127,7 +127,7 @@ async def __single_field_read_test( 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(), random_field_value) + self.assertEqual(await fut.read(), field_value) else: self.assertEqual(await fut.read(), reverse_bits(value=field_value, number_bits=fut.width)) From 653750110765a4e15b8bd34bed2e0b031b912482 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:37:44 +0000 Subject: [PATCH 10/80] Optimised the `test_field_properties` by merging it to main field tests closes #240 --- .../lib_test/_common_base_test_class.py | 68 ++++++++++++++++--- .../systemrdl_node_utility_functions.py | 2 +- .../templates/addrmap_tb.py.jinja | 41 +---------- 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index b2dba13f..023bbc92 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -20,18 +20,66 @@ """ import unittest from abc import ABC -from typing import Union -from unittest.mock import patch -from itertools import product +from typing import Union, Optional from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly -from ..sim_lib.dummy_callbacks import dummy_read -from ..sim_lib.dummy_callbacks import dummy_write - -from .utilities import reverse_bits, expected_reg_write_data -from .utilities import reg_value_for_field_read_with_random_base -from .utilities import random_field_value, random_field_parent_reg_value +from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite class CommonTestBase(unittest.TestCase, ABC): - ... + + 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, + bitmask: int, + inverse_bitmask: int, + max_value: int, + is_volatile: bool, + default: Optional[int] + + ): + self.assertEqual(fut.lsb, lsb) + self.assertEqual(fut.msb, msb) + self.assertEqual(fut.low, low) + self.assertEqual(fut.high, high) + self.assertEqual(fut.bitmask, bitmask) + self.assertEqual(fut.inverse_bitmask, inverse_bitmask) + self.assertEqual(fut.max_value, max_value) + 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 list(EnumCls.values()): + 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) diff --git a/src/peakrdl_python/systemrdl_node_utility_functions.py b/src/peakrdl_python/systemrdl_node_utility_functions.py index 5cf898e3..82ba3090 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 diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 4a55460f..36f2bc92 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -179,42 +179,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 @@ -382,12 +346,13 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: read_callback_mock.assert_not_called() {% endfor %} - {% if asyncoutput %}async {% endif %}def test_field_read_and_write(self) -> None: + {% if asyncoutput %}async {% endif %}def test_field(self) -> None: """ - Check the ability to read and write to fields integer and enum + Check the properties and function (read and write) on the fields both integer and enum """ {% 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}}, bitmask={{get_field_bitmask_hex_string(node)}}, inverse_bitmask={{get_field_inv_bitmask_hex_string(node)}}, max_value={{get_field_max_value_hex_string(node)}}, is_volatile={{node.is_hw_writable}}, default={{get_field_default_value(node)}}) {%- 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 %} From 97840ac73171c1ad68cfaaad754461f77a4abe52 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:57:36 +0000 Subject: [PATCH 11/80] Reduced the number of combinations tested in the test_export->TestExportUDP->test_selective_property_export closes #240 --- tests/unit_tests/test_export.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_export.py b/tests/unit_tests/test_export.py index d6f8bacc..9a600678 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: From c1eb69dd298f484ca841bd472f7ae8ad347b0844 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 14:57:36 +0000 Subject: [PATCH 12/80] Reduced the number of combinations tested in the test_export->TestExportUDP->test_selective_property_export closes #241 --- tests/unit_tests/test_export.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_export.py b/tests/unit_tests/test_export.py index d6f8bacc..9a600678 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: From 97fac0d64939777999a5ad80650c1aa666978878 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:02:47 +0000 Subject: [PATCH 13/80] Resolved some basic linting issues --- src/peakrdl_python/lib_test/_common_base_test_class.py | 9 ++++++--- src/peakrdl_python/lib_test/base_reg_test_class.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 023bbc92..3472c3e2 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -28,7 +28,12 @@ from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite class CommonTestBase(unittest.TestCase, ABC): + """ + Base Test class for the autogenerated register test to be used for for the async and + non-async cases + """ + # pylint:disable-next=too-many-arguments def _single_field_property_test(self, *, fut: Union[FieldReadWrite, FieldReadOnly, @@ -50,9 +55,7 @@ def _single_field_property_test(self, *, inverse_bitmask: int, max_value: int, is_volatile: bool, - default: Optional[int] - - ): + default: Optional[int]) -> None: self.assertEqual(fut.lsb, lsb) self.assertEqual(fut.msb, msb) self.assertEqual(fut.low, low) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index f004832f..59286718 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -18,7 +18,6 @@ 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 """ -import unittest from abc import ABC from typing import Union from unittest.mock import patch From ba9e469124397d754ee7f6709c2f36f5924173b8 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:11:53 +0000 Subject: [PATCH 14/80] Fixed the check for whether the value is in the enumeration or not --- src/peakrdl_python/lib_test/_common_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 3472c3e2..664975e3 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -78,7 +78,7 @@ def _single_field_property_test(self, *, # is unhappy with the name # pylint:disable-next=invalid-name EnumCls = fut.enum_cls - if default in list(EnumCls.values()): + 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 From 00c072e18498c7dbdec2307ba1514cc1a0fb87a6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:42:38 +0000 Subject: [PATCH 15/80] Fixed the typing on the call to `_single_enum_field_read_and_write_test` --- src/peakrdl_python/templates/addrmap_tb.py.jinja | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 4a55460f..a2a07a44 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -391,7 +391,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- 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 %} - {% if asyncoutput %}await {%endif %}self._single_enum_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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) + {% 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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) {%- endif %} {%- endfor %} From 9bffafdffc23cf4638be1eec3073bc5de54859ac Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:42:38 +0000 Subject: [PATCH 16/80] Fixed the typing on the call to `_single_enum_field_read_and_write_test` --- src/peakrdl_python/templates/addrmap_tb.py.jinja | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 36f2bc92..e3cf7157 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -356,7 +356,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- 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 %} - {% if asyncoutput %}await {%endif %}self._single_enum_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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) + {% 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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) {%- endif %} {%- endfor %} From 53da9a114fbee581bbb91dc635f6261215cef308 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:20:05 +0000 Subject: [PATCH 17/80] Optimised `test_register_read_and_write` closes #243 --- .../lib_test/async_reg_base_test_class.py | 69 ++++++++++++ .../lib_test/base_reg_test_class.py | 64 +++++++++++ src/peakrdl_python/lib_test/utilities.py | 9 ++ .../templates/addrmap_tb.py.jinja | 106 +----------------- 4 files changed, 143 insertions(+), 105 deletions(-) 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 index 880bbcfc..ecf4cd74 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -30,9 +30,12 @@ from itertools import product from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite +from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly from ..sim_lib.dummy_callbacks import async_dummy_read from ..sim_lib.dummy_callbacks import async_dummy_write +from .utilities import random_reg_value + from .utilities import reverse_bits, expected_reg_write_data @@ -183,3 +186,69 @@ async def __single_field_write_test( with self.assertRaises(ValueError): await fut.write(-1) + + async def _single_register_read_and_write_test(self, + rut: Union[RegAsyncReadOnly, + RegAsyncReadWrite, + RegAsyncWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + # the register properties are tested separately so are available to be used here + + if has_sw_readable: + if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + await self.__single_reg_read_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 fut is not a writable field') + await self.__single_reg_write_test(rut=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) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index d8ea16c1..8795864d 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -26,10 +26,12 @@ from itertools import product from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly +from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly from ..sim_lib.dummy_callbacks import dummy_read from ..sim_lib.dummy_callbacks import dummy_write from .utilities import reverse_bits, expected_reg_write_data +from .utilities import random_reg_value class LibTestBase(unittest.TestCase, ABC): """ @@ -164,3 +166,65 @@ def _single_int_field_read_and_write_test( if not isinstance(fut, (FieldWriteOnly, FieldReadWrite)): raise TypeError('Test can not proceed as the fut is not a writable field') self.__single_field_write_test(fut=fut) + + def _single_register_read_and_write_test(self, + rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + # the register properties are tested separately so are available to be used here + + if has_sw_readable: + if not isinstance(rut, (RegReadOnly, RegReadWrite)): + raise TypeError('Test can not proceed as the fut is not a readable field') + self.__single_reg_read_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 fut is not a writable field') + self.__single_reg_write_test(rut=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) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index f5cddf06..8834ac09 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -17,7 +17,10 @@ This package It provide methods used by the tests """ +import random + from ..lib import Field +from ..lib.base_register import BaseReg def reverse_bits(value: int, number_bits: int) -> int: """ @@ -67,3 +70,9 @@ def expected_reg_write_data(fut: Field, if fut.msb == fut.high: return field_value << fut.low return reverse_bits(value=field_value, number_bits=fut.width) << fut.low + +def random_reg_value(rut: BaseReg) -> int: + """ + Returns a random register value + """ + return random.randint(0, rut.max_value) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index dbb683e8..5fc2a503 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -274,112 +274,8 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ 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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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() + {% 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}}) {% endfor %} {% if asyncoutput %}async {% endif %}def test_int_field_read_and_write(self) -> None: From 0b1fbed328919c772851a4ee124f058792b1e2f4 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:20:48 +0000 Subject: [PATCH 18/80] Increased release candiate version in preparation for next pre-release --- src/peakrdl_python/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 68e62bac..40d7ac1b 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc1" +__version__ = "3.0.0rc2" From 53dcdd85a0faf54aba11880f88a3b62020d041be Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:25:48 +0000 Subject: [PATCH 19/80] resolving merge issued from release\3.0.0 branch update --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 4 ++-- src/peakrdl_python/lib_test/base_reg_test_class.py | 7 ++++--- src/peakrdl_python/lib_test/utilities.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) 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 index 3fdf0b53..19aa9682 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -257,7 +257,7 @@ async def __single_enum_field_write_test(self, patch.object(self, 'read_callback', return_value=0) as read_callback_mock: for enum_name, enum_value in enum_definition.items(): - random_reg_value = random_field_parent_reg_value(fut) + reg_value = random_field_parent_reg_value(fut) read_callback_mock.return_value = random_reg_value await fut.write(EnumCls[enum_name]) @@ -276,7 +276,7 @@ async def __single_enum_field_write_test(self, width=fut.parent_register.width, accesswidth=fut.parent_register.accesswidth, data=expected_reg_write_data(fut=fut, - reg_base_value=random_reg_value, + reg_base_value=reg_value, readable_reg=readable_reg, field_value=enum_value)) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index d98c7a4d..72b761bd 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -239,7 +239,7 @@ def __single_enum_field_write_test(self, patch.object(self, 'read_callback', return_value=0) as read_callback_mock: for enum_name, enum_value in enum_definition.items(): - random_reg_value = random_field_parent_reg_value(fut) + reg_value = random_field_parent_reg_value(fut) read_callback_mock.return_value = random_reg_value fut.write(EnumCls[enum_name]) @@ -258,7 +258,7 @@ def __single_enum_field_write_test(self, width=fut.parent_register.width, accesswidth=fut.parent_register.accesswidth, data=expected_reg_write_data(fut=fut, - reg_base_value=random_reg_value, + reg_base_value=reg_value, readable_reg=readable_reg, field_value=enum_value)) @@ -346,7 +346,8 @@ def __single_reg_write_test(self, rut: Union[RegWriteOnly, RegReadWrite]) -> Non data=reg_value) read_callback_mock.assert_not_called() - # test writing a value beyond the register range is blocked with an exception being raised + # test writing a value beyond the register range is blocked with an exception + # being raised with self.assertRaises(ValueError): rut.write(-1) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 07b25407..3cf9811a 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -112,4 +112,4 @@ def random_reg_value(rut: BaseReg) -> int: """ Returns a random register value """ - return random.randint(0, rut.max_value) \ No newline at end of file + return random.randint(0, rut.max_value) From ee1add3e853d85f4b673cdfe1c58a4b64d48e112 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:37:31 +0000 Subject: [PATCH 20/80] Optimise `test_register_properties` --- .../lib_test/_common_base_test_class.py | 13 +++++++++++++ src/peakrdl_python/templates/addrmap_tb.py.jinja | 15 +-------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 664975e3..9533894b 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -26,6 +26,7 @@ from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite +from ..lib.base_register import BaseReg class CommonTestBase(unittest.TestCase, ABC): """ @@ -86,3 +87,15 @@ def _single_field_property_test(self, *, self.assertIsNone(fut.default) else: self.assertEqual(fut.default, default) + + def _single_register_property_test(self, *, + rut: BaseReg, + address: int, + width: int, + accesswidth: Optional[int]) -> 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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 9ce24cdf..2329bfc4 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -148,20 +148,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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 @@ -239,6 +225,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: rut: Reg {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): + 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 %}) {% 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}}) {% endfor %} From d0857d10f906eb976d46afc4ffe24323d2544655 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:37:31 +0000 Subject: [PATCH 21/80] Optimise `test_register_properties` closes #247 --- .../lib_test/_common_base_test_class.py | 13 +++++++++++++ src/peakrdl_python/templates/addrmap_tb.py.jinja | 15 +-------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 664975e3..9533894b 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -26,6 +26,7 @@ from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite +from ..lib.base_register import BaseReg class CommonTestBase(unittest.TestCase, ABC): """ @@ -86,3 +87,15 @@ def _single_field_property_test(self, *, self.assertIsNone(fut.default) else: self.assertEqual(fut.default, default) + + def _single_register_property_test(self, *, + rut: BaseReg, + address: int, + width: int, + accesswidth: Optional[int]) -> 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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 9ce24cdf..2329bfc4 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -148,20 +148,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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 @@ -239,6 +225,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: rut: Reg {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): + 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 %}) {% 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}}) {% endfor %} From 28bcb033ae0c8f360327e9cc6b3b7f61813dc7e1 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:52:19 +0000 Subject: [PATCH 22/80] Fixed a merge bug reg_value --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- src/peakrdl_python/lib_test/base_reg_test_class.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 19aa9682..d681159b 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -258,7 +258,7 @@ async def __single_enum_field_write_test(self, for enum_name, enum_value in enum_definition.items(): reg_value = random_field_parent_reg_value(fut) - read_callback_mock.return_value = random_reg_value + 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 diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 72b761bd..dfd17b87 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -240,7 +240,7 @@ def __single_enum_field_write_test(self, for enum_name, enum_value in enum_definition.items(): reg_value = random_field_parent_reg_value(fut) - read_callback_mock.return_value = random_reg_value + 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 From 39c8adbbabeba55e47408d862c8f6caa31faee41 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Tue, 11 Nov 2025 20:58:23 +0000 Subject: [PATCH 23/80] Simplified the field property checks --- .../lib_test/_common_base_test_class.py | 12 ++++---- src/peakrdl_python/lib_test/utilities.py | 28 +++++++++++++++++++ .../templates/addrmap_tb.py.jinja | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 9533894b..44e27fd6 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -27,6 +27,7 @@ from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib.base_register import BaseReg +from .utilities import get_field_bitmask_int, get_field_inv_bitmask class CommonTestBase(unittest.TestCase, ABC): """ @@ -52,18 +53,17 @@ def _single_field_property_test(self, *, msb: int, low: int, high: int, - bitmask: int, - inverse_bitmask: int, - max_value: int, is_volatile: bool, default: Optional[int]) -> 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, bitmask) - self.assertEqual(fut.inverse_bitmask, inverse_bitmask) - self.assertEqual(fut.max_value, max_value) + 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: diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 3cf9811a..1a11bd00 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -21,6 +21,7 @@ from ..lib import Field from ..lib.base_register import BaseReg +from ..lib.utility_functions import calculate_bitmask def reverse_bits(value: int, number_bits: int) -> int: """ @@ -113,3 +114,30 @@ def random_reg_value(rut: BaseReg) -> int: Returns a random register value """ return random.randint(0, rut.max_value) + + +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 + + """ + return field.parent_register.max_value ^ get_field_bitmask_int(field) \ No newline at end of file diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 2329bfc4..bc2105f0 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -235,7 +235,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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}}, bitmask={{get_field_bitmask_hex_string(node)}}, inverse_bitmask={{get_field_inv_bitmask_hex_string(node)}}, max_value={{get_field_max_value_hex_string(node)}}, is_volatile={{node.is_hw_writable}}, default={{get_field_default_value(node)}}) + 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)}}) {%- 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 %} From 1fca0a9f06d2fc79728dd4d4c31367e99374ad5f Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:06:07 +0000 Subject: [PATCH 24/80] Fix linting issues --- src/peakrdl_python/lib_test/utilities.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 1a11bd00..1fa2cc64 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -140,4 +140,5 @@ def get_field_inv_bitmask(field: Field) -> str: inverse bitmask as a string prefixed by 0x """ - return field.parent_register.max_value ^ get_field_bitmask_int(field) \ No newline at end of file + reg_max_value = field.parent_register.max_value # type: ignore[attr-defined] + return reg_max_value ^ get_field_bitmask_int(field) From 63b489f30939a7a34322f6bd67bcc2b5bd569c7e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:54:07 +0000 Subject: [PATCH 25/80] Optimise `def test_register_read_fields` closes #249 --- .../lib_test/async_reg_base_test_class.py | 23 +++++++- .../lib_test/base_reg_test_class.py | 19 +++++- src/peakrdl_python/lib_test/utilities.py | 23 ++++++++ .../templates/addrmap_tb.py.jinja | 59 ------------------- 4 files changed, 62 insertions(+), 62 deletions(-) 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 index d681159b..32ac81c9 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -36,9 +36,8 @@ from .utilities import random_reg_value - from .utilities import reverse_bits, expected_reg_write_data -from .utilities import reg_value_for_field_read_with_random_base +from .utilities import reg_value_for_field_read_with_random_base,random_field_values_in_reg from .utilities import random_field_value, random_field_parent_reg_value from ._common_base_test_class import CommonTestBase @@ -322,6 +321,7 @@ async def _single_register_read_and_write_test(self, if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): raise TypeError('Test can not proceed as the fut is not a readable field') await self.__single_reg_read_test(rut=rut) + await self.__single_reg_read_fields_test(rut=rut) else: # test that a non-readable register has no read method and # attempting one generates and error @@ -374,3 +374,22 @@ async def __single_reg_write_test(self, with self.assertRaises(ValueError): await rut.write(rut.max_value + 1) + + async def __single_reg_read_fields_test( + self, + rut: Union[RegAsyncReadOnly, RegAsyncReadWrite]) -> None: + + # build up a register value, starting with a random register value + reg_value = random_field_values_in_reg(rut) + + 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(rut.read_fields(), await ref_read_fields) + read_callback_mock.assert_called_once() + write_callback_mock.assert_not_called() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index dfd17b87..79111a17 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -32,7 +32,7 @@ from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base from .utilities import random_field_value, random_field_parent_reg_value -from .utilities import random_reg_value +from .utilities import random_reg_value, random_field_values_in_reg from ._common_base_test_class import CommonTestBase @@ -302,6 +302,7 @@ def _single_register_read_and_write_test(self, if not isinstance(rut, (RegReadOnly, RegReadWrite)): raise TypeError('Test can not proceed as the fut is not a readable field') self.__single_reg_read_test(rut=rut) + self.__single_reg_read_fields_test(rut=rut) else: # test that a non-readable register has no read method and # attempting one generates and error @@ -353,3 +354,19 @@ def __single_reg_write_test(self, rut: Union[RegWriteOnly, RegReadWrite]) -> Non with self.assertRaises(ValueError): rut.write(rut.max_value + 1) + + def __single_reg_read_fields_test(self, rut: Union[RegReadOnly, RegReadWrite]) -> None: + + # build up a register value, starting with a random register value + reg_value = random_field_values_in_reg(rut) + + 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() + write_callback_mock.assert_not_called() diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 1fa2cc64..50494868 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -18,8 +18,10 @@ This package It provide methods used by the tests """ import random +from typing import Union from ..lib import Field +from ..lib import AsyncReg, Reg from ..lib.base_register import BaseReg from ..lib.utility_functions import calculate_bitmask @@ -115,6 +117,27 @@ def random_reg_value(rut: BaseReg) -> int: """ return random.randint(0, rut.max_value) +def random_field_values_in_reg(rut: Union[AsyncReg, Reg] ) -> int: + """ + 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 = random_reg_value(rut=rut) + for field in rut.fields: + if hasattr(field, 'enum_cls'): + reg_value = reg_value_for_field_read( + fut=field, + reg_base_value=reg_value, + field_value=random.choice(list(field.enum_cls)).value) + else: + reg_value = reg_value_for_field_read( + fut=field, + reg_base_value=reg_value, + field_value=random_field_value(field)) + + return reg_value + def get_field_bitmask_int(field: Field) -> int: """ diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index bc2105f0..0de203cb 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -244,65 +244,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- endif %} {%- endfor %} - {% 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)}}) | (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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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: """ Walk the register map and check every register read_fields method From 9974d1c74ecf0ce33128cf96b6c01f946f355b64 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:55:34 +0000 Subject: [PATCH 26/80] Moved await to the correct place --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 32ac81c9..868ec90d 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -390,6 +390,6 @@ async def __single_reg_read_fields_test( for field in rut.readable_fields } read_callback_mock.reset_mock() - self.assertDictEqual(rut.read_fields(), await ref_read_fields) + self.assertDictEqual(await rut.read_fields(), ref_read_fields) read_callback_mock.assert_called_once() write_callback_mock.assert_not_called() From 77837a704caa425f3feddafa569cda408e4be698 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:52:31 +0000 Subject: [PATCH 27/80] Optimise `test_register_read_context_manager` closes #250 --- .../lib_test/async_reg_base_test_class.py | 23 +++++- .../lib_test/base_reg_test_class.py | 24 ++++++- .../templates/addrmap_tb.py.jinja | 72 ------------------- 3 files changed, 41 insertions(+), 78 deletions(-) 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 index 868ec90d..1e1db948 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -321,7 +321,7 @@ async def _single_register_read_and_write_test(self, if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): raise TypeError('Test can not proceed as the fut is not a readable field') await self.__single_reg_read_test(rut=rut) - await self.__single_reg_read_fields_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 @@ -375,7 +375,7 @@ async def __single_reg_write_test(self, with self.assertRaises(ValueError): await rut.write(rut.max_value + 1) - async def __single_reg_read_fields_test( + async def __single_reg_read_fields_and_context_test( self, rut: Union[RegAsyncReadOnly, RegAsyncReadWrite]) -> None: @@ -391,5 +391,22 @@ async def __single_reg_read_fields_test( read_callback_mock.reset_mock() self.assertDictEqual(await rut.read_fields(), ref_read_fields) - read_callback_mock.assert_called_once() + 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 rut_context_inst.read() + for field in rut.readable_fields} + self.assertDictEqual(await rut.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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 79111a17..41870bad 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -302,7 +302,8 @@ def _single_register_read_and_write_test(self, if not isinstance(rut, (RegReadOnly, RegReadWrite)): raise TypeError('Test can not proceed as the fut is not a readable field') self.__single_reg_read_test(rut=rut) - self.__single_reg_read_fields_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 @@ -355,7 +356,11 @@ def __single_reg_write_test(self, rut: Union[RegWriteOnly, RegReadWrite]) -> Non with self.assertRaises(ValueError): rut.write(rut.max_value + 1) - def __single_reg_read_fields_test(self, rut: Union[RegReadOnly, RegReadWrite]) -> None: + 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 = random_field_values_in_reg(rut) @@ -368,5 +373,18 @@ def __single_reg_read_fields_test(self, rut: Union[RegReadOnly, RegReadWrite]) - read_callback_mock.reset_mock() self.assertDictEqual(rut.read_fields(), ref_read_fields) - read_callback_mock.assert_called_once() + read_callback_mock.assert_called_once_with( + addr=rut.address, + width=rut.width, + accesswidth=rut.accesswidth) + + with rut.single_read() as rut_context_inst: + context_ref_read_fields = {field.inst_name: rut_context_inst.read() + for field in rut.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() diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 0de203cb..e0cbbc8d 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -244,78 +244,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- endif %} {%- endfor %} - {% if asyncoutput %}async {% endif %}def test_register_read_context_manager(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 %} - # 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)}}) | (reverse_bits(value=rand_field_value, number_bits={{field.width}}) << {{ field.low }}) - {% endif %} - {% endif %} - {% else %} - # skipping {{field.inst_name}} - {% endif %} - {% endfor %} - with patch.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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 From 511a4908dbfc3618f80e39f78b980032aea0f107 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:00:04 +0000 Subject: [PATCH 28/80] remove the `skip_write` from register `single_read_modify_write` closes #135 --- src/peakrdl_python/lib/async_register_and_field.py | 14 +++----------- src/peakrdl_python/lib/register_and_field.py | 11 ++--------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/peakrdl_python/lib/async_register_and_field.py b/src/peakrdl_python/lib/async_register_and_field.py index ab3dc8ba..d6036dd6 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/register_and_field.py b/src/peakrdl_python/lib/register_and_field.py index 37589a73..ad04e846 100644 --- a/src/peakrdl_python/lib/register_and_field.py +++ b/src/peakrdl_python/lib/register_and_field.py @@ -667,7 +667,7 @@ def __init__(self, *, # pylint: enable=too-many-arguments, duplicate-code @contextmanager - def single_read_modify_write(self, verify: bool = False, skip_write: bool = False) -> \ + 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 @@ -682,11 +682,6 @@ def single_read_modify_write(self, verify: bool = False, skip_write: bool = Fals 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,9 +690,7 @@ 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 From 8c1995f4a6f26950cf208207779ef3a35cdd3561 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:15:50 +0000 Subject: [PATCH 29/80] clean up bugs from previous implementations --- src/peakrdl_python/lib/register_and_field.py | 1 - src/peakrdl_python/lib_test/async_reg_base_test_class.py | 9 +++------ src/peakrdl_python/lib_test/base_reg_test_class.py | 5 +++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/peakrdl_python/lib/register_and_field.py b/src/peakrdl_python/lib/register_and_field.py index ad04e846..3bc01004 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 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 index 1e1db948..89635f80 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -398,15 +398,12 @@ async def __single_reg_read_fields_and_context_test( read_callback_mock.reset_mock() async with rut.single_read() as rut_context_inst: - context_ref_read_fields = {field.inst_name: await rut_context_inst.read() - for field in rut.readable_fields} - self.assertDictEqual(await rut.read_fields(), context_ref_read_fields) + 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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 41870bad..8be17bee 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -377,10 +377,11 @@ def __single_reg_read_fields_and_context_test(self, 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: rut_context_inst.read() - for field in rut.readable_fields} + 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, From c5592673949d60dc0108c6031ea7ffafc568480a Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:09:59 +0000 Subject: [PATCH 30/80] Fix the unit tests that failed due to the withdrawal of skip_write --- tests/unit_tests/test_reg.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests/test_reg.py b/tests/unit_tests/test_reg.py index 2f598c64..aaac5981 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, From d34e6c51e6e137dd47d0ad12d5e9c799c841abff Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:32:25 +0000 Subject: [PATCH 31/80] Tidy up the file --- src/peakrdl_python/lib/register_and_field.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/peakrdl_python/lib/register_and_field.py b/src/peakrdl_python/lib/register_and_field.py index 3bc01004..1b878d0b 100644 --- a/src/peakrdl_python/lib/register_and_field.py +++ b/src/peakrdl_python/lib/register_and_field.py @@ -408,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 = \ @@ -666,15 +666,13 @@ def __init__(self, *, # pylint: enable=too-many-arguments, duplicate-code @contextmanager - def single_read_modify_write(self, verify: 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: @@ -695,8 +693,7 @@ def single_read_modify_write(self, verify: bool = False) -> \ 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 """ @@ -829,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 @@ -881,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 From 08aa7525f5f4f04469f29fb1fab9272e061f7524 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:34:49 +0000 Subject: [PATCH 32/80] Added some sleeps to see if it helps the CI problem --- .github/workflows/action.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index 66a797a5..63876988 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: From 4518a634f07e8a461319ee10b5ffcb2f13d4c15e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:20:27 +0000 Subject: [PATCH 33/80] optimise `test_register_write_context_manager` closes #251 --- .../lib_test/async_reg_base_test_class.py | 87 +++++++++++-- .../lib_test/base_reg_test_class.py | 82 +++++++++++-- src/peakrdl_python/lib_test/utilities.py | 116 +++++++++++++----- 3 files changed, 235 insertions(+), 50 deletions(-) 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 index 89635f80..20dff5fe 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -26,19 +26,20 @@ from abc import ABC from typing import Union from unittest.mock import patch -from itertools import product +from itertools import product, chain, combinations +from collections.abc import Iterable from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly +from ..lib import RegisterWriteVerifyError from ..sim_lib.dummy_callbacks import async_dummy_read from ..sim_lib.dummy_callbacks import async_dummy_write -from .utilities import random_reg_value - from .utilities import reverse_bits, expected_reg_write_data -from .utilities import reg_value_for_field_read_with_random_base,random_field_values_in_reg -from .utilities import random_field_value, random_field_parent_reg_value +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_reg_value, RandomReg, RegWriteTestSequence from ._common_base_test_class import CommonTestBase @@ -154,7 +155,7 @@ async def __single_field_write_test( 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_field_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 @@ -223,7 +224,7 @@ async def __single_enum_field_read_test(self, if fut.width <= 8: bad_field_value_iter = set(range(fut.max_value+1)) else: - bad_field_value_iter = {random_field_value(fut) for _ in range(100)} + 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() @@ -319,7 +320,7 @@ async def _single_register_read_and_write_test(self, if has_sw_readable: if not isinstance(rut, (RegAsyncReadOnly, RegAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable field') + 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: @@ -330,8 +331,13 @@ async def _single_register_read_and_write_test(self, if has_sw_writable: if not isinstance(rut, (RegAsyncWriteOnly, RegAsyncReadWrite)): - raise TypeError('Test can not proceed as the fut is not a writable field') + 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_context_test(rut) else: # test that a non-writable register has no write method and # attempting one generates and error @@ -380,7 +386,7 @@ async def __single_reg_read_fields_and_context_test( rut: Union[RegAsyncReadOnly, RegAsyncReadWrite]) -> None: # build up a register value, starting with a random register value - reg_value = random_field_values_in_reg(rut) + 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: @@ -407,3 +413,64 @@ async def __single_reg_read_fields_and_context_test( accesswidth=rut.accesswidth) write_callback_mock.assert_not_called() + + async def __single_reg_write_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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 8be17bee..e146f689 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -21,18 +21,20 @@ from abc import ABC from typing import Union from unittest.mock import patch -from itertools import product +from itertools import product, chain, combinations +from collections.abc import Iterable from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly +from ..lib import RegisterWriteVerifyError from ..sim_lib.dummy_callbacks import dummy_read from ..sim_lib.dummy_callbacks import dummy_write from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base -from .utilities import random_field_value, random_field_parent_reg_value -from .utilities import random_reg_value, random_field_values_in_reg +from .utilities import random_int_field_value, random_field_parent_reg_value +from .utilities import random_reg_value, RandomReg, RegWriteTestSequence from ._common_base_test_class import CommonTestBase @@ -112,7 +114,7 @@ def __single_int_field_write_test(self, fut: Union[FieldReadOnly, FieldWriteOnly 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_field_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 @@ -205,7 +207,7 @@ def __single_enum_field_read_test(self, if fut.width <= 8: bad_field_value_iter = set(range(fut.max_value+1)) else: - bad_field_value_iter = {random_field_value(fut) for _ in range(100)} + 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() @@ -300,7 +302,7 @@ def _single_register_read_and_write_test(self, if has_sw_readable: if not isinstance(rut, (RegReadOnly, RegReadWrite)): - raise TypeError('Test can not proceed as the fut is not a readable field') + 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) @@ -312,8 +314,13 @@ def _single_register_read_and_write_test(self, if has_sw_writable: if not isinstance(rut, (RegWriteOnly, RegReadWrite)): - raise TypeError('Test can not proceed as the fut is not a writable field') + 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_context_test(rut) else: # test that a non-writable register has no write method and # attempting one generates and error @@ -363,7 +370,7 @@ def __single_reg_read_fields_and_context_test(self, """ # build up a register value, starting with a random register value - reg_value = random_field_values_in_reg(rut) + 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: @@ -389,3 +396,62 @@ def __single_reg_read_fields_and_context_test(self, accesswidth=rut.accesswidth) write_callback_mock.assert_not_called() + + def __single_reg_write_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() diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 50494868..65607ef4 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -18,12 +18,18 @@ This package It provide methods used by the tests """ import random -from typing import Union +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 def reverse_bits(value: int, number_bits: int) -> int: """ @@ -60,14 +66,9 @@ def expected_reg_write_data(fut: Field, """ if readable_reg: - expected_data = reg_base_value & fut.inverse_bitmask - if fut.msb == fut.high: - expected_data |= (fut.bitmask & (field_value << fut.low)) - else: - expected_data |= (fut.bitmask & (reverse_bits(value=field_value, - number_bits=fut.width) << fut.low)) - - return expected_data + 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: @@ -97,12 +98,27 @@ def reg_value_for_field_read_with_random_base(fut: Field, field_value: int) -> i field_value=field_value, reg_base_value=random_field_parent_reg_value(fut)) -def random_field_value(fut: Field) -> int: +def random_int_field_value(fut: Field) -> int: """ - Return a random integer values within the legal range for a field + 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 @@ -113,30 +129,66 @@ def random_field_parent_reg_value(fut: Field) -> int: def random_reg_value(rut: BaseReg) -> int: """ - Returns a random register value + Returns a random register value (note that this value may not have legal field decodes) """ return random.randint(0, rut.max_value) -def random_field_values_in_reg(rut: Union[AsyncReg, Reg] ) -> int: - """ - 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 = random_reg_value(rut=rut) - for field in rut.fields: - if hasattr(field, 'enum_cls'): - reg_value = reg_value_for_field_read( - fut=field, - reg_base_value=reg_value, - field_value=random.choice(list(field.enum_cls)).value) - else: - reg_value = reg_value_for_field_read( - fut=field, - reg_base_value=reg_value, - field_value=random_field_value(field)) - - return reg_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. + """ + 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) def get_field_bitmask_int(field: Field) -> int: From 676dd0ccee73e9bcd43e44da777121fbd657501b Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:23:02 +0000 Subject: [PATCH 34/80] Remove `test_register_write_context_manager` from the automatically generated code --- .../templates/addrmap_tb.py.jinja | 87 ------------------- 1 file changed, 87 deletions(-) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index e0cbbc8d..f624d395 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -244,93 +244,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- 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.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 - 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 ) | (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 %} - {% if asyncoutput %}async {% endif %}def test_register_write_fields(self) -> None: """ Walk the register map and check every register write_fields method From bb1e905f843922deaa448df0d874fa0d5f6bd145 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:51:48 +0000 Subject: [PATCH 35/80] Optimise `test_register_write_fields` closes #252 --- .../lib_test/async_reg_base_test_class.py | 49 +++++++++- .../lib_test/base_reg_test_class.py | 50 ++++++++++- src/peakrdl_python/lib_test/utilities.py | 15 ++++ .../templates/addrmap_tb.py.jinja | 89 ------------------- 4 files changed, 108 insertions(+), 95 deletions(-) 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 index 20dff5fe..1ecc044b 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -39,7 +39,8 @@ 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_reg_value, RandomReg, RegWriteTestSequence +from .utilities import random_reg_value, RandomReg +from .utilities import RegWriteTestSequence,RegWriteZeroStartTestSequence from ._common_base_test_class import CommonTestBase @@ -337,7 +338,11 @@ async def _single_register_read_and_write_test(self, 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_context_test(rut) + 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 @@ -414,7 +419,7 @@ async def __single_reg_read_fields_and_context_test( write_callback_mock.assert_not_called() - async def __single_reg_write_context_test(self, rut: RegAsyncReadWrite) -> None: + 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 @@ -474,3 +479,41 @@ async def __single_reg_write_context_test(self, rut: RegAsyncReadWrite) -> None: 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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index e146f689..48da0322 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -34,7 +34,8 @@ 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_reg_value, RandomReg, RegWriteTestSequence +from .utilities import random_reg_value, RandomReg +from .utilities import RegWriteTestSequence,RegWriteZeroStartTestSequence from ._common_base_test_class import CommonTestBase @@ -320,7 +321,12 @@ def _single_register_read_and_write_test(self, 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_context_test(rut) + 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 @@ -397,7 +403,7 @@ def __single_reg_read_fields_and_context_test(self, write_callback_mock.assert_not_called() - def __single_reg_write_context_test(self, rut: RegReadWrite) -> None: + 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 @@ -455,3 +461,41 @@ def __single_reg_write_context_test(self, rut: RegReadWrite) -> None: 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() diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 65607ef4..6a0b291a 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -180,6 +180,8 @@ 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) @@ -190,6 +192,19 @@ def __post_init__(self) -> None: 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: """ diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index f624d395..e3f06ac1 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -244,95 +244,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- endif %} {%- endfor %} - {% if asyncoutput %}async {% endif %}def test_register_write_fields(self) -> None: - """ - Walk the register map and check every register write_fields method - """ - 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.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 - 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 ) | (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 - {%- 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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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 %} - {%- endfor %} - {% if uses_memory %} {% if asyncoutput %}async {% endif %}def test_memory_read_and_write(self) -> None: """ From b58c6c8e8b52290217efb08f9d6511e60113029c Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:39:46 +0000 Subject: [PATCH 36/80] Change the test classes over to using the simulator rather than the dummy callbacks. This is a preparation for testing simulator --- .../lib_test/async_reg_base_test_class.py | 15 +++++---------- .../lib_test/base_reg_test_class.py | 10 +++++----- .../templates/baseclass_tb.py.jinja | 9 +++++++++ 3 files changed, 19 insertions(+), 15 deletions(-) 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 index 1ecc044b..f884be34 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -23,7 +23,7 @@ # pylint:disable=duplicate-code import unittest -from abc import ABC +from abc import ABC, abstractmethod from typing import Union from unittest.mock import patch from itertools import product, chain, combinations @@ -33,8 +33,6 @@ from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly from ..lib import RegisterWriteVerifyError -from ..sim_lib.dummy_callbacks import async_dummy_read -from ..sim_lib.dummy_callbacks import async_dummy_write from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base @@ -59,10 +57,9 @@ async def outer_read_callback(self, addr: int, width: int, accesswidth: int) -> width=width, accesswidth=accesswidth) + @abstractmethod async def read_callback(self, addr: int, width: int, accesswidth: int) -> int: - return await async_dummy_read(addr=addr, - width=width, - accesswidth=accesswidth) + ... async def outer_write_callback(self, addr: int, width: int, accesswidth: int, @@ -72,11 +69,9 @@ async def outer_write_callback(self, addr: int, accesswidth=accesswidth, data=data) + @abstractmethod async def write_callback(self, addr: int, width: int, accesswidth: int, data: int) -> None: - return await async_dummy_write(addr=addr, - width=width, - accesswidth=accesswidth, - data=data) + ... # pylint:enable=missing-function-docstring diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 48da0322..66516c6f 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -18,7 +18,7 @@ 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 +from abc import ABC, abstractmethod from typing import Union from unittest.mock import patch from itertools import product, chain, combinations @@ -28,8 +28,6 @@ from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly from ..lib import RegisterWriteVerifyError -from ..sim_lib.dummy_callbacks import dummy_read -from ..sim_lib.dummy_callbacks import dummy_write from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base @@ -52,14 +50,16 @@ class LibTestBase(CommonTestBase, ABC): 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: - return dummy_read(addr=addr, width=width, accesswidth=accesswidth) + ... 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: - return dummy_write(addr=addr, width=width, accesswidth=accesswidth, data=data) + ... # pylint:enable=missing-function-docstring diff --git a/src/peakrdl_python/templates/baseclass_tb.py.jinja b/src/peakrdl_python/templates/baseclass_tb.py.jinja index 5c03a211..b68003c0 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -44,6 +44,7 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import NormalCallbackSet, NormalC from ..reg_model import RegModel +from ..sim import Simulator {% 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 @@ -92,7 +93,14 @@ def random_enum_reg_value(enum_class: type[{% if legacy_enum_type %}IntEnum{% el class {{top_node.inst_name}}_TestCase(TestCaseBase): # type: ignore[valid-type,misc] + {% 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) + + {% 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) + 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)) @@ -110,6 +118,7 @@ class {{top_node.inst_name}}_TestCase_AltBlockAccess(BlockTestBase): # type: ign 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, From 63adeae1ef15bc0a35b19f03fec7f23f3f395a28 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:09:59 +0000 Subject: [PATCH 37/80] Moved the simulator register test into the test library and merged it with the main test closes #245 --- .../lib_test/async_reg_base_test_class.py | 79 +++++++++++++++++-- .../lib_test/base_reg_test_class.py | 67 +++++++++++++++- .../templates/addrmap_simulation_tb.py.jinja | 65 --------------- .../templates/addrmap_tb.py.jinja | 3 +- 4 files changed, 139 insertions(+), 75 deletions(-) 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 index f884be34..16b33502 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -25,7 +25,7 @@ import unittest from abc import ABC, abstractmethod from typing import Union -from unittest.mock import patch +from unittest.mock import patch, Mock from itertools import product, chain, combinations from collections.abc import Iterable @@ -33,6 +33,8 @@ from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly from ..lib import RegisterWriteVerifyError +from ..sim_lib.register import Register as SimRegister +from ..sim_lib.register import MemoryRegister as SimMemoryRegister from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base @@ -305,15 +307,21 @@ async def _single_enum_field_read_and_write_test( 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) -> None: + async def _single_register_read_and_write_test( + self, + rut: Union[RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly], + sim_register: Union[SimRegister, SimMemoryRegister], + has_sw_readable: bool, + has_sw_writable: bool) -> None: # the register properties are tested separately so are available to be used here + await self.__single_register_simulator_read_and_write_test( + rut=rut, + sim_register=sim_register, + 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') @@ -512,3 +520,60 @@ async def __single_reg_full_write_fields_test(self, rut: RegAsyncWriteOnly) -> N 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], + sim_register: Union[SimRegister, SimMemoryRegister], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + register_read_callback = Mock() + register_write_callback = Mock() + + if has_sw_readable: + # 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: + # 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: + self.assertEqual(await rut.read(), random_value) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 66516c6f..4a048017 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -20,7 +20,7 @@ """ from abc import ABC, abstractmethod from typing import Union -from unittest.mock import patch +from unittest.mock import patch, Mock from itertools import product, chain, combinations from collections.abc import Iterable @@ -28,6 +28,8 @@ from ..lib import FieldEnumReadWrite, FieldEnumReadOnly, FieldEnumWriteOnly from ..lib import RegReadOnly, RegReadWrite, RegWriteOnly from ..lib import RegisterWriteVerifyError +from ..sim_lib.register import Register as SimRegister +from ..sim_lib.register import MemoryRegister as SimMemoryRegister from .utilities import reverse_bits, expected_reg_write_data from .utilities import reg_value_for_field_read_with_random_base @@ -296,11 +298,17 @@ def _single_enum_field_read_and_write_test( def _single_register_read_and_write_test(self, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], + sim_register: Union[SimRegister, SimMemoryRegister], has_sw_readable: bool, has_sw_writable: bool) -> None: # the register properties are tested separately so are available to be used here + self.__single_register_simulator_read_and_write_test(rut=rut, + sim_register=sim_register, + 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') @@ -499,3 +507,60 @@ def __single_reg_full_write_fields_test(self, rut: RegWriteOnly) -> None: 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], + sim_register: Union[SimRegister, SimMemoryRegister], + has_sw_readable: bool, + has_sw_writable: bool) -> None: + + self.assertIsInstance(sim_register, (SimRegister, SimMemoryRegister)) + register_read_callback = Mock() + register_write_callback = Mock() + + if has_sw_readable: + # 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: + # 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: + self.assertEqual(rut.read(), random_value) diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index ebb5bd13..b3e34153 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -55,71 +55,6 @@ from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits 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 diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index e3f06ac1..d9b57bbe 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -222,11 +222,10 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ Walk the register map and check every register can be read and written to correctly """ - rut: Reg {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): 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 %}) - {% 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}}) + {% if asyncoutput %}await {%endif %}self._single_register_read_and_write_test(rut=self.dut.{{'.'.join(get_python_path_segments(node))}}, sim_register=self.sim.register_by_full_name('{{'.'.join(node.get_path_segments())}}'), has_sw_readable={{node.has_sw_readable}}, has_sw_writable={{node.has_sw_writable}}) {% endfor %} {% if asyncoutput %}async {% endif %}def test_field(self) -> None: From 67ebadc57d2d309c7d93ae97b5a1ea04e772d64a Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:15:15 +0000 Subject: [PATCH 38/80] Up revision the version number for the simulator test optimisation --- src/peakrdl_python/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 40d7ac1b..a15bed25 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc2" +__version__ = "3.0.0rc3" From 39cb67b5e070f64c347001e72b8acb28bdf64da0 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 16 Nov 2025 15:00:58 +0000 Subject: [PATCH 39/80] Simplify the simulation register test --- .../lib_test/_common_base_test_class.py | 13 +++++++++++-- .../lib_test/async_reg_base_test_class.py | 12 +++++++++--- src/peakrdl_python/lib_test/base_reg_test_class.py | 12 +++++++++--- src/peakrdl_python/templates/addrmap_tb.py.jinja | 2 +- src/peakrdl_python/templates/baseclass_tb.py.jinja | 5 +++++ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 44e27fd6..125500fc 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -19,7 +19,7 @@ Python tool. It provide the base class common to both the async and non-async versions """ import unittest -from abc import ABC +from abc import ABC, abstractmethod from typing import Union, Optional from ..lib import FieldReadWrite, FieldReadOnly, FieldWriteOnly @@ -28,13 +28,22 @@ from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib.base_register import BaseReg from .utilities import get_field_bitmask_int, get_field_inv_bitmask +from ..sim_lib.simulator import BaseSimulator class CommonTestBase(unittest.TestCase, ABC): """ - Base Test class for the autogenerated register test to be used for for the async and + 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 + """ + ... + # pylint:disable-next=too-many-arguments def _single_field_property_test(self, *, fut: Union[FieldReadWrite, 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 index 16b33502..ca806039 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -310,7 +310,6 @@ async def _single_enum_field_read_and_write_test( async def _single_register_read_and_write_test( self, rut: Union[RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly], - sim_register: Union[SimRegister, SimMemoryRegister], has_sw_readable: bool, has_sw_writable: bool) -> None: @@ -318,7 +317,6 @@ async def _single_register_read_and_write_test( await self.__single_register_simulator_read_and_write_test( rut=rut, - sim_register=sim_register, has_sw_readable=has_sw_readable, has_sw_writable=has_sw_writable) @@ -524,15 +522,18 @@ async def __single_reg_full_write_fields_test(self, rut: RegAsyncWriteOnly) -> N async def __single_register_simulator_read_and_write_test( self, rut: Union[RegAsyncReadOnly,RegAsyncReadWrite,RegAsyncWriteOnly], - sim_register: Union[SimRegister, SimMemoryRegister], 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) @@ -556,6 +557,8 @@ async def __single_register_simulator_read_and_write_test( 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) @@ -576,4 +579,7 @@ async def __single_register_simulator_read_and_write_test( 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) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 4a048017..36cea940 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -298,14 +298,12 @@ def _single_enum_field_read_and_write_test( def _single_register_read_and_write_test(self, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], - sim_register: Union[SimRegister, SimMemoryRegister], has_sw_readable: bool, has_sw_writable: bool) -> None: # the register properties are tested separately so are available to be used here self.__single_register_simulator_read_and_write_test(rut=rut, - sim_register=sim_register, has_sw_readable=has_sw_readable, has_sw_writable=has_sw_writable) @@ -511,15 +509,18 @@ def __single_reg_full_write_fields_test(self, rut: RegWriteOnly) -> None: def __single_register_simulator_read_and_write_test( self, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], - sim_register: Union[SimRegister, SimMemoryRegister], 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) @@ -543,6 +544,8 @@ def __single_register_simulator_read_and_write_test( 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) @@ -563,4 +566,7 @@ def __single_register_simulator_read_and_write_test( 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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index d9b57bbe..c5ea6786 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -225,7 +225,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): 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 %}) - {% if asyncoutput %}await {%endif %}self._single_register_read_and_write_test(rut=self.dut.{{'.'.join(get_python_path_segments(node))}}, sim_register=self.sim.register_by_full_name('{{'.'.join(node.get_path_segments())}}'), has_sw_readable={{node.has_sw_readable}}, has_sw_writable={{node.has_sw_writable}}) + {% 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}}) {% endfor %} {% if asyncoutput %}async {% endif %}def test_field(self) -> None: diff --git a/src/peakrdl_python/templates/baseclass_tb.py.jinja b/src/peakrdl_python/templates/baseclass_tb.py.jinja index b68003c0..640f0def 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -45,6 +45,7 @@ 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 @@ -99,6 +100,10 @@ class {{top_node.inst_name}}_TestCase(TestCaseBase): # type: ignore[valid-type,m {% 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) + @property + def simulator_instance(self) -> BaseSimulator: + return self.sim + 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, From 8b2d6f299c0a04caae9595bdc68ff341ce7dfe16 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:28:12 +0000 Subject: [PATCH 40/80] Optimise the field simulator tests by merging them into the main register tests --- .../lib_test/_common_base_test_class.py | 1 - .../lib_test/async_reg_base_test_class.py | 310 +++++++++++++++++- .../lib_test/base_reg_test_class.py | 308 ++++++++++++++++- .../templates/addrmap_simulation_tb.py.jinja | 175 ---------- 4 files changed, 615 insertions(+), 179 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 125500fc..53c482d6 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -42,7 +42,6 @@ def simulator_instance(self) -> BaseSimulator: """ Simulator configured for the DUT """ - ... # pylint:disable-next=too-many-arguments def _single_field_property_test(self, *, 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 index ca806039..da85341b 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -35,10 +35,12 @@ 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 .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 @@ -88,6 +90,10 @@ async def _single_int_field_read_and_write_test( # `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') @@ -295,17 +301,21 @@ async def _single_enum_field_read_and_write_test( # `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) + 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) + 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) + enum_definition=enum_definition) async def _single_register_read_and_write_test( self, @@ -583,3 +593,299 @@ async def __single_register_simulator_read_and_write_test( 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(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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 36cea940..5c3a0cc7 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -30,10 +30,12 @@ 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 .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 @@ -163,6 +165,10 @@ def _single_int_field_read_and_write_test( # `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') @@ -190,7 +196,7 @@ def __single_enum_field_read_test(self, 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) + 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( @@ -284,6 +290,10 @@ def _single_enum_field_read_and_write_test( # `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) + if is_sw_readable: if not isinstance(fut, (FieldEnumReadOnly, FieldEnumReadWrite)): raise TypeError('Test can not proceed as the fut is not a readable field') @@ -570,3 +580,299 @@ def __single_register_simulator_read_and_write_test( 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() diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index b3e34153..ca55e7ec 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -55,181 +55,6 @@ from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # type: ignore[valid-type,misc] - {% 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 %}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 = 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 %}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 %}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 %}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 = 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 %}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 = 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 %}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 %}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 %}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 %}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 %}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 %}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 %}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 %}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: """ From 197f6e3388d0f1ef7cf5a0ba7ef83385f74e3401 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:33:25 +0000 Subject: [PATCH 41/80] Fix a bug with address maps that don't have memories --- src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index ca55e7ec..1d754f9d 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -54,6 +54,9 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # type: ignore[valid-type,misc] + """ + tests for all the non-block access methods + """ {% if uses_memory %} {% if asyncoutput %}async {% endif %}def test_memory_read_and_write(self) -> None: From 273dcee007990c99aba3163daae0204527ac38f8 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:38:10 +0000 Subject: [PATCH 42/80] Added a missing await --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index da85341b..5cd32da4 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -815,7 +815,7 @@ async def __single_enum_field_simulator_read_and_write_test( field_value=random_field_value.value) sim_register.value = random_value - self.assertEqual(fut.read(), random_field_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() From 06a126cba9fee66a6bdbaca1aae001140ccbc64e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:14:58 +0000 Subject: [PATCH 43/80] Move the hashing method inside the generated tests to reduce the number of matrix permutations that are generated. They were previously moved out as it was suspected that the file size was exceeding the github limit. However, this should be significatly reduced now. --- .github/workflows/action.yaml | 5 ++--- generate_testcases.py | 23 ++++++++--------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index 63876988..cbd50fb3 100644 --- a/.github/workflows/action.yaml +++ b/.github/workflows/action.yaml @@ -339,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 @@ -365,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/generate_testcases.py b/generate_testcases.py index 4e455ed6..eee43624 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: From b44670d3f98c73fdeb85fb2bcdce22d284d6fcc4 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:55:17 +0000 Subject: [PATCH 44/80] Optimise the `test_name_property` and `test_desc` into a single test --- .../lib_test/_common_base_test_class.py | 18 ++++++++++ .../templates/addrmap_tb.py.jinja | 35 +++---------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 53c482d6..35824865 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -27,6 +27,7 @@ from ..lib import FieldAsyncReadOnly, FieldAsyncWriteOnly, FieldAsyncReadWrite from ..lib import FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly, FieldEnumAsyncReadWrite from ..lib.base_register import BaseReg +from ..lib import Base from .utilities import get_field_bitmask_int, get_field_inv_bitmask from ..sim_lib.simulator import BaseSimulator @@ -107,3 +108,20 @@ def _single_register_property_test(self, *, self.assertEqual(rut.accesswidth, accesswidth) else: self.assertEqual(rut.accesswidth, width) + + def _single_node_rdl_name_and_desc_test(self, + dut: Base, + rdl_name: Optional[str], + rdl_desc: Optional[str]): + """ + 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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index c5ea6786..1c6aecc8 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -94,40 +94,15 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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: + def test_rdl_name_and_desc_property(self) -> None: """ - Walk the address map and check the name property has been correctly populated + Walk the address map and check the system rdl name and 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_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 %} - + self._single_node_rdl_name_and_desc_test(dut=self.dut.{{'.'.join(get_python_path_segments(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 %}) {% endfor %} def test_sizes(self) -> None: From d3d475d1753b1852cecbd526f883a6ee6a45a141 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:55:51 +0000 Subject: [PATCH 45/80] Revise the version number --- src/peakrdl_python/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index a15bed25..061451c4 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc3" +__version__ = "3.0.0rc4" From 204d2f8c9978be8df3a29de8aff71163652628cf Mon Sep 17 00:00:00 2001 From: Keith Brady <34693973+krcb197@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:22:02 +0000 Subject: [PATCH 46/80] Annotate return type for test method Add return type annotation to _single_node_rdl_name_and_desc_test method. --- src/peakrdl_python/lib_test/_common_base_test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 35824865..fc3e7b33 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -112,7 +112,7 @@ def _single_register_property_test(self, *, def _single_node_rdl_name_and_desc_test(self, dut: Base, rdl_name: Optional[str], - rdl_desc: Optional[str]): + rdl_desc: Optional[str]) -> None: """ Check the SystemRDL Name and Desc properties for a node """ From acb3fefc9311bdd15e795a4c369ba0e053fe30f7 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:19:29 +0000 Subject: [PATCH 47/80] Reduce the console output from generate_and_test.py --- generate_and_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_and_test.py b/generate_and_test.py index 884adf18..1d3d531a 100644 --- a/generate_and_test.py +++ b/generate_and_test.py @@ -205,7 +205,7 @@ def build_logging_cong(logfilepath:str): 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: From 17b5b766af92021037d4e7b9f767f6d75a089c11 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:23:37 +0000 Subject: [PATCH 48/80] Correct the license message in the file docstring --- src/peakrdl_python/lib_test/__init__.py | 10 +++++----- .../lib_test/_common_base_test_class.py | 10 +++++----- .../lib_test/async_reg_base_test_class.py | 10 +++++----- src/peakrdl_python/lib_test/base_reg_test_class.py | 11 +++++++---- src/peakrdl_python/lib_test/utilities.py | 13 +++++++------ 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/peakrdl_python/lib_test/__init__.py b/src/peakrdl_python/lib_test/__init__.py index c3fcc9d3..911dec22 100644 --- a/src/peakrdl_python/lib_test/__init__.py +++ b/src/peakrdl_python/lib_test/__init__.py @@ -3,16 +3,16 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License +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 diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index fc3e7b33..c7319d49 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -3,16 +3,16 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License +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 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 index 5cd32da4..5f49fe2c 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -3,16 +3,16 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License +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 diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 5c3a0cc7..29a93f98 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -3,14 +3,17 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +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 . diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 6a0b291a..230a029f 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -3,19 +3,20 @@ Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License +You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . -This package It provide methods used by the tests +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 46c856d4a7f2da1cf0c95e6487a0ebd359429cc4 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:29:56 +0000 Subject: [PATCH 49/80] Change the documentation to use `sphinx-book-theme` which which is much nicer. closes #263 --- docs/conf.py | 11 ++++++++++- docs/requirements.txt | 2 +- pyproject.toml | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) 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/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" From 60fbdd2411f0dfc7c9e59a547ad18c0e3d2625dc Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:06:31 +0000 Subject: [PATCH 50/80] Simplify the `test_inst_name` test and update the revision to rc5 --- src/peakrdl_python/__about__.py | 14 +++++++------- .../lib_test/_common_base_test_class.py | 11 +++++++++++ src/peakrdl_python/templates/addrmap_tb.py.jinja | 5 +++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 061451c4..0bd9ad8a 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -1,20 +1,20 @@ """ peakrdl-python is a tool to generate Python Register Access Layer (RAL) from SystemRDL -Copyright (C) 2021 - 2023 +Copyright (C) 2021 - 2025 This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License +You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc4" +__version__ = "3.0.0rc5" diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index c7319d49..23ba92b0 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -125,3 +125,14 @@ def _single_node_rdl_name_and_desc_test(self, 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:str, + inst_name:str) -> None: + """ + Test the `inst_name` and `full_inst_name` attributes of a node + """ + self.assertEqual(dut.inst_name, inst_name) + full_inst_name = parent_full_inst_name + '.' + inst_name + self.assertEqual(dut.full_inst_name, full_inst_name) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index df13e528..c2ace76f 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -90,8 +90,9 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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] + self._test_node_inst_name(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + inst_name='{{node.get_path_segments()[-1]}}', + parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') {% endfor %} def test_rdl_name_and_desc_property(self) -> None: From c0b47aca53da7f417d8f96f11ec05f00c628ee8e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:00:37 +0000 Subject: [PATCH 51/80] Move all the field interator tests into the single register test --- .../lib_test/_common_base_test_class.py | 49 ++++++++++++++++ .../lib_test/async_reg_base_test_class.py | 10 +++- .../lib_test/base_reg_test_class.py | 10 +++- .../templates/addrmap_tb.py.jinja | 58 ++----------------- 4 files changed, 71 insertions(+), 56 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index c7319d49..dbe64a16 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -26,6 +26,8 @@ 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.base_register import BaseReg from ..lib import Base from .utilities import get_field_bitmask_int, get_field_inv_bitmask @@ -125,3 +127,50 @@ def _single_node_rdl_name_and_desc_test(self, self.assertIsNone(dut.rdl_desc) else: self.assertEqual(dut.rdl_desc, rdl_desc) + + def _test_register_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]): + 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 readable, 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) + 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 index 5f49fe2c..a2a37e5e 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -321,10 +321,18 @@ async def _single_register_read_and_write_test( self, rut: Union[RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly], has_sw_readable: bool, - has_sw_writable: bool) -> None: + 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_register_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, diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 29a93f98..70bf562d 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -312,10 +312,18 @@ def _single_enum_field_read_and_write_test( def _single_register_read_and_write_test(self, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], has_sw_readable: bool, - has_sw_writable: bool) -> None: + 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_register_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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index df13e528..d072edda 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -200,7 +200,9 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): 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 %}) - {% 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}}) + {% 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_field(self) -> None: @@ -548,70 +550,18 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: Walk the address map and check that the iterators for each node as as expected """ {% 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 -%} From 1fd0758cf4471a073e61d1f3eaa13d4cfc4ddbe5 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 22 Nov 2025 17:16:42 +0000 Subject: [PATCH 52/80] Test for memory, register and section iterators --- .../lib_test/_common_base_test_class.py | 68 ++++++++- .../lib_test/async_reg_base_test_class.py | 10 +- .../lib_test/base_reg_test_class.py | 10 +- .../templates/addrmap_tb.py.jinja | 141 ++---------------- 4 files changed, 84 insertions(+), 145 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index dbe64a16..45304883 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -28,6 +28,8 @@ 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.base_register import BaseReg from ..lib import Base from .utilities import get_field_bitmask_int, get_field_inv_bitmask @@ -128,17 +130,17 @@ def _single_node_rdl_name_and_desc_test(self, else: self.assertEqual(dut.rdl_desc, rdl_desc) - def _test_register_iterators(self, - rut: Union[RegReadOnly, + 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]): + has_sw_readable: bool, + has_sw_writable: bool, + readable_fields: set[str], + writeable_fields: set[str]): if has_sw_readable: if not isinstance(rut, (RegReadOnly, RegReadWrite, @@ -174,3 +176,57 @@ def _test_register_iterators(self, child_field_names = {field.inst_name for field in rut.fields} self.assertEqual(readable_fields | writeable_fields, child_field_names) + def _test_register_iterators(self, + dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile], + readable_registers: set[str], + writeable_registers: set[str]): + + child_readable_reg_names = { reg.inst_name for reg in + dut.get_writable_registers(unroll=True)} + self.assertEqual(readable_registers, child_readable_reg_names) + child_writable_reg_names = {reg.inst_name for reg in + dut.get_writable_registers(unroll=True)} + self.assertEqual(writeable_registers, child_writable_reg_names) + + + def _test_memory_iterators(self, + dut: Union[AddressMap, AsyncAddressMap], + memories: set[str]): + child_mem_names = {reg.inst_name for reg in + dut.get_memories(unroll=True)} + self.assertEqual(memories, child_mem_names) + + def __test_section_iterators(self, + dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile], + memories: set[str]): + child_mem_names = {reg.inst_name for reg in + dut.get_memories(unroll=True)} + self.assertEqual(memories, child_mem_names) + + def _test_addrmap_iterators(self, + dut: Union[AddressMap, AsyncAddressMap], + memories: set[str], + sections: set[str], + readable_registers: set[str], + writeable_registers: set[str]): + 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) + + def _test_regfile_iterators(self, + dut: Union[RegFile, AsyncRegFile], + memories: set[str], + sections: set[str], + readable_registers: set[str], + writeable_registers: set[str]): + 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')) + 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 index a2a37e5e..71ed06cc 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -327,11 +327,11 @@ async def _single_register_read_and_write_test( # the register properties are tested separately so are available to be used here - self._test_register_iterators(rut=rut, - has_sw_readable=has_sw_readable, - has_sw_writable=has_sw_writable, - readable_fields=readable_fields, - writeable_fields=writeable_fields) + 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, diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 70bf562d..f854db27 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -318,11 +318,11 @@ def _single_register_read_and_write_test(self, # the register properties are tested separately so are available to be used here - self._test_register_iterators(rut=rut, - has_sw_readable=has_sw_readable, - has_sw_writable=has_sw_writable, - readable_fields=readable_fields, - writeable_fields=writeable_fields) + 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, diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index d072edda..a98ea1e5 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -445,153 +445,36 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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: """ Walk the address map and check that the iterators for each node as as expected """ - {% 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]] # 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 %} + self._test_register_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ])) {% 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}} + self._test_addrmap_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ]), + memories=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ])) {% 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] + self._test_regfile_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ])) {% endfor %} def test_name_map(self) -> None: From fc074066c0b31eedfc9fbeddcefa3c3f3ab3a8ad Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:44:49 +0000 Subject: [PATCH 53/80] Fixed a bug with the parent typing of write only registers --- src/peakrdl_python/templates/addrmap_register.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -%} ]): From 5ca81122f44ed20f60da219f30a664cc29d86aec Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:46:37 +0000 Subject: [PATCH 54/80] Optimise the remaining traversal interator checks closes #258 --- .../lib_test/_common_base_test_class.py | 65 +++++++--- .../templates/addrmap_tb.py.jinja | 122 ++---------------- tests/testcases/memories_with_registers.rdl | 30 ++++- 3 files changed, 89 insertions(+), 128 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 45304883..176cc5bf 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -30,6 +30,12 @@ from ..lib import RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly from ..lib import AddressMap, AsyncAddressMap from ..lib import RegFile, AsyncRegFile +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 Base from .utilities import get_field_bitmask_int, get_field_inv_bitmask @@ -140,7 +146,7 @@ def _test_field_iterators(self, has_sw_readable: bool, has_sw_writable: bool, readable_fields: set[str], - writeable_fields: set[str]): + writeable_fields: set[str]) -> None: if has_sw_readable: if not isinstance(rut, (RegReadOnly, RegReadWrite, @@ -177,38 +183,60 @@ def _test_field_iterators(self, self.assertEqual(readable_fields | writeable_fields, child_field_names) def _test_register_iterators(self, - dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile], + dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile, + MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], readable_registers: set[str], - writeable_registers: set[str]): + writeable_registers: set[str]) -> 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, child_readable_reg_names) + else: + self.assertFalse(hasattr(dut, 'get_readable_registers')) - child_readable_reg_names = { reg.inst_name for reg in - dut.get_writable_registers(unroll=True)} - self.assertEqual(readable_registers, child_readable_reg_names) - child_writable_reg_names = {reg.inst_name for reg in - dut.get_writable_registers(unroll=True)} - self.assertEqual(writeable_registers, child_writable_reg_names) + 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, 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 | writeable_registers, child_reg_names) def _test_memory_iterators(self, dut: Union[AddressMap, AsyncAddressMap], - memories: set[str]): - child_mem_names = {reg.inst_name for reg in - dut.get_memories(unroll=True)} + memories: set[str]) -> None: + child_mem_names = {reg.inst_name for reg in dut.get_memories(unroll=True)} self.assertEqual(memories, child_mem_names) def __test_section_iterators(self, dut: Union[AddressMap, AsyncAddressMap, RegFile, AsyncRegFile], - memories: set[str]): - child_mem_names = {reg.inst_name for reg in - dut.get_memories(unroll=True)} - self.assertEqual(memories, child_mem_names) + sections: set[str]) -> None: + child_section_names = {reg.inst_name for reg in dut.get_sections(unroll=True)} + self.assertEqual(sections, child_section_names) def _test_addrmap_iterators(self, dut: Union[AddressMap, AsyncAddressMap], memories: set[str], sections: set[str], readable_registers: set[str], - writeable_registers: set[str]): + writeable_registers: set[str]) -> None: self._test_register_iterators(dut=dut, readable_registers=readable_registers, writeable_registers=writeable_registers) @@ -219,10 +247,9 @@ def _test_addrmap_iterators(self, def _test_regfile_iterators(self, dut: Union[RegFile, AsyncRegFile], - memories: set[str], sections: set[str], readable_registers: set[str], - writeable_registers: set[str]): + writeable_registers: set[str]) -> None: self._test_register_iterators(dut=dut, readable_registers=readable_registers, writeable_registers=writeable_registers) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index a98ea1e5..adb81d6e 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -342,106 +342,12 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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) + self._test_addrmap_iterators(dut=self.dut, + writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + sections=set([ {%- for child_node in top_node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ]), + memories=set([ {%- for child_node in top_node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) {% endif %}{# if block == top_block #} @@ -456,25 +362,25 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% for node in owned_elements.memories -%} with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): self._test_register_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ])) + writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ])) {% endfor %} # test all the address maps {% for node in owned_elements.addr_maps -%} with self.subTest(msg='addrmap: {{'.'.join(node.get_path_segments())}}'): self._test_addrmap_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ]), - memories=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ])) + writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ]), + memories=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) {% endfor %} # test all the register files {% for node in owned_elements.reg_files -%} with self.subTest(msg='regfile: {{'.'.join(node.get_path_segments())}}'): self._test_regfile_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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_reaadable %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.inst_name}}',{% endif %}{% endif %}{% endfor %} ])) + writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), + sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) {% endfor %} def test_name_map(self) -> None: 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 From 999f1a04c17bf6213e5812f826eccd6169ef0a03 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:54:49 +0000 Subject: [PATCH 55/80] Linting cleanups --- src/peakrdl_python/lib_test/_common_base_test_class.py | 7 +++---- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- src/peakrdl_python/lib_test/base_reg_test_class.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 176cc5bf..c47dff7b 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -136,7 +136,7 @@ def _single_node_rdl_name_and_desc_test(self, else: self.assertEqual(dut.rdl_desc, rdl_desc) - def _test_field_iterators(self, + def _test_field_iterators(self, *, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly, @@ -169,7 +169,7 @@ def _test_field_iterators(self, RegAsyncWriteOnly, RegAsyncReadWrite, )): - raise TypeError(f'Register was expected to readable, got {type(rut)}') + raise TypeError(f'Register was expected to writable, got {type(rut)}') child_writeable_fields_names = {field.inst_name for field in rut.writable_fields} @@ -231,7 +231,7 @@ def __test_section_iterators(self, child_section_names = {reg.inst_name for reg in dut.get_sections(unroll=True)} self.assertEqual(sections, child_section_names) - def _test_addrmap_iterators(self, + def _test_addrmap_iterators(self, *, dut: Union[AddressMap, AsyncAddressMap], memories: set[str], sections: set[str], @@ -256,4 +256,3 @@ def _test_regfile_iterators(self, self.__test_section_iterators(dut=dut, sections=sections) self.assertFalse(hasattr(dut, 'get_memories')) - 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 index 71ed06cc..5556fab1 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -318,7 +318,7 @@ async def _single_enum_field_read_and_write_test( enum_definition=enum_definition) async def _single_register_read_and_write_test( - self, + self, *, rut: Union[RegAsyncReadOnly, RegAsyncReadWrite, RegAsyncWriteOnly], has_sw_readable: bool, has_sw_writable: bool, diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index f854db27..7aa7ca84 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -309,7 +309,7 @@ def _single_enum_field_read_and_write_test( self.__single_enum_field_write_test(fut=fut, enum_definition=enum_definition) - def _single_register_read_and_write_test(self, + def _single_register_read_and_write_test(self, *, rut: Union[RegReadOnly, RegReadWrite, RegWriteOnly], has_sw_readable: bool, has_sw_writable: bool, From 6a06a58e42ab836fcb5fa49046f8bc218c9c92d6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:24:57 +0000 Subject: [PATCH 56/80] Revise version number --- src/peakrdl_python/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 0bd9ad8a..94789b2b 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc5" +__version__ = "3.0.0rc6" From f3cda7d78a49d478f621e0a9dacd4094462dea38 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:52:26 +0000 Subject: [PATCH 57/80] Update to the iterator test: - Only list out the names of the rolled up forms, this simplifies arrays - test both the rolled and unrolled iterators --- src/peakrdl_python/exporter.py | 6 +- src/peakrdl_python/lib_test/__init__.py | 2 + .../lib_test/_common_base_test_class.py | 87 +++++++++++++++---- .../systemrdl_node_utility_functions.py | 15 ++++ .../templates/addrmap_tb.py.jinja | 27 +++--- 5 files changed, 106 insertions(+), 31 deletions(-) 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_test/__init__.py b/src/peakrdl_python/lib_test/__init__.py index 911dec22..33bb43da 100644 --- a/src/peakrdl_python/lib_test/__init__.py +++ b/src/peakrdl_python/lib_test/__init__.py @@ -22,3 +22,5 @@ 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 index c0c5c907..8a3c9573 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -21,6 +21,7 @@ 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 @@ -41,6 +42,46 @@ 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 @@ -201,8 +242,8 @@ def _test_register_iterators(self, MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], - readable_registers: set[str], - writeable_registers: set[str]) -> None: + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: if isinstance(dut, (AddressMap, AsyncAddressMap, RegFile, AsyncRegFile, MemoryReadOnly, MemoryReadOnlyLegacy, @@ -211,7 +252,10 @@ def _test_register_iterators(self, MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): child_readable_reg_names = { reg.inst_name for reg in dut.get_readable_registers(unroll=True)} - self.assertEqual(readable_registers, child_readable_reg_names) + 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')) @@ -222,32 +266,43 @@ def _test_register_iterators(self, MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): child_writable_reg_names = {reg.inst_name for reg in dut.get_writable_registers(unroll=True)} - self.assertEqual(writeable_registers, child_writable_reg_names) + 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 | writeable_registers, child_reg_names) + 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) def _test_memory_iterators(self, dut: Union[AddressMap, AsyncAddressMap], - memories: set[str]) -> None: + memories: NodeIterators) -> None: child_mem_names = {reg.inst_name for reg in dut.get_memories(unroll=True)} - self.assertEqual(memories, child_mem_names) + 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: set[str]) -> None: + sections: NodeIterators) -> None: child_section_names = {reg.inst_name for reg in dut.get_sections(unroll=True)} - self.assertEqual(sections, child_section_names) + 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: set[str], - sections: set[str], - readable_registers: set[str], - writeable_registers: set[str]) -> None: + 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) @@ -258,9 +313,9 @@ def _test_addrmap_iterators(self, *, def _test_regfile_iterators(self, dut: Union[RegFile, AsyncRegFile], - sections: set[str], - readable_registers: set[str], - writeable_registers: set[str]) -> None: + sections: NodeIterators, + readable_registers: NodeIterators, + writeable_registers: NodeIterators) -> None: self._test_register_iterators(dut=dut, readable_registers=readable_registers, writeable_registers=writeable_registers) diff --git a/src/peakrdl_python/systemrdl_node_utility_functions.py b/src/peakrdl_python/systemrdl_node_utility_functions.py index 1dbc569a..268b1c38 100644 --- a/src/peakrdl_python/systemrdl_node_utility_functions.py +++ b/src/peakrdl_python/systemrdl_node_utility_functions.py @@ -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_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 279447e5..ec77e43e 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -75,6 +75,7 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import Reg 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 @@ -345,10 +346,10 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: def test_top_traversal_iterators(self) -> None: self._test_addrmap_iterators(dut=self.dut, - writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - sections=set([ {%- for child_node in top_node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ]), - memories=set([ {%- for child_node in top_node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) + 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 #} @@ -363,25 +364,25 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% for node in owned_elements.memories -%} with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): self._test_register_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ])) + 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 %} # test all the address maps {% for node in owned_elements.addr_maps -%} with self.subTest(msg='addrmap: {{'.'.join(node.get_path_segments())}}'): self._test_addrmap_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ]), - memories=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, systemrdlMemNode) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) + 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 %} # test all the register files {% for node in owned_elements.reg_files -%} with self.subTest(msg='regfile: {{'.'.join(node.get_path_segments())}}'): self._test_regfile_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - writeable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - readable_registers=set([ {%- 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 %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endif %}{% endfor %} ]), - sections=set([ {%- for child_node in node.children(unroll=True) %}{%- if not hide_node_func(child_node) %}{%- if isinstance(child_node, (systemrdlRegfileNode,systemrdlAddrmapNode)) %}'{{child_node.get_path_segments()[-1]}}',{% endif %}{% endif %}{% endfor %} ])) + 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 %} def test_name_map(self) -> None: From 63cbede1dfd931e34943ee317f0f4a21e77014dc Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:17:00 +0000 Subject: [PATCH 58/80] Remove old jinja template logic checking for pre-3.8 --- .../templates/addrmap_simulation_tb.py.jinja | 10 ++-------- src/peakrdl_python/templates/addrmap_tb.py.jinja | 13 +------------ 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index 3a1b451d..a0d9fdce 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -27,7 +27,6 @@ along with this program. If not, see . {% 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 @@ -186,14 +185,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 279447e5..3265924d 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -26,7 +26,6 @@ along with this program. If not, see . 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 @@ -719,15 +718,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() From 7767e1a0bae275ad4bb3527af61236be0932cd7b Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:56:43 +0000 Subject: [PATCH 59/80] Move all the memory read/write testing into the test library --- src/peakrdl_python/lib/memory.py | 14 ++ .../lib_test/_common_base_test_class.py | 34 +++- .../lib_test/async_reg_base_test_class.py | 176 +++++++++++++++++- .../lib_test/base_reg_test_class.py | 175 ++++++++++++++++- src/peakrdl_python/lib_test/utilities.py | 13 ++ .../templates/addrmap_tb.py.jinja | 156 +++------------- .../templates/baseclass_tb.py.jinja | 4 + 7 files changed, 428 insertions(+), 144 deletions(-) diff --git a/src/peakrdl_python/lib/memory.py b/src/peakrdl_python/lib/memory.py index 2026b2fb..baaae63a 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: """ diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 8a3c9573..49f6d68e 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -31,6 +31,7 @@ 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 @@ -95,6 +96,13 @@ 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, @@ -160,6 +168,22 @@ def _single_register_property_test(self, *, else: self.assertEqual(rut.accesswidth, width) + def _single_memory_property_test(self, *, + mut: BaseMemory, + address: int, + width: int, + entries: int, + accesswidth: Optional[int], + array_typecode: 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.array_typecode, array_typecode) + else: + self.assertEqual(mut.accesswidth, width) + self.assertEqual(mut.entries, entries) + def _single_node_rdl_name_and_desc_test(self, dut: Base, rdl_name: Optional[str], @@ -190,11 +214,11 @@ def _test_node_inst_name(self, def _test_field_iterators(self, *, rut: Union[RegReadOnly, - RegReadWrite, - RegWriteOnly, - RegAsyncReadOnly, - RegAsyncReadWrite, - RegAsyncWriteOnly], + RegReadWrite, + RegWriteOnly, + RegAsyncReadOnly, + RegAsyncReadWrite, + RegAsyncWriteOnly], has_sw_readable: bool, has_sw_writable: bool, readable_fields: set[str], 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 index 5556fab1..67e15bd7 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -25,13 +25,17 @@ import unittest from abc import ABC, abstractmethod from typing import Union -from unittest.mock import patch, Mock +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 @@ -43,8 +47,9 @@ 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 +from ._common_base_test_class import CommonTestBase, NodeIterators class AsyncLibTestBase(unittest.IsolatedAsyncioTestCase, CommonTestBase, ABC): """ @@ -897,3 +902,170 @@ async def __single_enum_field_simulator_read_and_write_test( 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) + + await self.__single_memory_simulator_read_and_write_test(mut=mut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + 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) + # check the read fields and read context manager + # TODO see if there is context manager test for the memory + # self.__single_reg_read_fields_and_context_test(rut=rut) + 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) + 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)] + 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: + + 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=entry, 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) + + read_callback_mock.assert_not_called() + + async def __single_memory_simulator_read_and_write_test( + self, + mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], + is_sw_readable: bool, + is_sw_writable: bool + ) -> None: + pass + # TODO copy in the test of the simulator diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 7aa7ca84..247caa21 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -23,13 +23,17 @@ """ from abc import ABC, abstractmethod from typing import Union -from unittest.mock import patch, Mock +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 @@ -41,8 +45,9 @@ 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 +from ._common_base_test_class import CommonTestBase, NodeIterators class LibTestBase(CommonTestBase, ABC): """ @@ -887,3 +892,169 @@ def __single_enum_field_simulator_read_and_write_test( 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) + + self.__single_memory_simulator_read_and_write_test(mut=mut, + is_sw_readable=is_sw_readable, + is_sw_writable=is_sw_writable) + + 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) + # check the read fields and read context manager + # TODO see if there is context manager test for the memory + # self.__single_reg_read_fields_and_context_test(rut=rut) + 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) + 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)] + 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: + + 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=entry, 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) + + read_callback_mock.assert_not_called() + + def __single_memory_simulator_read_and_write_test( + self, + mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy], + is_sw_readable: bool, + is_sw_writable: bool + ) -> None: + pass + # TODO copy in the test of the simulator + diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py index 230a029f..f23a83f4 100644 --- a/src/peakrdl_python/lib_test/utilities.py +++ b/src/peakrdl_python/lib_test/utilities.py @@ -31,6 +31,7 @@ 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: """ @@ -233,3 +234,15 @@ def get_field_inv_bitmask(field: Field) -> str: """ 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/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 5b11f4a1..cfb49be4 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -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 %} @@ -124,23 +122,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: self.assertEqual(self.dut.{{'.'.join(get_python_path_segments(block))}}.size, {{block.size}}) # type: ignore[union-attr] {% endif %} - 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_encoding_properties(self) -> None: """ Check that enumeration has the name and desc meta data from the systemRDL @@ -194,9 +175,27 @@ 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 + """ + {% 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='{{get_array_typecode(node.get_property('memwidth'))}}') + {% 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_register(self) -> None: + """ + Walk the registers in the register map and check: + - the properties + - it can be read and written to correctly """ {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): @@ -221,110 +220,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- endif %} {%- endfor %} - {% if uses_memory %} - {% if asyncoutput %}async {% endif %}def test_memory_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.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.object(self,'write_callback') as write_callback_mock, \ - patch.object(self,'read_callback', 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 @@ -357,15 +252,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: Walk the address map and check that the iterators for each node as as expected """ - - - # test all the memories - {% for node in owned_elements.memories -%} - with self.subTest(msg='memory: {{'.'.join(node.get_path_segments())}}'): - self._test_register_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 %})) - {% endfor %} # test all the address maps {% for node in owned_elements.addr_maps -%} with self.subTest(msg='addrmap: {{'.'.join(node.get_path_segments())}}'): diff --git a/src/peakrdl_python/templates/baseclass_tb.py.jinja b/src/peakrdl_python/templates/baseclass_tb.py.jinja index f3871358..672e2b40 100644 --- a/src/peakrdl_python/templates/baseclass_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_tb.py.jinja @@ -104,6 +104,10 @@ class {{top_node.inst_name}}_TestCase(TestCaseBase): # type: ignore[valid-type,m def simulator_instance(self) -> BaseSimulator: return self.sim + @property + def legacy_block_access(self) -> bool: + return {{ legacy_block_access }} + 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, From 1a9c1b9bea2d97f17852c56a56d623c27f1eaea7 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:04:13 +0000 Subject: [PATCH 60/80] Fix some bugs from the previus commit --- src/peakrdl_python/lib_test/_common_base_test_class.py | 5 +++-- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 2 +- src/peakrdl_python/lib_test/base_reg_test_class.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 49f6d68e..2f7a81ad 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -168,6 +168,7 @@ def _single_register_property_test(self, *, else: self.assertEqual(rut.accesswidth, width) + # pylint:disable-next=too-many-arguments def _single_memory_property_test(self, *, mut: BaseMemory, address: int, @@ -179,10 +180,10 @@ def _single_memory_property_test(self, *, self.assertEqual(mut.width, width) self.assertEqual(mut.entries, entries) if accesswidth is not None: - self.assertEqual(mut.array_typecode, array_typecode) + self.assertEqual(mut.accesswidth, accesswidth) else: self.assertEqual(mut.accesswidth, width) - self.assertEqual(mut.entries, entries) + self.assertEqual(mut.array_typecode, array_typecode) def _single_node_rdl_name_and_desc_test(self, dut: Base, 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 index 67e15bd7..b6c68ac1 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1045,7 +1045,7 @@ async def __single_memory_write_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=entry, data=Array(mut.array_typecode, rand_data_list)) + 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)}') diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 247caa21..b193a682 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1033,7 +1033,7 @@ def __single_memory_write_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=entry, data=Array(mut.array_typecode, rand_data_list)) + 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)}') From 211c829d485d02793f8fd4e4f08ed4d26305740e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:07:01 +0000 Subject: [PATCH 61/80] Added checks for illegal values --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 7 +++++++ src/peakrdl_python/lib_test/base_reg_test_class.py | 7 +++++++ 2 files changed, 14 insertions(+) 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 index b6c68ac1..5c8cfe3e 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1057,6 +1057,13 @@ async def __single_memory_write_test( 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 + 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_and_write_test( diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index b193a682..83fa6c92 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1045,6 +1045,13 @@ def __single_memory_write_test( 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 + 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_and_write_test( From 7166ce39e002f15d1d22be5c70848da02966e862 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:09:26 +0000 Subject: [PATCH 62/80] Added checks for illegal values --- .../lib_test/async_reg_base_test_class.py | 15 ++++++++++----- .../lib_test/base_reg_test_class.py | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) 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 index 5c8cfe3e..7a42c970 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1058,11 +1058,16 @@ async def __single_memory_write_test( write_callback_mock.assert_has_calls(calls, any_order=False) # check invalid write values bounce - 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) + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 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]) read_callback_mock.assert_not_called() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 83fa6c92..99143190 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1046,11 +1046,16 @@ def __single_memory_write_test( write_callback_mock.assert_has_calls(calls, any_order=False) # check invalid write values bounce - 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) + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 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]) read_callback_mock.assert_not_called() From a612cba8c5b4e6047eebe82946681739cf95cab7 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:09:54 +0000 Subject: [PATCH 63/80] Line length fixes --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 3 ++- src/peakrdl_python/lib_test/base_reg_test_class.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 index 7a42c970..f4bbb4fc 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1062,7 +1062,8 @@ async def __single_memory_write_test( if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') with self.assertRaises(ValueError): - await mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) else: if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 99143190..342bd255 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1050,7 +1050,8 @@ def __single_memory_write_test( if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') with self.assertRaises(ValueError): - mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) + mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) else: if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') From f579deea3218cf32fcb926acd7082e31b049294f Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:15:07 +0000 Subject: [PATCH 64/80] Fix the illegal range checks to check lower value too --- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 5 +++++ src/peakrdl_python/lib_test/base_reg_test_class.py | 4 ++++ 2 files changed, 9 insertions(+) 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 index f4bbb4fc..8123225c 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1064,11 +1064,16 @@ async def __single_memory_write_test( with self.assertRaises(ValueError): await mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) + with self.assertRaises(ValueError): + 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() diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 342bd255..f8cf46c4 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1052,11 +1052,15 @@ def __single_memory_write_test( with self.assertRaises(ValueError): mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) + with self.assertRaises(ValueError): + 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() From 10d5923c7e3480b8e0eb02168dcbb0269c9f42e2 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:21:37 +0000 Subject: [PATCH 65/80] Change the typecode for 16-bit memory width to `H` and correct the error message returned in a bad value Array --- src/peakrdl_python/lib/utility_functions.py | 2 +- src/peakrdl_python/lib_test/async_reg_base_test_class.py | 4 ++-- src/peakrdl_python/lib_test/base_reg_test_class.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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/async_reg_base_test_class.py b/src/peakrdl_python/lib_test/async_reg_base_test_class.py index 8123225c..57b6407e 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1061,10 +1061,10 @@ async def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(ValueError): + with self.assertRaises(OverflowError): await mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) - with self.assertRaises(ValueError): + with self.assertRaises(OverflowError): await mut.write(start_entry=0, data=Array(mut.array_typecode, [-1])) else: diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index f8cf46c4..9bc4d8e2 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1049,10 +1049,10 @@ def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(ValueError): + with self.assertRaises(OverflowError): mut.write(start_entry=0, data=Array(mut.array_typecode, [mut.max_entry_value + 1])) - with self.assertRaises(ValueError): + with self.assertRaises(OverflowError): mut.write(start_entry=0, data=Array(mut.array_typecode,[-1])) else: if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): From 73c681a34a1528a142285f8c5af3edf72361ace4 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:22:09 +0000 Subject: [PATCH 66/80] Add min/max value checking to the memory write --- src/peakrdl_python/lib/async_memory.py | 4 ++++ src/peakrdl_python/lib/memory.py | 4 ++++ 2 files changed, 8 insertions(+) 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/memory.py b/src/peakrdl_python/lib/memory.py index baaae63a..ee7683d2 100644 --- a/src/peakrdl_python/lib/memory.py +++ b/src/peakrdl_python/lib/memory.py @@ -519,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}') From f1b6a970d6452aea7e4c6db65c27e3d8375555a6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:22:38 +0000 Subject: [PATCH 67/80] Move all the simulator memory tests into the `lib_test` --- .../lib_test/async_reg_base_test_class.py | 110 +++++++++++++++--- .../lib_test/base_reg_test_class.py | 110 ++++++++++++++++-- src/peakrdl_python/sim_lib/simulator.py | 16 +++ .../templates/addrmap_simulation_tb.py.jinja | 64 ---------- .../templates/addrmap_tb.py.jinja | 6 +- .../baseclass_simulation_tb.py.jinja | 7 -- 6 files changed, 214 insertions(+), 99 deletions(-) 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 index 57b6407e..c5363719 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -40,6 +40,7 @@ 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 @@ -51,6 +52,9 @@ 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 @@ -922,18 +926,12 @@ async def _single_memory_read_and_write_test(self, *, readable_registers=readable_registers, writeable_registers=writeable_registers) - await self.__single_memory_simulator_read_and_write_test(mut=mut, - is_sw_readable=is_sw_readable, - is_sw_writable=is_sw_writable) - 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) - # check the read fields and read context manager - # TODO see if there is context manager test for the memory - # self.__single_reg_read_fields_and_context_test(rut=rut) + 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 @@ -945,6 +943,7 @@ async def _single_memory_read_and_write_test(self, *, 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 @@ -987,6 +986,9 @@ async def __single_memory_read_test( # 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] @@ -1011,6 +1013,10 @@ async def __single_memory_write_test( 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: @@ -1077,13 +1083,91 @@ async def __single_memory_write_test( read_callback_mock.assert_not_called() - async def __single_memory_simulator_read_and_write_test( + async def __single_memory_simulator_read_test( self, mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, - MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], - is_sw_readable: bool, - is_sw_writable: bool ) -> None: - pass - # TODO copy in the test of the simulator + + 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 index 9bc4d8e2..a3254680 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -38,6 +38,7 @@ 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 @@ -49,6 +50,10 @@ 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 @@ -911,18 +916,14 @@ def _single_memory_read_and_write_test(self, *, readable_registers=readable_registers, writeable_registers=writeable_registers) - self.__single_memory_simulator_read_and_write_test(mut=mut, - is_sw_readable=is_sw_readable, - is_sw_writable=is_sw_writable) + 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) - # check the read fields and read context manager - # TODO see if there is context manager test for the memory - # self.__single_reg_read_fields_and_context_test(rut=rut) + 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 @@ -934,6 +935,7 @@ def _single_memory_read_and_write_test(self, *, 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 @@ -976,6 +978,9 @@ def __single_memory_read_test( # 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] @@ -1000,6 +1005,10 @@ def __single_memory_write_test( 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: @@ -1064,14 +1073,91 @@ def __single_memory_write_test( read_callback_mock.assert_not_called() - def __single_memory_simulator_read_and_write_test( + def __single_memory_simulator_read_test( self, mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, - MemoryWriteOnly, MemoryWriteOnlyLegacy, MemoryReadWrite, MemoryReadWriteLegacy], - is_sw_readable: bool, - is_sw_writable: bool ) -> None: - pass - # TODO copy in the test of the simulator + 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/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/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index a0d9fdce..02eb6ad6 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -52,70 +52,6 @@ from {{ peakrdl_python_lib(depth=lib_depth) }} import SystemRDLEnum from {{ peakrdl_python_lib_test(depth=lib_depth) }} import reverse_bits -class {{fq_block_name}}_single_access({{top_node.inst_name}}_SimTestCase): # type: ignore[valid-type,misc] - """ - tests for all the non-block access methods - """ - - {% 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 %} - - - class {{fq_block_name}}_block_access({{top_node.inst_name}}_SimTestCase_BlockAccess): # type: ignore[valid-type,misc] """ tests for all the block access methods diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index cfb49be4..07b54c38 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -185,9 +185,9 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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='{{get_array_typecode(node.get_property('memwidth'))}}') - {% 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 %})) + {% 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 #} diff --git a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja index 3adcef16..7d18a516 100644 --- a/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/baseclass_simulation_tb.py.jinja @@ -47,13 +47,6 @@ TestCaseBase = unittest.IsolatedAsyncioTestCase TestCaseBase = unittest.TestCase {% endif %} -class {{top_node.inst_name}}_SimTestCase(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.sim.read, - write_callback=self.sim.write)) - class {{top_node.inst_name}}_SimTestCase_BlockAccess(TestCaseBase): # type: ignore[valid-type,misc] def setUp(self) -> None: From 814e323a17cf170ae4521a2c68e5bfcada032dd5 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:00:45 +0000 Subject: [PATCH 68/80] Put in tests for the `addrmap` and `regfile`, then remove the individual test functions for checking the system rdl name and desc, inst name and size --- .../lib_test/_common_base_test_class.py | 93 ++++++++++++- .../templates/addrmap_tb.py.jinja | 128 +++++++++--------- 2 files changed, 149 insertions(+), 72 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 2f7a81ad..c76051fe 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -122,7 +122,12 @@ def _single_field_property_test(self, *, low: int, high: int, is_volatile: bool, - default: Optional[int]) -> None: + 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) @@ -156,17 +161,40 @@ def _single_field_property_test(self, *, 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) + def _single_register_property_test(self, *, rut: BaseReg, address: int, width: int, - accesswidth: Optional[int]) -> None: + 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) # pylint:disable-next=too-many-arguments def _single_memory_property_test(self, *, @@ -175,7 +203,13 @@ def _single_memory_property_test(self, *, width: int, entries: int, accesswidth: Optional[int], - array_typecode: str) -> None: + array_typecode: 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) @@ -184,8 +218,55 @@ def _single_memory_property_test(self, *, else: self.assertEqual(mut.accesswidth, width) self.assertEqual(mut.array_typecode, array_typecode) - - def _single_node_rdl_name_and_desc_test(self, + 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) + + def _single_addrmap_property_test(self, *, + dut: Union[AddressMap, AsyncAddressMap], + size: int, + rdl_name: Optional[str], + rdl_desc: Optional[str], + parent_full_inst_name: str, + inst_name: str + ): + + 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) + + 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 + ): + + 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) + + def __single_node_rdl_name_and_desc_test(self, dut: Base, rdl_name: Optional[str], rdl_desc: Optional[str]) -> None: @@ -202,7 +283,7 @@ def _single_node_rdl_name_and_desc_test(self, else: self.assertEqual(dut.rdl_desc, rdl_desc) - def _test_node_inst_name(self, + def __test_node_inst_name(self, dut: Base, parent_full_inst_name:str, inst_name:str) -> None: diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 07b54c38..d7995263 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -82,46 +82,6 @@ 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._test_node_inst_name(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, - inst_name='{{node.get_path_segments()[-1]}}', - parent_full_inst_name='{{'.'.join(node.get_path_segments()[:-1])}}') - {% endfor %} - - def test_rdl_name_and_desc_property(self) -> None: - """ - Walk the address map and check the system rdl name and desc property has been correctly populated - """ - {% for node in owned_elements.nodes -%} - with self.subTest(msg='node: {{'.'.join(node.get_path_segments())}}'): - self._single_node_rdl_name_and_desc_test(dut=self.dut.{{'.'.join(get_python_path_segments(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 %}) - {% 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_field_encoding_properties(self) -> None: """ Check that enumeration has the name and desc meta data from the systemRDL @@ -184,7 +144,11 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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='{{get_array_typecode(node.get_property('memwidth'))}}') + 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='{{get_array_typecode(node.get_property('memwidth'))}}', 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 %})) @@ -199,7 +163,11 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% for node in owned_elements.registers -%} with self.subTest(msg='register: {{'.'.join(node.get_path_segments())}}'): - 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 %}) + 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 %} ]) ) @@ -211,7 +179,11 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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)}}) + 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 %} @@ -220,56 +192,80 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {%- endif %} {%- endfor %} - def test_adding_attributes(self) -> None: + def test_addrmap(self) -> None: """ - Walk the address map and attempt to set a new value on each node - - The attribute name: cppkbrgmgeloagvfgjjeiiushygirh was randomly generated to be unlikely to - every be a attribute name - + Check the properties on the addrmaps files """ - {% 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] - {% endfor %} - - {% if block == top_node %} - def test_top_traversal_iterators(self) -> None: + {% if block == 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='{{top_node.get_path_segments()[-1]}}') 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 #} - {% endif %}{# if block == top_block #} - - def test_traversal_iterators(self) -> None: - """ - Walk the address map and check that the iterators for each node as as expected - """ # 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 %} + + def test_regfile(self) -> None: + """ + Check the properties on the register files + """ + # test all the register files {% for node in owned_elements.reg_files -%} with self.subTest(msg='regfile: {{'.'.join(node.get_path_segments())}}'): - self._test_regfile_iterators(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + 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._single_regfile_property_test(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 %} + + + def test_adding_attributes(self) -> None: + """ + Walk the address map and attempt to set a new value on each node + + 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] + {% endfor %} + def test_name_map(self) -> None: """ Check that the function for getting a node by its original systemRDL name works From 354a3675815ec2b689940c336737c95be82b5ba4 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:01:49 +0000 Subject: [PATCH 69/80] Fix bug with import of simulator test class --- src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index 02eb6ad6..ca184676 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -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 From 7993fabc6d0501d13c08f1690a67ab25174c0f13 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:01:49 +0000 Subject: [PATCH 70/80] Fix bug with import of simulator test class --- src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja index 02eb6ad6..ca184676 100644 --- a/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_simulation_tb.py.jinja @@ -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 From cc05bbbb6232bcce36972efb1ccc6a0a435723f6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:16:00 +0000 Subject: [PATCH 71/80] Remove the typecode checking in non-legacy mode --- src/peakrdl_python/lib_test/_common_base_test_class.py | 7 +++++-- src/peakrdl_python/templates/addrmap_tb.py.jinja | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 2f7a81ad..3a67d76b 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -175,7 +175,7 @@ def _single_memory_property_test(self, *, width: int, entries: int, accesswidth: Optional[int], - array_typecode: str) -> None: + array_typecode: Optional[str]) -> None: self.assertEqual(mut.address, address) self.assertEqual(mut.width, width) self.assertEqual(mut.entries, entries) @@ -183,7 +183,10 @@ def _single_memory_property_test(self, *, self.assertEqual(mut.accesswidth, accesswidth) else: self.assertEqual(mut.accesswidth, width) - self.assertEqual(mut.array_typecode, array_typecode) + if self.legacy_block_access: + self.assertEqual(mut.array_typecode, array_typecode) + else: + self.assertIsNone(array_typecode) def _single_node_rdl_name_and_desc_test(self, dut: Base, diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 07b54c38..a845ab6c 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -184,7 +184,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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='{{get_array_typecode(node.get_property('memwidth'))}}') + 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 %}) {% 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 %})) From 38644f93beb757808f0090479b376971c5be2fb2 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:16:00 +0000 Subject: [PATCH 72/80] Merge fixes --- src/peakrdl_python/lib_test/_common_base_test_class.py | 7 +++++-- src/peakrdl_python/templates/addrmap_tb.py.jinja | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index c76051fe..bad64381 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -203,7 +203,7 @@ def _single_memory_property_test(self, *, width: int, entries: int, accesswidth: Optional[int], - array_typecode: str, + array_typecode: Optional[str], size: int, rdl_name: Optional[str], rdl_desc: Optional[str], @@ -217,7 +217,10 @@ def _single_memory_property_test(self, *, self.assertEqual(mut.accesswidth, accesswidth) else: self.assertEqual(mut.accesswidth, width) - self.assertEqual(mut.array_typecode, array_typecode) + 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, diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index d7995263..b90ab18b 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -144,7 +144,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% 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='{{get_array_typecode(node.get_property('memwidth'))}}', size={{node.size}}, + 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]}}', From 64609795f90bd02b098bb6f90ac707a89fd7e996 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:32:14 +0000 Subject: [PATCH 73/80] Fix issue with the testing of parent addrmap and error in the regfile test formation --- .../lib_test/_common_base_test_class.py | 19 ++++++++----- .../templates/addrmap_tb.py.jinja | 27 ++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index bad64381..7b73a5bb 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -169,6 +169,7 @@ def _single_field_property_test(self, *, parent_full_inst_name=parent_full_inst_name, inst_name=inst_name) + # pylint:disable-next=too-many-arguments def _single_register_property_test(self, *, rut: BaseReg, address: int, @@ -231,14 +232,15 @@ def _single_memory_property_test(self, *, parent_full_inst_name=parent_full_inst_name, inst_name=inst_name) + # 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: str, + parent_full_inst_name: Optional[str], inst_name: str - ): + ) -> None: self.assertEqual(dut.size, size) @@ -250,6 +252,7 @@ def _single_addrmap_property_test(self, *, parent_full_inst_name=parent_full_inst_name, inst_name=inst_name) + # pylint:disable-next=too-many-arguments def _single_regfile_property_test(self, *, dut: Union[RegFile, AsyncRegFile], size: int, @@ -257,7 +260,7 @@ def _single_regfile_property_test(self, *, rdl_desc: Optional[str], parent_full_inst_name: str, inst_name: str - ): + ) -> None: self.assertEqual(dut.size, size) @@ -288,14 +291,18 @@ def __single_node_rdl_name_and_desc_test(self, def __test_node_inst_name(self, dut: Base, - parent_full_inst_name:str, + 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) - full_inst_name = parent_full_inst_name + '.' + inst_name - self.assertEqual(dut.full_inst_name, full_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 _test_field_iterators(self, *, rut: Union[RegReadOnly, diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index b90ab18b..48b583a6 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -198,17 +198,18 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: """ {% if block == 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='{{top_node.get_path_segments()[-1]}}') - 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 %})) + 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 #} @@ -236,13 +237,13 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: # test all the register files {% for node in owned_elements.reg_files -%} with self.subTest(msg='regfile: {{'.'.join(node.get_path_segments())}}'): - self._single_addrmap_property_test(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + 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._single_regfile_property_test(dut=self.dut.{{'.'.join(get_python_path_segments(node))}}, + 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 %})) From bfe6107984276f043cef2f4430ef8bab9e7f9b82 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:19:10 +0000 Subject: [PATCH 74/80] Handle the case where the hardware array sizes are larger than needed. --- .../lib_test/async_reg_base_test_class.py | 14 +++++++++++--- src/peakrdl_python/lib_test/base_reg_test_class.py | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) 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 index c5363719..e65d6431 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1067,9 +1067,17 @@ async def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(OverflowError): - await mut.write(start_entry=0, data=Array(mut.array_typecode, - [mut.max_entry_value + 1])) + # 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])) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index a3254680..f89e4f8e 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1058,9 +1058,17 @@ def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(OverflowError): - mut.write(start_entry=0, data=Array(mut.array_typecode, - [mut.max_entry_value + 1])) + # 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: From ae02f379adf120ad6785d64662e7e8d1806859ef Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:19:10 +0000 Subject: [PATCH 75/80] Handle the case where the hardware array sizes are larger than needed. --- .../lib_test/async_reg_base_test_class.py | 14 +++++++++++--- src/peakrdl_python/lib_test/base_reg_test_class.py | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) 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 index c5363719..e65d6431 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -1067,9 +1067,17 @@ async def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(OverflowError): - await mut.write(start_entry=0, data=Array(mut.array_typecode, - [mut.max_entry_value + 1])) + # 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])) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index a3254680..f89e4f8e 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -1058,9 +1058,17 @@ def __single_memory_write_test( if self.legacy_block_access: if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): raise TypeError(f'Memory should be legacy type but got {type(mut)}') - with self.assertRaises(OverflowError): - mut.write(start_entry=0, data=Array(mut.array_typecode, - [mut.max_entry_value + 1])) + # 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: From eccbaf4e04f60370ad14dfb22cb56945dc90c1e6 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:23:57 +0000 Subject: [PATCH 76/80] Move the bad attribute test inside the library --- .../lib_test/_common_base_test_class.py | 26 ++++++++++++++++--- .../templates/addrmap_tb.py.jinja | 18 ------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 7b73a5bb..62547aa4 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -169,6 +169,8 @@ def _single_field_property_test(self, *, 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, @@ -197,6 +199,8 @@ def _single_register_property_test(self, *, 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, @@ -232,6 +236,8 @@ def _single_memory_property_test(self, *, 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], @@ -252,6 +258,8 @@ def _single_addrmap_property_test(self, *, 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], @@ -272,6 +280,8 @@ def _single_regfile_property_test(self, *, 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], @@ -290,9 +300,9 @@ def __single_node_rdl_name_and_desc_test(self, 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: + dut: Base, + parent_full_inst_name:Optional[str], + inst_name:str) -> None: """ Test the `inst_name` and `full_inst_name` attributes of a node """ @@ -304,6 +314,16 @@ def __test_node_inst_name(self, 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_field_iterators(self, *, rut: Union[RegReadOnly, RegReadWrite, diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 48b583a6..a7a9e91f 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -249,24 +249,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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 %} - - - def test_adding_attributes(self) -> None: - """ - Walk the address map and attempt to set a new value on each node - - 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] - {% endfor %} - def test_name_map(self) -> None: """ Check that the function for getting a node by its original systemRDL name works From 5c020fa1d73d5da0474f6b19282911c892316435 Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:22:50 +0000 Subject: [PATCH 77/80] Merge the `test_name_map` into the iterator test closes #274 --- .../lib_test/_common_base_test_class.py | 36 +++++++++++++++++++ .../templates/addrmap_tb.py.jinja | 14 -------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 62547aa4..85163505 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -39,6 +39,7 @@ 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 .utilities import get_field_bitmask_int, get_field_inv_bitmask from ..sim_lib.simulator import BaseSimulator @@ -324,6 +325,16 @@ def __bad_attribute_test(self, dut: Base) -> None: 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, @@ -370,6 +381,9 @@ def _test_field_iterators(self, *, 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, @@ -416,6 +430,19 @@ def _test_register_iterators(self, 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], @@ -447,6 +474,10 @@ def _test_addrmap_iterators(self, *, 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, @@ -458,3 +489,8 @@ def _test_regfile_iterators(self, 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) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index a7a9e91f..8f6c837d 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -249,20 +249,6 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: 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 %} - 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 %} - {% endfor %} - {% if owned_elements.has_hidden_nodes %} def test_hidden(self) -> None: """ From 3154d9f3339fa24440faea32e5dfb9ee0a39119e Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:43:57 +0000 Subject: [PATCH 78/80] Optimise the `test_field_encoding_properties` by inclduing within the main field test closes #276 --- src/peakrdl_python/__about__.py | 2 +- .../lib_test/_common_base_test_class.py | 130 +++++++++++++----- .../lib_test/async_reg_base_test_class.py | 8 +- .../lib_test/base_reg_test_class.py | 8 +- .../templates/addrmap_tb.py.jinja | 28 +--- 5 files changed, 108 insertions(+), 68 deletions(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 94789b2b..43013e05 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc6" +__version__ = "3.0.0rc7" diff --git a/src/peakrdl_python/lib_test/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 85163505..0ceb5ed1 100644 --- a/src/peakrdl_python/lib_test/_common_base_test_class.py +++ b/src/peakrdl_python/lib_test/_common_base_test_class.py @@ -41,20 +41,23 @@ 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]]]): + + 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: + def __rolled_item(item: Union[str, tuple[str, list[int]]]) -> str: if isinstance(item, tuple): return item[0] return item @@ -64,7 +67,7 @@ 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 } + return {self.__rolled_item(item) for item in self.__node_descriptions} @property def unrolled(self) -> set[str]: @@ -74,7 +77,7 @@ def unrolled(self) -> set[str]: return_list = [] for item in self.__node_descriptions: if isinstance(item, tuple): - dim_set = list(product(*[range(dim) for dim in item[1]])) + 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] @@ -84,6 +87,7 @@ def unrolled(self) -> set[str]: 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 @@ -107,17 +111,17 @@ def legacy_block_access(self) -> bool: # 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], + FieldReadOnly, + FieldWriteOnly, + FieldEnumReadWrite, + FieldEnumReadOnly, + FieldEnumWriteOnly, + FieldAsyncReadOnly, + FieldAsyncWriteOnly, + FieldAsyncReadWrite, + FieldEnumAsyncReadOnly, + FieldEnumAsyncWriteOnly, + FieldEnumAsyncReadWrite], lsb: int, msb: int, low: int, @@ -137,7 +141,7 @@ def _single_field_property_test(self, *, 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.max_value, (2 ** width) - 1) self.assertEqual(fut.is_volatile, is_volatile) if default is None: @@ -284,9 +288,9 @@ def _single_regfile_property_test(self, *, 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: + dut: Base, + rdl_name: Optional[str], + rdl_desc: Optional[str]) -> None: """ Check the SystemRDL Name and Desc properties for a node """ @@ -302,8 +306,8 @@ def __single_node_rdl_name_and_desc_test(self, def __test_node_inst_name(self, dut: Base, - parent_full_inst_name:Optional[str], - inst_name:str) -> None: + parent_full_inst_name: Optional[str], + inst_name: str) -> None: """ Test the `inst_name` and `full_inst_name` attributes of a node """ @@ -337,11 +341,11 @@ def __test_name_map(self, dut: Node, child_names: set[str]) -> None: def _test_field_iterators(self, *, rut: Union[RegReadOnly, - RegReadWrite, - RegWriteOnly, - RegAsyncReadOnly, - RegAsyncReadWrite, - RegAsyncWriteOnly], + RegReadWrite, + RegWriteOnly, + RegAsyncReadOnly, + RegAsyncReadWrite, + RegAsyncWriteOnly], has_sw_readable: bool, has_sw_writable: bool, readable_fields: set[str], @@ -354,7 +358,7 @@ def _test_field_iterators(self, *, )): raise TypeError(f'Register was expected to readable, got {type(rut)}') - child_readable_field_names = { field.inst_name for field in rut.readable_fields} + child_readable_field_names = {field.inst_name for field in rut.readable_fields} self.assertEqual(readable_fields, child_readable_field_names) else: @@ -382,16 +386,16 @@ def _test_field_iterators(self, *, 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) + 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], + MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy, + MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], readable_registers: NodeIterators, writeable_registers: NodeIterators) -> None: @@ -400,8 +404,8 @@ def _test_register_iterators(self, MemoryReadWrite, MemoryReadWriteLegacy, MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): - child_readable_reg_names = { reg.inst_name for reg in - dut.get_readable_registers(unroll=True)} + 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)} @@ -443,7 +447,6 @@ def _test_register_iterators(self, child_names=readable_registers.rolled | writeable_registers.rolled) - def _test_memory_iterators(self, dut: Union[AddressMap, AsyncAddressMap], memories: NodeIterators) -> None: @@ -494,3 +497,56 @@ def _test_regfile_iterators(self, 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 index e65d6431..2a1e6a55 100644 --- a/src/peakrdl_python/lib_test/async_reg_base_test_class.py +++ b/src/peakrdl_python/lib_test/async_reg_base_test_class.py @@ -24,7 +24,7 @@ import unittest from abc import ABC, abstractmethod -from typing import Union +from typing import Union, Optional from unittest.mock import patch, Mock, call from itertools import product, chain, combinations from collections.abc import Iterable @@ -299,7 +299,7 @@ async def __single_enum_field_write_test(self, async def _single_enum_field_read_and_write_test( self, fut: Union[FieldEnumAsyncReadOnly, FieldEnumAsyncReadOnly, FieldEnumAsyncWriteOnly], - enum_definition: dict[str, int], + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]], is_sw_readable: bool, is_sw_writable: bool) -> None: """ @@ -314,6 +314,10 @@ async def _single_enum_field_read_and_write_test( 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') diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index f89e4f8e..97d9171c 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -22,7 +22,7 @@ Python tool. It provide the base class for the autogenerated tests """ from abc import ABC, abstractmethod -from typing import Union +from typing import Union, Optional from unittest.mock import patch, Mock, call from itertools import product, chain, combinations from collections.abc import Iterable @@ -292,7 +292,7 @@ def __single_enum_field_write_test(self, def _single_enum_field_read_and_write_test( self, fut: Union[FieldEnumReadOnly, FieldEnumReadOnly, FieldEnumWriteOnly], - enum_definition: dict[str, int], + full_enum_def: dict[str, tuple[int, Optional[str], Optional[str]]], is_sw_readable: bool, is_sw_writable: bool) -> None: """ @@ -307,6 +307,10 @@ def _single_enum_field_read_and_write_test( 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') diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index 8f6c837d..e7e20586 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -82,31 +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_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: """ @@ -188,7 +164,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 %} {% 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}}, enum_definition={ {% for enum_entry in node.get_property('encode') %}'{{enum_entry.name.upper()}}':{{enum_entry.value}},{% endfor %} }) + 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 %}None{% else %}{{enum_entry.rdl_name | tojson}}{% endif %}, {% if enum_entry.rdl_desc is none %}None{% else %}{{enum_entry.rdl_desc | tojson}}{% endif %}),{% endfor %} }) {%- endif %} {%- endfor %} From f4eee76741f45ca3f813e4e64332aa43646ac1cc Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:56:12 +0000 Subject: [PATCH 79/80] Fix a problem with the legacy enum type not generating the correct test vectors --- src/peakrdl_python/templates/addrmap_tb.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peakrdl_python/templates/addrmap_tb.py.jinja b/src/peakrdl_python/templates/addrmap_tb.py.jinja index e7e20586..426cd1d7 100644 --- a/src/peakrdl_python/templates/addrmap_tb.py.jinja +++ b/src/peakrdl_python/templates/addrmap_tb.py.jinja @@ -164,7 +164,7 @@ class {{fq_block_name}}_single_access({{top_node.inst_name}}_TestCase): # type: {% 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 %} {% 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 %}None{% else %}{{enum_entry.rdl_name | tojson}}{% endif %}, {% if enum_entry.rdl_desc is none %}None{% else %}{{enum_entry.rdl_desc | tojson}}{% endif %}),{% endfor %} }) + 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 %} From 307c5d5c43d0777e2e6004a0687e9d873994b97c Mon Sep 17 00:00:00 2001 From: krcb197 <34693973+krcb197@users.noreply.github.com> Date: Tue, 9 Dec 2025 08:48:35 +0000 Subject: [PATCH 80/80] Remove the `decode_read_value` and `encode_write_value` public API. closes #184 --- src/peakrdl_python/__about__.py | 2 +- src/peakrdl_python/lib/base_field.py | 45 ---------------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/src/peakrdl_python/__about__.py b/src/peakrdl_python/__about__.py index 43013e05..51a469d4 100644 --- a/src/peakrdl_python/__about__.py +++ b/src/peakrdl_python/__about__.py @@ -17,4 +17,4 @@ Variables that describes the peakrdl-python Package """ -__version__ = "3.0.0rc7" +__version__ = "3.0.0rc8" 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