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 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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 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 10/12] 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 11/12] 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 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 12/12] 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: