Coverage for hdl_registers/field/enumeration.py: 100%
63 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +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# --------------------------------------------------------------------------------------------------
10# Local folder libraries
11from .register_field import RegisterField
14class EnumerationElement:
15 """
16 Represent a single element, also known as a "member"/"enumerator"/"option"/"choice",
17 within an enumeration type.
19 Optionally we could use a python dataclass: https://docs.python.org/3/library/dataclasses.html
20 Would get the __repr__ method for free, but also a lot of other stuff that we do not need.
22 We could also use Python enum class: https://docs.python.org/3/library/enum.html
23 Which would be a whole other concept.
25 It is deemed more flexible to use a simple class for now.
26 """
28 def __init__(self, name: str, value: int, description: str):
29 self._name = name
30 self._value = value
31 self.description = description
33 @property
34 def name(self) -> str:
35 """
36 Getter for ``name``.
37 This member is read-only, since changing the name of an element poses some risks for
38 the functionality of the enumeration field:
40 * Could cause name collisions with other elements.
41 * Could invalidate the currently selected default value of the field.
42 """
43 return self._name
45 @property
46 def value(self) -> int:
47 """
48 Getter for ``value``.
49 This member is read-only, since changing the value of an element poses the risk of value
50 collisions with other elements in the enumeration field.
51 """
52 return self._value
54 def __repr__(self) -> str:
55 return f"""{self.__class__.__name__}(\
56_name={self._name},\
57_value={self._value},\
58description={self.description},\
59)"""
62class Enumeration(RegisterField):
63 """
64 Used to represent an enumeration field in a register.
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 ):
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 num_bits = (num_elements - 1).bit_length() if num_elements > 1 else 1
113 return num_bits
115 @property
116 def elements(self) -> list[EnumerationElement]:
117 """
118 Getter for elements.
119 """
120 return self._elements
122 def get_element_by_name(self, name: str) -> EnumerationElement:
123 """
124 Get an enumeration element by name.
126 Arguments:
127 name: The name of the element.
129 Return:
130 The enumeration element with the provided name.
131 """
132 for element in self._elements:
133 if element.name == name:
134 return element
136 message = (
137 f'Enumeration "{self.name}", requested element name does not exist. Got: "{name}".'
138 )
139 raise ValueError(message)
141 def get_element_by_value(self, value: int) -> EnumerationElement:
142 """
143 Get an enumeration element by value.
145 Arguments:
146 value: The value of the element.
148 Return:
149 The enumeration element with the provided value.
150 """
151 for element in self._elements:
152 if element.value == value:
153 return element
155 message = (
156 f'Enumeration "{self.name}", requested element value does not exist. Got: "{value}".'
157 )
158 raise ValueError(message)
160 @property # type: ignore[override]
161 def default_value(self) -> EnumerationElement:
162 """
163 Getter for ``default_value``.
164 """
165 return self._default_value
167 def set_default_value(self, name: str) -> None:
168 """
169 Set the default value for this enumeration field.
171 Arguments:
172 value: The name of the enumeration element that shall be set as default.
173 """
174 self._default_value = self.get_element_by_name(name=name)
176 @property
177 def default_value_uint(self) -> int:
178 return self.default_value.value
180 def get_value(self, register_value: int) -> EnumerationElement: # type: ignore[override]
181 """
182 See super method for details.
183 This subclass method uses a different type to represent the field value, and also
184 adds some sanity checks.
185 """
186 value_integer = super().get_value(register_value=register_value)
187 return self.get_element_by_value(value=value_integer)
189 def set_value(self, field_value: EnumerationElement) -> int: # type: ignore[override]
190 """
191 See super method for details.
192 This subclass method uses a different type to represent the field value.
193 """
194 return super().set_value(field_value=field_value.value)
196 def __repr__(self) -> str:
197 return f"""{self.__class__.__name__}(\
198name={self.name},\
199_base_index={self._base_index},\
200description={self.description},\
201_elements={self._elements},\
202_default_value={self._default_value},\
203)"""