1use std::{array, marker::PhantomData, ops::Deref};
4
5use binius_core::oracle::ShiftVariant;
6use binius_field::{
7 Field, PackedExtension, PackedField, PackedFieldIndexable, PackedSubfield, TowerField,
8 packed::set_packed_slice,
9};
10use itertools::izip;
11
12use crate::builder::{
13 B32, B64, B128, TableBuilder, column::Col, types::B1, witness::TableWitnessSegment,
14};
15
16#[derive(Debug)]
21pub struct U32Add {
22 pub xin: Col<B1, 32>,
24 pub yin: Col<B1, 32>,
25
26 cin: Col<B1, 32>,
28 cout: Col<B1, 32>,
29 cout_shl: Col<B1, 32>,
30
31 pub zout: Col<B1, 32>,
35 pub final_carry: Option<Col<B1>>,
37 pub flags: U32AddFlags,
39}
40
41#[derive(Debug, Default, Clone)]
43pub struct U32AddFlags {
44 pub carry_in_bit: Option<Col<B1, 32>>,
47 pub commit_zout: bool,
48 pub expose_final_carry: bool,
49}
50
51impl U32Add {
52 pub fn new(
53 table: &mut TableBuilder,
54 xin: Col<B1, 32>,
55 yin: Col<B1, 32>,
56 flags: U32AddFlags,
57 ) -> Self {
58 let cout = table.add_committed::<B1, 32>("cout");
59 let cout_shl = table.add_shifted("cout_shl", cout, 5, 1, ShiftVariant::LogicalLeft);
60
61 let cin = if let Some(carry_in_bit) = flags.carry_in_bit {
62 table.add_computed("cin", cout_shl + carry_in_bit)
63 } else {
64 cout_shl
65 };
66
67 let final_carry = flags
68 .expose_final_carry
69 .then(|| table.add_selected("final_carry", cout, 31));
70
71 table.assert_zero("carry_out", (xin + cin) * (yin + cin) + cin - cout);
72
73 let zout = if flags.commit_zout {
74 let zout = table.add_committed::<B1, 32>("zout");
75 table.assert_zero("zout", xin + yin + cin - zout);
76 zout
77 } else {
78 table.add_computed("zout", xin + yin + cin)
79 };
80
81 Self {
82 xin,
83 yin,
84 cin,
85 cout,
86 cout_shl,
87 final_carry,
88 zout,
89 flags,
90 }
91 }
92
93 pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
94 where
95 P: PackedFieldIndexable<Scalar = B128> + PackedExtension<B1>,
96 {
97 let xin: std::cell::RefMut<'_, [u32]> = index.get_mut_as(self.xin)?;
98 let yin = index.get_mut_as(self.yin)?;
99 let mut cout = index.get_mut_as(self.cout)?;
100 let mut zout = index.get_mut_as(self.zout)?;
101 let mut final_carry = if let Some(final_carry) = self.final_carry {
102 let final_carry = index.get_mut(final_carry)?;
103 Some(final_carry)
104 } else {
105 None
106 };
107
108 if let Some(carry_in_bit_col) = self.flags.carry_in_bit {
109 let carry_in_bit = index.get_mut_as(carry_in_bit_col)?;
111
112 let mut cin = index.get_mut_as(self.cin)?;
113 let mut cout_shl = index.get_mut_as(self.cout_shl)?;
114 for i in 0..index.size() {
115 let (x_plus_y, carry0) = xin[i].overflowing_add(yin[i]);
116 let carry1;
117 (zout[i], carry1) = x_plus_y.overflowing_add(carry_in_bit[i]);
118 let carry = carry0 | carry1;
119
120 cin[i] = xin[i] ^ yin[i] ^ zout[i];
121 cout[i] = (carry as u32) << 31 | cin[i] >> 1;
122 cout_shl[i] = cout[i] << 1;
123
124 if let Some(ref mut final_carry) = final_carry {
125 set_packed_slice(&mut *final_carry, i, if carry { B1::ONE } else { B1::ZERO });
126 }
127 }
128 } else {
129 let mut cin = index.get_mut_as(self.cin)?;
131 for i in 0..index.size() {
132 let carry;
133 (zout[i], carry) = xin[i].overflowing_add(yin[i]);
134 cin[i] = xin[i] ^ yin[i] ^ zout[i];
135 cout[i] = (carry as u32) << 31 | cin[i] >> 1;
136 if let Some(ref mut final_carry) = final_carry {
137 set_packed_slice(&mut *final_carry, i, if carry { B1::ONE } else { B1::ZERO });
138 }
139 }
140 };
141 Ok(())
142 }
143}
144
145#[derive(Debug)]
159pub struct U32AddStacked<const V: usize = 32> {
160 pub xin: Col<B1, V>,
162 pub yin: Col<B1, V>,
163
164 cin: Col<B1, V>,
166 cout: Col<B1, V>,
167 cout_shl: Col<B1, V>,
168
169 carry_in_bit: Option<Col<B1, V>>,
170 pub zout: Col<B1, V>,
171}
172
173impl<const V: usize> U32AddStacked<V> {
174 pub fn new(
175 table: &mut TableBuilder,
176 xin: Col<B1, V>,
177 yin: Col<B1, V>,
178 commit_zout: bool,
179 carry_in_bit: Option<Col<B1, V>>,
180 ) -> Self {
181 assert!(
182 V.is_power_of_two() && V >= 32,
183 "V must be a power of two greater than or equal to 32"
184 );
185 let cout = table.add_committed::<B1, V>("cout");
186 let cout_shl = table.add_shifted("cout_shl", cout, 5, 1, ShiftVariant::LogicalLeft);
187
188 let cin = if let Some(carry_in_bit) = carry_in_bit {
189 table.add_computed("cin", cout_shl + carry_in_bit)
190 } else {
191 cout_shl
192 };
193
194 table.assert_zero("carry_out", (xin + cin) * (yin + cin) + cin - cout);
195
196 let zout = if commit_zout {
197 let zout = table.add_committed::<B1, V>("zout");
198 table.assert_zero("zout", xin + yin + cin - zout);
199 zout
200 } else {
201 table.add_computed("zout", xin + yin + cin)
202 };
203
204 Self {
205 xin,
206 yin,
207 cin,
208 cout,
209 cout_shl,
210 zout,
211 carry_in_bit,
212 }
213 }
214
215 pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
216 where
217 P: PackedFieldIndexable<Scalar = B128> + PackedExtension<B1>,
218 {
219 let stacking_factor = V / 32;
220 let xin: std::cell::RefMut<'_, [u32]> = index.get_mut_as(self.xin)?;
221 let yin = index.get_mut_as(self.yin)?;
222 let mut cout = index.get_mut_as(self.cout)?;
223 let mut zout = index.get_mut_as(self.zout)?;
224
225 if let Some(carry_in_bit_col) = self.carry_in_bit {
226 let carry_in_bit = index.get_mut_as(carry_in_bit_col)?;
228
229 let mut cin = index.get_mut_as(self.cin)?;
230 let mut cout_shl = index.get_mut_as(self.cout_shl)?;
231
232 for i in 0..index.size() * stacking_factor {
233 let (x_plus_y, carry0) = xin[i].overflowing_add(yin[i]);
234 let carry1;
235 (zout[i], carry1) = x_plus_y.overflowing_add(carry_in_bit[i]);
236 let carry = carry0 | carry1;
237
238 cin[i] = xin[i] ^ yin[i] ^ zout[i];
239 cout[i] = (carry as u32) << 31 | cin[i] >> 1;
240 cout_shl[i] = cout[i] << 1;
241 }
242 } else {
243 let mut cin = index.get_mut_as(self.cin)?;
245 for i in 0..index.size() * stacking_factor {
246 let carry;
247 (zout[i], carry) = xin[i].overflowing_add(yin[i]);
248 cin[i] = xin[i] ^ yin[i] ^ zout[i];
249 cout[i] = (carry as u32) << 31 | cin[i] >> 1;
250 }
251 };
252 Ok(())
253 }
254}
255#[derive(Debug)]
257pub struct WideAdd<UX: UnsignedAddPrimitives, const BIT_LENGTH: usize> {
258 pub x_in: [Col<B1>; BIT_LENGTH],
259 pub y_in: [Col<B1>; BIT_LENGTH],
260
261 cin: [Col<B1>; BIT_LENGTH],
262
263 pub z_out: [Col<B1>; BIT_LENGTH],
264 pub final_carry_out: Col<B1>,
265 pub flags: U32AddFlags,
267 _marker: PhantomData<UX>,
269}
270impl<UX: UnsignedAddPrimitives, const BIT_LENGTH: usize> WideAdd<UX, BIT_LENGTH> {
271 pub fn new(
272 table: &mut TableBuilder,
273 x_in: [Col<B1>; BIT_LENGTH],
274 y_in: [Col<B1>; BIT_LENGTH],
275 flags: U32AddFlags,
276 ) -> Self {
277 assert_eq!(BIT_LENGTH, UX::BIT_LENGTH);
278 let cin = array::from_fn(|i| {
279 if i != 0 {
280 table.add_committed(format!("cin[{i}]"))
281 } else {
282 flags
283 .carry_in_bit
284 .map(|carry_col| table.add_selected("cin[0]", carry_col, 0))
285 .unwrap_or_else(|| table.add_committed("cin[0]"))
286 }
287 });
288 let zout = if flags.commit_zout {
289 let zout = table.add_committed_multiple("z_out");
290 for bit in 0..BIT_LENGTH {
291 table.assert_zero(
292 format!("sum[{bit}]"),
293 x_in[bit] + y_in[bit] + cin[bit] - zout[bit],
294 );
295 }
296 zout
297 } else {
298 array::from_fn(|i| table.add_computed(format!("zout[{i}]"), x_in[i] + y_in[i] + cin[i]))
299 };
300 let final_carry_out = table.add_committed("final_carry_out");
301 let cout: [_; BIT_LENGTH] = array::from_fn(|i| {
302 if i != BIT_LENGTH - 1 {
303 cin[i + 1]
304 } else {
305 final_carry_out
306 }
307 });
308 for bit in 0..BIT_LENGTH {
309 table.assert_zero(
310 format!("carry_{bit}"),
311 (x_in[bit] + cin[bit]) * (y_in[bit] + cin[bit]) + cin[bit] - cout[bit],
312 );
313 }
314
315 Self {
316 x_in,
317 y_in,
318 cin,
319 z_out: zout,
320 final_carry_out,
321 flags,
322 _marker: PhantomData,
323 }
324 }
325
326 pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
327 where
328 P: PackedField<Scalar = B128> + PackedExtension<B1>,
329 {
330 let x_in = array_util::try_map(self.x_in, |bit| index.get(bit))?;
331 let y_in = array_util::try_map(self.y_in, |bit| index.get(bit))?;
332 let mut cin = array_util::try_map(self.cin, |bit| index.get_mut(bit))?;
333 let mut zout = array_util::try_map(self.z_out, |bit| index.get_mut(bit))?;
334 let mut final_carry_out = index.get_mut(self.final_carry_out)?;
335
336 let mut carry_in = if self.flags.carry_in_bit.is_none() {
337 let initial_carry = PackedSubfield::<P, B1>::default();
338 vec![initial_carry; cin[0].len()]
339 } else {
340 index
341 .get(self.flags.carry_in_bit.expect("carry_in_bit is not None"))?
342 .deref()
343 .to_vec()
344 };
345 for bit in 0..BIT_LENGTH {
346 for (x_in_packed, y_in_packed, c_in_packed, zout_packed, carry_in) in izip!(
347 x_in[bit].iter(),
348 y_in[bit].iter(),
349 cin[bit].iter_mut(),
350 zout[bit].iter_mut(),
351 carry_in.iter_mut()
352 ) {
353 let sum = *x_in_packed + *y_in_packed + *carry_in;
354 let new_carry = (*x_in_packed + *carry_in) * (*y_in_packed + *carry_in) + *carry_in;
355 *c_in_packed = *carry_in;
356 *zout_packed = sum;
357 *carry_in = new_carry;
358 }
359 }
360
361 for (final_carry, carry) in izip!(final_carry_out.iter_mut(), carry_in.iter()) {
362 *final_carry = *carry;
363 }
364
365 Ok(())
366 }
367}
368
369pub trait UnsignedAddPrimitives {
371 type F: TowerField;
372 const BIT_LENGTH: usize;
373}
374
375impl UnsignedAddPrimitives for u32 {
376 type F = B32;
377
378 const BIT_LENGTH: usize = 32;
379}
380
381impl UnsignedAddPrimitives for u64 {
382 type F = B64;
383
384 const BIT_LENGTH: usize = 64;
385}
386
387#[derive(Debug)]
389pub struct Incr<UPrimitive: UnsignedAddPrimitives, const BIT_LENGTH: usize> {
390 cin: [Col<B1>; BIT_LENGTH],
391
392 pub input: [Col<B1>; BIT_LENGTH],
394 pub zout: [Col<B1>; BIT_LENGTH],
396 pub final_carry_out: Col<B1>,
398
399 _marker: PhantomData<UPrimitive>,
400}
401
402impl<UPrimitive: UnsignedAddPrimitives, const BIT_LENGTH: usize> Incr<UPrimitive, BIT_LENGTH> {
403 pub fn new(table: &mut TableBuilder, input: [Col<B1>; BIT_LENGTH]) -> Self {
404 assert_eq!(BIT_LENGTH, UPrimitive::BIT_LENGTH);
405 let cin = array::from_fn(|i| {
406 if i != 0 {
407 table.add_committed(format!("cout[{i}]"))
408 } else {
409 table.add_constant("cout[0]", [B1::ONE])
410 }
411 });
412 let zout = table.add_committed_multiple("zout");
413 let final_carry_out = table.add_committed("final_carry_out");
414 let cout: [_; BIT_LENGTH] = array::from_fn(|i| {
415 if i != BIT_LENGTH - 1 {
416 cin[i + 1]
417 } else {
418 final_carry_out
419 }
420 });
421
422 for (i, &xin) in input.iter().enumerate() {
423 table.assert_zero(format!("sum[{i}]"), xin + cin[i] - zout[i]);
424 table.assert_zero(format!("carry[{i}]"), xin * cin[i] - cout[i]);
425 }
426
427 Self {
428 cin,
429 input,
430 zout,
431 final_carry_out,
432 _marker: PhantomData,
433 }
434 }
435
436 pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
437 where
438 P: PackedField<Scalar = B128> + PackedExtension<B1>,
439 {
440 let in_bits = array_util::try_map(self.input, |bit| index.get(bit))?;
442 let mut cin = array_util::try_map(self.cin, |bit| index.get_mut(bit))?;
444 let mut zout = array_util::try_map(self.zout, |bit| index.get_mut(bit))?;
446 let mut final_carry_out = index.get_mut(self.final_carry_out)?;
447
448 let p_inv = PackedSubfield::<P, B1>::broadcast(B1::ONE);
449 let mut carry_in = vec![p_inv; cin[0].len()];
450 for bit in 0..BIT_LENGTH {
451 for (in_bit, cin, zout, carry_in) in izip!(
452 in_bits[bit].iter(),
453 cin[bit].iter_mut(),
454 zout[bit].iter_mut(),
455 carry_in.iter_mut()
456 ) {
457 let sum = *in_bit + *carry_in;
458 let new_carry = (*in_bit) * (*carry_in);
459 *cin = *carry_in;
460 *zout = sum;
461 *carry_in = new_carry;
462 }
463 }
464 for (final_carry, carry) in izip!(final_carry_out.iter_mut(), carry_in.iter()) {
465 *final_carry = *carry;
466 }
467
468 Ok(())
469 }
470}
471
472pub type U32Incr = Incr<u32, 32>;
473pub type U64Incr = Incr<u64, 64>;
474
475#[cfg(test)]
476mod tests {
477 use std::iter::repeat_with;
478
479 use binius_compute::cpu::alloc::CpuComputeAllocator;
480 use binius_field::{
481 arch::OptimalUnderlier128b, as_packed_field::PackedType, packed::get_packed_slice,
482 };
483 use rand::{Rng as _, SeedableRng, prelude::StdRng};
484
485 use super::*;
486 use crate::builder::{ConstraintSystem, WitnessIndex, test_utils::validate_system_witness};
487
488 #[test]
489 fn prop_test_no_carry() {
490 const N_ITER: usize = 1 << 14;
491
492 let mut rng = StdRng::seed_from_u64(0);
493 let test_vector: Vec<(u32, u32, u32, u32, bool)> = (0..N_ITER)
494 .map(|_| {
495 let x: u32 = rng.random();
496 let y: u32 = rng.random();
497 let (z, carry) = x.overflowing_add(y);
498 (x, y, 0x00000000, z, carry)
500 })
501 .collect();
502
503 TestPlan {
504 dyn_carry_in: false,
505 expose_final_carry: true,
506 commit_zout: false,
507 test_vector,
508 }
509 .execute();
510 }
511
512 #[test]
513 fn prop_test_with_carry() {
514 const N_ITER: usize = 1 << 14;
515
516 let mut rng = StdRng::seed_from_u64(0);
517 let test_vector: Vec<(u32, u32, u32, u32, bool)> = (0..N_ITER)
518 .map(|_| {
519 let x: u32 = rng.random();
520 let y: u32 = rng.random();
521 let carry_in = rng.random::<bool>() as u32;
522 let (x_plus_y, carry1) = x.overflowing_add(y);
523 let (z, carry2) = x_plus_y.overflowing_add(carry_in);
524 let final_carry = carry1 | carry2;
525 (x, y, carry_in, z, final_carry)
526 })
527 .collect();
528
529 TestPlan {
530 dyn_carry_in: true,
531 expose_final_carry: true,
532 commit_zout: true,
533 test_vector,
534 }
535 .execute();
536 }
537
538 #[test]
539 fn test_add_with_carry() {
540 let test_vector = [
542 (0xFFFFFFFF, 0x00000001, 0x00000000, 0x00000000, true), (0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, false), (0x7FFFFFFF, 0x00000001, 0x00000000, 0x80000000, false), (0xFFFF0000, 0x0000FFFF, 0x00000001, 0x00000000, true), ];
547 TestPlan {
548 dyn_carry_in: true,
549 expose_final_carry: true,
550 commit_zout: false,
551 test_vector: test_vector.to_vec(),
552 }
553 .execute();
554 }
555
556 struct TestPlan {
557 dyn_carry_in: bool,
558 expose_final_carry: bool,
559 commit_zout: bool,
560 test_vector: Vec<(u32, u32, u32, u32, bool)>,
562 }
563
564 impl TestPlan {
565 fn execute(self) {
566 let mut cs = ConstraintSystem::new();
567 let mut table = cs.add_table("u32_add");
568
569 let xin = table.add_committed::<B1, 32>("xin");
570 let yin = table.add_committed::<B1, 32>("yin");
571
572 let carry_in = self
573 .dyn_carry_in
574 .then_some(table.add_committed::<B1, 32>("carry_in"));
575
576 let flags = U32AddFlags {
577 carry_in_bit: carry_in,
578 expose_final_carry: self.expose_final_carry,
579 commit_zout: self.commit_zout,
580 };
581 let adder = U32Add::new(&mut table, xin, yin, flags);
582 assert!(adder.final_carry.is_some() == self.expose_final_carry);
583
584 let table_id = table.id();
585 let mut allocator = CpuComputeAllocator::new(1 << 16);
586 let allocator = allocator.into_bump_allocator();
587 let mut witness =
588 WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
589
590 let table_witness = witness
591 .init_table(table_id, self.test_vector.len())
592 .unwrap();
593 let mut segment = table_witness.full_segment();
594
595 {
596 let mut xin_bits = segment.get_mut_as::<u32, _, 32>(adder.xin).unwrap();
597 let mut yin_bits = segment.get_mut_as::<u32, _, 32>(adder.yin).unwrap();
598 let mut carry_in_bits =
599 carry_in.map(|carry_in| segment.get_mut_as::<u32, _, 32>(carry_in).unwrap());
600 for (i, (x, y, carry_in, _, _)) in self.test_vector.iter().enumerate() {
601 xin_bits[i] = *x;
602 yin_bits[i] = *y;
603 if let Some(ref mut carry_in_bits) = carry_in_bits {
604 carry_in_bits[i] = *carry_in;
605 }
606 }
607 }
608
609 adder.populate(&mut segment).unwrap();
611
612 {
613 let zout_bits = segment.get_as::<u32, _, 32>(adder.zout).unwrap();
615 let final_carry = adder
616 .final_carry
617 .map(|final_carry| segment.get(final_carry).unwrap());
618 for (i, (_, _, _, zout, expected_carry)) in self.test_vector.iter().enumerate() {
619 assert_eq!(zout_bits[i], *zout);
620
621 if let Some(ref final_carry) = final_carry {
622 assert_eq!(get_packed_slice(final_carry, i), B1::from(*expected_carry));
623 }
624 }
625 }
626
627 let ccs = cs.compile().unwrap();
629 let table_sizes = witness.table_sizes();
630 let witness = witness.into_multilinear_extension_index();
631
632 binius_core::constraint_system::validate::validate_witness(
633 &ccs,
634 &[],
635 &table_sizes,
636 &witness,
637 )
638 .unwrap();
639 }
640 }
641
642 #[test]
643 fn test_incr() {
644 const TABLE_SIZE: usize = 1 << 9;
645
646 let mut cs = ConstraintSystem::new();
647 let mut table = cs.add_table("u32_incr");
648
649 let table_id = table.id();
650 let mut rng = StdRng::seed_from_u64(0);
651 let test_values = repeat_with(|| B32::new(rng.random::<u32>()))
652 .take(TABLE_SIZE)
653 .collect::<Vec<_>>();
654
655 let xin = table.add_committed_multiple("xin");
656
657 let incr = U32Incr::new(&mut table, xin);
658
659 let mut allocator = CpuComputeAllocator::new(1 << 12);
660 let allocator = allocator.into_bump_allocator();
661 let mut witness =
662 WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
663
664 let table_witness = witness.init_table(table_id, TABLE_SIZE).unwrap();
665 let mut segment = table_witness.full_segment();
666
667 {
668 let mut xin_witness =
669 array_util::try_map(xin, |bit_col| segment.get_mut(bit_col)).unwrap();
670 for (i, value) in test_values.iter().enumerate() {
671 for (bit, packed) in xin_witness.iter_mut().enumerate() {
672 set_packed_slice(packed, i, B1::from(((value.val() >> bit) & 1) == 1))
673 }
674 }
675 }
676
677 incr.populate(&mut segment).unwrap();
678
679 {
680 let zouts = array_util::try_map(incr.zout, |bit_col| segment.get(bit_col)).unwrap();
681 for (i, value) in test_values.iter().enumerate() {
682 let expected = value.val().wrapping_add(1);
683 let mut got = 0u32;
684 for bit in (0..32).rev() {
685 got <<= 1;
686 if get_packed_slice(&zouts[bit], i) == B1::ONE {
687 got |= 1;
688 }
689 }
690 assert_eq!(expected, got);
691 }
692 }
693 validate_system_witness::<OptimalUnderlier128b>(&cs, witness, vec![]);
695 }
696
697 #[test]
698 fn test_u32_add_stacked() {
699 let mut cs = ConstraintSystem::new();
700 let mut table = cs.add_table("u32_add_stacked");
701 const TABLE_SIZE: usize = 1 << 6;
702 const LOG_STACKING_FACTOR: usize = 2;
703 const TOTAL_BITS: usize = 32 << LOG_STACKING_FACTOR;
704 let xin = table.add_committed::<B1, TOTAL_BITS>("xin");
705 let yin = table.add_committed::<B1, TOTAL_BITS>("yin");
706 let add = U32AddStacked::new(&mut table, xin, yin, true, None);
707 let table_id = table.id();
708
709 let mut rng = StdRng::seed_from_u64(0);
710 let test_values: Vec<(u32, u32)> = (0..TABLE_SIZE << LOG_STACKING_FACTOR)
711 .map(|_| (rng.random::<u32>(), rng.random::<u32>()))
712 .collect();
713
714 let mut allocator = CpuComputeAllocator::new(1 << 12);
715 let allocator = allocator.into_bump_allocator();
716 let mut witness =
717 WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
718 let table_witness = witness.init_table(table_id, TABLE_SIZE).unwrap();
719 let mut segment = table_witness.full_segment();
720
721 {
722 let mut xin_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.xin).unwrap();
723 let mut yin_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.yin).unwrap();
724 for (i, (x, y)) in test_values.iter().enumerate() {
725 xin_bits[i] = *x;
726 yin_bits[i] = *y;
727 }
728 }
729
730 add.populate(&mut segment).unwrap();
731
732 {
733 let zout_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.zout).unwrap();
734
735 for (i, (x, y)) in test_values.iter().enumerate() {
736 let expected = x.wrapping_add(*y);
737 assert_eq!(zout_bits[i], expected, "row {i}: {} + {} != {}", x, y, zout_bits[i]);
738 }
739 }
740
741 validate_system_witness::<OptimalUnderlier128b>(&cs, witness, vec![]);
742 }
743}