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

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

65 

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 

85 

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

89 

90 if not elements: 

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

92 raise ValueError(message) 

93 

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) 

102 

103 self._default_value = self._elements[0] 

104 self.set_default_value(name=default_value) 

105 

106 self._width = self._calculate_width() 

107 

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 

111 

112 @property 

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

114 """ 

115 Getter for elements. 

116 """ 

117 return self._elements 

118 

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

120 """ 

121 Get an enumeration element by name. 

122 

123 Arguments: 

124 name: The name of the element. 

125 

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 

132 

133 message = ( 

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

135 ) 

136 raise ValueError(message) 

137 

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

139 """ 

140 Get an enumeration element by value. 

141 

142 Arguments: 

143 value: The value of the element. 

144 

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 

151 

152 message = ( 

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

154 ) 

155 raise ValueError(message) 

156 

157 @property 

158 def default_value(self) -> EnumerationElement: 

159 """ 

160 Getter for ``default_value``. 

161 """ 

162 return self._default_value 

163 

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

165 """ 

166 Set the default value for this enumeration field. 

167 

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) 

172 

173 @property 

174 def default_value_uint(self) -> int: 

175 return self.default_value.value 

176 

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) 

185 

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) 

192 

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