Coverage for hdl_registers/register_field_type.py: 95%

117 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-29 22:03 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run 

5# in real time. 

6# https://hdl-registers.com 

7# https://gitlab.com/tsfpga/hdl_registers 

8# -------------------------------------------------------------------------------------------------- 

9 

10from abc import ABC, abstractmethod 

11 

12 

13def _from_unsigned_binary( 

14 bit_width: int, 

15 unsigned_binary: int, 

16 integer_bit_width: int = None, 

17 fraction_bit_width: int = 0, 

18 is_signed: bool = False, 

19) -> float: 

20 """ 

21 Convert from an unsigned binary to one of: 

22 - unsigned integer 

23 - signed integer 

24 - unsigned fixed point 

25 - signed fixed point 

26 

27 Signed uses two's complement representation. 

28 

29 Sources: 

30 https://en.wikibooks.org/wiki/Floating_Point/Fixed-Point_Numbers 

31 https://vhdlguru.blogspot.com/2010/03/fixed-point-operations-in-vhdl-tutorial.html 

32 

33 Arguments: 

34 bit_width (int) : Width of the field. 

35 unsigned_binary (int): Unsigned binary integer representation of the field. 

36 integer_bit_width (int): If fixed point, the number of bits assigned to the 

37 integer part of the field value. 

38 fraction_bit_width (int): If fixed point, the number of bits assigned to the 

39 fractional part of the field value. 

40 is_signed (bool): Is the field signed (Two's compliment)? 

41 

42 Returns: 

43 float: Native Python representation of the field value. 

44 """ 

45 integer_bit_width = bit_width if integer_bit_width is None else integer_bit_width 

46 

47 if integer_bit_width + fraction_bit_width != bit_width: 

48 raise ValueError("Inconsistent bit width") 

49 

50 value = unsigned_binary * 2**-fraction_bit_width 

51 if is_signed: 

52 sign_bit = unsigned_binary & (1 << (bit_width - 1)) 

53 if sign_bit != 0: 

54 # If sign bit is set, compute negative value. 

55 value -= 2**integer_bit_width 

56 

57 return value 

58 

59 

60def _to_unsigned_binary( 

61 bit_width: int, 

62 value: float, 

63 integer_bit_width: int = None, 

64 fraction_bit_width: int = 0, 

65 is_signed: bool = False, 

66) -> int: 

67 """ 

68 Convert from one of: 

69 - unsigned integer 

70 - signed integer 

71 - unsigned fixed point 

72 - signed fixed point 

73 into an unsigned binary. 

74 

75 Signed uses two's complement representation. 

76 

77 Sources: 

78 https://en.wikibooks.org/wiki/Floating_Point/Fixed-Point_Numbers 

79 https://vhdlguru.blogspot.com/2010/03/fixed-point-operations-in-vhdl-tutorial.html 

80 

81 Arguments: 

82 bit_width (int) : Width of the field. 

83 value (float): Native Python representation of the field value. 

84 integer_bit_width (int): If fixed point, the number of bits assigned to the 

85 integer part of the field value. 

86 fraction_bit_width (int): If fixed point, the number of bits assigned to the 

87 fractional part of the field value. 

88 is_signed (bool): Is the field signed (Two's compliment)? 

89 

90 Returns: 

91 int: Unsigned binary integer representation of the field. 

92 """ 

93 integer_bit_width = bit_width if integer_bit_width is None else integer_bit_width 

94 

95 if integer_bit_width + fraction_bit_width != bit_width: 

96 raise ValueError("Inconsistent bit width") 

97 

98 binary_value = round(value * 2**fraction_bit_width) 

99 if value < 0: 

100 if is_signed: 

101 binary_value += 1 << bit_width 

102 else: 

103 raise ValueError("Attempting to convert negative value to unsigned") 

104 

105 return binary_value 

106 

107 

108class FieldType(ABC): 

109 @abstractmethod 

110 def min_value(self, bit_width: int): 

111 """ 

112 Minimum representable value for this field type. 

113 """ 

114 

115 @abstractmethod 

116 def max_value(self, bit_width: int): 

117 """ 

118 Maximum representable value for this field type. 

119 """ 

120 

121 @abstractmethod 

122 def convert_from_unsigned_binary(self, bit_width: int, unsigned_binary: int): 

123 """ 

124 Convert from the unsigned binary integer representation of a field, 

125 into the native Python value of the field. 

126 

127 Arguments: 

128 bit_width (int) : Width of the field. 

129 unsigned_binary (int): Unsigned binary integer representation of the field. 

130 """ 

131 

132 @abstractmethod 

133 def convert_to_unsigned_binary(self, bit_width: int, value) -> int: 

134 """ 

135 Convert from the native Python value of the field, into the 

136 unsigned binary integer representation which can be written to a register. 

137 

138 Arguments: 

139 bit_width (int) : Width of the field. 

140 value : Native Python representation of the field value. 

141 

142 Returns: 

143 int: Unsigned binary integer representation of the field. 

144 """ 

145 

146 @abstractmethod 

147 def vhdl_typedef(self, bit_width: int) -> str: 

148 """ 

149 VHDL representation of the field type for the VHDL generation. 

150 

151 Arguments: 

152 bit_width (int) : Width of the field. 

153 

154 Returns: 

155 str 

156 """ 

157 

158 @abstractmethod 

159 def __repr__(self): 

160 pass 

161 

162 def _check_value_in_range(self, bit_width: int, value): 

163 """ 

164 Check that a given field value is valid within the allowed range. 

165 

166 Arguments: 

167 bit_width (int) : Width of the field. 

168 value : Native Python representation of the field value. 

169 """ 

170 min_ = self.min_value(bit_width) 

171 max_ = self.max_value(bit_width) 

172 if not min_ <= value <= max_: 

173 raise ValueError(f"Value: {value} out of range of {bit_width}-bit ({min_}, {max_}).") 

174 

175 

176class Unsigned(FieldType): 

177 """ 

178 Unsigned integer. 

179 """ 

180 

181 def min_value(self, bit_width: int) -> int: 

182 return 0 

183 

184 def max_value(self, bit_width: int) -> int: 

185 return 2**bit_width - 1 

186 

187 def convert_from_unsigned_binary(self, bit_width: int, unsigned_binary: int) -> int: 

188 return unsigned_binary 

189 

190 def convert_to_unsigned_binary(self, bit_width: int, value: float) -> int: 

191 self._check_value_in_range(bit_width, value) 

192 return round(value) 

193 

194 def vhdl_typedef(self, bit_width: int) -> str: 

195 return f"unsigned({bit_width-1} downto 0)" 

196 

197 def __repr__(self): 

198 return self.__class__.__name__ 

199 

200 

201class Signed(FieldType): 

202 """ 

203 Two's complement signed integer format. 

204 """ 

205 

206 def min_value(self, bit_width: int) -> int: 

207 return -(2 ** (bit_width - 1)) 

208 

209 def max_value(self, bit_width: int) -> int: 

210 return 2 ** (bit_width - 1) - 1 

211 

212 def convert_from_unsigned_binary(self, bit_width: int, unsigned_binary: int) -> int: 

213 return int(_from_unsigned_binary(bit_width, unsigned_binary, is_signed=True)) 

214 

215 def convert_to_unsigned_binary(self, bit_width: int, value: float) -> int: 

216 self._check_value_in_range(bit_width, value) 

217 return _to_unsigned_binary(bit_width, value, is_signed=True) 

218 

219 def vhdl_typedef(self, bit_width: int) -> str: 

220 return f"signed({bit_width-1} downto 0)" 

221 

222 def __repr__(self): 

223 return self.__class__.__name__ 

224 

225 

226class Fixed(FieldType, ABC): 

227 def __init__(self, is_signed: bool, max_bit_index: int, min_bit_index: int): 

228 """ 

229 Abstract baseclass for Fixed field types. 

230 

231 The bit_index arguments indicates the position of the decimal point in 

232 relation to the number expressed by the field. The decimal point is 

233 between the "0" and "-1" bit index. If the bit_index argument is 

234 negative, then the number represented by the field will include a 

235 fractional portion. 

236 

237 Arguments: 

238 is_signed (bool): Is the field signed (Two's compliment)? 

239 max_bit_index (int): Position of the upper bit relative to the decimal point. 

240 min_bit_index (int): Position of the lower bit relative to the decimal point. 

241 """ 

242 self.is_signed = is_signed 

243 self._integer = Signed() if is_signed else Unsigned() 

244 self.max_bit_index = max_bit_index 

245 self.min_bit_index = min_bit_index 

246 if not self.max_bit_index >= self.min_bit_index: 

247 raise ValueError("max_bit_index must be >= min_bit_index") 

248 

249 self.integer_bit_width = self.max_bit_index + 1 

250 self.fraction_bit_width = -self.min_bit_index 

251 self.expected_bit_width = self.integer_bit_width + self.fraction_bit_width 

252 

253 def min_value(self, bit_width: int) -> float: 

254 min_integer_value = self._integer.min_value(bit_width) 

255 min_integer_binary = self._integer.convert_to_unsigned_binary(bit_width, min_integer_value) 

256 return self.convert_from_unsigned_binary(bit_width, min_integer_binary) 

257 

258 def max_value(self, bit_width: int) -> float: 

259 max_integer_value = self._integer.max_value(bit_width) 

260 max_integer_binary = self._integer.convert_to_unsigned_binary(bit_width, max_integer_value) 

261 return self.convert_from_unsigned_binary(bit_width, max_integer_binary) 

262 

263 def convert_from_unsigned_binary(self, bit_width: int, unsigned_binary: int) -> float: 

264 return _from_unsigned_binary( 

265 bit_width=bit_width, 

266 unsigned_binary=unsigned_binary, 

267 integer_bit_width=self.integer_bit_width, 

268 fraction_bit_width=self.fraction_bit_width, 

269 is_signed=self.is_signed, 

270 ) 

271 

272 def convert_to_unsigned_binary(self, bit_width: int, value: float) -> int: 

273 self._check_value_in_range(bit_width, value) 

274 return _to_unsigned_binary( 

275 bit_width=bit_width, 

276 value=value, 

277 integer_bit_width=self.integer_bit_width, 

278 fraction_bit_width=self.fraction_bit_width, 

279 is_signed=self.is_signed, 

280 ) 

281 

282 @abstractmethod 

283 def vhdl_typedef(self, bit_width: int) -> str: 

284 pass 

285 

286 def __repr__(self): 

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

288max_bit_index={self.max_bit_index},\ 

289min_bit_index={self.min_bit_index},\ 

290)""" 

291 

292 

293class UnsignedFixedPoint(Fixed): 

294 def __init__(self, max_bit_index: int, min_bit_index: int): 

295 """ 

296 Unsigned fixed point format to represent fractional values. 

297 

298 The bit_index arguments indicates the position of the decimal point in 

299 relation to the number expressed by the field. The decimal point is 

300 between the "0" and "-1" bit index. If the bit_index argument is 

301 negative, then the number represented by the field will include a 

302 fractional portion. 

303 

304 e.g. ufixed (4 downto -5) specifies unsigned fixed point, 10 bits wide, 

305 with 5 bits of decimal. Consequently y = 6.5 = "00110.10000". 

306 

307 Arguments: 

308 max_bit_index (int): Position of the upper bit relative to the decimal point. 

309 min_bit_index (int): Position of the lower bit relative to the decimal point. 

310 """ 

311 super().__init__(is_signed=False, max_bit_index=max_bit_index, min_bit_index=min_bit_index) 

312 

313 def vhdl_typedef(self, bit_width: int) -> str: 

314 if bit_width != self.expected_bit_width: 

315 raise ValueError("Inconsistent bit width") 

316 return f"ufixed({self.max_bit_index} downto {self.min_bit_index})" 

317 

318 @classmethod 

319 def from_bit_widths( 

320 cls, integer_bit_width: int, fraction_bit_width: int 

321 ) -> "UnsignedFixedPoint": 

322 """ 

323 Create instance via the respective fixed point bit widths. 

324 

325 Arguments: 

326 integer_bit_width (int): The number of bits assigned to the 

327 integer part of the field value. 

328 fraction_bit_width (int): The number of bits assigned to the 

329 fractional part of the field value. 

330 """ 

331 return cls(max_bit_index=integer_bit_width - 1, min_bit_index=-fraction_bit_width) 

332 

333 

334class SignedFixedPoint(Fixed): 

335 def __init__(self, max_bit_index: int, min_bit_index: int): 

336 """ 

337 Signed fixed point format to represent fractional values. 

338 Signed integer uses two's complement representation. 

339 

340 The bit_index arguments indicates the position of the decimal point in 

341 relation to the number expressed by the field. The decimal point is 

342 between the "0" and "-1" bit index. If the bit_index argument is 

343 negative, then the number represented by the field will include a 

344 fractional portion. 

345 

346 e.g. sfixed (4 downto -5) specifies signed fixed point, 10 bits wide, 

347 with 5 bits of decimal. Consequently y = -6.5 = "11001.10000". 

348 

349 Arguments: 

350 max_bit_index (int): Position of the upper bit relative to the decimal point. 

351 min_bit_index (int): Position of the lower bit relative to the decimal point. 

352 """ 

353 super().__init__(is_signed=True, max_bit_index=max_bit_index, min_bit_index=min_bit_index) 

354 

355 def vhdl_typedef(self, bit_width: int) -> str: 

356 if bit_width != self.expected_bit_width: 

357 raise ValueError("Inconsistent bit width") 

358 return f"sfixed({self.max_bit_index} downto {self.min_bit_index})" 

359 

360 @classmethod 

361 def from_bit_widths(cls, integer_bit_width: int, fraction_bit_width: int) -> "SignedFixedPoint": 

362 """ 

363 Create instance via the respective fixed point bit widths. 

364 

365 Arguments: 

366 integer_bit_width (int): The number of bits assigned to the 

367 integer part of the field value. 

368 fraction_bit_width (int): The number of bits assigned to the 

369 fractional part of the field value. 

370 """ 

371 return cls(max_bit_index=integer_bit_width - 1, min_bit_index=-fraction_bit_width)