Skip to content

Commit acc0041

Browse files
authored
Merge pull request #91 from krcb197/enum_test_cases
Enhanced Enum support
2 parents e606139 + 803a73a commit acc0041

15 files changed

Lines changed: 877 additions & 658 deletions

.github/workflows/action.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ jobs:
7777
python -m pip install --upgrade pip
7878
python -m pip install .
7979
80-
python -m pip install pytest
8180
python -m pip install mypy
8281
8382
@@ -89,7 +88,7 @@ jobs:
8988
mypy testcase_output --config-file=tests/.mypy.ini
9089
# pylint --rcfile tests/pylint.rc testcase_output/autopep8 --disable=duplicate-code,line-too-long,too-many-statements,invalid-name,unused-import,too-many-instance-attributes,too-many-arguments,too-many-lines
9190
92-
pytest testcase_output
91+
python -m unittest discover -s testcase_output
9392
9493
- name: Backward Compatibility Test
9594
run: |
@@ -101,11 +100,11 @@ jobs:
101100
102101
peakrdl python tests/testcases/basic.rdl -o peakrdl_out/raw/
103102
peakrdl python tests/testcases/simple.xml tests/testcases/multifile.rdl -o peakrdl_out/raw
104-
pytest peakrdl_out/raw
103+
python -m unittest discover -s peakrdl_out/raw
105104
peakrdl python tests/testcases/basic.rdl -o peakrdl_out/autopep8/ --autoformat
106-
pytest peakrdl_out/autopep8
105+
python -m unittest discover -s peakrdl_out/autopep8
107106
peakrdl python tests/testcases/basic.rdl -o peakrdl_out/raw_async/ --async
108-
pytest peakrdl_out/raw_async
107+
python -m unittest discover -s peakrdl_out/raw_async
109108
110109
peakrdl python tests/testcases/basic.rdl -o peakrdl_out/no_test/ --skip_test_case_generation
111110

generate_testcases.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
if testcase_name == 'multifile':
2525
# this needs the simple.xml file included
2626
root = pp.compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'simple.xml')])
27+
elif testcase_name == 'multi_block':
28+
# this needs the simple.xml file included
29+
root = pp.compile_rdl(rdl_file, ipxact_files=[os.path.join(test_case_path, 'block_a.xml'),
30+
os.path.join(test_case_path, 'block_b.xml')])
2731
else:
2832
root = pp.compile_rdl(rdl_file)
2933

src/peakrdl_python/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""
22
Variables that describes the PeakRDL Python Package
33
"""
4-
__version__ = "0.4.1"
4+
__version__ = "0.4.2"

src/peakrdl_python/exporter.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
from pathlib import Path
66
from shutil import copyfile
7-
from typing import List, NoReturn
7+
from typing import List, NoReturn, Iterable, Tuple
88
from glob import glob
99

1010
import autopep8 # type: ignore
@@ -13,15 +13,16 @@
1313
from systemrdl.node import RootNode, Node, RegNode, AddrmapNode, RegfileNode # type: ignore
1414
from systemrdl.node import FieldNode, MemNode, AddressableNode # type: ignore
1515
from systemrdl.node import SignalNode # type: ignore
16-
from systemrdl.rdltypes import OnReadType, OnWriteType, PropertyReference # type: ignore
16+
from systemrdl.rdltypes import OnReadType, OnWriteType, PropertyReference # type: ignore
17+
from systemrdl.rdltypes.user_enum import UserEnumMeta # type: ignore
1718

1819
from .systemrdl_node_utility_functions import get_reg_readable_fields, get_reg_writable_fields, \
19-
get_array_dim, get_table_block, get_dependent_enum, get_dependent_component, \
20+
get_array_dim, get_table_block, get_dependent_component, \
2021
get_field_bitmask_hex_string, get_field_inv_bitmask_hex_string, \
2122
get_field_max_value_hex_string, get_reg_max_value_hex_string, get_fully_qualified_type_name, \
22-
uses_enum, fully_qualified_enum_type, uses_memory, \
23+
uses_enum, uses_memory, \
2324
get_memory_max_entry_value_hex_string, get_memory_width_bytes, \
24-
get_field_default_value
25+
get_field_default_value, get_enum_values
2526

2627
from .lib import get_array_typecode
2728

@@ -147,8 +148,9 @@ def export(self, node: Node, path: str,
147148
'get_fully_qualified_type_name': self._lookup_type_name,
148149
'get_array_dim': get_array_dim,
149150
'get_dependent_component': get_dependent_component,
150-
'get_dependent_enum': get_dependent_enum,
151-
'get_fully_qualified_enum_type': fully_qualified_enum_type,
151+
'get_dependent_enum': self._get_dependent_enum,
152+
'get_enum_values': get_enum_values,
153+
'get_fully_qualified_enum_type': self._fully_qualified_enum_type,
152154
'get_field_bitmask_hex_string': get_field_bitmask_hex_string,
153155
'get_field_inv_bitmask_hex_string': get_field_inv_bitmask_hex_string,
154156
'get_field_max_value_hex_string': get_field_max_value_hex_string,
@@ -292,3 +294,57 @@ def _raise_template_error(self, message: str) -> NoReturn:
292294
293295
"""
294296
raise PythonExportTemplateError(message)
297+
298+
def _fully_qualified_enum_type(self,
299+
field_enum: UserEnumMeta,
300+
root_node: AddressableNode,
301+
owning_field: FieldNode) -> str:
302+
"""
303+
Returns the fully qualified class type name, for an enum
304+
"""
305+
if not hasattr(field_enum, '_parent_scope'):
306+
# this happens if the enum is has been declared in an IPXACT file
307+
# which is imported
308+
return self._lookup_type_name(owning_field) + '_' + field_enum.__name__
309+
310+
parent_scope = getattr(field_enum, '_parent_scope')
311+
312+
if parent_scope is None:
313+
# this happens if the enum is has been declared in an IPXACT file
314+
# which is imported
315+
return self._lookup_type_name(owning_field) + '_' + field_enum.__name__
316+
317+
if root_node.inst.original_def == parent_scope:
318+
return field_enum.__name__
319+
320+
dependent_components = get_dependent_component(root_node)
321+
322+
for component in dependent_components:
323+
if component.inst.original_def == parent_scope:
324+
return get_fully_qualified_type_name(component) + '_' + field_enum.__name__
325+
326+
raise RuntimeError('Failed to find parent node to reference')
327+
328+
def _get_dependent_enum(self, node: AddressableNode) -> \
329+
Iterable[Tuple[UserEnumMeta, FieldNode]]:
330+
"""
331+
iterable of enums which is used by a descendant of the input node,
332+
this list is de-duplicated
333+
334+
:param node: node to analysis
335+
:return: nodes that are dependent on the specified node
336+
"""
337+
enum_needed = []
338+
for child_node in node.descendants():
339+
if isinstance(child_node, FieldNode):
340+
if 'encode' in child_node.list_properties():
341+
# found an field with an enumeration
342+
343+
field_enum = child_node.get_property('encode')
344+
fully_qualified_enum_name = self._fully_qualified_enum_type(field_enum,
345+
node,
346+
child_node)
347+
348+
if fully_qualified_enum_name not in enum_needed:
349+
enum_needed.append(fully_qualified_enum_name)
350+
yield field_enum, child_node

src/peakrdl_python/lib/fields.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,16 @@ def enum_cls(self) -> EnumMeta:
796796
The enumeration class for this field
797797
"""
798798

799+
@property
800+
def _enum_values(self) -> List[int]:
801+
"""
802+
checks whether the default value is within the legal range for the enum
803+
804+
Returns:
805+
806+
"""
807+
return [e.value for e in self.enum_cls] # type: ignore[var-annotated]
808+
799809

800810
class FieldEnumReadWrite(FieldReadWrite, FieldEnum, ABC):
801811
"""

src/peakrdl_python/systemrdl_node_utility_functions.py

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
A set of utility functions that perform supplementary processing on a node in a compiled
33
system RDL dataset.
44
"""
5-
from typing import Iterable, Optional
5+
from typing import Iterable, Optional, List
66

77
import textwrap
88

99
from systemrdl.node import Node, RegNode # type: ignore
1010
from systemrdl.node import FieldNode, AddressableNode # type: ignore
1111
from systemrdl.node import MemNode # type: ignore
1212
from systemrdl.node import SignalNode # type: ignore
13-
from systemrdl.rdltypes import UserEnum # type: ignore
13+
from systemrdl.rdltypes.user_enum import UserEnumMeta # type: ignore
1414

1515
def get_fully_qualified_type_name(node: Node) -> str:
1616
"""
@@ -63,52 +63,6 @@ def get_dependent_component(node: AddressableNode) -> Iterable[Node]:
6363

6464
yield child_node
6565

66-
67-
def get_dependent_enum(node: AddressableNode) -> Iterable[FieldNode]:
68-
"""
69-
iterable of enums which is used by a descendant of the input node,
70-
this list is de-duplicated
71-
72-
:param node: node to analysis
73-
:return: nodes that are dependent on the specified node
74-
"""
75-
enum_needed = []
76-
for child_node in node.descendants():
77-
if isinstance(child_node, FieldNode):
78-
if 'encode' in child_node.list_properties():
79-
# found an field with an enumeration
80-
81-
field_enum = child_node.get_property('encode')
82-
fully_qualified_enum_name = fully_qualified_enum_type(field_enum, node)
83-
84-
if fully_qualified_enum_name not in enum_needed:
85-
enum_needed.append(fully_qualified_enum_name)
86-
yield field_enum
87-
88-
89-
def fully_qualified_enum_type(field_enum: UserEnum, root_node: AddressableNode) -> str:
90-
"""
91-
Returns the fully qualified class type name, for an enum
92-
"""
93-
if not hasattr(field_enum, '_parent_scope'):
94-
# this happens if the enum is has been declared in an IPXACT file
95-
# which is imported
96-
return field_enum.__name__
97-
98-
parent_scope = getattr(field_enum, '_parent_scope')
99-
100-
if root_node.inst.original_def == parent_scope:
101-
return field_enum.__name__
102-
103-
dependent_components = get_dependent_component(root_node)
104-
105-
for component in dependent_components:
106-
if component.inst.original_def == parent_scope:
107-
return get_fully_qualified_type_name(component) + '_' + field_enum.__name__
108-
109-
raise RuntimeError('Failed to find parent node to reference')
110-
111-
11266
def get_table_block(node: Node) -> str:
11367
"""
11468
Converts the documentation for a systemRDL node into a nicely formated table that can be
@@ -358,13 +312,16 @@ def get_field_default_value(node: FieldNode) -> Optional[int]:
358312
None if the field is not reset or if the reset value is a signal that can be in an unknown
359313
state
360314
"""
315+
if not isinstance(node, FieldNode):
316+
raise TypeError(f'node is not a {type(FieldNode)} got {type(node)}')
361317

362318
value = node.get_property('reset')
363319

364320
if value is None:
365321
return None
366322

367323
if isinstance(value, int):
324+
368325
return value
369326

370327
if isinstance(value, (FieldNode, SignalNode)):
@@ -373,3 +330,18 @@ def get_field_default_value(node: FieldNode) -> Optional[int]:
373330
return None
374331

375332
raise TypeError(f'unhandled type for field default type={type(value)}')
333+
334+
335+
def get_enum_values(enum: UserEnumMeta) -> List[int]:
336+
"""
337+
338+
Args:
339+
enum: a field enum
340+
341+
Returns: A list of all the values for an enum
342+
343+
"""
344+
if not isinstance(enum, UserEnumMeta):
345+
raise TypeError(f'node is not a {type(UserEnumMeta)} got {type(enum)}')
346+
347+
return [e.value for e in enum]

src/peakrdl_python/templates/addrmap.py.jinja

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ from typing import AsyncGenerator
1212
{% else %}
1313
from typing import Generator
1414
{% endif %}
15+
import warnings
1516

1617

1718
from contextlib import {% if asyncoutput %}async{% endif %}contextmanager
@@ -357,9 +358,9 @@ class {{get_fully_qualified_type_name(node)}}_array_cls(AddressMapArray):
357358

358359
{% if uses_enum %}
359360
# root level enum definitions
360-
{%- for enum_needed in get_dependent_enum(top_node.parent) %}
361+
{%- for enum_needed, owning_field in get_dependent_enum(top_node.parent) %}
361362
@unique
362-
class {{get_fully_qualified_enum_type(enum_needed, top_node.parent)}}_enumcls(IntEnum):
363+
class {{get_fully_qualified_enum_type(enum_needed, top_node.parent, owning_field)}}_enumcls(IntEnum):
363364

364365
{% for value_of_enum_needed in enum_needed -%}
365366
{{ value_of_enum_needed.name.upper() }} = {{ value_of_enum_needed.value }} {%- if value_of_enum_needed.rdl_desc is not none -%}# {{ value_of_enum_needed.rdl_desc }} {%- endif %}

src/peakrdl_python/templates/addrmap_field.py.jinja

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class {{get_fully_qualified_type_name(node)}}_cls(Field{%- if 'encode' in node.l
1313
"""
1414

1515
{%- if 'encode' in node.list_properties() %}
16-
__enum_cls = {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls
16+
__enum_cls = {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls
1717
{% endif %}
1818

1919
__slots__ : List[str] = []
@@ -27,7 +27,7 @@ class {{get_fully_qualified_type_name(node)}}_cls(Field{%- if 'encode' in node.l
2727
return self.__enum_cls
2828

2929
{% if node.is_sw_readable %}
30-
def decode_read_value(self, value: int) -> {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls:
30+
def decode_read_value(self, value: int) -> {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls:
3131
"""
3232
extracts the field value from a register value, by applying the bit
3333
mask and shift needed and conversion to the enum associated with the
@@ -46,7 +46,7 @@ class {{get_fully_qualified_type_name(node)}}_cls(Field{%- if 'encode' in node.l
4646

4747
return self.enum_cls(field_value)
4848

49-
{% if asyncoutput %}async {% endif %}def read(self) -> {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls:
49+
{% if asyncoutput %}async {% endif %}def read(self) -> {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls:
5050
"""
5151
read the register and then perform the necessary actions, to report the
5252
value as the enumeration including:
@@ -63,23 +63,23 @@ class {{get_fully_qualified_type_name(node)}}_cls(Field{%- if 'encode' in node.l
6363
{% endif %}
6464

6565
{% if node.is_sw_writable %}
66-
def encode_write_value(self, value: {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls) -> int: # type: ignore[override]
66+
def encode_write_value(self, value: {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls) -> int: # type: ignore[override]
6767

6868
if not isinstance(value, self.enum_cls):
69-
raise TypeError('value must be an {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls but got %s' % type(value))
69+
raise TypeError('value must be an {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls but got %s' % type(value))
7070

7171
return super().encode_write_value(value.value)
7272

73-
{% if asyncoutput %}async {% endif %}def write(self, value : {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls) -> None: # type: ignore[override]
73+
{% if asyncoutput %}async {% endif %}def write(self, value : {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls) -> None: # type: ignore[override]
7474

7575
if not isinstance(value, self.enum_cls):
76-
raise TypeError('value must be an {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls but got %s' % type(value))
76+
raise TypeError('value must be an {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls but got %s' % type(value))
7777

7878
{% if asyncoutput %}await {% endif %}super().write(value.value)
7979
{% endif %}
8080

8181
@property
82-
def default(self) -> Optional[{{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls]:
82+
def default(self) -> Optional[{{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls]:
8383
"""
8484
The default value of the field
8585

@@ -90,7 +90,13 @@ class {{get_fully_qualified_type_name(node)}}_cls(Field{%- if 'encode' in node.l
9090
int_default = super().default
9191

9292
if int_default is not None:
93-
return {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent)}}_enumcls(int_default)
93+
if int_default in self._enum_values:
94+
return {{get_fully_qualified_enum_type(node.get_property('encode'), top_node.parent, node)}}_enumcls(int_default)
95+
else:
96+
msg = f'reset value {int_default:d} is not within the enumeration for the class'
97+
self._logger.warn(msg)
98+
warnings.warn(msg)
99+
return None
94100

95101
return None
96102
{% endif %}

src/peakrdl_python/templates/addrmap_register.py.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class {{get_fully_qualified_type_name(node)}}_cls(Reg{% if asyncoutput %}Async{%
130130

131131

132132
{% else %}
133-
{% if asyncoutput %}async {% endif %}def write_fields(self, {%- for child_node in node.fields() -%} {{safe_node_name(child_node)}} : {%- if 'encode' in child_node.list_properties() %}{{get_fully_qualified_enum_type(child_node.get_property('encode'), top_node.parent)}}_enumcls{% else %}int{% endif %}{%- if not loop.last -%},{%- endif -%}{%- endfor -%}) -> None: # type: ignore[override]
133+
{% if asyncoutput %}async {% endif %}def write_fields(self, {%- for child_node in node.fields() -%} {{safe_node_name(child_node)}} : {%- if 'encode' in child_node.list_properties() %}{{get_fully_qualified_enum_type(child_node.get_property('encode'), top_node.parent, child_node)}}_enumcls{% else %}int{% endif %}{%- if not loop.last -%},{%- endif -%}{%- endfor -%}) -> None: # type: ignore[override]
134134
"""
135135
Do a write to the register, updating all fields
136136
"""

0 commit comments

Comments
 (0)