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

79 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-15 12:31 +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 .numerical_interpretation import Signed, Unsigned 

11from .register_field import RegisterField 

12 

13 

14class Integer(RegisterField): 

15 """ 

16 Used to represent an integer field in a register. 

17 """ 

18 

19 def __init__( 

20 self, 

21 name: str, 

22 base_index: int, 

23 description: str, 

24 min_value: int, 

25 max_value: int, 

26 default_value: int, 

27 ) -> None: 

28 """ 

29 Arguments: 

30 name: The name of the field. 

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

32 description: Textual field description. 

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

34 max_value: The *maximum* value that this field shall be able to represent. 

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

36 """ 

37 self.name = name 

38 self._base_index = base_index 

39 self.description = description 

40 

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

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

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

44 self._min_value = min_value 

45 self._max_value = max_value 

46 

47 self._width = self._calculate_width() 

48 

49 self._default_value = 0 

50 # Assign self._default_value via setter 

51 self.default_value = default_value 

52 

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

54 self._numerical_interpretation = ( 

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

56 ) 

57 

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

59 """ 

60 Perform some sanity checks on user-supplied values. 

61 Will raise exception if something is wrong. 

62 """ 

63 if not isinstance(min_value, int): 

64 message = ( 

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

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

67 ) 

68 raise TypeError(message) 

69 

70 if not isinstance(max_value, int): 

71 message = ( 

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

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

74 ) 

75 raise TypeError(message) 

76 

77 if min_value > max_value: 

78 message = ( 

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

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

81 ) 

82 raise ValueError(message) 

83 

84 def _calculate_width(self) -> int: 

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

86 error_message = ( 

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

88 "in a register." 

89 ) 

90 

91 if self._min_value >= 0: 

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

93 num_bits = self._max_value.bit_length() 

94 

95 if num_bits > 32: 

96 raise ValueError(error_message) 

97 

98 return num_bits 

99 

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

101 for num_bits in range(1, 33): 

102 # Two's complement range for signed numbers. 

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

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

105 

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

107 return num_bits 

108 

109 raise ValueError(error_message) 

110 

111 @property 

112 def min_value(self) -> int: 

113 """ 

114 Minimum numeric value this field can assume. 

115 Getter for private member. 

116 """ 

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

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

119 # enabled by the bit width. 

120 return self._min_value 

121 

122 @property 

123 def max_value(self) -> int: 

124 """ 

125 Maximum numeric value this field can assume. 

126 Getter for private member. 

127 """ 

128 # Same comment as for 'min_value'. 

129 return self._max_value 

130 

131 @property 

132 def is_signed(self) -> bool: 

133 """ 

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

135 Getter for private member. 

136 """ 

137 return self._numerical_interpretation.is_signed 

138 

139 @property 

140 def default_value(self) -> int: 

141 """ 

142 Getter for private member. 

143 """ 

144 return self._default_value 

145 

146 @default_value.setter 

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

148 """ 

149 Setter for default_value that performs sanity checks. 

150 """ 

151 if not isinstance(value, int): 

152 message = ( 

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

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

155 ) 

156 raise TypeError(message) 

157 

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

159 message = ( 

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

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

162 ) 

163 raise ValueError(message) 

164 

165 self._default_value = value 

166 

167 @property 

168 def default_value_uint(self) -> int: 

169 if self.default_value >= 0: 

170 return self.default_value 

171 

172 if not self.is_signed: 

173 raise ValueError("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: 

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