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
« 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# --------------------------------------------------------------------------------------------------
10from .numerical_interpretation import Signed, Unsigned
11from .register_field import RegisterField
14class Integer(RegisterField):
15 """
16 Used to represent an integer field in a register.
17 """
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
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
47 self._width = self._calculate_width()
49 self._default_value = 0
50 # Assign self._default_value via setter
51 self.default_value = default_value
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 )
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)
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)
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)
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 )
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()
95 if num_bits > 32:
96 raise ValueError(error_message)
98 return num_bits
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
106 if self._min_value >= min_range and self._max_value <= max_range:
107 return num_bits
109 raise ValueError(error_message)
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
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
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
139 @property
140 def default_value(self) -> int:
141 """
142 Getter for private member.
143 """
144 return self._default_value
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)
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)
165 self._default_value = value
167 @property
168 def default_value_uint(self) -> int:
169 if self.default_value >= 0:
170 return self.default_value
172 if not self.is_signed:
173 raise ValueError("Should not end up here unless signed.")
175 # Offset the sign bit.
176 result: int = self.default_value + 2**self.width
178 return result
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 )
191 if self.min_value <= result <= self.max_value:
192 return result
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 )
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 )
210 unsigned_value = self._numerical_interpretation.convert_to_unsigned_binary(
211 value=field_value
212 )
213 return super().set_value(field_value=unsigned_value)
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)"""