Skip to main content

rustdv_gpi/
value.rs

1//! 4-state value types: port of cocotb's `Logic`/`LogicArray`
2//! (cocotb: types/, design-doc mapping row 23). Conversion failures are
3//! `Result`s, not exceptions.
4
5use std::fmt;
6
7use num_bigint::BigUint;
8
9use crate::ValueError;
10
11/// One 4-state bit.
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
13pub enum Logic {
14    Zero,
15    One,
16    X,
17    Z,
18}
19
20impl Logic {
21    pub fn from_char(c: char) -> Logic {
22        match c {
23            '0' => Logic::Zero,
24            '1' => Logic::One,
25            'z' | 'Z' => Logic::Z,
26            _ => Logic::X,
27        }
28    }
29    pub fn to_char(self) -> char {
30        match self {
31            Logic::Zero => '0',
32            Logic::One => '1',
33            Logic::X => 'x',
34            Logic::Z => 'z',
35        }
36    }
37    pub fn is_resolvable(self) -> bool {
38        matches!(self, Logic::Zero | Logic::One)
39    }
40}
41
42impl From<bool> for Logic {
43    fn from(b: bool) -> Logic {
44        if b { Logic::One } else { Logic::Zero }
45    }
46}
47
48impl fmt::Display for Logic {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "{}", self.to_char())
51    }
52}
53
54/// A fixed-width vector of 4-state bits using VPI's `a_val`/`b_val`
55/// representation. Bit pairs encode `00=0`, `10=1`, `11=X`, and `01=Z`.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct LogicArray {
58    a_val: BigUint,
59    b_val: BigUint,
60    width: usize,
61}
62
63impl LogicArray {
64    /// Construct from VPI vector words in least-significant-word-first order.
65    /// Each tuple is `(aval, bval)`. `width` preserves the exact signal width
66    /// when the final word uses fewer than 32 bits.
67    pub fn from_vpi_words(words: &[(u32, u32)], width: usize) -> LogicArray {
68        let word_count = width.div_ceil(32);
69        assert_eq!(
70            words.len(),
71            word_count,
72            "{width}-bit LogicArray needs {word_count} VPI word(s), got {}",
73            words.len()
74        );
75
76        let mut a_words: Vec<u32> = words.iter().map(|(aval, _)| *aval).collect();
77        let mut b_words: Vec<u32> = words.iter().map(|(_, bval)| *bval).collect();
78        if !width.is_multiple_of(32) {
79            let mask = (1u32 << (width % 32)) - 1;
80            *a_words.last_mut().expect("partial width has a VPI word") &= mask;
81            *b_words.last_mut().expect("partial width has a VPI word") &= mask;
82        }
83
84        LogicArray {
85            a_val: BigUint::from_slice(&a_words),
86            b_val: BigUint::from_slice(&b_words),
87            width,
88        }
89    }
90
91    pub fn from_binstr(s: &str) -> LogicArray {
92        let mut a_val = BigUint::ZERO;
93        let mut b_val = BigUint::ZERO;
94        let mut width = 0;
95        for (bit, c) in s.chars().rev().enumerate() {
96            let bit = bit as u64;
97            match Logic::from_char(c) {
98                Logic::Zero => {}
99                Logic::One => a_val.set_bit(bit, true),
100                Logic::X => {
101                    a_val.set_bit(bit, true);
102                    b_val.set_bit(bit, true);
103                }
104                Logic::Z => b_val.set_bit(bit, true),
105            }
106            width += 1;
107        }
108        LogicArray {
109            a_val,
110            b_val,
111            width,
112        }
113    }
114
115    pub fn from_bool(v: bool, width: usize) -> LogicArray {
116        LogicArray::from_u128(v as u128, width)
117    }
118
119    pub fn from_u8(v: u8, width: usize) -> LogicArray {
120        LogicArray::from_u128(v as u128, width)
121    }
122
123    pub fn from_u16(v: u16, width: usize) -> LogicArray {
124        LogicArray::from_u128(v as u128, width)
125    }
126
127    pub fn from_u32(v: u32, width: usize) -> LogicArray {
128        LogicArray::from_u128(v as u128, width)
129    }
130
131    pub fn from_u64(v: u64, width: usize) -> LogicArray {
132        LogicArray::from_u128(v as u128, width)
133    }
134
135    pub fn from_u128(v: u128, width: usize) -> LogicArray {
136        let mut a_val = BigUint::ZERO;
137        for bit in 0..width.min(u128::BITS as usize) {
138            if v & (1u128 << bit) != 0 {
139                a_val.set_bit(bit as u64, true);
140            }
141        }
142        LogicArray {
143            a_val,
144            b_val: BigUint::ZERO,
145            width,
146        }
147    }
148
149    pub fn from_bigint(v: &BigUint, width: usize) -> LogicArray {
150        let mask = (BigUint::from(1u8) << width) - BigUint::from(1u8);
151        LogicArray {
152            a_val: v & mask,
153            b_val: BigUint::ZERO,
154            width,
155        }
156    }
157
158    /// Return VPI vector words in least-significant-word-first order.
159    /// Each tuple is `(aval, bval)`.
160    pub fn to_vpi_words(&self) -> Vec<(u32, u32)> {
161        let word_count = self.width.div_ceil(32);
162        let mut a_words = self.a_val.iter_u32_digits();
163        let mut b_words = self.b_val.iter_u32_digits();
164        (0..word_count)
165            .map(|_| (a_words.next().unwrap_or(0), b_words.next().unwrap_or(0)))
166            .collect()
167    }
168
169    pub fn to_binstr(&self) -> String {
170        (0..self.width)
171            .rev()
172            .map(|index| {
173                self[index]
174                    .expect("index generated from LogicArray width")
175                    .to_char()
176            })
177            .collect()
178    }
179
180    pub fn len(&self) -> usize {
181        self.width
182    }
183    pub fn is_empty(&self) -> bool {
184        self.width == 0
185    }
186
187    /// Return true if the value is resolvable to a 2-state value (no X/Z bits).
188    pub fn is_resolvable(&self) -> bool {
189        self.b_val == BigUint::ZERO
190    }
191}
192
193impl TryFrom<&LogicArray> for bool {
194    type Error = ValueError;
195    fn try_from(v: &LogicArray) -> Result<bool, ValueError> {
196        if !v.is_resolvable() {
197            return Err(ValueError::FourState(v.to_binstr()));
198        }
199        Ok(v.a_val != BigUint::ZERO)
200    }
201}
202
203impl TryFrom<&LogicArray> for u8 {
204    type Error = ValueError;
205    fn try_from(v: &LogicArray) -> Result<u8, ValueError> {
206        if !v.is_resolvable() {
207            return Err(ValueError::FourState(v.to_binstr()));
208        }
209        Ok(v.a_val.iter_u32_digits().next().unwrap_or(0) as u8)
210    }
211}
212
213impl TryFrom<&LogicArray> for u16 {
214    type Error = ValueError;
215    fn try_from(v: &LogicArray) -> Result<u16, ValueError> {
216        if !v.is_resolvable() {
217            return Err(ValueError::FourState(v.to_binstr()));
218        }
219        Ok(v.a_val.iter_u32_digits().next().unwrap_or(0) as u16)
220    }
221}
222
223impl TryFrom<&LogicArray> for u32 {
224    type Error = ValueError;
225    fn try_from(v: &LogicArray) -> Result<u32, ValueError> {
226        if !v.is_resolvable() {
227            return Err(ValueError::FourState(v.to_binstr()));
228        }
229        Ok(v.a_val.iter_u32_digits().next().unwrap_or(0))
230    }
231}
232
233impl TryFrom<&LogicArray> for u64 {
234    type Error = ValueError;
235    fn try_from(v: &LogicArray) -> Result<u64, ValueError> {
236        if !v.is_resolvable() {
237            return Err(ValueError::FourState(v.to_binstr()));
238        }
239        Ok(v.a_val.iter_u64_digits().next().unwrap_or(0))
240    }
241}
242
243impl TryFrom<&LogicArray> for u128 {
244    type Error = ValueError;
245    fn try_from(v: &LogicArray) -> Result<u128, ValueError> {
246        if !v.is_resolvable() {
247            return Err(ValueError::FourState(v.to_binstr()));
248        }
249        let mut digits = v.a_val.iter_u64_digits();
250        let low = digits.next().unwrap_or(0) as u128;
251        let high = digits.next().unwrap_or(0) as u128;
252        Ok(low | (high << 64))
253    }
254}
255
256impl TryFrom<&LogicArray> for BigUint {
257    type Error = ValueError;
258    fn try_from(v: &LogicArray) -> Result<BigUint, ValueError> {
259        if !v.is_resolvable() {
260            return Err(ValueError::FourState(v.to_binstr()));
261        }
262        Ok(v.a_val.clone())
263    }
264}
265
266/// Access bits least-significant-bit first, returning `None` out of bounds.
267impl std::ops::Index<usize> for LogicArray {
268    type Output = Option<Logic>;
269
270    fn index(&self, index: usize) -> &Self::Output {
271        // Return references to the decoded logic states.
272        static ZERO: Option<Logic> = Some(Logic::Zero);
273        static ONE: Option<Logic> = Some(Logic::One);
274        static X: Option<Logic> = Some(Logic::X);
275        static Z: Option<Logic> = Some(Logic::Z);
276        // Check if the index is out of bounds first
277        if index >= self.width {
278            return &None;
279        }
280        // Return a reference to the appropriate static value based on
281        // a_val and b_val
282        match (self.a_val.bit(index as u64), self.b_val.bit(index as u64)) {
283            (false, false) => &ZERO,
284            (true, false) => &ONE,
285            (true, true) => &X,
286            (false, true) => &Z,
287        }
288    }
289}
290
291impl fmt::Display for LogicArray {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        write!(f, "{}", self.to_binstr())
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn roundtrip_u64() {
303        let v = LogicArray::from_u64(0xA5, 8);
304        assert_eq!(v.to_binstr(), "10100101");
305        assert_eq!(u64::try_from(&v).unwrap(), 0xA5);
306    }
307
308    #[test]
309    fn constructs_from_two_state_primitive_types() {
310        let v = LogicArray::from_bool(true, 1);
311        assert_eq!(v.to_binstr(), "1");
312
313        let v = LogicArray::from_u8(0xa5, 8);
314        assert_eq!(u8::try_from(&v), Ok(0xa5));
315
316        let v = LogicArray::from_u16(0xa5b6, 16);
317        assert_eq!(u16::try_from(&v), Ok(0xa5b6));
318
319        let v = LogicArray::from_u32(0xa5b6_c7d8, 32);
320        assert_eq!(u32::try_from(&v), Ok(0xa5b6_c7d8));
321
322        let v = LogicArray::from_u64(0x0123_4567_89ab_cdef, 64);
323        assert_eq!(u64::try_from(&v), Ok(0x0123_4567_89ab_cdef));
324
325        let value = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210;
326        let v = LogicArray::from_u128(value, 128);
327        assert_eq!(u128::try_from(&v), Ok(value));
328
329        let truncated = LogicArray::from_u8(0xa5, 4);
330        assert_eq!(truncated.to_binstr(), "0101");
331    }
332
333    #[test]
334    fn bigint_conversion_preserves_arbitrary_width_values() {
335        let value = BigUint::from_slice(&[
336            0x7654_3210,
337            0xfedc_ba98,
338            0x89ab_cdef,
339            0x0123_4567,
340            0xa5a5_5a5a,
341        ]);
342        let v = LogicArray::from_bigint(&value, 160);
343
344        assert_eq!(v.len(), 160);
345        assert_eq!(BigUint::try_from(&v), Ok(value));
346
347        let truncated = LogicArray::from_bigint(&BigUint::from(0xffu8), 4);
348        assert_eq!(BigUint::try_from(&truncated), Ok(BigUint::from(0x0fu8)));
349    }
350
351    #[test]
352    fn converts_to_two_state_primitive_types() {
353        let zero = LogicArray::from_binstr("0");
354        assert_eq!(bool::try_from(&zero), Ok(false));
355
356        let v = LogicArray::from_vpi_words(
357            &[
358                (0x7654_3210, 0),
359                (0xfedc_ba98, 0),
360                (0x89ab_cdef, 0),
361                (0x0123_4567, 0),
362            ],
363            128,
364        );
365        assert_eq!(bool::try_from(&v), Ok(true));
366        assert_eq!(u8::try_from(&v), Ok(0x10));
367        assert_eq!(u16::try_from(&v), Ok(0x3210));
368        assert_eq!(u32::try_from(&v), Ok(0x7654_3210));
369        assert_eq!(u64::try_from(&v), Ok(0xfedc_ba98_7654_3210));
370        assert_eq!(
371            u128::try_from(&v),
372            Ok(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210)
373        );
374    }
375
376    #[test]
377    fn four_state_rejected() {
378        let v = LogicArray::from_binstr("1x0z");
379        assert!(bool::try_from(&v).is_err());
380        assert!(u8::try_from(&v).is_err());
381        assert!(u16::try_from(&v).is_err());
382        assert!(u32::try_from(&v).is_err());
383        assert!(u64::try_from(&v).is_err());
384        assert!(u128::try_from(&v).is_err());
385        assert!(BigUint::try_from(&v).is_err());
386        assert!(!v.is_resolvable());
387    }
388
389    #[test]
390    fn vpi_encoding_and_lsb_first_bit_access() {
391        let v = LogicArray::from_binstr("01xz");
392
393        assert_eq!(v.a_val, BigUint::from(0b0110u8));
394        assert_eq!(v.b_val, BigUint::from(0b0011u8));
395        assert_eq!(v[0], Some(Logic::Z));
396        assert_eq!(v[1], Some(Logic::X));
397        assert_eq!(v[2], Some(Logic::One));
398        assert_eq!(v[3], Some(Logic::Zero));
399        assert_eq!(v[4], None);
400        assert_eq!(v.to_binstr(), "01xz");
401    }
402
403    #[test]
404    fn constructs_from_vpi_words_and_masks_unused_bits() {
405        let states = LogicArray::from_vpi_words(&[(0b0110, 0b0011)], 4);
406        assert_eq!(states.to_binstr(), "01xz");
407        assert_eq!(states.to_vpi_words(), vec![(0b0110, 0b0011)]);
408
409        let partial = LogicArray::from_vpi_words(&[(0xffff_fff5, 0xffff_fff8)], 3);
410        assert_eq!(partial.to_binstr(), "101");
411        assert!(partial.is_resolvable());
412        assert_eq!(partial.to_vpi_words(), vec![(0b101, 0)]);
413    }
414
415    #[test]
416    fn width_is_preserved_independently_of_biguint_magnitude() {
417        let zeros = LogicArray::from_binstr("00000");
418        assert_eq!(zeros.len(), 5);
419        assert_eq!(zeros.to_binstr(), "00000");
420
421        let wide = LogicArray::from_u64(1, 65);
422        assert_eq!(wide.len(), 65);
423        assert_eq!(wide[0], Some(Logic::One));
424        assert_eq!(wide[64], Some(Logic::Zero));
425        assert_eq!(u64::try_from(&wide), Ok(1));
426
427        let empty = LogicArray::from_binstr("");
428        assert!(empty.is_empty());
429        assert_eq!(empty.to_binstr(), "");
430    }
431}