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 packed_bytes(value: &BigUint, width: usize) -> Vec<u8> {
50    let mut bytes = masked_value(value, width).to_bytes_be();
51    let count = width.div_ceil(8);
52    if bytes.len() < count {
53        let mut padded = vec![0; count - bytes.len()];
54        padded.extend(bytes);
55        bytes = padded;
56    }
57    bytes.truncate(count);
58    bytes
59}
60
61/// Format an argument as bytes. Character conversions never decode UTF-8.
62pub fn format_display_arg(arg: &DisplayFormatArg<'_>, spec: Option<char>) -> Vec<u8> {
63    if arg.is_string {
64        return packed_bytes(arg.value, arg.width);
65    }
66    match spec.unwrap_or('d').to_ascii_lowercase() {
67        'c' if !has_mask(arg) => {
68            vec![
69                (masked_value(arg.value, arg.width) & BigUint::from(0xffu8))
70                    .to_u8()
71                    .unwrap(),
72            ]
73        }
74        's' if !has_mask(arg) => packed_bytes(arg.value, arg.width),
75        _ => format_numeric_arg(arg, spec).into_bytes(),
76    }
77}
78
79fn format_binary(arg: &DisplayFormatArg<'_>) -> String {
80    let digits = arg.width.max(1);
81    if !has_mask(arg) {
82        let mut out = masked_value(arg.value, arg.width).to_str_radix(2);
83        if out.len() < digits {
84            out.insert_str(0, &"0".repeat(digits - out.len()));
85        }
86        return out;
87    }
88    let mask = arg.mask.expect("masked argument");
89    let mut out = String::with_capacity(digits);
90    for bit_idx in (0..digits).rev() {
91        if bit(mask, bit_idx) {
92            out.push('x');
93        } else if bit(arg.value, bit_idx) {
94            out.push('1');
95        } else {
96            out.push('0');
97        }
98    }
99    out
100}
101
102fn format_masked_radix(arg: &DisplayFormatArg<'_>, bits_per_digit: usize) -> String {
103    let mask = arg.mask.expect("masked argument");
104    let digits = arg.width.div_ceil(bits_per_digit).max(1);
105    let mut out = String::with_capacity(digits);
106    for digit_idx in (0..digits).rev() {
107        let start = digit_idx * bits_per_digit;
108        let end = (start + bits_per_digit).min(arg.width);
109        if (start..end).any(|bit_idx| bit(mask, bit_idx)) {
110            out.push('x');
111            continue;
112        }
113        let mut digit = 0u32;
114        for bit_idx in start..end {
115            if bit(arg.value, bit_idx) {
116                digit |= 1 << (bit_idx - start);
117            }
118        }
119        out.push(char::from_digit(digit, 1 << bits_per_digit).unwrap());
120    }
121    out
122}
123
124fn format_numeric_arg(arg: &DisplayFormatArg<'_>, spec: Option<char>) -> String {
125    match spec.unwrap_or('d') {
126        'b' | 'B' => format_binary(arg),
127        'o' | 'O' => {
128            if has_mask(arg) {
129                format_masked_radix(arg, 3)
130            } else {
131                masked_value(arg.value, arg.width).to_str_radix(8)
132            }
133        }
134        'x' | 'h' => {
135            if has_mask(arg) {
136                format_masked_radix(arg, 4)
137            } else {
138                masked_value(arg.value, arg.width).to_str_radix(16)
139            }
140        }
141        'X' | 'H' => {
142            let mut out = if has_mask(arg) {
143                format_masked_radix(arg, 4)
144            } else {
145                masked_value(arg.value, arg.width).to_str_radix(16)
146            };
147            out.make_ascii_uppercase();
148            out
149        }
150        'd' | 'D' | 'i' | 'I' => {
151            if has_mask(arg) {
152                "x".to_string()
153            } else if arg.signed {
154                value_to_signed_bigint(arg.value, arg.width).to_string()
155            } else {
156                masked_value(arg.value, arg.width).to_string()
157            }
158        }
159        'c' | 'C' | 's' | 'S' => "x".to_string(),
160        _ => {
161            if has_mask(arg) {
162                "x".to_string()
163            } else if arg.signed {
164                value_to_signed_bigint(arg.value, arg.width).to_string()
165            } else {
166                masked_value(arg.value, arg.width).to_string()
167            }
168        }
169    }
170}
171
172/// Number of characters the largest value of `arg` takes in decimal.
173fn decimal_digits(arg: &DisplayFormatArg<'_>) -> usize {
174    if arg.width == 0 {
175        return 1;
176    }
177    if arg.signed {
178        // The most negative value, with its sign, is the widest.
179        ((BigUint::from(1u8) << (arg.width - 1)).to_string().len()) + 1
180    } else {
181        ((BigUint::from(1u8) << arg.width) - BigUint::from(1u8))
182            .to_string()
183            .len()
184    }
185}
186
187/// Formats `arg` with the sizing of IEEE 1800-2023 21.2.1.2. Without a
188/// `field_width`, hexadecimal, octal, and binary values take as many digits
189/// as the largest value of the argument's width, with leading zeros, and
190/// decimal values as many characters, with leading spaces. With one, the
191/// value is padded to that width (`0` displays it in the minimum width), and
192/// a wider value is never truncated.
193pub fn format_sized_display_arg(
194    arg: &DisplayFormatArg<'_>,
195    spec: char,
196    field_width: Option<usize>,
197) -> Vec<u8> {
198    format_sv_display_arg(
199        arg,
200        spec,
201        FieldSpec {
202            width: field_width,
203            ..FieldSpec::default()
204        },
205    )
206}
207
208/// IEEE display sizing with the `-` and leading `0` field flags supported
209/// by Icarus and Verilator. As in Icarus, decimal zero padding follows the sign;
210/// left justification takes precedence over zero padding.
211pub fn format_sv_display_arg(arg: &DisplayFormatArg<'_>, spec: char, field: FieldSpec) -> Vec<u8> {
212    let field_width = field.width;
213    // Table 21-1 gives each uppercase specifier the meaning of its lowercase
214    // one, so `%H` prints lowercase digits like `%h`.
215    let spec = spec.to_ascii_lowercase();
216    // Packed string literals still undergo an explicit numeric conversion.
217    let arg = &DisplayFormatArg {
218        value: arg.value,
219        mask: arg.mask,
220        width: arg.width,
221        signed: arg.signed,
222        is_string: arg.is_string && spec == 's',
223    };
224    if spec == 's' && !has_mask(arg) {
225        // Integral %s is right-justified byte data; omit leading zero bytes
226        // and include a partial most-significant byte (21.2.1.7).
227        let bytes = packed_bytes(arg.value, arg.width);
228        let start = bytes
229            .iter()
230            .position(|byte| *byte != 0)
231            .unwrap_or(bytes.len());
232        return pad_veryl_field(
233            bytes[start..].to_vec(),
234            spec,
235            FieldSpec {
236                width: Some(field_width.unwrap_or_else(|| arg.width.div_ceil(8))),
237                ..field
238            },
239        );
240    }
241    if spec == 'c' {
242        return pad_veryl_field(format_display_arg(arg, Some(spec)), spec, field);
243    }
244    let text = format_numeric_arg(arg, Some(spec));
245    let bits_per_digit = match spec {
246        'b' => Some(1),
247        'o' => Some(3),
248        'x' | 'h' => Some(4),
249        _ => None,
250    };
251    match bits_per_digit {
252        Some(bits_per_digit) => {
253            let text = if has_mask(arg) {
254                radix_digits(arg, bits_per_digit)
255            } else {
256                text
257            };
258            if field.left_justify {
259                // Icarus retains the argument's radix digits before padding
260                // on the right (Verilator instead uses the minimum digits).
261                let width = arg.width.div_ceil(bits_per_digit).max(1);
262                return pad_veryl_field(pad(text, '0', width).into_bytes(), spec, field);
263            }
264            let minimal = text.trim_start_matches('0');
265            let minimal = if minimal.is_empty() { "0" } else { minimal };
266            let width = field_width.unwrap_or_else(|| arg.width.div_ceil(bits_per_digit).max(1));
267            pad(minimal.to_string(), '0', width)
268        }
269        None if matches!(spec, 'd' | 'i') => {
270            let text = match unknown_group(arg, 0, arg.width) {
271                Some(digit) => digit.to_string(),
272                None => text,
273            };
274            let width = field_width.unwrap_or_else(|| decimal_digits(arg));
275            return pad_veryl_field(
276                text.into_bytes(),
277                spec,
278                FieldSpec {
279                    width: Some(width),
280                    ..field
281                },
282            );
283        }
284        None => return pad_veryl_field(text.into_bytes(), spec, field),
285    }
286    .into_bytes()
287}
288
289/// The character IEEE 1800-2023 21.2.1.3 displays for the bits `start..end`
290/// when any of them is unknown: lowercase when all of them are X (or all Z),
291/// uppercase when only some are, and X over Z when both appear.
292fn unknown_group(arg: &DisplayFormatArg<'_>, start: usize, end: usize) -> Option<char> {
293    let mask = arg.mask?;
294    let (mut x, mut z) = (0, 0);
295    for bit_idx in start..end {
296        if bit(mask, bit_idx) {
297            if bit(arg.value, bit_idx) {
298                x += 1;
299            } else {
300                z += 1;
301            }
302        }
303    }
304    let bits = end - start;
305    match (x, z) {
306        (0, 0) => None,
307        (x, _) if x == bits => Some('x'),
308        (0, z) if z == bits => Some('z'),
309        (0, _) => Some('Z'),
310        _ => Some('X'),
311    }
312}
313
314/// Hexadecimal, octal, or binary digits of a value, one for each group of
315/// its bits, unknown ones included.
316fn radix_digits(arg: &DisplayFormatArg<'_>, bits_per_digit: usize) -> String {
317    let digits = arg.width.div_ceil(bits_per_digit).max(1);
318    (0..digits)
319        .rev()
320        .map(|digit_idx| {
321            let start = digit_idx * bits_per_digit;
322            let end = (start + bits_per_digit).min(arg.width);
323            unknown_group(arg, start, end).unwrap_or_else(|| {
324                let digit = (start..end)
325                    .filter(|&bit_idx| bit(arg.value, bit_idx))
326                    .fold(0, |digit, bit_idx| digit | 1 << (bit_idx - start));
327                char::from_digit(digit, 1 << bits_per_digit).unwrap()
328            })
329        })
330        .collect()
331}
332
333/// Ceiling on a field width: the digits come from source text, and a wider
334/// field would only be an allocation the source sizes by typing. Veryl's
335/// simulator caps its field widths the same way.
336pub const MAX_FIELD_WIDTH: usize = 1 << 16;
337
338/// The flags and field width between `%` and a conversion character.
339#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
340pub struct FieldSpec {
341    pub width: Option<usize>,
342    /// `-`: pad on the right when flag parsing is enabled.
343    pub left_justify: bool,
344    /// A leading `0` digit: pad numbers with zeros.
345    pub zero_pad: bool,
346}
347
348impl FieldSpec {
349    /// Read the flags and width that follow a `%`. `-` is a flag only with
350    /// `c_flags`; otherwise it is left as the conversion character.
351    pub fn parse(
352        chars: &mut std::iter::Peekable<impl Iterator<Item = char>>,
353        c_flags: bool,
354    ) -> Self {
355        let left_justify = c_flags && chars.next_if_eq(&'-').is_some();
356        let zero_pad = chars.peek() == Some(&'0');
357        let mut width = None;
358        while let Some(digit) = chars.peek().and_then(|c| c.to_digit(10)) {
359            chars.next();
360            width = Some(
361                width
362                    .unwrap_or(0usize)
363                    .saturating_mul(10)
364                    .saturating_add(digit as usize)
365                    .min(MAX_FIELD_WIDTH),
366            );
367        }
368        Self {
369            width,
370            left_justify,
371            zero_pad,
372        }
373    }
374}
375
376/// Formats `arg` as Veryl's simulator does. Hexadecimal, octal, and binary
377/// values take a digit for each group of the argument's bits, and decimal
378/// values their minimum width. With `%0`, radices drop their leading zeros.
379/// A field width then pads as [`pad_veryl_field`] does.
380pub fn format_veryl_display_arg(
381    arg: &DisplayFormatArg<'_>,
382    spec: char,
383    field: FieldSpec,
384) -> Vec<u8> {
385    let spec = spec.to_ascii_lowercase();
386    let bits_per_digit = match spec {
387        'b' => Some(1),
388        'o' => Some(3),
389        'x' | 'h' => Some(4),
390        _ => None,
391    };
392    let body = match bits_per_digit {
393        _ if arg.is_string => format_display_arg(arg, Some(spec)),
394        Some(bits_per_digit) => {
395            let digits = radix_digits(arg, bits_per_digit);
396            if field.width == Some(0) {
397                let minimal = digits.trim_start_matches('0');
398                if minimal.is_empty() { "0" } else { minimal }.to_string()
399            } else {
400                digits
401            }
402            .into_bytes()
403        }
404        // Veryl prints a lowercase x for a decimal value with any unknown bit.
405        None if matches!(spec, 'd' | 'i') && has_mask(arg) => b"x".to_vec(),
406        None => format_display_arg(arg, Some(spec)),
407    };
408    pad_veryl_field(body, spec, field)
409}
410
411/// Pads a converted value to its field width as Veryl's simulator does: on
412/// the left with spaces, on the right with `-`, or with zeros behind the sign
413/// for a number with a leading `0`. A longer value is never truncated.
414pub fn pad_veryl_field(mut body: Vec<u8>, spec: char, field: FieldSpec) -> Vec<u8> {
415    let Some(width) = field.width else {
416        return body;
417    };
418    if body.len() >= width {
419        return body;
420    }
421    let count = width - body.len();
422    let numeric = matches!(spec.to_ascii_lowercase(), 'd' | 'i' | 'h' | 'x' | 'o' | 'b');
423    if field.left_justify {
424        body.extend(std::iter::repeat_n(b' ', count));
425    } else {
426        let zero = field.zero_pad && numeric;
427        let at = usize::from(zero && body.starts_with(b"-"));
428        body.splice(
429            at..at,
430            std::iter::repeat_n(if zero { b'0' } else { b' ' }, count),
431        );
432    }
433    body
434}
435
436fn pad(text: String, fill: char, width: usize) -> String {
437    let len = text.chars().count();
438    pad_counted(text, fill, width, len)
439}
440
441fn pad_counted(text: String, fill: char, width: usize, len: usize) -> String {
442    if len >= width {
443        return text;
444    }
445    let mut out: String = std::iter::repeat_n(fill, width - len).collect();
446    out.push_str(&text);
447    out
448}
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453
454    #[test]
455    fn character_conversions_preserve_each_byte_and_count_byte_columns() {
456        let value = BigUint::from(0xff0080u32);
457        let arg = DisplayFormatArg {
458            value: &value,
459            mask: None,
460            width: 24,
461            signed: false,
462            is_string: false,
463        };
464        assert_eq!(format_sized_display_arg(&arg, 's', None), [255, 0, 128]);
465        assert_eq!(
466            format_sized_display_arg(&arg, 's', Some(5)),
467            [32, 32, 255, 0, 128]
468        );
469        assert_eq!(format_sized_display_arg(&arg, 'c', None), [128]);
470        assert_eq!(
471            format_veryl_display_arg(&arg, 's', FieldSpec::default()),
472            [255, 0, 128]
473        );
474        let value = (BigUint::from(1u8) << 64) | BigUint::from(0xfeu8);
475        let arg = DisplayFormatArg {
476            value: &value,
477            mask: None,
478            width: 65,
479            signed: false,
480            is_string: false,
481        };
482        assert_eq!(format_sized_display_arg(&arg, 'c', None), [254]);
483        let value = BigUint::from(0u8);
484        let arg = DisplayFormatArg {
485            value: &value,
486            mask: None,
487            width: 0,
488            signed: false,
489            is_string: false,
490        };
491        assert_eq!(format_sized_display_arg(&arg, 's', Some(0)), []);
492    }
493
494    fn sized(value: u32, width: usize, signed: bool, spec: char, field: Option<usize>) -> String {
495        let value = BigUint::from(value);
496        let arg = DisplayFormatArg {
497            value: &value,
498            mask: None,
499            width,
500            signed,
501            is_string: false,
502        };
503        String::from_utf8(format_sized_display_arg(&arg, spec, field)).unwrap()
504    }
505
506    #[test]
507    fn sizes_values_as_ieee_1800_2023_21_2_1_2_shows() {
508        assert_eq!(sized(10, 32, false, 'd', None), "        10");
509        assert_eq!(sized(10, 32, false, 'd', Some(0)), "10");
510        assert_eq!(sized(10, 32, false, 'h', None), "0000000a");
511        assert_eq!(sized(10, 32, false, 'h', Some(0)), "a");
512        assert_eq!(sized(5, 32, false, 'd', Some(3)), "  5");
513        assert_eq!(sized(1234, 32, false, 'd', Some(3)), "1234");
514        assert_eq!(sized(5, 32, false, 'h', Some(3)), "005");
515        assert_eq!(sized(0x1234, 32, false, 'h', Some(3)), "1234");
516        assert_eq!(sized(0, 8, false, 'b', Some(0)), "0");
517        assert_eq!(sized(0x80, 8, true, 'd', None), "-128");
518        assert_eq!(sized(0xab, 8, false, 'H', None), "ab");
519        // 16'hc3a9 is two 8-bit codes, one UTF-8 character.
520        let value = BigUint::from(0xc3a9u32);
521        let arg = DisplayFormatArg {
522            value: &value,
523            mask: None,
524            width: 16,
525            signed: false,
526            is_string: true,
527        };
528        assert_eq!(
529            format_sized_display_arg(&arg, 's', Some(3)),
530            " \u{e9}".as_bytes()
531        );
532    }
533
534    fn four_state(payload: u32, mask: u32, width: usize, spec: char) -> String {
535        let (value, mask) = (BigUint::from(payload), BigUint::from(mask));
536        let arg = DisplayFormatArg {
537            value: &value,
538            mask: Some(&mask),
539            width,
540            signed: false,
541            is_string: false,
542        };
543        String::from_utf8(format_sized_display_arg(&arg, spec, None)).unwrap()
544    }
545
546    #[test]
547    fn displays_unknown_bits_as_ieee_1800_2023_21_2_1_3_shows() {
548        // X is payload 1 / mask 1, Z is payload 0 / mask 1.
549        assert_eq!(four_state(1, 1, 1, 'd'), "x");
550        // 14'bx01010
551        assert_eq!(four_state(0x3fea, 0x3fe0, 14, 'h'), "xxXa");
552        // 12'b001xxx101x01 as %h and %o
553        assert_eq!(four_state(0x3ed, 0x1c4, 12, 'h'), "XXX");
554        assert_eq!(four_state(0x3ed, 0x1c4, 12, 'o'), "1x5X");
555        assert_eq!(four_state(0b1000, 0b1100, 4, 'b'), "xz00");
556        assert_eq!(four_state(0, 0xff, 8, 'h'), "zz");
557        assert_eq!(four_state(0, 0x0f, 8, 'h'), "0z");
558        assert_eq!(four_state(0x01, 0x03, 8, 'h'), "0X");
559        assert_eq!(four_state(0, 0xff, 8, 'd'), "  z");
560        assert_eq!(four_state(0, 0x01, 8, 'd'), "  Z");
561        assert_eq!(four_state(0x01, 0x03, 8, 'd'), "  X");
562    }
563
564    fn veryl(payload: u32, mask: u32, width: usize, signed: bool, spec: &str) -> String {
565        let (value, mask) = (BigUint::from(payload), BigUint::from(mask));
566        let arg = DisplayFormatArg {
567            value: &value,
568            mask: Some(&mask),
569            width,
570            signed,
571            is_string: false,
572        };
573        let mut chars = spec.chars().peekable();
574        let field = FieldSpec::parse(&mut chars, true);
575        String::from_utf8(format_veryl_display_arg(&arg, chars.next().unwrap(), field)).unwrap()
576    }
577
578    #[test]
579    fn formats_as_veryls_simulator() {
580        // The cases of Veryl's display_format_tests.
581        assert_eq!(veryl(10, 0, 8, false, "0d"), "10");
582        assert_eq!(veryl(11, 0, 8, false, "d"), "11");
583        assert_eq!(veryl(12, 0, 32, false, "h"), "0000000c");
584        assert_eq!(veryl(12, 0, 32, false, "0h"), "c");
585        assert_eq!(veryl(7, 0, 8, false, "5d"), "    7");
586        assert_eq!(veryl(7, 0, 8, false, "-5d"), "7    ");
587        assert_eq!(veryl(9, 0, 32, false, "2h"), "00000009");
588        assert_eq!(veryl(7, 0, 8, false, "05d"), "00007");
589        assert_eq!(veryl(0xc, 0, 8, false, "04h"), "000c");
590        assert_eq!(veryl(0xf9, 0, 8, true, "05d"), "-0007");
591        assert_eq!(veryl(7, 0, 8, false, "-05d"), "7    ");
592        assert_eq!(veryl(0x41, 0, 8, false, "05c"), "    A");
593        // Uppercase specifiers print lowercase digits, and unknown digits
594        // follow the IEEE rules; a decimal value with any is a lowercase x.
595        assert_eq!(veryl(0xab, 0, 8, false, "H"), "ab");
596        assert_eq!(veryl(0x01, 0x03, 8, false, "h"), "0X");
597        assert_eq!(veryl(0b1000, 0b1100, 4, false, "b"), "xz00");
598        assert_eq!(veryl(0, 0x01, 8, false, "d"), "x");
599    }
600
601    #[test]
602    fn sv_decimal_flags_preserve_sign_and_do_not_truncate() {
603        let value = BigUint::from(0xd6u8); // -42
604        let arg = DisplayFormatArg {
605            value: &value,
606            mask: None,
607            width: 8,
608            signed: true,
609            is_string: false,
610        };
611        // Icarus places zeros behind the sign; Verilator instead prints 00-42.
612        for (format, expected) in [("05d", "-0042"), ("-05d", "-42  "), ("02d", "-42")] {
613            let mut chars = format.chars().peekable();
614            let field = FieldSpec::parse(&mut chars, true);
615            assert_eq!(
616                format_sv_display_arg(&arg, chars.next().unwrap(), field),
617                expected.as_bytes()
618            );
619        }
620    }
621
622    #[test]
623    fn sv_left_justified_radices_keep_icarus_digit_sizing() {
624        let value = BigUint::from(5u8);
625        let arg = DisplayFormatArg {
626            value: &value,
627            mask: None,
628            width: 8,
629            signed: false,
630            is_string: false,
631        };
632        let field = FieldSpec {
633            width: Some(5),
634            left_justify: true,
635            zero_pad: false,
636        };
637        assert_eq!(format_sv_display_arg(&arg, 'h', field), b"05   ");
638        assert_eq!(format_sv_display_arg(&arg, 'b', field), b"00000101");
639    }
640
641    #[test]
642    fn caps_field_widths() {
643        let mut chars = "99999999999999999999d".chars().peekable();
644        assert_eq!(
645            FieldSpec::parse(&mut chars, true).width,
646            Some(MAX_FIELD_WIDTH)
647        );
648        assert_eq!(chars.next(), Some('d'));
649        let mut chars = "-5d".chars().peekable();
650        assert_eq!(FieldSpec::parse(&mut chars, false), FieldSpec::default());
651    }
652
653    #[test]
654    fn sizing_keeps_unknown_digits() {
655        let value = BigUint::from(0u8);
656        let mask = BigUint::from(0xf0u8);
657        let arg = DisplayFormatArg {
658            value: &value,
659            mask: Some(&mask),
660            width: 12,
661            signed: false,
662            is_string: false,
663        };
664        assert_eq!(format_sized_display_arg(&arg, 'h', None), b"0z0");
665        assert_eq!(format_sized_display_arg(&arg, 'h', Some(0)), b"z0");
666    }
667}