Coverage for hdl_registers/register_field_type.py: 95%

117 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-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/hdl_registers/hdl_registers 

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

9 

10# Standard libraries 

11from abc import ABC, abstractmethod 

12 

13 

14def _from_unsigned_binary( 

15 bit_width: int, 

16 unsigned_binary: int, 

17 integer_bit_width: int = None, 

18 fraction_bit_width: int = 0, 

19 is_signed: bool = False, 

20) -> float: 

21 """ 

22 Convert from an unsigned binary to one of: 

23 - unsigned integer 

24 - signed integer 

25 - unsigned fixed point 

26 - signed fixed point 

27 

28 Signed uses two's complement representation. 

29 

30 Sources: 

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

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

33 

34 Arguments: 

35 bit_width (int) : Width of the field. 

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

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

38 integer part of the field value. 

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

40 fractional part of the field value. 

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

42 

43 Returns: 

44 float: Native Python representation of the field value. 

45 """ 

46 integer_bit_width = bit_width if integer_bit_width is None else integer_bit_width 

47 

48 if integer_bit_width + fraction_bit_width != bit_width: 

49 raise ValueError("Inconsistent bit width") 

50 

51 value = unsigned_binary * 2**-fraction_bit_width 

52 if is_signed: 

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

54 if sign_bit != 0: 

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

56 value -= 2**integer_bit_width 

57 

58 return value 

59 

60 

61def _to_unsigned_binary( 

62 bit_width: int, 

63 value: float, 

64 integer_bit_width: int = None, 

65 fraction_bit_width: int = 0, 

66 is_signed: bool = False, 

67) -> int: 

68 """ 

69 Convert from one of: 

70 - unsigned integer 

71 - signed integer 

72 - unsigned fixed point 

73 - signed fixed point 

74 into an unsigned binary. 

75 

76 Signed uses two's complement representation. 

77 

78 Sources: 

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

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

81 

82 Arguments: 

83 bit_width (int) : Width of the field. 

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

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

86 integer part of the field value. 

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

88 fractional part of the field value. 

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

90 

91 Returns: 

92 int: Unsigned binary integer representation of the field. 

93 """ 

94 integer_bit_width = bit_width if integer_bit_width is None else integer_bit_width 

95 

96 if integer_bit_width + fraction_bit_width != bit_width: 

97 raise ValueError("Inconsistent bit width") 

98 

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

100 if value < 0: 

101 if is_signed: 

102 binary_value += 1 << bit_width 

103 else: 

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

105 

106 return binary_value 

107 

108 

109class FieldType(ABC): 

110 @abstractmethod 

111 def min_value(self, bit_width: int): 

112 """ 

113 Minimum representable value for this field type. 

114 """ 

115 

116 @abstractmethod 

117 def max_value(self, bit_width: int): 

118 """ 

119 Maximum representable value for this field type. 

120 """ 

121 

122 @abstractmethod 

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

124 """ 

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

126 into the native Python value of the field. 

127 

128 Arguments: 

129 bit_width (int) : Width of the field. 

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

131 """ 

132 

133 @abstractmethod 

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

135 """ 

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

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

138 

139 Arguments: 

140 bit_width (int) : Width of the field. 

141 value : Native Python representation of the field value. 

142 

143 Returns: 

144 int: Unsigned binary integer representation of the field. 

145 """ 

146 

147 @abstractmethod 

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

149 """ 

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

151 

152 Arguments: 

153 bit_width (int) : Width of the field. 

154 

155 Returns: 

156 str 

157 """ 

158 

159 @abstractmethod 

160 def __repr__(self): 

161 pass 

162 

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

164 """ 

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

166 

167 Arguments: 

168 bit_width (int) : Width of the field. 

169 value : Native Python representation of the field value. 

170 """ 

171 min_ = self.min_value(bit_width) 

172 max_ = self.max_value(bit_width) 

173 if not min_ <= value <= max_: 

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

175 

176 

177class Unsigned(FieldType): 

178 """ 

179 Unsigned integer. 

180 """ 

181 

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

183 return 0 

184 

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

186 return 2**bit_width - 1 

187 

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

189 return unsigned_binary 

190 

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

192 self._check_value_in_range(bit_width, value) 

193 return round(value) 

194 

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

196 return f"u_unsigned({bit_width-1} downto 0)" 

197 

198 def __repr__(self): 

199 return self.__class__.__name__ 

200 

201 

202class Signed(FieldType): 

203 """ 

204 Two's complement signed integer format. 

205 """ 

206 

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

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

209 

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

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

212 

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

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

215 

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

217 self._check_value_in_range(bit_width, value) 

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

219 

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

221 return f"u_signed({bit_width-1} downto 0)" 

222 

223 def __repr__(self): 

224 return self.__class__.__name__ 

225 

226 

227class Fixed(FieldType, ABC): 

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

229 """ 

230 Abstract baseclass for Fixed field types. 

231 

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

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

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

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

236 fractional portion. 

237 

238 Arguments: 

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

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

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

242 """ 

243 self.is_signed = is_signed 

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

245 self.max_bit_index = max_bit_index 

246 self.min_bit_index = min_bit_index 

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

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

249 

250 self.integer_bit_width = self.max_bit_index + 1 

251 self.fraction_bit_width = -self.min_bit_index 

252 self.expected_bit_width = self.integer_bit_width + self.fraction_bit_width 

253 

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

255 min_integer_value = self._integer.min_value(bit_width) 

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

257 return self.convert_from_unsigned_binary(bit_width, min_integer_binary) 

258 

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

260 max_integer_value = self._integer.max_value(bit_width) 

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

262 return self.convert_from_unsigned_binary(bit_width, max_integer_binary) 

263 

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

265 return _from_unsigned_binary( 

266 bit_width=bit_width, 

267 unsigned_binary=unsigned_binary, 

268 integer_bit_width=self.integer_bit_width, 

269 fraction_bit_width=self.fraction_bit_width, 

270 is_signed=self.is_signed, 

271 ) 

272 

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

274 self._check_value_in_range(bit_width, value) 

275 return _to_unsigned_binary( 

276 bit_width=bit_width, 

277 value=value, 

278 integer_bit_width=self.integer_bit_width, 

279 fraction_bit_width=self.fraction_bit_width, 

280 is_signed=self.is_signed, 

281 ) 

282 

283 @abstractmethod 

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

285 pass 

286 

287 def __repr__(self): 

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

289max_bit_index={self.max_bit_index},\ 

290min_bit_index={self.min_bit_index},\ 

291)""" 

292 

293 

294class UnsignedFixedPoint(Fixed): 

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

296 """ 

297 Unsigned fixed point format to represent fractional values. 

298 

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

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

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

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

303 fractional portion. 

304 

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

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

307 

308 Arguments: 

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

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

311 """ 

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

313 

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

315 if bit_width != self.expected_bit_width: 

316 raise ValueError("Inconsistent bit width") 

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

318 

319 @classmethod 

320 def from_bit_widths( 

321 cls, integer_bit_width: int, fraction_bit_width: int 

322 ) -> "UnsignedFixedPoint": 

323 """ 

324 Create instance via the respective fixed point bit widths. 

325 

326 Arguments: 

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

328 integer part of the field value. 

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

330 fractional part of the field value. 

331 """ 

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

333 

334 

335class SignedFixedPoint(Fixed): 

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

337 """ 

338 Signed fixed point format to represent fractional values. 

339 Signed integer uses two's complement representation. 

340 

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

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

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

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

345 fractional portion. 

346 

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

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

349 

350 Arguments: 

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

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

353 """ 

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

355 

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

357 if bit_width != self.expected_bit_width: 

358 raise ValueError("Inconsistent bit width") 

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

360 

361 @classmethod 

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

363 """ 

364 Create instance via the respective fixed point bit widths. 

365 

366 Arguments: 

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

368 integer part of the field value. 

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

370 fractional part of the field value. 

371 """ 

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