Coverage for hdl_registers/field/integer.py: 99%
79 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +0000
« 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# --------------------------------------------------------------------------------------------------
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 See :ref:`field_integer` for details.
18 """
20 def __init__(
21 self,
22 name: str,
23 base_index: int,
24 description: str,
25 min_value: int,
26 max_value: int,
27 default_value: int,
28 ) -> None:
29 """
30 Arguments:
31 name: The name of the field.
32 base_index: The zero-based index within the register for the lowest bit of this field.
33 description: Textual field description.
34 min_value: The *minimum* value that this field shall be able to represent.
35 max_value: The *maximum* value that this field shall be able to represent.
36 default_value: Default value. Must be within the specified range.
37 """
38 self.name = name
39 self._base_index = base_index
40 self.description = description
42 # The min and max values determine the width of the field.
43 # Hence the user is not allowed to change them after initialization.
44 self._check_range(min_value=min_value, max_value=max_value)
45 self._min_value = min_value
46 self._max_value = max_value
48 self._width = self._calculate_width()
50 self._default_value = 0
51 # Assign self._default_value via setter
52 self.default_value = default_value
54 # Helper object to convert between unsigned binary and signed/unsigned integer.
55 self._numerical_interpretation = (
56 Signed(bit_width=self._width) if min_value < 0 else Unsigned(self._width)
57 )
59 def _check_range(self, min_value: int, max_value: int) -> None:
60 """
61 Perform some sanity checks on user-supplied values.
62 Will raise exception if something is wrong.
63 """
64 if not isinstance(min_value, int):
65 message = (
66 f'Integer field "{self.name}" should have integer value for "min_value". '
67 f'Got: "{min_value}".'
68 )
69 raise TypeError(message)
71 if not isinstance(max_value, int):
72 message = (
73 f'Integer field "{self.name}" should have integer value for "max_value". '
74 f'Got: "{max_value}".'
75 )
76 raise TypeError(message)
78 if min_value > max_value:
79 message = (
80 f'Integer field "{self.name}" should have ascending range. '
81 f"Got: [{min_value}, {max_value}]."
82 )
83 raise ValueError(message)
85 def _calculate_width(self) -> int:
86 # Calculate the width based on the supplied numerical limits.
87 error_message = (
88 f"Supplied integer range [{self._min_value}, {self._max_value}] does not fit "
89 "in a register."
90 )
92 if self._min_value >= 0:
93 # Calculate the number of bits needed to represent UNSIGNED numbers [0, max_value].
94 num_bits = self._max_value.bit_length()
96 if num_bits > 32:
97 raise ValueError(error_message)
99 return num_bits
101 # Calculate the number of bits needed to represent SIGNED numbers [min_value, max_value].
102 for num_bits in range(1, 33):
103 # Two's complement range for signed numbers.
104 min_range = -(2 ** (num_bits - 1))
105 max_range = 2 ** (num_bits - 1) - 1
107 if self._min_value >= min_range and self._max_value <= max_range:
108 return num_bits
110 raise ValueError(error_message)
112 @property
113 def min_value(self) -> int:
114 """
115 Minimum numeric value this field can assume.
116 Getter for private member.
117 """
118 # Note that it would be wrong to return 'self._numeric_interpretation.min_value'.
119 # The range of an integer field is not necessarily the same as the total range
120 # enabled by the bit width.
121 return self._min_value
123 @property
124 def max_value(self) -> int:
125 """
126 Maximum numeric value this field can assume.
127 Getter for private member.
128 """
129 # Same comment as for 'min_value'.
130 return self._max_value
132 @property
133 def is_signed(self) -> bool:
134 """
135 Is the field signed (two's complement)?
136 Getter for private member.
137 """
138 return self._numerical_interpretation.is_signed
140 @property
141 def default_value(self) -> int:
142 """
143 Getter for private member.
144 """
145 return self._default_value
147 @default_value.setter
148 def default_value(self, value: int) -> None:
149 """
150 Setter for default_value that performs sanity checks.
151 """
152 if not isinstance(value, int):
153 message = (
154 f'Integer field "{self.name}" should have integer value for "default_value". '
155 f'Got: "{value}".'
156 )
157 raise TypeError(message)
159 if value < self.min_value or value > self.max_value:
160 message = (
161 f'Integer field "{self.name}" should have "default_value" within range '
162 f'[{self.min_value}, {self.max_value}]. Got: "{value}".'
163 )
164 raise ValueError(message)
166 self._default_value = value
168 @property
169 def default_value_uint(self) -> int:
170 if self.default_value >= 0:
171 return self.default_value
173 if not self.is_signed:
174 raise ValueError("Should not end up here unless signed.")
176 # Offset the sign bit.
177 result: int = self.default_value + 2**self.width
179 return result
181 def get_value(self, register_value: int) -> int:
182 """
183 See super method for details.
184 Adds signed/unsigned logic, and sanity checks of the value.
185 """
186 unsigned_value = super().get_value(register_value=register_value)
187 # We know that this is an integer (not a float) since there are no fractional bits set.
188 result: int = self._numerical_interpretation.convert_from_unsigned_binary(
189 unsigned_binary=unsigned_value
190 )
192 if self.min_value <= result <= self.max_value:
193 return result
195 raise ValueError(
196 f'Register field value "{result}" not inside "{self.name}" field\'s '
197 f"legal range: ({self.min_value}, {self.max_value})."
198 )
200 def set_value(self, field_value: int) -> int:
201 """
202 See super method for details.
203 Adds signed/unsigned logic, and sanity checks of the value.
204 """
205 if not self.min_value <= field_value <= self.max_value:
206 raise ValueError(
207 f'Value "{field_value}" not inside "{self.name}" field\'s '
208 f"legal range: ({self.min_value}, {self.max_value})."
209 )
211 unsigned_value = self._numerical_interpretation.convert_to_unsigned_binary(
212 value=field_value
213 )
214 return super().set_value(field_value=unsigned_value)
216 def __repr__(self) -> str:
217 return f"""{self.__class__.__name__}(\
218name={self.name},\
219_base_index={self._base_index},\
220description={self.description},
221_min_value={self._min_value},\
222_max_value={self._max_value},\
223_default_value={self._default_value},\
224)"""