2525import unittest
2626from abc import ABC
2727from typing import Union
28- import random
2928from unittest .mock import patch
3029from itertools import product
3130
3231from ..lib import FieldAsyncReadOnly , FieldAsyncWriteOnly , FieldAsyncReadWrite
32+ from ..lib import FieldEnumAsyncReadOnly , FieldEnumAsyncWriteOnly , FieldEnumAsyncReadWrite
3333from ..sim_lib .dummy_callbacks import async_dummy_read
3434from ..sim_lib .dummy_callbacks import async_dummy_write
3535
3636
3737from .utilities import reverse_bits , expected_reg_write_data
38+ from .utilities import reg_value_for_field_read_with_random_base
39+ from .utilities import random_field_value , random_field_parent_reg_value
3840
3941class AsyncLibTestBase (unittest .IsolatedAsyncioTestCase , ABC ):
4042 """
@@ -119,14 +121,14 @@ async def __single_field_read_test(
119121
120122 # read back - random value
121123 read_callback_mock .reset_mock ()
122- random_value = random . randrange ( 0 , fut . parent_register . max_value + 1 )
124+ random_value = random_field_parent_reg_value ( fut )
123125 read_callback_mock .return_value = random_value
124- random_field_value = (random_value & fut .bitmask ) >> fut .low
126+ field_value = (random_value & fut .bitmask ) >> fut .low
125127 if fut .msb == fut .high :
126- self .assertEqual (await fut .read (), random_field_value )
128+ self .assertEqual (await fut .read (), field_value )
127129 else :
128130 self .assertEqual (await fut .read (),
129- reverse_bits (value = random_field_value , number_bits = fut .width ))
131+ reverse_bits (value = field_value , number_bits = fut .width ))
130132 read_callback_mock .assert_called_once_with (
131133 addr = fut .parent_register .address ,
132134 width = fut .parent_register .width ,
@@ -146,13 +148,9 @@ async def __single_field_write_test(
146148 # pylint:disable-next=protected-access
147149 readable_reg = fut .parent_register ._is_readable
148150
149-
150-
151- random_reg_value = random .randrange (0 , fut .parent_register .max_value + 1 )
152- random_field_value = random .randrange (0 , fut .max_value + 1 )
153151 for reg_base_value , field_value in product (
154- [0 , fut .parent_register .max_value , random_reg_value ],
155- [0 , fut .max_value , random_field_value ]):
152+ [0 , fut .parent_register .max_value , random_field_parent_reg_value ( fut ) ],
153+ [0 , fut .max_value , random_field_value ( fut ) ]):
156154 read_callback_mock .reset_mock ()
157155 write_callback_mock .reset_mock ()
158156 read_callback_mock .return_value = reg_base_value
@@ -183,3 +181,125 @@ async def __single_field_write_test(
183181
184182 with self .assertRaises (ValueError ):
185183 await fut .write (- 1 )
184+
185+ async def __single_enum_field_read_test (self ,
186+ fut : Union [FieldEnumAsyncReadOnly , FieldEnumAsyncReadOnly ],
187+ enum_definition : dict [str , int ],
188+ ) -> None :
189+
190+ # pylint does not realise this is a class being returned rather than an object, so
191+ # is unhappy with the name
192+ #pylint:disable-next=invalid-name
193+ EnumCls = fut .enum_cls
194+
195+ with patch .object (self , 'write_callback' ) as write_callback_mock , \
196+ patch .object (self , 'read_callback' , return_value = 0 ) as read_callback_mock :
197+
198+ # read back - each legal enum value
199+ for enum_name , enum_value in enum_definition .items ():
200+ read_callback_mock .reset_mock ()
201+ reg_value = reg_value_for_field_read_with_random_base (fut = fut ,
202+ field_value = enum_value )
203+ read_callback_mock .return_value = reg_value
204+ self .assertEqual (await fut .read (), EnumCls [enum_name ])
205+ read_callback_mock .assert_called_once_with (
206+ addr = fut .parent_register .address ,
207+ width = fut .parent_register .width ,
208+ accesswidth = fut .parent_register .accesswidth )
209+
210+ # check register values that don't map to a legal enum value create an exception
211+ # this check is only relevant if there are potential field values that do not map to
212+ # the enum
213+ if len (enum_definition ) < (2 ** fut .width ):
214+ # there are two versions of this:
215+ # 1) for small fields (up to 8 bit wide) every value is tested
216+ # 2) for large fields (typically occurring with a sparse enum 100 values are
217+ # checked
218+ legal_enum_values_set = set (enum_definition .values ())
219+ if fut .width <= 8 :
220+ bad_field_value_iter = set (range (fut .max_value + 1 ))
221+ else :
222+ bad_field_value_iter = {random_field_value (fut ) for _ in range (100 )}
223+
224+ for bad_field_value in bad_field_value_iter - legal_enum_values_set :
225+ read_callback_mock .reset_mock ()
226+ reg_value = reg_value_for_field_read_with_random_base (
227+ fut = fut ,
228+ field_value = bad_field_value )
229+ read_callback_mock .return_value = reg_value
230+ with self .assertRaises (ValueError ):
231+ _ = await fut .read ()
232+ read_callback_mock .assert_called_once_with (
233+ addr = fut .parent_register .address ,
234+ width = fut .parent_register .width ,
235+ accesswidth = fut .parent_register .accesswidth )
236+
237+ # at the end of the read tests the write should not have been called
238+ write_callback_mock .assert_not_called ()
239+
240+ async def __single_enum_field_write_test (self ,
241+ fut : Union [FieldEnumAsyncWriteOnly , FieldEnumAsyncReadWrite ],
242+ enum_definition : dict [str , int ]) -> None :
243+ # pylint does not realise this is a class being returned rather than an object, so
244+ # is unhappy with the name
245+ # pylint:disable-next=invalid-name
246+ EnumCls = fut .enum_cls
247+
248+ # pylint:disable-next=protected-access
249+ readable_reg = fut .parent_register ._is_readable
250+
251+ with patch .object (self , 'write_callback' ) as write_callback_mock , \
252+ patch .object (self , 'read_callback' , return_value = 0 ) as read_callback_mock :
253+
254+ for enum_name , enum_value in enum_definition .items ():
255+ random_reg_value = random_field_parent_reg_value (fut )
256+ read_callback_mock .return_value = random_reg_value
257+ await fut .write (EnumCls [enum_name ])
258+
259+ # the read is skipped if the register is not readable or has the same width
260+ # as the field
261+ if (fut .width < fut .parent_register .width ) and readable_reg :
262+ read_callback_mock .assert_called_once_with (
263+ addr = fut .parent_register .address ,
264+ width = fut .parent_register .width ,
265+ accesswidth = fut .parent_register .accesswidth )
266+ else :
267+ read_callback_mock .assert_not_called ()
268+
269+ write_callback_mock .assert_called_once_with (
270+ addr = fut .parent_register .address ,
271+ width = fut .parent_register .width ,
272+ accesswidth = fut .parent_register .accesswidth ,
273+ data = expected_reg_write_data (fut = fut ,
274+ reg_base_value = random_reg_value ,
275+ readable_reg = readable_reg ,
276+ field_value = enum_value ))
277+
278+ read_callback_mock .reset_mock ()
279+ write_callback_mock .reset_mock ()
280+
281+ async def _single_enum_field_read_and_write_test (
282+ self ,
283+ fut : Union [FieldEnumAsyncReadOnly , FieldEnumAsyncReadOnly , FieldEnumAsyncWriteOnly ],
284+ enum_definition : dict [str , int ],
285+ is_sw_readable : bool ,
286+ is_sw_writable : bool ) -> None :
287+ """
288+ Check the ability to read and write to integer (non-encoded) field
289+ """
290+
291+ # the lsb, msb, high, low, bitmask, inv_bitmask and max_value are all checked as part of
292+ # `test_field_properties` so do not need to checked here. Similarly, the properties of
293+ # parent register are checked as part of `test_register_properties`
294+
295+ if is_sw_readable :
296+ if not isinstance (fut , (FieldEnumAsyncReadOnly , FieldEnumAsyncReadWrite )):
297+ raise TypeError ('Test can not proceed as the fut is not a readable field' )
298+ await self .__single_enum_field_read_test (fut = fut ,
299+ enum_definition = enum_definition )
300+
301+ if is_sw_writable :
302+ if not isinstance (fut , (FieldEnumAsyncWriteOnly , FieldEnumAsyncReadWrite )):
303+ raise TypeError ('Test can not proceed as the fut is not a writable field' )
304+ await self .__single_enum_field_write_test (fut = fut ,
305+ enum_definition = enum_definition )
0 commit comments