Coverage for hdl_registers/field/enumeration.py: 100%
62 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +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 See :ref:`field_enumeration` for details.
65 """
67 def __init__(
68 self,
69 name: str,
70 base_index: int,
71 description: str,
72 elements: dict[str, str],
73 default_value: str,
74 ) -> None:
75 """
76 Arguments:
77 name: The name of the register field.
78 base_index: The zero-based index within the register for the lowest bit of this field.
79 description: Textual field description.
80 elements: Dictionary mapping element names to their description.
81 default_value: The name of the element that shall be set as default.
82 """
83 self.name = name
84 self._base_index = base_index
85 self.description = description
87 # The number of elements decides the width of the field.
88 # Hence the user is not allowed to change the element set after initialization.
89 self._elements = []
91 if not elements:
92 message = f'Enumeration "{self.name}", must have at least one element.'
93 raise ValueError(message)
95 # The enumeration values are sequentially incremented starting from zero.
96 # This works because the 'value' field of the enumeration element is read-only.
97 # Note that dictionaries in Python are guaranteed ordered since version 3.7.
98 for element_index, (element_name, element_description) in enumerate(elements.items()):
99 element = EnumerationElement(
100 name=element_name, value=element_index, description=element_description
101 )
102 self._elements.append(element)
104 self._default_value = self._elements[0]
105 self.set_default_value(name=default_value)
107 self._width = self._calculate_width()
109 def _calculate_width(self) -> int:
110 num_elements = len(self._elements)
111 return (num_elements - 1).bit_length() if num_elements > 1 else 1
113 @property
114 def elements(self) -> list[EnumerationElement]:
115 """
116 Getter for elements.
117 """
118 return self._elements
120 def get_element_by_name(self, name: str) -> EnumerationElement:
121 """
122 Get an enumeration element by name.
124 Arguments:
125 name: The name of the element.
127 Return:
128 The enumeration element with the provided name.
129 """
130 for element in self._elements:
131 if element.name == name:
132 return element
134 message = (
135 f'Enumeration "{self.name}", requested element name does not exist. Got: "{name}".'
136 )
137 raise ValueError(message)
139 def get_element_by_value(self, value: int) -> EnumerationElement:
140 """
141 Get an enumeration element by value.
143 Arguments:
144 value: The value of the element.
146 Return:
147 The enumeration element with the provided value.
148 """
149 for element in self._elements:
150 if element.value == value:
151 return element
153 message = (
154 f'Enumeration "{self.name}", requested element value does not exist. Got: "{value}".'
155 )
156 raise ValueError(message)
158 @property
159 def default_value(self) -> EnumerationElement:
160 """
161 Getter for ``default_value``.
162 """
163 return self._default_value
165 def set_default_value(self, name: str) -> None:
166 """
167 Set the default value for this enumeration field.
169 Arguments:
170 name: The name of the enumeration element that shall be set as default.
171 """
172 self._default_value = self.get_element_by_name(name=name)
174 @property
175 def default_value_uint(self) -> int:
176 return self.default_value.value
178 def get_value(self, register_value: int) -> EnumerationElement:
179 """
180 See super method for details.
181 This subclass method uses a different type to represent the field value, and also
182 adds some sanity checks.
183 """
184 value_integer = super().get_value(register_value=register_value)
185 return self.get_element_by_value(value=value_integer)
187 def set_value(self, field_value: EnumerationElement) -> int:
188 """
189 See super method for details.
190 This subclass method uses a different type to represent the field value.
191 """
192 return super().set_value(field_value=field_value.value)
194 def __repr__(self) -> str:
195 return f"""{self.__class__.__name__}(\
196name={self.name},\
197_base_index={self._base_index},\
198description={self.description},\
199_elements={self._elements},\
200_default_value={self._default_value},\
201)"""