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 2026b2fb..ee7683d2 100644 --- a/src/peakrdl_python/lib/memory.py +++ b/src/peakrdl_python/lib/memory.py @@ -99,6 +99,20 @@ def width(self) -> int: """ return self.__memwidth + @property + def max_entry_value(self) -> int: + """ + maximum unsigned integer value that can be stored in a memory entry + + For example: + + * 8-bit memory width returns 0xFF (255) + * 16-bit memory width returns 0xFFFF (65535) + * 32-bit memory width returns 0xFFFF_FFFF (4294967295) + + """ + return (2 ** self.width) - 1 + @property def width_in_bytes(self) -> int: """ @@ -505,6 +519,10 @@ def _write(self, start_entry: int, data: Union[Array, list[int]]) -> None: if not isinstance(data, (Array, list)): raise TypeError(f'data should be an List or array.array got {type(data)}') + if (max(data) > self.max_entry_value) or (min(data) < 0): + raise ValueError('Data out of range for memory must be in the ' + f'range 0 to {self.max_entry_value}') + if len(data) not in range(0, self.entries - start_entry + 1): raise ValueError(f'data length must be in range 0 to {self.entries - start_entry:d} ' f'but got {len(data):d}') diff --git a/src/peakrdl_python/lib/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/_common_base_test_class.py b/src/peakrdl_python/lib_test/_common_base_test_class.py index 8a3c9573..3a67d76b 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,26 @@ 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, + width: int, + entries: int, + accesswidth: Optional[int], + array_typecode: Optional[str]) -> None: + self.assertEqual(mut.address, address) + self.assertEqual(mut.width, width) + self.assertEqual(mut.entries, entries) + if accesswidth is not None: + self.assertEqual(mut.accesswidth, accesswidth) + else: + self.assertEqual(mut.accesswidth, width) + if self.legacy_block_access: + self.assertEqual(mut.array_typecode, array_typecode) + else: + self.assertIsNone(array_typecode) + def _single_node_rdl_name_and_desc_test(self, dut: Base, rdl_name: Optional[str], @@ -190,11 +218,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..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 @@ -25,17 +25,22 @@ 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 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 @@ -43,8 +48,12 @@ 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 + +# 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): """ @@ -897,3 +906,276 @@ 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) + + if is_sw_readable: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a readable memory') + await self.__single_memory_read_test(mut=mut) + await self.__single_memory_simulator_read_test(mut=mut) + else: + # test that a non-readable memory has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= await mut.read(0) # type: ignore[union-attr,call-arg] + + if is_sw_writable: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a writable memory') + await self.__single_memory_write_test(mut=mut) + await self.__single_memory_simulator_write_test(mut=mut) + else: + # test that a non-writable memory has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + await mut.write(0) # type: ignore[union-attr,call-arg] + + async def __single_memory_read_test( + self, + mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry read test + for entry, value in product( + [0, mut.entries-1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + read_callback_mock.return_value = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1),[value]) + + read_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth) + read_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + # the following needs to have the same parameters as the callback so has some unused + # args + # pylint:disable-next=unused-argument + def read_data_mock(addr: int, width: int, accesswidth: int) -> int: + mem_entry = (addr - mut.address) // mut.width_in_bytes + return rand_data_list[mem_entry] + read_callback_mock.side_effect = read_data_mock + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + write_callback_mock.assert_not_called() + + async def __single_memory_write_test( + self, + mut: Union[MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + # this function will simplify once all the legacy modes are removed later so we have + # allowed more branches for now + # pylint:disable=too-many-branches + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data = Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data = [value]) + + write_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=value) + write_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=rand_data_list) + + calls = [call(addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=rand_data_list[entry]) for entry in range(entries_to_test)] + write_callback_mock.assert_has_calls(calls, any_order=False) + + # check invalid write values bounce + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + # depending the hardware array sizes the error may be at the point the array is + # constructed on internal + dummy_array = Array(mut.array_typecode,[0]) + if dummy_array.itemsize > mut.width_in_bytes: + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + else: + with self.assertRaises(OverflowError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + with self.assertRaises(OverflowError): + await mut.write(start_entry=0, data=Array(mut.array_typecode, + [-1])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=[mut.max_entry_value + 1]) + with self.assertRaises(ValueError): + await mut.write(start_entry=0, data=[-1]) + + read_callback_mock.assert_not_called() + + async def __single_memory_simulator_read_test( + self, + mut: Union[MemoryAsyncReadOnly, MemoryAsyncReadOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy], + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry read test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + sim_memory.value[entry] = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=entry, number_entries=1), [value]) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + for entry in range(entries_to_test): + sim_memory.value[entry] = rand_data_list[entry] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncReadOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncReadOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(await mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + async def __single_memory_simulator_write_test( + self, + mut: Union[MemoryAsyncWriteOnly, MemoryAsyncWriteOnlyLegacy, + MemoryAsyncReadWrite, MemoryAsyncReadWriteLegacy] + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data=Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError( + f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=entry, data=[value]) + + self.assertEqual(sim_memory.value[entry], value) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryAsyncWriteOnlyLegacy, MemoryAsyncReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryAsyncWriteOnly, MemoryAsyncReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + await mut.write(start_entry=0, data=rand_data_list) + + for entry in range(entries_to_test): + self.assertEqual(sim_memory.value[entry], rand_data_list[entry]) diff --git a/src/peakrdl_python/lib_test/base_reg_test_class.py b/src/peakrdl_python/lib_test/base_reg_test_class.py index 7aa7ca84..f89e4f8e 100644 --- a/src/peakrdl_python/lib_test/base_reg_test_class.py +++ b/src/peakrdl_python/lib_test/base_reg_test_class.py @@ -23,17 +23,22 @@ """ 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 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 @@ -41,8 +46,13 @@ from .utilities import random_encoded_field_value, reg_value_for_field_read from .utilities import random_reg_value, RandomReg from .utilities import RegWriteTestSequence,RegWriteZeroStartTestSequence +from .utilities import random_memory_entry,random_memory_entry_value + +from ._common_base_test_class import CommonTestBase, NodeIterators + +# This module is planned to be split, see #272, for now the length is supressed +# pylint:disable=too-many-lines -from ._common_base_test_class import CommonTestBase class LibTestBase(CommonTestBase, ABC): """ @@ -887,3 +897,275 @@ 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) + + + + if is_sw_readable: + if not isinstance(mut, (MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a readable memory') + self.__single_memory_read_test(mut=mut) + self.__single_memory_simulator_read_test(mut=mut) + else: + # test that a non-readable memory has no read method and + # attempting one generates and error + with self.assertRaises(AttributeError): + _= mut.read(0) # type: ignore[union-attr,call-arg] + + if is_sw_writable: + if not isinstance(mut, (MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy)): + raise TypeError('Test can not proceed as the mut is not a writable memory') + self.__single_memory_write_test(mut=mut) + self.__single_memory_simulator_write_test(mut=mut) + else: + # test that a non-writable memory has no write method and + # attempting one generates and error + with self.assertRaises(AttributeError): + mut.write(0) # type: ignore[union-attr,call-arg] + + def __single_memory_read_test( + self, + mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry read test + for entry, value in product( + [0, mut.entries-1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + read_callback_mock.return_value = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1),[value]) + + read_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth) + read_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + # the following needs to have the same parameters as the callback so has some unused + # args + # pylint:disable-next=unused-argument + def read_data_mock(addr: int, width: int, accesswidth: int) -> int: + mem_entry = (addr - mut.address) // mut.width_in_bytes + return rand_data_list[mem_entry] + read_callback_mock.side_effect = read_data_mock + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + write_callback_mock.assert_not_called() + + def __single_memory_write_test( + self, + mut: Union[MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + # this function will simplify once all the legacy modes are removed later so we have + # allowed more branches for now + # pylint:disable=too-many-branches + + with patch.object(self, 'write_callback') as write_callback_mock, \ + patch.object(self, 'read_callback', return_value=0) as read_callback_mock: + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=entry, data = Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=entry, data = [value]) + + write_callback_mock.assert_called_once_with( + addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=value) + write_callback_mock.reset_mock() + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=0, data=rand_data_list) + + calls = [call(addr=mut.address + (entry * mut.width_in_bytes), + width=mut.width, + accesswidth=mut.accesswidth, + data=rand_data_list[entry]) for entry in range(entries_to_test)] + write_callback_mock.assert_has_calls(calls, any_order=False) + + # check invalid write values bounce + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + # depending the hardware array sizes the error may be at the point the array is + # constructed on internal + dummy_array = Array(mut.array_typecode, [0]) + if dummy_array.itemsize > mut.width_in_bytes: + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + else: + with self.assertRaises(OverflowError): + mut.write(start_entry=0, data=Array(mut.array_typecode, + [mut.max_entry_value + 1])) + with self.assertRaises(OverflowError): + mut.write(start_entry=0, data=Array(mut.array_typecode,[-1])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=[mut.max_entry_value + 1]) + with self.assertRaises(ValueError): + mut.write(start_entry=0, data=[-1]) + + read_callback_mock.assert_not_called() + + def __single_memory_simulator_read_test( + self, + mut: Union[MemoryReadOnly, MemoryReadOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy], + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry read test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + sim_memory.value[entry] = value + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), + Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=entry, number_entries=1), [value]) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + for entry in range(entries_to_test): + sim_memory.value[entry] = rand_data_list[entry] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryReadOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0,number_entries=entries_to_test), + Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryReadOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + self.assertEqual(mut.read(start_entry=0, number_entries=entries_to_test), + rand_data_list) + + def __single_memory_simulator_write_test( + self, + mut: Union[MemoryWriteOnly, MemoryWriteOnlyLegacy, + MemoryReadWrite, MemoryReadWriteLegacy] + ) -> None: + + sim_memory = self.simulator_instance.memory_by_full_name(mut.full_inst_name) + self.assertIsInstance(sim_memory, SimMemory) + + # single entry write test + for entry, value in product( + [0, mut.entries - 1, random_memory_entry(mut)], + [0, mut.max_entry_value, random_memory_entry_value(mut)]): + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=entry, data=Array(mut.array_typecode, [value])) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError( + f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=entry, data=[value]) + + self.assertEqual(sim_memory.value[entry], value) + + # multi-entry read + # check a multi-entry read, if the memory is small do the entire memory, however, if + # it is large limit the number of entries to 10 + entries_to_test = mut.entries if mut.entries < 10 else 10 + rand_data_list = [random_memory_entry_value(mut) for _ in range(entries_to_test)] + + if self.legacy_block_access: + if not isinstance(mut, (MemoryWriteOnlyLegacy, MemoryReadWriteLegacy)): + raise TypeError(f'Memory should be legacy type but got {type(mut)}') + mut.write(start_entry=0, data=Array(mut.array_typecode, rand_data_list)) + else: + if not isinstance(mut, (MemoryWriteOnly, MemoryReadWrite)): + raise TypeError(f'Memory should be non-legacy type but got {type(mut)}') + mut.write(start_entry=0, data=rand_data_list) + + for entry in range(entries_to_test): + self.assertEqual(sim_memory.value[entry], rand_data_list[entry]) diff --git a/src/peakrdl_python/lib_test/utilities.py b/src/peakrdl_python/lib_test/utilities.py 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/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..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 @@ -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 5b11f4a1..a845ab6c 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={% 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 %})) + {% 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_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: 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,