Coverage for hdl_registers/field/enumeration.py: 100%
62 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl-registers project, an HDL register generator fast enough to run
5# in real time.
6# https://hdl-registers.com
7# https://github.com/hdl-registers/hdl-registers
8# --------------------------------------------------------------------------------------------------
10from .register_field import RegisterField
13class EnumerationElement:
14 """
15 Represent a single element, also known as a "member"/"enumerator"/"option"/"choice",
16 within an enumeration type.
18 Optionally we could use a python dataclass: https://docs.python.org/3/library/dataclasses.html
19 Would get the __repr__ method for free, but also a lot of other stuff that we do not need.
21 We could also use Python enum class: https://docs.python.org/3/library/enum.html
22 Which would be a whole other concept.
24 It is deemed more flexible to use a simple class for now.
25 """
27 def __init__(self, name: str, value: int, description: str) -> None:
28 self._name = name
29 self._value = value
30 self.description = description
32 @property
33 def name(self) -> str:
34 """
35 Getter for ``name``.
36 This member is read-only, since changing the name of an element poses some risks for
37 the functionality of the enumeration field:
39 * Could cause name collisions with other elements.
40 * Could invalidate the currently selected default value of the field.
41 """
42 return self._name
44 @property
45 def value(self) -> int:
46 """
47 Getter for ``value``.
48 This member is read-only, since changing the value of an element poses the risk of value
49 collisions with other elements in the enumeration field.
50 """
51 return self._value
53 def __repr__(self) -> str:
54 return f"""{self.__class__.__name__}(\
55_name={self._name},\
56_value={self._value},\
57description={self.description},\
58)"""
61class Enumeration(RegisterField):
62 """
63 Used to represent an enumeration field in a register.
64 """
66 def __init__(
67 self,
68 name: str,
69 base_index: int,
70 description: str,
71 elements: dict[str, str],
72 default_value: str,
73 ) -> None:
74 """
75 Arguments:
76 name: The name of the register field.
77 base_index: The zero-based index within the register for the lowest bit of this field.
78 description: Textual field description.
79 elements: Dictionary mapping element names to their description.
80 default_value: The name of the element that shall be set as default.
81 """
82 self.name = name
83 self._base_index = base_index
84 self.description = description
86 # The number of elements decides the width of the field.
87 # Hence the user is not allowed to change the element set after initialization.
88 self._elements = []
90 if not elements:
91 message = f'Enumeration "{self.name}", must have at least one element.'
92 raise ValueError(message)
94 # The enumeration values are sequentially incremented starting from zero.
95 # This works because the 'value' field of the enumeration element is read-only.
96 # Note that dictionaries in Python are guaranteed ordered since version 3.7.
97 for element_index, (element_name, element_description) in enumerate(elements.items()):
98 element = EnumerationElement(
99 name=element_name, value=element_index, description=element_description
100 )
101 self._elements.append(element)
103 self._default_value = self._elements[0]
104 self.set_default_value(name=default_value)
106 self._width = self._calculate_width()
108 def _calculate_width(self) -> int:
109 num_elements = len(self._elements)
110 return (num_elements - 1).bit_length() if num_elements > 1 else 1
112 @property
113 def elements(self) -> list[EnumerationElement]:
114 """
115 Getter for elements.
116 """
117 return self._elements
119 def get_element_by_name(self, name: str) -> EnumerationElement:
120 """
121 Get an enumeration element by name.
123 Arguments:
124 name: The name of the element.
126 Return:
127 The enumeration element with the provided name.
128 """
129 for element in self._elements:
130 if element.name == name:
131 return element
133 message = (
134 f'Enumeration "{self.name}", requested element name does not exist. Got: "{name}".'
135 )
136 raise ValueError(message)
138 def get_element_by_value(self, value: int) -> EnumerationElement:
139 """
140 Get an enumeration element by value.
142 Arguments:
143 value: The value of the element.
145 Return:
146 The enumeration element with the provided value.
147 """
148 for element in self._elements:
149 if element.value == value:
150 return element
152 message = (
153 f'Enumeration "{self.name}", requested element value does not exist. Got: "{value}".'
154 )
155 raise ValueError(message)
157 @property
158 def default_value(self) -> EnumerationElement:
159 """
160 Getter for ``default_value``.
161 """
162 return self._default_value
164 def set_default_value(self, name: str) -> None:
165 """
166 Set the default value for this enumeration field.
168 Arguments:
169 name: The name of the enumeration element that shall be set as default.
170 """
171 self._default_value = self.get_element_by_name(name=name)
173 @property
174 def default_value_uint(self) -> int:
175 return self.default_value.value
177 def get_value(self, register_value: int) -> EnumerationElement:
178 """
179 See super method for details.
180 This subclass method uses a different type to represent the field value, and also
181 adds some sanity checks.
182 """
183 value_integer = super().get_value(register_value=register_value)
184 return self.get_element_by_value(value=value_integer)
186 def set_value(self, field_value: EnumerationElement) -> int:
187 """
188 See super method for details.
189 This subclass method uses a different type to represent the field value.
190 """
191 return super().set_value(field_value=field_value.value)
193 def __repr__(self) -> str:
194 return f"""{self.__class__.__name__}(\
195name={self.name},\
196_base_index={self._base_index},\
197description={self.description},\
198_elements={self._elements},\
199_default_value={self._default_value},\
200)"""