Skip to main content

celox_testbench/
format.rs

1use num_bigint::{BigInt, BigUint, Sign};
2use num_traits::ToPrimitive as _;
3
4pub struct DisplayFormatArg<'a> {
5    pub value: &'a BigUint,
6    pub mask: Option<&'a BigUint>,
7    pub width: usize,
8    pub signed: bool,
9    pub is_string: bool,
10}
11
12fn bit(value: &BigUint, bit: usize) -> bool {
13    ((value >> bit) & BigUint::from(1u8)) != BigUint::from(0u8)
14}
15
16fn has_mask(arg: &DisplayFormatArg<'_>) -> bool {
17    let Some(mask) = arg.mask else {
18        return false;
19    };
20    for bit_idx in 0..arg.width {
21        if bit(mask, bit_idx) {
22            return true;
23        }
24    }
25    false
26}
27
28fn masked_value(value: &BigUint, width: usize) -> BigUint {
29    if width > 0 {
30        value & ((BigUint::from(1u8) << width) - BigUint::from(1u8))
31    } else {
32        BigUint::from(0u8)
33    }
34}
35
36fn value_to_signed_bigint(value: &BigUint, width: usize) -> BigInt {
37    if width == 0 {
38        return BigInt::from(0);
39    }
40    let unsigned = masked_value(value, width);
41    let sign_bit = BigUint::from(1u8) << (width - 1);
42    if (&unsigned & &sign_bit) != BigUint::from(0u8) {
43        BigInt::from_biguint(Sign::Plus, unsigned) - (BigInt::from(1u8) << width)
44    } else {
45        BigInt::from_biguint(Sign::Plus, unsigned)
46    }
47}
48
49fn value_to_utf8(value: &BigUint, width: usize) -> Option<String> {
50    if !width.is_multiple_of(8) {
51        return None;
52    }
53    let num_bytes = width / 8;
54    let mut bytes = vec![0u8; num_bytes];
55    let mut payload = masked_value(value, width);
56    let byte_mask = BigUint::from(0xffu64);
57    for idx in (0..num_bytes).rev() {
58        bytes[idx] = (&payload & &byte_mask).to_u64().unwrap_or(0) as u8;
59        payload >>= 8;
60    }
61    String::from_utf8(bytes).ok()
62}
63
64fn format_binary(arg: &DisplayFormatArg<'_>) -> String {
65    let digits = arg.width.max(1);
66    if !has_mask(arg) {
67        let mut out = masked_value(arg.value, arg.width).to_str_radix(2);
68        if out.len() < digits {
69            out.insert_str(0, &"0".repeat(digits - out.len()));
70        }
71        return out;
72    }
73    let mask = arg.mask.expect("masked argument");
74    let mut out = String::with_capacity(digits);
75    for bit_idx in (0..digits).rev() {
76        if bit(mask, bit_idx) {
77            out.push('x');
78        } else if bit(arg.value, bit_idx) {
79            out.push('1');
80        } else {
81            out.push('0');
82        }
83    }
84    out
85}
86
87fn format_masked_radix(arg: &DisplayFormatArg<'_>, bits_per_digit: usize) -> String {
88    let mask = arg.mask.expect("masked argument");
89    let digits = arg.width.div_ceil(bits_per_digit).max(1);
90    let mut out = String::with_capacity(digits);
91    for digit_idx in (0..digits).rev() {
92        let start = digit_idx * bits_per_digit;
93        let end = (start + bits_per_digit).min(arg.width);
94        if (start..end).any(|bit_idx| bit(mask, bit_idx)) {
95            out.push('x');
96            continue;
97        }
98        let mut digit = 0u32;
99        for bit_idx in start..end {
100            if bit(arg.value, bit_idx) {
101                digit |= 1 << (bit_idx - start);
102            }
103        }
104        out.push(char::from_digit(digit, 1 << bits_per_digit).unwrap());
105    }
106    out
107}
108
109pub fn format_display_arg(arg: &DisplayFormatArg<'_>, spec: Option<char>) -> String {
110    if arg.is_string {
111        return value_to_utf8(arg.value, arg.width).unwrap_or_else(|| format!("{:?}", arg.value));
112    }
113    match spec.unwrap_or('d') {
114        'b' | 'B' => format_binary(arg),
115        'o' | 'O' => {
116            if has_mask(arg) {
117                format_masked_radix(arg, 3)
118            } else {
119                masked_value(arg.value, arg.width).to_str_radix(8)
120            }
121        }
122        'x' | 'h' => {
123            if has_mask(arg) {
124                format_masked_radix(arg, 4)
125            } else {
126                masked_value(arg.value, arg.width).to_str_radix(16)
127            }
128        }
129        'X' | 'H' => {
130            let mut out = if has_mask(arg) {
131                format_masked_radix(arg, 4)
132            } else {
133                masked_value(arg.value, arg.width).to_str_radix(16)
134            };
135            out.make_ascii_uppercase();
136            out
137        }
138        'd' | 'D' | 'i' | 'I' => {
139            if has_mask(arg) {
140                "x".to_string()
141            } else if arg.signed {
142                value_to_signed_bigint(arg.value, arg.width).to_string()
143            } else {
144                masked_value(arg.value, arg.width).to_string()
145            }
146        }
147        'c' | 'C' => {
148            if has_mask(arg) {
149                "x".to_string()
150            } else {
151                char::from((masked_value(arg.value, arg.width).to_u64().unwrap_or(0) & 0xff) as u8)
152                    .to_string()
153            }
154        }
155        's' | 'S' => {
156            if has_mask(arg) {
157                "x".to_string()
158            } else {
159                value_to_utf8(arg.value, arg.width).unwrap_or_else(|| format!("{:?}", arg.value))
160            }
161        }
162        _ => {
163            if has_mask(arg) {
164                "x".to_string()
165            } else if arg.signed {
166                value_to_signed_bigint(arg.value, arg.width).to_string()
167            } else {
168                masked_value(arg.value, arg.width).to_string()
169            }
170        }
171    }
172}