Coverage for hdl_registers/field/bit_vector.py: 97%

61 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 __future__ import annotations 

11 

12from .numerical_interpretation import NumericalInterpretation, Unsigned 

13from .register_field import RegisterField 

14 

15 

16class BitVector(RegisterField): 

17 """ 

18 Used to represent a bit vector field in a register. 

19 See :ref:`field_bit_vector` for details. 

20 """ 

21 

22 def __init__( 

23 self, 

24 name: str, 

25 base_index: int, 

26 description: str, 

27 width: int, 

28 default_value: str | float, 

29 numerical_interpretation: NumericalInterpretation | None = None, 

30 ) -> None: 

31 """ 

32 Arguments: 

33 name: The name of the bit vector. 

34 base_index: The zero-based index within the register for the lowest bit of this 

35 bit vector. 

36 description: Textual field description. 

37 width: The width of the bit vector field. 

38 default_value: Default value. 

39 Must be either a string of length ``width`` containing only "1" and "0". 

40 Or a numeric value according to the ``numerical_interpretation`` that fits in 

41 the ``width``. 

42 numerical_interpretation: The mode used when interpreting the bits of this field as 

43 a numeric value. 

44 Default is unsigned with no fractional bits. 

45 """ 

46 self.name = name 

47 self._base_index = base_index 

48 self.description = description 

49 

50 self._numerical_interpretation = ( 

51 Unsigned(bit_width=width) 

52 if numerical_interpretation is None 

53 else numerical_interpretation 

54 ) 

55 

56 self._check_width(width=width) 

57 self._width = width 

58 

59 self._default_value = "" 

60 # Assign self._default_value via setter 

61 self.default_value = default_value 

62 

63 @property 

64 def numerical_interpretation(self) -> NumericalInterpretation: 

65 """ 

66 The mode used when interpreting the bits of this field as a numeric value 

67 (E.g. signed, unsigned fixed-point, etc.). 

68 Is used by :meth:`get_value` and :meth:`set_value`. 

69 

70 Getter for private member. 

71 """ 

72 return self._numerical_interpretation 

73 

74 def _check_width(self, width: int) -> None: 

75 """ 

76 Sanity checks for the provided width 

77 Will raise exception if something is wrong. 

78 """ 

79 if not isinstance(width, int): 

80 message = ( 

81 f'Bit vector "{self.name}" should have integer value for "width". Got: "{width}".' 

82 ) 

83 raise TypeError(message) 

84 

85 if width < 1 or width > 32: 

86 raise ValueError(f'Invalid width for bit vector "{self.name}". Got: "{width}".') 

87 

88 if width != self.numerical_interpretation.bit_width: 

89 raise ValueError( 

90 f'Inconsistent width for bit vector "{self.name}". ' 

91 f'Field is "{width}" bits, numerical interpretation specification is ' 

92 f'"{self.numerical_interpretation.bit_width}".' 

93 ) 

94 

95 @property 

96 def default_value(self) -> str: 

97 """ 

98 Getter for private member. 

99 """ 

100 return self._default_value 

101 

102 @default_value.setter 

103 def default_value(self, value: str | float) -> None: 

104 """ 

105 Setter for ``default_value`` that performs sanity checks. 

106 """ 

107 if not isinstance(value, (str, int, float)): 

108 message = ( 

109 f'Bit vector "{self.name}" should have string or numeric value ' 

110 f'for "default_value". Got: "{value}".' 

111 ) 

112 raise TypeError(message) 

113 

114 if isinstance(value, str): 

115 if len(value) != self.width: 

116 message = ( 

117 f'Bit vector "{self.name}" should have "default_value" of length {self.width}. ' 

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

119 ) 

120 raise ValueError(message) 

121 

122 for character in value: 

123 if character not in ["0", "1"]: 

124 message = ( 

125 f'Bit vector "{self.name}" invalid binary "default_value". Got: "{value}".' 

126 ) 

127 raise ValueError(message) 

128 

129 self._default_value = value 

130 return 

131 

132 try: 

133 default_value_uint = self._numerical_interpretation.convert_to_unsigned_binary( 

134 value=value 

135 ) 

136 except ValueError as error: 

137 message = ( 

138 f'Bit vector "{self.name}" should have "default_value" that fits in ' 

139 f'{self.width} {self._numerical_interpretation.name} bits. Got: "{value}".' 

140 ) 

141 raise ValueError(message) from error 

142 

143 formatting_string = f"{ :0{self.width}b} " 

144 default_value_bin = formatting_string.format(default_value_uint) 

145 

146 self._default_value = default_value_bin 

147 

148 def get_value(self, register_value: int) -> int | float: 

149 """ 

150 See super method for details. 

151 This subclass method uses the native numeric representation of the field value 

152 (not the raw value of the bits). 

153 If the field has a non-zero number of fractional bits, the type of the result 

154 will be a ``float``. 

155 Otherwise it will be an ``int``. 

156 """ 

157 value_unsigned = super().get_value(register_value=register_value) 

158 return self.numerical_interpretation.convert_from_unsigned_binary( 

159 unsigned_binary=value_unsigned 

160 ) 

161 

162 def set_value(self, field_value: float) -> int: 

163 """ 

164 See super method for details. 

165 This subclass method uses the native numeric representation of the field value 

166 (not the raw value of the bits). 

167 If the field has a non-zero number of fractional bits, the type of the argument 

168 should be a ``float``. 

169 Otherwise it should be an ``int``. 

170 """ 

171 unsigned_value = self.numerical_interpretation.convert_to_unsigned_binary(value=field_value) 

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

173 

174 @property 

175 def default_value_uint(self) -> int: 

176 return int(self.default_value, base=2) 

177 

178 def __repr__(self) -> str: 

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

180name={self.name},\ 

181_base_index={self._base_index},\ 

182description={self.description}, 

183_width={self._width},\ 

184_default_value={self._default_value},\ 

185_numerical_interpretation={self._numerical_interpretation},\ 

186)"""