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

78 statements  

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

9 

10# Local folder libraries 

11from .numerical_interpretation import Signed, Unsigned 

12from .register_field import RegisterField 

13 

14 

15class Integer(RegisterField): # pylint: disable=too-many-instance-attributes 

16 """ 

17 Used to represent an integer field in a register. 

18 """ 

19 

20 def __init__( 

21 self, 

22 name: str, 

23 base_index: int, 

24 description: str, 

25 min_value: int, 

26 max_value: int, 

27 default_value: int, 

28 ): # pylint: disable=too-many-arguments 

29 """ 

30 Arguments: 

31 name: The name of the field. 

32 base_index: The zero-based index within the register for the lowest bit of this field. 

33 description: Textual field description. 

34 min_value: The minimum value that this field shall be able to represent. 

35 min_value: The maximum value that this field shall be able to represent. 

36 default_value: Default value. Must be within the specified range. 

37 """ 

38 self.name = name 

39 self._base_index = base_index 

40 self.description = description 

41 

42 # The min and max values determine the width of the field. 

43 # Hence the user is not allowed to change them after initialization. 

44 self._check_range(min_value=min_value, max_value=max_value) 

45 self._min_value = min_value 

46 self._max_value = max_value 

47 

48 self._width = self._calculate_width() 

49 

50 self._default_value = 0 

51 # Assign self._default_value via setter 

52 self.default_value = default_value 

53 

54 # Helper object to convert between unsigned binary and signed/unsigned integer. 

55 self._numerical_interpretation = ( 

56 Signed(bit_width=self._width) if min_value < 0 else Unsigned(self._width) 

57 ) 

58 

59 def _check_range(self, min_value: int, max_value: int) -> None: 

60 """ 

61 Perform some sanity checks on user-supplied values. 

62 Will raise exception if something is wrong. 

63 """ 

64 if not isinstance(min_value, int): 

65 message = ( 

66 f'Integer field "{self.name}" should have integer value for "min_value". ' 

67 f'Got: "{min_value}".' 

68 ) 

69 raise ValueError(message) 

70 

71 if not isinstance(max_value, int): 

72 message = ( 

73 f'Integer field "{self.name}" should have integer value for "max_value". ' 

74 f'Got: "{max_value}".' 

75 ) 

76 raise ValueError(message) 

77 

78 if min_value > max_value: 

79 message = ( 

80 f'Integer field "{self.name}" should have ascending range. ' 

81 f"Got: [{min_value}, {max_value}]." 

82 ) 

83 raise ValueError(message) 

84 

85 def _calculate_width(self) -> int: 

86 # Calculate the width based on the supplied numerical limits. 

87 error_message = ( 

88 f"Supplied integer range [{self._min_value}, {self._max_value}] does not fit " 

89 "in a register." 

90 ) 

91 

92 if self._min_value >= 0: 

93 # Calculate the number of bits needed to represent UNSIGNED numbers [0, max_value]. 

94 num_bits = self._max_value.bit_length() 

95 

96 if num_bits > 32: 

97 raise ValueError(error_message) 

98 

99 return num_bits 

100 

101 # Calculate the number of bits needed to represent SIGNED numbers [min_value, max_value]. 

102 for num_bits in range(1, 33): 

103 # Two's complement range for signed numbers. 

104 min_range = -(2 ** (num_bits - 1)) 

105 max_range = 2 ** (num_bits - 1) - 1 

106 

107 if self._min_value >= min_range and self._max_value <= max_range: 

108 return num_bits 

109 

110 raise ValueError(error_message) 

111 

112 @property 

113 def min_value(self) -> int: 

114 """ 

115 Minimum numeric value this field can assume. 

116 Getter for private member. 

117 """ 

118 # Note that it would be wrong to return 'self._numeric_interpretation.min_value'. 

119 # The range of an integer field is not necessarily the same as the total range 

120 # enabled by the bit width. 

121 return self._min_value 

122 

123 @property 

124 def max_value(self) -> int: 

125 """ 

126 Maximum numeric value this field can assume. 

127 Getter for private member. 

128 """ 

129 # Same comment as for 'min_value'. 

130 return self._max_value 

131 

132 @property 

133 def is_signed(self) -> bool: 

134 """ 

135 Is the field signed (two's complement)? 

136 Getter for private member. 

137 """ 

138 return self._numerical_interpretation.is_signed 

139 

140 @property # type: ignore[override] 

141 def default_value(self) -> int: 

142 """ 

143 Getter for private member. 

144 """ 

145 return self._default_value 

146 

147 @default_value.setter 

148 def default_value(self, value: int) -> None: 

149 """ 

150 Setter for default_value that performs sanity checks. 

151 """ 

152 if not isinstance(value, int): 

153 message = ( 

154 f'Integer field "{self.name}" should have integer value for "default_value". ' 

155 f'Got: "{value}".' 

156 ) 

157 raise ValueError(message) 

158 

159 if value < self.min_value or value > self.max_value: 

160 message = ( 

161 f'Integer field "{self.name}" should have "default_value" within range ' 

162 f'[{self.min_value}, {self.max_value}]. Got: "{value}".' 

163 ) 

164 raise ValueError(message) 

165 

166 self._default_value = value 

167 

168 @property 

169 def default_value_uint(self) -> int: 

170 if self.default_value >= 0: 

171 return self.default_value 

172 

173 assert self.is_signed, "Should not end up here unless signed." 

174 

175 # Offset the sign bit. 

176 result: int = self.default_value + 2**self.width 

177 

178 return result 

179 

180 def get_value(self, register_value: int) -> int: 

181 """ 

182 See super method for details. 

183 Adds signed/unsigned logic, and sanity checks of the value. 

184 """ 

185 unsigned_value = super().get_value(register_value=register_value) 

186 # We know that this is an integer (not a float) since there are no fractional bits set. 

187 result: int = self._numerical_interpretation.convert_from_unsigned_binary( 

188 unsigned_binary=unsigned_value 

189 ) 

190 

191 if self.min_value <= result <= self.max_value: 

192 return result 

193 

194 raise ValueError( 

195 f'Register field value "{result}" not inside "{self.name}" field\'s ' 

196 f"legal range: ({self.min_value}, {self.max_value})." 

197 ) 

198 

199 def set_value(self, field_value: int) -> int: # type: ignore 

200 """ 

201 See super method for details. 

202 Adds signed/unsigned logic, and sanity checks of the value. 

203 """ 

204 if not self.min_value <= field_value <= self.max_value: 

205 raise ValueError( 

206 f'Value "{field_value}" not inside "{self.name}" field\'s ' 

207 f"legal range: ({self.min_value}, {self.max_value})." 

208 ) 

209 

210 unsigned_value = self._numerical_interpretation.convert_to_unsigned_binary( 

211 value=field_value 

212 ) 

213 return super().set_value(field_value=unsigned_value) 

214 

215 def __repr__(self) -> str: 

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

217name={self.name},\ 

218_base_index={self._base_index},\ 

219description={self.description}, 

220_min_value={self._min_value},\ 

221_max_value={self._max_value},\ 

222_default_value={self._default_value},\ 

223)"""