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

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# -------------------------------------------------------------------------------------------------- 

9 

10from .register_field import RegisterField 

11 

12 

13class EnumerationElement: 

14 """ 

15 Represent a single element, also known as a "member"/"enumerator"/"option"/"choice", 

16 within an enumeration type. 

17 

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. 

20 

21 We could also use Python enum class: https://docs.python.org/3/library/enum.html 

22 Which would be a whole other concept. 

23 

24 It is deemed more flexible to use a simple class for now. 

25 """ 

26 

27 def __init__(self, name: str, value: int, description: str) -> None: 

28 self._name = name 

29 self._value = value 

30 self.description = description 

31 

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: 

38 

39 * Could cause name collisions with other elements. 

40 * Could invalidate the currently selected default value of the field. 

41 """ 

42 return self._name 

43 

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 

52 

53 def __repr__(self) -> str: 

54 return f"""{self.__class__.__name__}(\ 

55_name={self._name},\ 

56_value={self._value},\ 

57description={self.description},\ 

58)""" 

59 

60 

61class Enumeration(RegisterField): 

62 """ 

63 Used to represent an enumeration field in a register. 

64 See :ref:`field_enumeration` for details. 

65 """ 

66 

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 

86 

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 = [] 

90 

91 if not elements: 

92 message = f'Enumeration "{self.name}", must have at least one element.' 

93 raise ValueError(message) 

94 

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) 

103 

104 self._default_value = self._elements[0] 

105 self.set_default_value(name=default_value) 

106 

107 self._width = self._calculate_width() 

108 

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 

112 

113 @property 

114 def elements(self) -> list[EnumerationElement]: 

115 """ 

116 Getter for elements. 

117 """ 

118 return self._elements 

119 

120 def get_element_by_name(self, name: str) -> EnumerationElement: 

121 """ 

122 Get an enumeration element by name. 

123 

124 Arguments: 

125 name: The name of the element. 

126 

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 

133 

134 message = ( 

135 f'Enumeration "{self.name}", requested element name does not exist. Got: "{name}".' 

136 ) 

137 raise ValueError(message) 

138 

139 def get_element_by_value(self, value: int) -> EnumerationElement: 

140 """ 

141 Get an enumeration element by value. 

142 

143 Arguments: 

144 value: The value of the element. 

145 

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 

152 

153 message = ( 

154 f'Enumeration "{self.name}", requested element value does not exist. Got: "{value}".' 

155 ) 

156 raise ValueError(message) 

157 

158 @property 

159 def default_value(self) -> EnumerationElement: 

160 """ 

161 Getter for ``default_value``. 

162 """ 

163 return self._default_value 

164 

165 def set_default_value(self, name: str) -> None: 

166 """ 

167 Set the default value for this enumeration field. 

168 

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) 

173 

174 @property 

175 def default_value_uint(self) -> int: 

176 return self.default_value.value 

177 

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) 

186 

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) 

193 

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)"""