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