Coverage for hdl_registers/field/enumeration.py: 100%

63 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-07 20:51 +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 

10# Local folder libraries 

11from .register_field import RegisterField 

12 

13 

14class EnumerationElement: 

15 """ 

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

17 within an enumeration type. 

18 

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. 

21 

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

23 Which would be a whole other concept. 

24 

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

26 """ 

27 

28 def __init__(self, name: str, value: int, description: str): 

29 self._name = name 

30 self._value = value 

31 self.description = description 

32 

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: 

39 

40 * Could cause name collisions with other elements. 

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

42 """ 

43 return self._name 

44 

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 

53 

54 def __repr__(self) -> str: 

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

56_name={self._name},\ 

57_value={self._value},\ 

58description={self.description},\ 

59)""" 

60 

61 

62class Enumeration(RegisterField): 

63 """ 

64 Used to represent an enumeration field in a register. 

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

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 num_bits = (num_elements - 1).bit_length() if num_elements > 1 else 1 

112 

113 return num_bits 

114 

115 @property 

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

117 """ 

118 Getter for elements. 

119 """ 

120 return self._elements 

121 

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

123 """ 

124 Get an enumeration element by name. 

125 

126 Arguments: 

127 name: The name of the element. 

128 

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 

135 

136 message = ( 

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

138 ) 

139 raise ValueError(message) 

140 

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

142 """ 

143 Get an enumeration element by value. 

144 

145 Arguments: 

146 value: The value of the element. 

147 

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 

154 

155 message = ( 

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

157 ) 

158 raise ValueError(message) 

159 

160 @property # type: ignore[override] 

161 def default_value(self) -> EnumerationElement: 

162 """ 

163 Getter for ``default_value``. 

164 """ 

165 return self._default_value 

166 

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

168 """ 

169 Set the default value for this enumeration field. 

170 

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) 

175 

176 @property 

177 def default_value_uint(self) -> int: 

178 return self.default_value.value 

179 

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) 

188 

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) 

195 

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