celox_testbench/
format.rs1use 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}