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
61pub 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
172fn decimal_digits(arg: &DisplayFormatArg<'_>) -> usize {
174 if arg.width == 0 {
175 return 1;
176 }
177 if arg.signed {
178 ((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
187pub 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
208pub fn format_sv_display_arg(arg: &DisplayFormatArg<'_>, spec: char, field: FieldSpec) -> Vec<u8> {
212 let field_width = field.width;
213 let spec = spec.to_ascii_lowercase();
216 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 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 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
289fn 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
314fn 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
333pub const MAX_FIELD_WIDTH: usize = 1 << 16;
337
338#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
340pub struct FieldSpec {
341 pub width: Option<usize>,
342 pub left_justify: bool,
344 pub zero_pad: bool,
346}
347
348impl FieldSpec {
349 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
376pub 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 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
411pub 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 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 assert_eq!(four_state(1, 1, 1, 'd'), "x");
550 assert_eq!(four_state(0x3fea, 0x3fe0, 14, 'h'), "xxXa");
552 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 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 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); let arg = DisplayFormatArg {
605 value: &value,
606 mask: None,
607 width: 8,
608 signed: true,
609 is_string: false,
610 };
611 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}