1use num_bigint::{BigInt, BigUint, Sign};
2use num_traits::ToPrimitive as _;
3
4use crate::{ExprBytecode, ExprOpcode as TbOpcode, TestbenchOperator as Op};
5
6#[derive(Clone, Debug)]
10pub struct CompiledExpr {
11 bytecode: ExprBytecode,
12}
13
14#[derive(Clone, Debug)]
16pub enum TestbenchValue {
17 U64(u64),
18 Wide(BigUint),
19}
20
21impl TestbenchValue {
22 #[inline]
23 pub fn to_u64(&self) -> u64 {
24 match self {
25 TestbenchValue::U64(v) => *v,
26 TestbenchValue::Wide(v) => {
27 let digits = v.to_u64_digits();
28 digits.first().copied().unwrap_or(0)
29 }
30 }
31 }
32
33 #[inline]
34 pub fn is_zero(&self) -> bool {
35 match self {
36 TestbenchValue::U64(v) => *v == 0,
37 TestbenchValue::Wide(v) => *v == BigUint::ZERO,
38 }
39 }
40
41 #[inline]
42 pub fn to_biguint(&self) -> BigUint {
43 match self {
44 TestbenchValue::U64(v) => BigUint::from(*v),
45 TestbenchValue::Wide(v) => v.clone(),
46 }
47 }
48}
49
50impl CompiledExpr {
51 pub fn new(bytecode: ExprBytecode) -> Self {
52 Self { bytecode }
53 }
54
55 pub fn eval_u64(&self, memory: *mut u8) -> u64 {
58 self.eval(memory).to_u64()
59 }
60
61 pub fn eval_value(&self, memory: *mut u8) -> TestbenchValue {
63 self.eval(memory)
64 }
65
66 pub fn eval_bool(&self, memory: *mut u8) -> bool {
67 !self.eval(memory).is_zero()
68 }
69
70 pub fn constant_u64(&self) -> Option<u64> {
72 if self.bytecode.ops().iter().any(|op| {
73 matches!(
74 op,
75 TbOpcode::LoadU64 { .. }
76 | TbOpcode::LoadWide { .. }
77 | TbOpcode::LoadIndexed { .. }
78 | TbOpcode::LoadBitSelect { .. }
79 | TbOpcode::StoreU64 { .. }
80 )
81 }) {
82 return None;
83 }
84 Some(self.eval_u64(std::ptr::null_mut()))
85 }
86
87 fn eval(&self, memory: *mut u8) -> TestbenchValue {
91 let mut stack: Vec<TestbenchValue> = Vec::with_capacity(16);
92 let mut pc: usize = 0;
93 let ops = self.bytecode.ops();
94
95 while pc < ops.len() {
96 self.exec_at(ops, &mut pc, &mut stack, memory);
97 }
98 stack.pop().unwrap_or_else(|| {
99 debug_assert!(false, "testbench bytecode: stack empty after evaluation");
100 TestbenchValue::U64(0)
101 })
102 }
103
104 fn exec_at(
108 &self,
109 ops: &[TbOpcode],
110 pc: &mut usize,
111 stack: &mut Vec<TestbenchValue>,
112 memory: *mut u8,
113 ) {
114 match &ops[*pc] {
115 TbOpcode::ConstU64(v) => {
116 stack.push(TestbenchValue::U64(*v));
117 *pc += 1;
118 }
119 TbOpcode::ConstWide(v) => {
120 stack.push(TestbenchValue::Wide(v.clone()));
121 *pc += 1;
122 }
123 TbOpcode::LoadU64 {
124 location,
125 byte_size,
126 mask,
127 } => {
128 let val = unsafe { read_le_u64(memory.add(*location), *byte_size) } & mask;
130 stack.push(TestbenchValue::U64(val));
131 *pc += 1;
132 }
133 TbOpcode::LoadWide {
134 location,
135 byte_size,
136 width,
137 } => {
138 let val = unsafe { read_le_wide(memory.add(*location), *byte_size, *width) };
139 stack.push(TestbenchValue::Wide(val));
140 *pc += 1;
141 }
142 TbOpcode::BinOp(op) => {
143 let r = stack.pop().unwrap_or_else(|| {
144 debug_assert!(false, "testbench bytecode: BinOp rhs underflow");
145 TestbenchValue::U64(0)
146 });
147 let l = stack.pop().unwrap_or_else(|| {
148 debug_assert!(false, "testbench bytecode: BinOp lhs underflow");
149 TestbenchValue::U64(0)
150 });
151 stack.push(eval_binop(l, *op, r));
152 *pc += 1;
153 }
154 TbOpcode::TypedBinOp {
155 op,
156 lhs_width,
157 rhs_width,
158 result_width,
159 lhs_signed,
160 rhs_signed,
161 } => {
162 let r = stack.pop().unwrap_or_else(|| {
163 debug_assert!(false, "testbench bytecode: TypedBinOp rhs underflow");
164 TestbenchValue::U64(0)
165 });
166 let l = stack.pop().unwrap_or_else(|| {
167 debug_assert!(false, "testbench bytecode: TypedBinOp lhs underflow");
168 TestbenchValue::U64(0)
169 });
170 stack.push(eval_typed_binop(
171 l,
172 *op,
173 r,
174 *lhs_width,
175 *rhs_width,
176 *result_width,
177 *lhs_signed,
178 *rhs_signed,
179 ));
180 *pc += 1;
181 }
182 TbOpcode::TypedUnary {
183 op,
184 operand_width,
185 result_width,
186 } => {
187 if let Some(top) = stack.last_mut() {
188 *top = eval_typed_unop(*op, top, *operand_width, *result_width);
189 } else {
190 debug_assert!(false, "testbench bytecode: TypedUnary underflow");
191 }
192 *pc += 1;
193 }
194 TbOpcode::Resize {
195 source_width,
196 target_width,
197 signed,
198 } => {
199 if let Some(top) = stack.last_mut() {
200 *top = resize_tb_value(top, *source_width, *target_width, *signed);
201 } else {
202 debug_assert!(false, "testbench bytecode: Resize underflow");
203 }
204 *pc += 1;
205 }
206 TbOpcode::ConcatPart {
207 part_width,
208 result_width,
209 } => {
210 let part = stack.pop().unwrap_or_else(|| {
211 debug_assert!(false, "testbench bytecode: ConcatPart value underflow");
212 TestbenchValue::U64(0)
213 });
214 let accumulator = stack.pop().unwrap_or_else(|| {
215 debug_assert!(
216 false,
217 "testbench bytecode: ConcatPart accumulator underflow"
218 );
219 TestbenchValue::U64(0)
220 });
221 if let (TestbenchValue::U64(accumulator), TestbenchValue::U64(part)) =
222 (&accumulator, &part)
223 && *result_width <= 64
224 {
225 let shifted = if *part_width >= 64 {
226 0
227 } else {
228 accumulator << part_width
229 };
230 stack.push(TestbenchValue::U64(
231 shifted | (part & width_mask_u64(*part_width)),
232 ));
233 } else {
234 let value = (accumulator.to_biguint() << part_width)
235 | normalized_bits(&part, *part_width);
236 stack.push(tb_value_from_bits(value, *result_width));
237 }
238 *pc += 1;
239 }
240 TbOpcode::Ternary { then_len, else_len } => {
241 let cond = stack.pop().unwrap_or_else(|| {
242 debug_assert!(false, "testbench bytecode: Ternary cond underflow");
243 TestbenchValue::U64(0)
244 });
245 *pc += 1; if !cond.is_zero() {
247 let then_end = *pc + then_len;
248 while *pc < then_end {
249 self.exec_at(ops, pc, stack, memory);
250 }
251 *pc += else_len; } else {
253 *pc += then_len; let else_end = *pc + else_len;
255 while *pc < else_end {
256 self.exec_at(ops, pc, stack, memory);
257 }
258 }
259 }
260 TbOpcode::LoadIndexed {
261 location,
262 stride_bits,
263 base_bit_offset,
264 element_width,
265 } => {
266 let idx = stack.pop().unwrap_or_else(|| {
267 debug_assert!(false, "testbench bytecode: LoadIndexed underflow");
268 TestbenchValue::U64(0)
269 });
270 let i = idx.to_u64() as usize;
271 let bit_offset = base_bit_offset.saturating_add(i.saturating_mul(*stride_bits));
272 let val = unsafe { read_bits(memory.add(*location), bit_offset, *element_width) };
273 stack.push(val);
274 *pc += 1;
275 }
276 TbOpcode::LoadBitSelect {
277 location,
278 base_byte_size,
279 select_width,
280 } => {
281 let bit_idx = stack.pop().unwrap_or_else(|| {
282 debug_assert!(false, "testbench bytecode: LoadBitSelect underflow");
283 TestbenchValue::U64(0)
284 });
285 let shift = bit_idx.to_u64() as usize;
286 if *base_byte_size <= 8 && *select_width <= 64 {
287 let full_val = unsafe { read_le_u64(memory.add(*location), *base_byte_size) };
288 let mask = if *select_width == 64 {
289 u64::MAX
290 } else {
291 (1u64 << select_width) - 1
292 };
293 stack.push(TestbenchValue::U64((full_val >> shift) & mask));
294 } else {
295 let full_width = base_byte_size.saturating_mul(8);
296 let full_val =
297 unsafe { read_le_wide(memory.add(*location), *base_byte_size, full_width) };
298 let val = (full_val >> shift) & width_mask(*select_width);
299 stack.push(tb_value_from_bits(val, *select_width));
300 }
301 *pc += 1;
302 }
303 TbOpcode::StoreU64 {
304 location,
305 byte_size,
306 } => {
307 let val = stack.pop().unwrap_or_else(|| {
308 debug_assert!(false, "testbench bytecode: StoreU64 underflow");
309 TestbenchValue::U64(0)
310 });
311 let v = val.to_u64();
312 let bytes = v.to_le_bytes();
313 let n = (*byte_size).min(8);
314 unsafe {
315 std::ptr::copy_nonoverlapping(bytes.as_ptr(), memory.add(*location), n);
316 }
317 *pc += 1;
318 }
319 }
320 }
321}
322
323#[inline(always)]
326unsafe fn read_le_u64(ptr: *const u8, byte_size: usize) -> u64 {
327 let mut buf = [0u8; 8];
328 unsafe {
329 std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), byte_size.min(8));
330 }
331 u64::from_le_bytes(buf)
332}
333
334unsafe fn read_le_wide(ptr: *const u8, byte_size: usize, width: usize) -> BigUint {
337 let mut buf = vec![0u8; byte_size];
338 unsafe {
339 std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), byte_size);
340 }
341 let mut val = BigUint::from_bytes_le(&buf);
342 let extra_bits = byte_size * 8 - width;
343 if extra_bits > 0 {
344 val &= (BigUint::from(1u32) << width) - BigUint::from(1u32);
345 }
346 val
347}
348
349unsafe fn read_bits(ptr: *const u8, bit_offset: usize, width: usize) -> TestbenchValue {
352 let byte_offset = bit_offset / 8;
353 let sub = bit_offset % 8;
354 let span_width = sub.saturating_add(width);
355 let byte_size = span_width.div_ceil(8);
356 if span_width <= 64 {
357 let value = unsafe { read_le_u64(ptr.add(byte_offset), byte_size) } >> sub;
358 TestbenchValue::U64(value & width_mask_u64(width))
359 } else {
360 let value = unsafe { read_le_wide(ptr.add(byte_offset), byte_size, span_width) } >> sub;
361 tb_value_from_bits(value & width_mask(width), width)
362 }
363}
364
365#[inline]
370fn eval_binop(l: TestbenchValue, op: Op, r: TestbenchValue) -> TestbenchValue {
371 match (&l, &r) {
372 (TestbenchValue::U64(lv), TestbenchValue::U64(rv)) => {
373 TestbenchValue::U64(eval_binop_u64(*lv, op, *rv))
374 }
375 _ => {
376 let lv = l.to_biguint();
377 let rv = r.to_biguint();
378 match op {
380 Op::Eq
381 | Op::Ne
382 | Op::Less
383 | Op::LessEq
384 | Op::Greater
385 | Op::GreaterEq
386 | Op::LogicAnd
387 | Op::LogicOr => TestbenchValue::U64(eval_binop_wide_cmp(&lv, op, &rv)),
388 _ => TestbenchValue::Wide(eval_binop_wide(lv, op, rv)),
389 }
390 }
391 }
392}
393
394fn width_mask(width: usize) -> BigUint {
395 if width == 0 {
396 BigUint::ZERO
397 } else {
398 (BigUint::from(1u8) << width) - BigUint::from(1u8)
399 }
400}
401
402#[inline]
403fn width_mask_u64(width: usize) -> u64 {
404 match width {
405 0 => 0,
406 1..=63 => (1u64 << width) - 1,
407 _ => u64::MAX,
408 }
409}
410
411#[inline]
412fn signed_i128(value: u64, width: usize) -> i128 {
413 let value = value & width_mask_u64(width);
414 if width == 0 || width >= 64 {
415 (value as i64) as i128
416 } else if value & (1u64 << (width - 1)) == 0 {
417 value as i128
418 } else {
419 value as i128 - (1i128 << width)
420 }
421}
422
423fn normalized_bits(value: &TestbenchValue, width: usize) -> BigUint {
424 value.to_biguint() & width_mask(width)
425}
426
427fn tb_value_from_bits(value: BigUint, width: usize) -> TestbenchValue {
428 let value = value & width_mask(width);
429 if width <= 64 {
430 TestbenchValue::U64(value.to_u64().unwrap_or(0))
431 } else {
432 TestbenchValue::Wide(value)
433 }
434}
435
436fn signed_bigint(value: &TestbenchValue, width: usize) -> BigInt {
437 let raw = normalized_bits(value, width);
438 if width == 0 || !raw.bit((width - 1) as u64) {
439 BigInt::from(raw)
440 } else {
441 BigInt::from(raw) - (BigInt::from(1u8) << width)
442 }
443}
444
445fn signed_bits(value: BigInt, width: usize) -> BigUint {
446 if width == 0 {
447 return BigUint::ZERO;
448 }
449 let modulus = BigUint::from(1u8) << width;
450 match value.sign() {
451 Sign::Minus => {
452 let magnitude = (-value).to_biguint().unwrap_or_default() % &modulus;
453 if magnitude == BigUint::ZERO {
454 BigUint::ZERO
455 } else {
456 modulus - magnitude
457 }
458 }
459 _ => value.to_biguint().unwrap_or_default() % modulus,
460 }
461}
462
463fn resize_tb_value(
464 value: &TestbenchValue,
465 source_width: usize,
466 target_width: usize,
467 signed: bool,
468) -> TestbenchValue {
469 if target_width == 0 {
470 return TestbenchValue::U64(0);
471 }
472 if source_width == 0 {
473 let fill = if value.to_u64() & 1 == 0 {
474 BigUint::ZERO
475 } else {
476 width_mask(target_width)
477 };
478 return tb_value_from_bits(fill, target_width);
479 }
480
481 if let TestbenchValue::U64(value) = value
482 && source_width <= 64
483 && target_width <= 64
484 {
485 let mut value = value & width_mask_u64(source_width);
486 if target_width > source_width && signed && value & (1u64 << (source_width - 1)) != 0 {
487 value |= width_mask_u64(target_width) ^ width_mask_u64(source_width);
488 }
489 return TestbenchValue::U64(value & width_mask_u64(target_width));
490 }
491
492 let mut value = normalized_bits(value, source_width);
493 if target_width > source_width && signed && value.bit((source_width - 1) as u64) {
494 value |= width_mask(target_width) ^ width_mask(source_width);
495 }
496 tb_value_from_bits(value, target_width)
497}
498
499fn eval_typed_binop_u64(
500 l: u64,
501 op: Op,
502 r: u64,
503 lhs_width: usize,
504 rhs_width: usize,
505 result_width: usize,
506 lhs_signed: bool,
507 rhs_signed: bool,
508) -> u64 {
509 let l = l & width_mask_u64(lhs_width);
510 let r = r & width_mask_u64(rhs_width);
511 let result_mask = width_mask_u64(result_width);
512 let signed = lhs_signed && rhs_signed;
513 let bool_value = |value: bool| u64::from(value);
514
515 match op {
516 Op::Eq | Op::EqWildcard => bool_value(l == r),
517 Op::Ne | Op::NeWildcard => bool_value(l != r),
518 Op::Less if signed => bool_value(signed_i128(l, lhs_width) < signed_i128(r, rhs_width)),
519 Op::Less => bool_value(l < r),
520 Op::LessEq if signed => bool_value(signed_i128(l, lhs_width) <= signed_i128(r, rhs_width)),
521 Op::LessEq => bool_value(l <= r),
522 Op::Greater if signed => bool_value(signed_i128(l, lhs_width) > signed_i128(r, rhs_width)),
523 Op::Greater => bool_value(l > r),
524 Op::GreaterEq if signed => {
525 bool_value(signed_i128(l, lhs_width) >= signed_i128(r, rhs_width))
526 }
527 Op::GreaterEq => bool_value(l >= r),
528 Op::LogicAnd => bool_value(l != 0 && r != 0),
529 Op::LogicOr => bool_value(l != 0 || r != 0),
530 Op::Add => l.wrapping_add(r) & result_mask,
531 Op::Sub => l.wrapping_sub(r) & result_mask,
532 Op::Mul => l.wrapping_mul(r) & result_mask,
533 Op::Div if signed => {
534 let divisor = signed_i128(r, rhs_width);
535 if divisor == 0 {
536 0
537 } else {
538 (signed_i128(l, lhs_width) / divisor) as u64 & result_mask
539 }
540 }
541 Op::Div => l.checked_div(r).unwrap_or(0) & result_mask,
542 Op::Rem if signed => {
543 let divisor = signed_i128(r, rhs_width);
544 if divisor == 0 {
545 0
546 } else {
547 (signed_i128(l, lhs_width) % divisor) as u64 & result_mask
548 }
549 }
550 Op::Rem => l.checked_rem(r).unwrap_or(0) & result_mask,
551 Op::Pow => {
552 let mut exponent = r;
553 let mut base = l & result_mask;
554 let mut value = 1u64 & result_mask;
555 while exponent != 0 {
556 if exponent & 1 != 0 {
557 value = ((value as u128 * base as u128) as u64) & result_mask;
558 }
559 exponent >>= 1;
560 if exponent != 0 {
561 base = ((base as u128 * base as u128) as u64) & result_mask;
562 }
563 }
564 value
565 }
566 Op::BitAnd => (l & r) & result_mask,
567 Op::BitOr => (l | r) & result_mask,
568 Op::BitXor => (l ^ r) & result_mask,
569 Op::BitXnor => (!(l ^ r)) & result_mask,
570 Op::BitNand => (!(l & r)) & result_mask,
571 Op::BitNor => (!(l | r)) & result_mask,
572 Op::LogicShiftL | Op::ArithShiftL => {
573 if r >= result_width as u64 {
574 0
575 } else {
576 l.wrapping_shl(r as u32) & result_mask
577 }
578 }
579 Op::LogicShiftR => {
580 if r >= result_width as u64 {
581 0
582 } else {
583 (l >> r) & result_mask
584 }
585 }
586 Op::ArithShiftR if lhs_signed => {
587 let value = signed_i128(l, lhs_width);
588 if r >= result_width as u64 {
589 if value < 0 { result_mask } else { 0 }
590 } else {
591 ((value >> r) as u64) & result_mask
592 }
593 }
594 Op::ArithShiftR => {
595 if r >= result_width as u64 {
596 0
597 } else {
598 (l >> r) & result_mask
599 }
600 }
601 _ => unreachable!("operator is not a source-language binary op: {op:?}"),
602 }
603}
604
605fn eval_typed_binop(
606 l: TestbenchValue,
607 op: Op,
608 r: TestbenchValue,
609 lhs_width: usize,
610 rhs_width: usize,
611 result_width: usize,
612 lhs_signed: bool,
613 rhs_signed: bool,
614) -> TestbenchValue {
615 if let (TestbenchValue::U64(l), TestbenchValue::U64(r)) = (&l, &r)
616 && lhs_width <= 64
617 && rhs_width <= 64
618 && result_width <= 64
619 {
620 return TestbenchValue::U64(eval_typed_binop_u64(
621 *l,
622 op,
623 *r,
624 lhs_width,
625 rhs_width,
626 result_width,
627 lhs_signed,
628 rhs_signed,
629 ));
630 }
631 let lb = normalized_bits(&l, lhs_width);
632 let rb = normalized_bits(&r, rhs_width);
633 let signed = lhs_signed && rhs_signed;
634
635 let comparison = |value: bool| TestbenchValue::U64(u64::from(value));
636 match op {
637 Op::Eq | Op::EqWildcard => comparison(lb == rb),
638 Op::Ne | Op::NeWildcard => comparison(lb != rb),
639 Op::Less if signed => {
640 comparison(signed_bigint(&l, lhs_width) < signed_bigint(&r, rhs_width))
641 }
642 Op::Less => comparison(lb < rb),
643 Op::LessEq if signed => {
644 comparison(signed_bigint(&l, lhs_width) <= signed_bigint(&r, rhs_width))
645 }
646 Op::LessEq => comparison(lb <= rb),
647 Op::Greater if signed => {
648 comparison(signed_bigint(&l, lhs_width) > signed_bigint(&r, rhs_width))
649 }
650 Op::Greater => comparison(lb > rb),
651 Op::GreaterEq if signed => {
652 comparison(signed_bigint(&l, lhs_width) >= signed_bigint(&r, rhs_width))
653 }
654 Op::GreaterEq => comparison(lb >= rb),
655 Op::LogicAnd => comparison(lb != BigUint::ZERO && rb != BigUint::ZERO),
656 Op::LogicOr => comparison(lb != BigUint::ZERO || rb != BigUint::ZERO),
657 Op::Add => tb_value_from_bits(lb + rb, result_width),
658 Op::Sub => tb_value_from_bits(
659 signed_bits(BigInt::from(lb) - BigInt::from(rb), result_width),
660 result_width,
661 ),
662 Op::Mul => tb_value_from_bits(lb * rb, result_width),
663 Op::Div if signed => {
664 let divisor = signed_bigint(&r, rhs_width);
665 if divisor == BigInt::from(0u8) {
666 TestbenchValue::U64(0)
667 } else {
668 let quotient = signed_bigint(&l, lhs_width) / divisor;
669 tb_value_from_bits(signed_bits(quotient, result_width), result_width)
670 }
671 }
672 Op::Div => {
673 if rb == BigUint::ZERO {
674 TestbenchValue::U64(0)
675 } else {
676 tb_value_from_bits(lb / rb, result_width)
677 }
678 }
679 Op::Rem if signed => {
680 let divisor = signed_bigint(&r, rhs_width);
681 if divisor == BigInt::from(0u8) {
682 TestbenchValue::U64(0)
683 } else {
684 let remainder = signed_bigint(&l, lhs_width) % divisor;
685 tb_value_from_bits(signed_bits(remainder, result_width), result_width)
686 }
687 }
688 Op::Rem => {
689 if rb == BigUint::ZERO {
690 TestbenchValue::U64(0)
691 } else {
692 tb_value_from_bits(lb % rb, result_width)
693 }
694 }
695 Op::Pow => {
696 if result_width == 0 {
697 TestbenchValue::U64(0)
698 } else {
699 let modulus = BigUint::from(1u8) << result_width;
700 tb_value_from_bits(lb.modpow(&rb, &modulus), result_width)
701 }
702 }
703 Op::BitAnd => tb_value_from_bits(lb & rb, result_width),
704 Op::BitOr => tb_value_from_bits(lb | rb, result_width),
705 Op::BitXor => tb_value_from_bits(lb ^ rb, result_width),
706 Op::BitXnor => tb_value_from_bits((lb ^ rb) ^ width_mask(result_width), result_width),
707 Op::LogicShiftL | Op::ArithShiftL => {
708 let shift = rb.to_usize().unwrap_or(usize::MAX);
709 if shift >= result_width {
710 TestbenchValue::U64(0)
711 } else {
712 tb_value_from_bits(lb << shift, result_width)
713 }
714 }
715 Op::LogicShiftR => {
716 let shift = rb.to_usize().unwrap_or(usize::MAX);
717 if shift >= result_width {
718 TestbenchValue::U64(0)
719 } else {
720 tb_value_from_bits(lb >> shift, result_width)
721 }
722 }
723 Op::ArithShiftR if lhs_signed => {
724 let shift = rb.to_usize().unwrap_or(usize::MAX);
725 let value = signed_bigint(&l, lhs_width);
726 let shifted = if shift >= result_width {
727 if value.sign() == Sign::Minus {
728 BigInt::from(-1)
729 } else {
730 BigInt::from(0)
731 }
732 } else {
733 value >> shift
734 };
735 tb_value_from_bits(signed_bits(shifted, result_width), result_width)
736 }
737 Op::ArithShiftR => {
738 let shift = rb.to_usize().unwrap_or(usize::MAX);
739 if shift >= result_width {
740 TestbenchValue::U64(0)
741 } else {
742 tb_value_from_bits(lb >> shift, result_width)
743 }
744 }
745 Op::BitNand => tb_value_from_bits((lb & rb) ^ width_mask(result_width), result_width),
746 Op::BitNor => tb_value_from_bits((lb | rb) ^ width_mask(result_width), result_width),
747 _ => unreachable!("operator is not a source-language binary op: {op:?}"),
748 }
749}
750
751fn eval_typed_unop(
752 op: Op,
753 value: &TestbenchValue,
754 operand_width: usize,
755 result_width: usize,
756) -> TestbenchValue {
757 if let TestbenchValue::U64(value) = value
758 && operand_width <= 64
759 && result_width <= 64
760 {
761 let bits = value & width_mask_u64(operand_width);
762 let value = match op {
763 Op::LogicNot => u64::from(bits == 0),
764 Op::BitAnd => u64::from(bits == width_mask_u64(operand_width)),
765 Op::BitNand => u64::from(bits != width_mask_u64(operand_width)),
766 Op::BitOr => u64::from(bits != 0),
767 Op::BitNor => u64::from(bits == 0),
768 Op::BitXor => u64::from(!bits.count_ones().is_multiple_of(2)),
769 Op::BitXnor => u64::from(bits.count_ones().is_multiple_of(2)),
770 Op::Add => bits & width_mask_u64(result_width),
771 Op::Sub => bits.wrapping_neg() & width_mask_u64(result_width),
772 Op::BitNot => !bits & width_mask_u64(result_width),
773 _ => unreachable!("operator is not a source-language unary op: {op:?}"),
774 };
775 return TestbenchValue::U64(value);
776 }
777
778 let bits = normalized_bits(value, operand_width);
779 let reduced = match op {
780 Op::LogicNot => Some(bits == BigUint::ZERO),
781 Op::BitAnd => Some(bits == width_mask(operand_width)),
782 Op::BitNand => Some(bits != width_mask(operand_width)),
783 Op::BitOr => Some(bits != BigUint::ZERO),
784 Op::BitNor => Some(bits == BigUint::ZERO),
785 Op::BitXor | Op::BitXnor => {
786 let odd = bits.iter_u64_digits().map(u64::count_ones).sum::<u32>() % 2 != 0;
787 Some(if matches!(op, Op::BitXor) { odd } else { !odd })
788 }
789 _ => None,
790 };
791 if let Some(value) = reduced {
792 return TestbenchValue::U64(u64::from(value));
793 }
794
795 match op {
796 Op::Add => tb_value_from_bits(bits, result_width),
797 Op::Sub => tb_value_from_bits(signed_bits(-BigInt::from(bits), result_width), result_width),
798 Op::BitNot => tb_value_from_bits(bits ^ width_mask(operand_width), result_width),
799 _ => unreachable!("operator is not a source-language unary op: {op:?}"),
800 }
801}
802
803#[inline]
804fn eval_binop_u64(l: u64, op: Op, r: u64) -> u64 {
805 match op {
806 Op::Add => l.wrapping_add(r),
807 Op::Sub => l.wrapping_sub(r),
808 Op::Mul => l.wrapping_mul(r),
809 Op::Div => l.checked_div(r).unwrap_or(0),
810 Op::Rem => l.checked_rem(r).unwrap_or(0),
811 Op::BitAnd => l & r,
812 Op::BitOr => l | r,
813 Op::BitXor => l ^ r,
814 Op::LogicShiftL => {
815 if r >= 64 {
816 0
817 } else {
818 l << r
819 }
820 }
821 Op::LogicShiftR => {
822 if r >= 64 {
823 0
824 } else {
825 l >> r
826 }
827 }
828 Op::ArithShiftL => {
829 if r >= 64 {
830 0
831 } else {
832 l << r
833 }
834 }
835 Op::ArithShiftR => {
836 if r >= 64 {
837 ((l as i64) >> 63) as u64
838 } else {
839 ((l as i64) >> r) as u64
840 }
841 }
842 Op::Eq => (l == r) as u64,
843 Op::Ne => (l != r) as u64,
844 Op::Less => (l < r) as u64,
845 Op::LessEq => (l <= r) as u64,
846 Op::Greater => (l > r) as u64,
847 Op::GreaterEq => (l >= r) as u64,
848 Op::LogicAnd => ((l != 0) && (r != 0)) as u64,
849 Op::LogicOr => ((l != 0) || (r != 0)) as u64,
850 _ => unreachable!("operator is not testbench bytecode plumbing: {op:?}"),
851 }
852}
853
854fn eval_binop_wide(l: BigUint, op: Op, r: BigUint) -> BigUint {
855 match op {
856 Op::Add => l + r,
857 Op::Sub => {
858 if l >= r {
859 l - r
860 } else {
861 BigUint::ZERO
862 }
863 }
864 Op::Mul => l * r,
865 Op::Div => {
866 if r == BigUint::ZERO {
867 BigUint::ZERO
868 } else {
869 l / r
870 }
871 }
872 Op::Rem => {
873 if r == BigUint::ZERO {
874 BigUint::ZERO
875 } else {
876 l % r
877 }
878 }
879 Op::BitAnd => l & r,
880 Op::BitOr => l | r,
881 Op::BitXor => l ^ r,
882 Op::LogicShiftL => {
883 let s: u64 = (&r).try_into().unwrap_or(256);
884 l << s
885 }
886 Op::LogicShiftR => {
887 let s: u64 = (&r).try_into().unwrap_or(256);
888 l >> s
889 }
890 _ => unreachable!("operator is not wide testbench bytecode plumbing: {op:?}"),
891 }
892}
893
894fn eval_binop_wide_cmp(l: &BigUint, op: Op, r: &BigUint) -> u64 {
895 match op {
896 Op::Eq => (l == r) as u64,
897 Op::Ne => (l != r) as u64,
898 Op::Less => (l < r) as u64,
899 Op::LessEq => (l <= r) as u64,
900 Op::Greater => (l > r) as u64,
901 Op::GreaterEq => (l >= r) as u64,
902 Op::LogicAnd => ((*l != BigUint::ZERO) && (*r != BigUint::ZERO)) as u64,
903 Op::LogicOr => ((*l != BigUint::ZERO) || (*r != BigUint::ZERO)) as u64,
904 _ => unreachable!("operator is not testbench comparison plumbing: {op:?}"),
905 }
906}