Skip to main content

ab_riscv_primitives/instructions/
utils.rs

1//! Utility types
2
3#[cfg(test)]
4mod tests;
5
6use core::fmt;
7use core::ops::{Shl, Shr};
8
9/// New type for unsigned integers that stores 24-bit numbers
10#[derive(Clone, Copy, Hash)]
11#[derive_const(PartialEq, Eq)]
12pub struct U24([u8; 3]);
13
14const impl Default for U24 {
15    #[inline(always)]
16    fn default() -> Self {
17        Self([0; _])
18    }
19}
20
21impl fmt::Debug for U24 {
22    #[inline(always)]
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        fmt::Debug::fmt(&self.to_u32(), f)
25    }
26}
27
28impl fmt::Display for U24 {
29    #[inline(always)]
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        fmt::Display::fmt(&self.to_u32(), f)
32    }
33}
34
35impl fmt::LowerHex for U24 {
36    #[inline(always)]
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        fmt::LowerHex::fmt(&self.to_u32(), f)
39    }
40}
41
42impl fmt::UpperHex for U24 {
43    #[inline(always)]
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        fmt::UpperHex::fmt(&self.to_u32(), f)
46    }
47}
48
49const impl Shl<u8> for U24 {
50    type Output = Self;
51
52    #[inline(always)]
53    fn shl(self, rhs: u8) -> Self::Output {
54        Self::from_u32(self.to_u32().shl(rhs))
55    }
56}
57
58const impl Shr<u8> for U24 {
59    type Output = Self;
60
61    #[inline(always)]
62    fn shr(self, rhs: u8) -> Self::Output {
63        Self::from_u32(self.to_u32().shr(rhs))
64    }
65}
66
67const impl Shl<u8> for &U24 {
68    type Output = U24;
69
70    #[inline(always)]
71    fn shl(self, rhs: u8) -> Self::Output {
72        U24::from_u32(self.to_u32().shl(rhs))
73    }
74}
75
76const impl Shr<u8> for &U24 {
77    type Output = U24;
78
79    #[inline(always)]
80    fn shr(self, rhs: u8) -> Self::Output {
81        U24::from_u32(self.to_u32().shr(rhs))
82    }
83}
84
85const impl From<U24> for u32 {
86    #[inline(always)]
87    fn from(v: U24) -> Self {
88        v.to_u32()
89    }
90}
91
92const impl From<U24> for u64 {
93    #[inline(always)]
94    fn from(v: U24) -> Self {
95        u64::from(v.to_u32())
96    }
97}
98
99impl U24 {
100    /// Create a new `U24` from an unsigned 32-bit integer.
101    ///
102    /// The input value is truncated to 24 bits, providing larger value panics in a debug build.
103    #[inline(always)]
104    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
105    pub const fn from_u32(v: u32) -> Self {
106        let b = v.to_le_bytes();
107        debug_assert!(
108            (v << u8::BITS) >> u8::BITS == v,
109            "Input value exceeds 24 bits"
110        );
111        Self([b[0], b[1], b[2]])
112    }
113
114    /// Convert to an unsigned 32-bit integer
115    #[inline(always)]
116    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
117    pub const fn to_u32(self) -> u32 {
118        let [a, b, c] = self.0;
119        u32::from_le_bytes([a, b, c, 0])
120    }
121}
122
123/// New type for signed integers that stores 24-bit numbers
124#[derive(Clone, Copy, Hash)]
125#[derive_const(PartialEq, Eq)]
126pub struct I24([u8; 3]);
127
128const impl Default for I24 {
129    #[inline(always)]
130    fn default() -> Self {
131        Self([0; _])
132    }
133}
134
135impl fmt::Debug for I24 {
136    #[inline(always)]
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        fmt::Debug::fmt(&self.to_i32(), f)
139    }
140}
141
142impl fmt::Display for I24 {
143    #[inline(always)]
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        fmt::Display::fmt(&self.to_i32(), f)
146    }
147}
148
149impl fmt::LowerHex for I24 {
150    #[inline(always)]
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        fmt::LowerHex::fmt(&self.to_i32(), f)
153    }
154}
155
156impl fmt::UpperHex for I24 {
157    #[inline(always)]
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        fmt::UpperHex::fmt(&self.to_i32(), f)
160    }
161}
162
163const impl Shl<u8> for I24 {
164    type Output = Self;
165
166    #[inline(always)]
167    fn shl(self, rhs: u8) -> Self::Output {
168        Self::from_i32(self.to_i32().shl(rhs))
169    }
170}
171
172const impl Shr<u8> for I24 {
173    type Output = Self;
174
175    #[inline(always)]
176    fn shr(self, rhs: u8) -> Self::Output {
177        Self::from_i32(self.to_i32().shr(rhs))
178    }
179}
180
181const impl Shl<u8> for &I24 {
182    type Output = I24;
183
184    #[inline(always)]
185    fn shl(self, rhs: u8) -> Self::Output {
186        I24::from_i32(self.to_i32().shl(rhs))
187    }
188}
189
190const impl Shr<u8> for &I24 {
191    type Output = I24;
192
193    #[inline(always)]
194    fn shr(self, rhs: u8) -> Self::Output {
195        I24::from_i32(self.to_i32().shr(rhs))
196    }
197}
198
199const impl From<I24> for i32 {
200    #[inline(always)]
201    fn from(v: I24) -> Self {
202        v.to_i32()
203    }
204}
205
206const impl From<I24> for i64 {
207    #[inline(always)]
208    fn from(v: I24) -> Self {
209        i64::from(v.to_i32())
210    }
211}
212
213impl I24 {
214    /// Create a new `I24` from a signed 32-bit integer.
215    ///
216    /// The input value is truncated to 24 bits, providing larger value panics in a debug build.
217    #[inline(always)]
218    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
219    pub const fn from_i32(v: i32) -> Self {
220        let b = v.to_le_bytes();
221        debug_assert!(
222            (v << u8::BITS) >> u8::BITS == v,
223            "Input value exceeds 24 bits"
224        );
225        Self([b[0], b[1], b[2]])
226    }
227
228    /// Convert to a signed 32-bit integer
229    #[inline(always)]
230    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
231    pub const fn to_i32(self) -> i32 {
232        let [a, b, c] = self.0;
233        // Sign-extend
234        i32::from_le_bytes([a, b, c, 0]) << u8::BITS >> u8::BITS
235    }
236}
237
238/// New type for signed integers that stores 32-bit numbers with `LOW_ZEROED_BITS` low bits zeroed
239/// and truncated to 24-bits
240#[derive(Clone, Copy, Hash)]
241#[derive_const(PartialEq, Eq)]
242pub struct I24WithZeroedBits<const LOW_ZEROED_BITS: u8>([u8; 3]);
243
244const impl<const LOW_ZEROED_BITS: u8> Default for I24WithZeroedBits<LOW_ZEROED_BITS> {
245    #[inline(always)]
246    fn default() -> Self {
247        Self([0; _])
248    }
249}
250
251impl<const LOW_ZEROED_BITS: u8> fmt::Debug for I24WithZeroedBits<LOW_ZEROED_BITS> {
252    #[inline(always)]
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        fmt::Debug::fmt(&self.to_i32(), f)
255    }
256}
257
258impl<const LOW_ZEROED_BITS: u8> fmt::Display for I24WithZeroedBits<LOW_ZEROED_BITS> {
259    #[inline(always)]
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        fmt::Display::fmt(&self.to_i32(), f)
262    }
263}
264
265impl<const LOW_ZEROED_BITS: u8> fmt::LowerHex for I24WithZeroedBits<LOW_ZEROED_BITS> {
266    #[inline(always)]
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        fmt::LowerHex::fmt(&self.to_i32(), f)
269    }
270}
271
272impl<const LOW_ZEROED_BITS: u8> fmt::UpperHex for I24WithZeroedBits<LOW_ZEROED_BITS> {
273    #[inline(always)]
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        fmt::UpperHex::fmt(&self.to_i32(), f)
276    }
277}
278
279const impl<const LOW_ZEROED_BITS: u8> From<I24WithZeroedBits<LOW_ZEROED_BITS>> for i32 {
280    #[inline(always)]
281    fn from(v: I24WithZeroedBits<LOW_ZEROED_BITS>) -> Self {
282        v.to_i32()
283    }
284}
285
286const impl<const LOW_ZEROED_BITS: u8> From<I24WithZeroedBits<LOW_ZEROED_BITS>> for i64 {
287    #[inline(always)]
288    fn from(v: I24WithZeroedBits<LOW_ZEROED_BITS>) -> Self {
289        i64::from(v.to_i32())
290    }
291}
292
293impl<const LOW_ZEROED_BITS: u8> I24WithZeroedBits<LOW_ZEROED_BITS> {
294    /// Create a new `I24WithZeroedBits` from a signed 32-bit integer.
295    ///
296    /// The input value is shifted right arithmetically by `LOW_ZEROED_BITS` before being stored.
297    /// When converted back with [`Self::to_i32`], the value is shifted back with low bits being
298    /// zero.
299    #[inline(always)]
300    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
301    pub const fn from_i32(v_original: i32) -> Self {
302        let v = v_original >> LOW_ZEROED_BITS;
303        let b = v.to_le_bytes();
304        let return_value = Self([b[0], b[1], b[2]]);
305
306        debug_assert!(
307            return_value.to_i32() == v_original,
308            "Input has non-zero low bits"
309        );
310
311        return_value
312    }
313
314    /// Convert to a signed 32-bit integer
315    #[inline(always)]
316    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
317    pub const fn to_i32(self) -> i32 {
318        let [a, b, c] = self.0;
319        // Sign-extend and shift back
320        (((i32::from_le_bytes([a, b, c, 0]) << u8::BITS >> u8::BITS) << LOW_ZEROED_BITS)
321            .cast_unsigned()
322            & (u32::MAX << LOW_ZEROED_BITS))
323            .cast_signed()
324    }
325}