1use rhdl::prelude::*;
10use rhdl_fpga::core::dff::DFF;
11
12use crate::{
13 protocol::Words512,
14 scalar::{ScalarWords, scalar_ge_kernel, scalar_l_kernel, scalar_sub_kernel},
15};
16
17#[derive(Copy, Clone, Debug, PartialEq, Digital)]
19pub struct Radix16X17 {
20 pub limbs: [b16; 17],
22}
23
24#[derive(Copy, Clone, Debug, PartialEq, Digital)]
26pub struct Radix16X25 {
27 pub limbs: [b16; 25],
29}
30
31#[derive(Copy, Clone, Debug, PartialEq, Digital)]
33pub struct ConstQMulIn {
34 pub valid: bool,
36 pub x: Radix16X17,
38 pub tag: b16,
40}
41
42#[derive(Copy, Clone, Debug, PartialEq, Digital)]
44pub struct ConstQMulOut {
45 pub valid: bool,
47 pub product: Radix16X25,
49 pub tag: b16,
51}
52
53#[derive(Copy, Clone, Debug, PartialEq, Digital)]
54#[doc(hidden)]
55pub struct ConstQCellIn {
56 x: [b16; 8],
57}
58
59#[derive(Copy, Clone, Debug, PartialEq, Digital)]
60#[doc(hidden)]
61pub struct ConstQCellOut {
62 coefficient: b35,
63}
64
65#[derive(Copy, Clone, Debug, PartialEq, Digital)]
66#[doc(hidden)]
67pub struct ConstQCellRegs {
68 x: [b16; 8],
69 products: [b32; 8],
70 pairs: [b33; 4],
71 quads: [b34; 2],
72 coefficient: b35,
73}
74
75#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
76#[doc(hidden)]
77pub struct ConstQCell {
78 regs: DFF<ConstQCellRegs>,
79}
80
81impl Default for ConstQCell {
82 fn default() -> Self {
83 Self {
84 regs: DFF::new(ConstQCellRegs {
85 x: [b16(0); 8],
86 products: [b32(0); 8],
87 pairs: [b33(0); 4],
88 quads: [b34(0); 2],
89 coefficient: b35(0),
90 }),
91 }
92 }
93}
94
95impl SynchronousIO for ConstQCell {
96 type I = ConstQCellIn;
97 type O = ConstQCellOut;
98 type Kernel = const_q_cell_kernel;
99}
100
101#[kernel]
103#[doc(hidden)]
104pub fn const_q_cell_kernel(
105 _cr: ClockReset,
106 i: ConstQCellIn,
107 q: ConstQCellQ,
108) -> (ConstQCellOut, ConstQCellD) {
109 let constants = [
110 b16(0xd3ed),
111 b16(0x5cf5),
112 b16(0x631a),
113 b16(0x5812),
114 b16(0x9cd6),
115 b16(0xa2f7),
116 b16(0xf9de),
117 b16(0x14de),
118 ];
119 let r = q.regs;
120 let mut n = r;
121 n.x = i.x;
122 for index in 0..8 {
123 n.products[index] = r.x[index].resize::<32>() * constants[index].resize::<32>();
124 }
125 for index in 0..4 {
126 n.pairs[index] =
127 r.products[index * 2].resize::<33>() + r.products[index * 2 + 1].resize::<33>();
128 }
129 n.quads[0] = r.pairs[0].resize::<34>() + r.pairs[1].resize::<34>();
130 n.quads[1] = r.pairs[2].resize::<34>() + r.pairs[3].resize::<34>();
131 n.coefficient = r.quads[0].resize::<35>() + r.quads[1].resize::<35>();
132 (
133 ConstQCellOut {
134 coefficient: r.coefficient,
135 },
136 ConstQCellD { regs: n },
137 )
138}
139
140#[derive(Copy, Clone, Debug, PartialEq, Digital)]
141#[doc(hidden)]
142pub struct ConstQBankIn {
143 windows: [[b16; 8]; 4],
144}
145
146#[derive(Copy, Clone, Debug, PartialEq, Digital)]
147#[doc(hidden)]
148pub struct ConstQBankOut {
149 coefficients: [b35; 4],
150}
151
152#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
153#[doc(hidden)]
154pub struct ConstQBank {
155 cell0: ConstQCell,
156 cell1: ConstQCell,
157 cell2: ConstQCell,
158 cell3: ConstQCell,
159}
160
161impl SynchronousIO for ConstQBank {
162 type I = ConstQBankIn;
163 type O = ConstQBankOut;
164 type Kernel = const_q_bank_kernel;
165}
166
167#[kernel]
169#[doc(hidden)]
170pub fn const_q_bank_kernel(
171 _cr: ClockReset,
172 i: ConstQBankIn,
173 q: ConstQBankQ,
174) -> (ConstQBankOut, ConstQBankD) {
175 let output = ConstQBankOut {
176 coefficients: [
177 q.cell0.coefficient,
178 q.cell1.coefficient,
179 q.cell2.coefficient,
180 q.cell3.coefficient,
181 ],
182 };
183 let mut d = ConstQBankD::dont_care();
184 d.cell0 = ConstQCellIn { x: i.windows[0] };
185 d.cell1 = ConstQCellIn { x: i.windows[1] };
186 d.cell2 = ConstQCellIn { x: i.windows[2] };
187 d.cell3 = ConstQCellIn { x: i.windows[3] };
188 (output, d)
189}
190
191#[derive(Copy, Clone, Debug, PartialEq, Digital)]
192#[doc(hidden)]
193pub struct NormalizeState {
194 coefficients: [b35; 24],
195 limbs: [b16; 24],
196 carry: b20,
197}
198
199#[derive(Copy, Clone, Debug, PartialEq, Digital)]
200#[doc(hidden)]
201pub struct NormalizePairIn {
202 state: NormalizeState,
203 start: b6,
204}
205
206#[derive(Copy, Clone, Debug, PartialEq, Digital)]
207#[doc(hidden)]
208pub struct NormalizePairOut {
209 state: NormalizeState,
210}
211
212#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
213#[doc(hidden)]
214pub struct NormalizePair {
215 state: DFF<NormalizeState>,
216}
217
218impl Default for NormalizePair {
219 fn default() -> Self {
220 Self {
221 state: DFF::new(NormalizeState {
222 coefficients: [b35(0); 24],
223 limbs: [b16(0); 24],
224 carry: b20(0),
225 }),
226 }
227 }
228}
229
230impl SynchronousIO for NormalizePair {
231 type I = NormalizePairIn;
232 type O = NormalizePairOut;
233 type Kernel = normalize_pair_kernel;
234}
235
236#[kernel]
238#[doc(hidden)]
239pub fn normalize_pair_kernel(
240 _cr: ClockReset,
241 i: NormalizePairIn,
242 q: NormalizePairQ,
243) -> (NormalizePairOut, NormalizePairD) {
244 let mut next = i.state;
245 let first = i.state.coefficients[i.start].resize::<36>() + i.state.carry.resize::<36>();
246 let second_index = i.start + b6(1);
247 let second = i.state.coefficients[second_index].resize::<36>() + (first >> 16);
248 next.limbs[i.start] = first.resize::<16>();
249 next.limbs[second_index] = second.resize::<16>();
250 next.carry = (second >> 16).resize::<20>();
251 (
252 NormalizePairOut { state: q.state },
253 NormalizePairD { state: next },
254 )
255}
256
257#[derive(Copy, Clone, Debug, PartialEq, Digital)]
258#[doc(hidden)]
259pub struct NormalizeGroupIn {
260 state: NormalizeState,
261 start: b6,
262}
263
264#[derive(Copy, Clone, Debug, PartialEq, Digital)]
265#[doc(hidden)]
266pub struct NormalizeGroupOut {
267 state: NormalizeState,
268}
269
270#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
271#[doc(hidden)]
272pub struct NormalizeGroup {
273 pair0: NormalizePair,
274 pair1: NormalizePair,
275 pair2: NormalizePair,
276 pair3: NormalizePair,
277}
278
279impl SynchronousIO for NormalizeGroup {
280 type I = NormalizeGroupIn;
281 type O = NormalizeGroupOut;
282 type Kernel = normalize_group_kernel;
283}
284
285#[kernel]
287#[doc(hidden)]
288pub fn normalize_group_kernel(
289 _cr: ClockReset,
290 i: NormalizeGroupIn,
291 q: NormalizeGroupQ,
292) -> (NormalizeGroupOut, NormalizeGroupD) {
293 let mut d = NormalizeGroupD::dont_care();
294 d.pair0 = NormalizePairIn {
295 state: i.state,
296 start: i.start,
297 };
298 d.pair1 = NormalizePairIn {
299 state: q.pair0.state,
300 start: i.start + b6(2),
301 };
302 d.pair2 = NormalizePairIn {
303 state: q.pair1.state,
304 start: i.start + b6(4),
305 };
306 d.pair3 = NormalizePairIn {
307 state: q.pair2.state,
308 start: i.start + b6(6),
309 };
310 (
311 NormalizeGroupOut {
312 state: q.pair3.state,
313 },
314 d,
315 )
316}
317
318#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
323#[rhdl(dq_no_prefix)]
324pub struct ConstQMulPipe {
325 bank0: ConstQBank,
326 bank1: ConstQBank,
327 bank2: ConstQBank,
328 bank3: ConstQBank,
329 bank4: ConstQBank,
330 bank5: ConstQBank,
331 normalize0: NormalizeGroup,
332 normalize1: NormalizeGroup,
333 normalize2: NormalizeGroup,
334 valid: DFF<[bool; 17]>,
335 tags: DFF<[b16; 17]>,
336}
337
338impl Default for ConstQMulPipe {
339 fn default() -> Self {
340 Self {
341 bank0: ConstQBank::default(),
342 bank1: ConstQBank::default(),
343 bank2: ConstQBank::default(),
344 bank3: ConstQBank::default(),
345 bank4: ConstQBank::default(),
346 bank5: ConstQBank::default(),
347 normalize0: NormalizeGroup::default(),
348 normalize1: NormalizeGroup::default(),
349 normalize2: NormalizeGroup::default(),
350 valid: DFF::new([false; 17]),
351 tags: DFF::new([b16(0); 17]),
352 }
353 }
354}
355
356impl SynchronousIO for ConstQMulPipe {
357 type I = ConstQMulIn;
358 type O = ConstQMulOut;
359 type Kernel = const_q_mul_kernel;
360}
361
362#[kernel]
364pub fn const_q_mul_kernel(_cr: ClockReset, i: ConstQMulIn, q: Q) -> (ConstQMulOut, D) {
365 let bank_coefficients = [
366 q.bank0.coefficients,
367 q.bank1.coefficients,
368 q.bank2.coefficients,
369 q.bank3.coefficients,
370 q.bank4.coefficients,
371 q.bank5.coefficients,
372 ];
373 let mut coefficients = [b35(0); 24];
374 let mut windows = [[[b16(0); 8]; 4]; 6];
375 for bank in 0..6 {
376 for slot in 0..4 {
377 let coefficient = bank * 4 + slot;
378 coefficients[coefficient] = bank_coefficients[bank][slot];
379 for x_limb in 0..17 {
380 for q_limb in 0..8 {
381 if coefficient == x_limb + q_limb {
382 windows[bank][slot][q_limb] = i.x.limbs[x_limb];
383 }
384 }
385 }
386 }
387 }
388 let initial = NormalizeState {
389 coefficients,
390 limbs: [b16(0); 24],
391 carry: b20(0),
392 };
393 let product_limbs = q.normalize2.state.limbs;
394 let product = Radix16X25 {
395 limbs: [
396 product_limbs[0],
397 product_limbs[1],
398 product_limbs[2],
399 product_limbs[3],
400 product_limbs[4],
401 product_limbs[5],
402 product_limbs[6],
403 product_limbs[7],
404 product_limbs[8],
405 product_limbs[9],
406 product_limbs[10],
407 product_limbs[11],
408 product_limbs[12],
409 product_limbs[13],
410 product_limbs[14],
411 product_limbs[15],
412 product_limbs[16],
413 product_limbs[17],
414 product_limbs[18],
415 product_limbs[19],
416 product_limbs[20],
417 product_limbs[21],
418 product_limbs[22],
419 product_limbs[23],
420 q.normalize2.state.carry.resize::<16>(),
421 ],
422 };
423 let output = ConstQMulOut {
424 valid: q.valid[16],
425 product,
426 tag: q.tags[16],
427 };
428 let mut d = D::dont_care();
429 d.bank0 = ConstQBankIn {
430 windows: windows[0],
431 };
432 d.bank1 = ConstQBankIn {
433 windows: windows[1],
434 };
435 d.bank2 = ConstQBankIn {
436 windows: windows[2],
437 };
438 d.bank3 = ConstQBankIn {
439 windows: windows[3],
440 };
441 d.bank4 = ConstQBankIn {
442 windows: windows[4],
443 };
444 d.bank5 = ConstQBankIn {
445 windows: windows[5],
446 };
447 d.normalize0 = NormalizeGroupIn {
448 state: initial,
449 start: b6(0),
450 };
451 d.normalize1 = NormalizeGroupIn {
452 state: q.normalize0.state,
453 start: b6(8),
454 };
455 d.normalize2 = NormalizeGroupIn {
456 state: q.normalize1.state,
457 start: b6(16),
458 };
459 d.valid[0] = i.valid;
460 d.tags[0] = i.tag;
461 for stage in 1..17 {
462 d.valid[stage] = q.valid[stage - 1];
463 d.tags[stage] = q.tags[stage - 1];
464 }
465 (output, d)
466}
467
468#[derive(Copy, Clone, Debug, PartialEq, Digital)]
469#[doc(hidden)]
470pub struct Scalar252 {
471 limbs: [b16; 16],
472}
473
474#[derive(Copy, Clone, Debug, PartialEq, Digital)]
475#[doc(hidden)]
476pub struct Fold2Side {
477 x0: Scalar252,
478 p1_low: Scalar252,
479}
480
481#[derive(Copy, Clone, Debug, PartialEq, Digital)]
482#[doc(hidden)]
483pub struct Fold3Side {
484 x0: Scalar252,
485 p1_low: Scalar252,
486 p2_low: Scalar252,
487}
488
489#[derive(Copy, Clone, Debug, PartialEq, Digital)]
490#[doc(hidden)]
491pub struct FoldSideDelayIn {
492 value: Fold3Side,
493}
494
495#[derive(Copy, Clone, Debug, PartialEq, Digital)]
496#[doc(hidden)]
497pub struct FoldSideDelayOut {
498 value: Fold3Side,
499}
500
501#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
502#[doc(hidden)]
503pub struct FoldSideDelay {
504 x0: DFF<[Scalar252; 17]>,
505 p1_low: DFF<[Scalar252; 17]>,
506 p2_low: DFF<[Scalar252; 17]>,
507}
508
509impl Default for FoldSideDelay {
510 fn default() -> Self {
511 let zero = zero_scalar252();
512 Self {
513 x0: DFF::new([zero; 17]),
514 p1_low: DFF::new([zero; 17]),
515 p2_low: DFF::new([zero; 17]),
516 }
517 }
518}
519
520impl SynchronousIO for FoldSideDelay {
521 type I = FoldSideDelayIn;
522 type O = FoldSideDelayOut;
523 type Kernel = fold_side_delay_kernel;
524}
525
526#[kernel]
528#[doc(hidden)]
529pub fn fold_side_delay_kernel(
530 _cr: ClockReset,
531 i: FoldSideDelayIn,
532 q: FoldSideDelayQ,
533) -> (FoldSideDelayOut, FoldSideDelayD) {
534 let mut d = FoldSideDelayD::dont_care();
535 d.x0[0] = i.value.x0;
536 d.p1_low[0] = i.value.p1_low;
537 d.p2_low[0] = i.value.p2_low;
538 for stage in 1..17 {
539 d.x0[stage] = q.x0[stage - 1];
540 d.p1_low[stage] = q.p1_low[stage - 1];
541 d.p2_low[stage] = q.p2_low[stage - 1];
542 }
543 (
544 FoldSideDelayOut {
545 value: Fold3Side {
546 x0: q.x0[16],
547 p1_low: q.p1_low[16],
548 p2_low: q.p2_low[16],
549 },
550 },
551 d,
552 )
553}
554
555#[derive(Copy, Clone, Debug, PartialEq, Digital)]
556#[doc(hidden)]
557pub struct FinalReduceRegs {
558 valid: [bool; 6],
559 tags: [b16; 6],
560 positive: ScalarWords,
561 negative: ScalarWords,
562 biased: ScalarWords,
563 reduced1: ScalarWords,
564 reduced2: ScalarWords,
565 low: [b64; 2],
566 low_borrow: bool,
567 high_operand: [b64; 2],
568 original: ScalarWords,
569 result: ScalarWords,
570}
571
572#[derive(Copy, Clone, Debug, PartialEq, Digital)]
574pub struct ScalarReducePipeIn {
575 pub valid: bool,
577 pub wide: Words512,
579 pub tag: b16,
581}
582
583#[derive(Copy, Clone, Debug, PartialEq, Digital)]
585pub struct ScalarReducePipeOut {
586 pub valid: bool,
588 pub scalar: ScalarWords,
590 pub tag: b16,
592}
593
594fn zero_scalar252() -> Scalar252 {
595 Scalar252 {
596 limbs: [b16(0); 16],
597 }
598}
599
600fn final_reset() -> FinalReduceRegs {
601 let zero = ScalarWords { words: [b64(0); 4] };
602 FinalReduceRegs {
603 valid: [false; 6],
604 tags: [b16(0); 6],
605 positive: zero,
606 negative: zero,
607 biased: zero,
608 reduced1: zero,
609 reduced2: zero,
610 low: [b64(0); 2],
611 low_borrow: false,
612 high_operand: [b64(0); 2],
613 original: zero,
614 result: zero,
615 }
616}
617
618#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
624pub struct ScalarReducePipe {
625 fold1: ConstQMulPipe,
626 fold2: ConstQMulPipe,
627 fold3: ConstQMulPipe,
628 side1: FoldSideDelay,
629 side2: FoldSideDelay,
630 side3: FoldSideDelay,
631 final_reduce: DFF<FinalReduceRegs>,
632}
633
634impl Default for ScalarReducePipe {
635 fn default() -> Self {
636 Self {
637 fold1: ConstQMulPipe::default(),
638 fold2: ConstQMulPipe::default(),
639 fold3: ConstQMulPipe::default(),
640 side1: FoldSideDelay::default(),
641 side2: FoldSideDelay::default(),
642 side3: FoldSideDelay::default(),
643 final_reduce: DFF::new(final_reset()),
644 }
645 }
646}
647
648impl SynchronousIO for ScalarReducePipe {
649 type I = ScalarReducePipeIn;
650 type O = ScalarReducePipeOut;
651 type Kernel = scalar_reduce_pipe_kernel;
652}
653
654#[kernel]
655fn wide_radix16(value: Words512) -> [b16; 32] {
656 let mut limbs = [b16(0); 32];
657 for word in 0..8 {
658 for part in 0..4 {
659 limbs[word * 4 + part] = (value.words[word] >> ((part * 16) as u128)).resize::<16>();
660 }
661 }
662 limbs
663}
664
665#[kernel]
666fn low252(value: [b16; 32]) -> Scalar252 {
667 let mut limbs = [b16(0); 16];
668 for index in 0..16 {
669 limbs[index] = value[index];
670 }
671 limbs[15] &= b16(0x0fff);
672 Scalar252 { limbs }
673}
674
675#[kernel]
676fn high252(value: [b16; 32]) -> Radix16X17 {
677 let mut limbs = [b16(0); 17];
678 for index in 0..16 {
679 limbs[index] = (value[15 + index] >> 12) | (value[16 + index] << 4);
680 }
681 limbs[16] = value[31] >> 12;
682 Radix16X17 { limbs }
683}
684
685#[kernel]
686fn product_low252(value: Radix16X25) -> Scalar252 {
687 let mut limbs = [b16(0); 16];
688 for index in 0..16 {
689 limbs[index] = value.limbs[index];
690 }
691 limbs[15] &= b16(0x0fff);
692 Scalar252 { limbs }
693}
694
695#[kernel]
696fn product_high252(value: Radix16X25) -> Radix16X17 {
697 let mut limbs = [b16(0); 17];
698 for index in 0..9 {
699 limbs[index] = (value.limbs[15 + index] >> 12) | (value.limbs[16 + index] << 4);
700 }
701 limbs[9] = value.limbs[24] >> 12;
702 Radix16X17 { limbs }
703}
704
705#[kernel]
706fn scalar252_words(value: Scalar252) -> ScalarWords {
707 let mut words = [b64(0); 4];
708 for word in 0..4 {
709 for part in 0..4 {
710 words[word] |= value.limbs[word * 4 + part].resize::<64>() << ((part * 16) as u128);
711 }
712 }
713 ScalarWords { words }
714}
715
716#[kernel]
717fn add_words(a: ScalarWords, b: ScalarWords) -> ScalarWords {
718 let mut output = ScalarWords { words: [b64(0); 4] };
719 let mut carry = false;
720 for index in 0..4 {
721 let first = a.words[index] + b.words[index];
722 let first_carry = first < a.words[index];
723 let second = first + if carry { b64(1) } else { b64(0) };
724 carry = first_carry || second < first;
725 output.words[index] = second;
726 }
727 output
728}
729
730#[kernel]
731fn two_l() -> ScalarWords {
732 ScalarWords {
733 words: [
734 b64(0xb024c634b9eba7da),
735 b64(0x29bdf3bd45ef39ac),
736 b64(0),
737 b64(0x2000000000000000),
738 ],
739 }
740}
741
742#[kernel]
744pub fn scalar_reduce_pipe_kernel(
745 _cr: ClockReset,
746 i: ScalarReducePipeIn,
747 q: ScalarReducePipeQ,
748) -> (ScalarReducePipeOut, ScalarReducePipeD) {
749 let wide = wide_radix16(i.wide);
750 let p1_low = product_low252(q.fold1.product);
751 let p2_low = product_low252(q.fold2.product);
752 let p3_low = product_low252(q.fold3.product);
753 let mut final_next = q.final_reduce;
754 final_next.valid = [
755 q.fold3.valid,
756 q.final_reduce.valid[0],
757 q.final_reduce.valid[1],
758 q.final_reduce.valid[2],
759 q.final_reduce.valid[3],
760 q.final_reduce.valid[4],
761 ];
762 final_next.tags = [
763 q.fold3.tag,
764 q.final_reduce.tags[0],
765 q.final_reduce.tags[1],
766 q.final_reduce.tags[2],
767 q.final_reduce.tags[3],
768 q.final_reduce.tags[4],
769 ];
770 final_next.positive = add_words(
771 scalar252_words(q.side3.value.x0),
772 scalar252_words(q.side3.value.p2_low),
773 );
774 final_next.negative = add_words(
775 scalar252_words(q.side3.value.p1_low),
776 scalar252_words(p3_low),
777 );
778 final_next.biased = scalar_sub_kernel(
779 add_words(q.final_reduce.positive, two_l()),
780 q.final_reduce.negative,
781 );
782 let modulus = scalar_l_kernel(false);
783 final_next.reduced1 = if scalar_ge_kernel(q.final_reduce.biased, modulus) {
784 scalar_sub_kernel(q.final_reduce.biased, modulus)
785 } else {
786 q.final_reduce.biased
787 };
788 final_next.reduced2 = if scalar_ge_kernel(q.final_reduce.reduced1, modulus) {
789 scalar_sub_kernel(q.final_reduce.reduced1, modulus)
790 } else {
791 q.final_reduce.reduced1
792 };
793 let low0 = q.final_reduce.reduced2.words[0] - modulus.words[0];
794 let borrow0 = q.final_reduce.reduced2.words[0] < modulus.words[0];
795 let low1_first = q.final_reduce.reduced2.words[1] - modulus.words[1];
796 let borrow1_first = q.final_reduce.reduced2.words[1] < modulus.words[1];
797 let low1 = low1_first - if borrow0 { b64(1) } else { b64(0) };
798 final_next.low = [low0, low1];
799 final_next.low_borrow = borrow1_first || (borrow0 && low1_first == b64(0));
800 final_next.high_operand = [
801 q.final_reduce.reduced2.words[2],
802 q.final_reduce.reduced2.words[3],
803 ];
804 final_next.original = q.final_reduce.reduced2;
805 let high0 = q.final_reduce.high_operand[0]
806 - modulus.words[2]
807 - if q.final_reduce.low_borrow {
808 b64(1)
809 } else {
810 b64(0)
811 };
812 let borrow_high0 = q.final_reduce.high_operand[0] < modulus.words[2]
813 || (q.final_reduce.low_borrow && q.final_reduce.high_operand[0] == modulus.words[2]);
814 let high1_first = q.final_reduce.high_operand[1] - modulus.words[3];
815 let borrow_high1_first = q.final_reduce.high_operand[1] < modulus.words[3];
816 let high1 = high1_first - if borrow_high0 { b64(1) } else { b64(0) };
817 let underflow = borrow_high1_first || (borrow_high0 && high1_first == b64(0));
818 final_next.result = if underflow {
819 q.final_reduce.original
820 } else {
821 ScalarWords {
822 words: [q.final_reduce.low[0], q.final_reduce.low[1], high0, high1],
823 }
824 };
825
826 let output = ScalarReducePipeOut {
827 valid: q.final_reduce.valid[5],
828 scalar: q.final_reduce.result,
829 tag: q.final_reduce.tags[5],
830 };
831 let mut d = ScalarReducePipeD::dont_care();
832 d.fold1 = ConstQMulIn {
833 valid: i.valid,
834 x: high252(wide),
835 tag: i.tag,
836 };
837 d.fold2 = ConstQMulIn {
838 valid: q.fold1.valid,
839 x: product_high252(q.fold1.product),
840 tag: q.fold1.tag,
841 };
842 d.fold3 = ConstQMulIn {
843 valid: q.fold2.valid,
844 x: product_high252(q.fold2.product),
845 tag: q.fold2.tag,
846 };
847 let zero = Scalar252 {
848 limbs: [b16(0); 16],
849 };
850 d.side1 = FoldSideDelayIn {
851 value: Fold3Side {
852 x0: low252(wide),
853 p1_low: zero,
854 p2_low: zero,
855 },
856 };
857 d.side2 = FoldSideDelayIn {
858 value: Fold3Side {
859 x0: q.side1.value.x0,
860 p1_low,
861 p2_low: zero,
862 },
863 };
864 d.side3 = FoldSideDelayIn {
865 value: Fold3Side {
866 x0: q.side2.value.x0,
867 p1_low: q.side2.value.p1_low,
868 p2_low,
869 },
870 };
871 d.final_reduce = final_next;
872 (output, d)
873}
874
875#[derive(Copy, Clone, Debug, PartialEq, Digital)]
876#[doc(hidden)]
877pub struct WideCoeffCellIn {
878 x: [b16; 16],
879 y: [b16; 16],
880}
881
882#[derive(Copy, Clone, Debug, PartialEq, Digital)]
883#[doc(hidden)]
884pub struct WideCoeffCellOut {
885 coefficient: b36,
886}
887
888#[derive(Copy, Clone, Debug, PartialEq, Digital)]
889#[doc(hidden)]
890pub struct WideCoeffCellRegs {
891 x: [b16; 16],
892 y: [b16; 16],
893 products: [b32; 16],
894 pairs: [b33; 8],
895 quads: [b34; 4],
896 octets: [b35; 2],
897 coefficient: b36,
898}
899
900#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
901#[doc(hidden)]
902pub struct WideCoeffCell {
903 regs: DFF<WideCoeffCellRegs>,
904}
905
906impl Default for WideCoeffCell {
907 fn default() -> Self {
908 Self {
909 regs: DFF::new(WideCoeffCellRegs {
910 x: [b16(0); 16],
911 y: [b16(0); 16],
912 products: [b32(0); 16],
913 pairs: [b33(0); 8],
914 quads: [b34(0); 4],
915 octets: [b35(0); 2],
916 coefficient: b36(0),
917 }),
918 }
919 }
920}
921
922impl SynchronousIO for WideCoeffCell {
923 type I = WideCoeffCellIn;
924 type O = WideCoeffCellOut;
925 type Kernel = wide_coeff_cell_kernel;
926}
927
928#[kernel]
930#[doc(hidden)]
931pub fn wide_coeff_cell_kernel(
932 _cr: ClockReset,
933 i: WideCoeffCellIn,
934 q: WideCoeffCellQ,
935) -> (WideCoeffCellOut, WideCoeffCellD) {
936 let r = q.regs;
937 let mut n = r;
938 n.x = i.x;
939 n.y = i.y;
940 for index in 0..16 {
941 n.products[index] = r.x[index].resize::<32>() * r.y[index].resize::<32>();
942 }
943 for index in 0..8 {
944 n.pairs[index] =
945 r.products[index * 2].resize::<33>() + r.products[index * 2 + 1].resize::<33>();
946 }
947 for index in 0..4 {
948 n.quads[index] = r.pairs[index * 2].resize::<34>() + r.pairs[index * 2 + 1].resize::<34>();
949 }
950 n.octets[0] = r.quads[0].resize::<35>() + r.quads[1].resize::<35>();
951 n.octets[1] = r.quads[2].resize::<35>() + r.quads[3].resize::<35>();
952 n.coefficient = r.octets[0].resize::<36>() + r.octets[1].resize::<36>();
953 (
954 WideCoeffCellOut {
955 coefficient: r.coefficient,
956 },
957 WideCoeffCellD { regs: n },
958 )
959}
960
961#[derive(Copy, Clone, Debug, PartialEq, Digital)]
962#[doc(hidden)]
963pub struct WideCoeffBankIn {
964 windows: [[b16; 16]; 4],
965 y: [b16; 16],
966}
967
968#[derive(Copy, Clone, Debug, PartialEq, Digital)]
969#[doc(hidden)]
970pub struct WideCoeffBankOut {
971 coefficients: [b36; 4],
972}
973
974#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
975#[doc(hidden)]
976pub struct WideCoeffBank {
977 cell0: WideCoeffCell,
978 cell1: WideCoeffCell,
979 cell2: WideCoeffCell,
980 cell3: WideCoeffCell,
981}
982
983impl SynchronousIO for WideCoeffBank {
984 type I = WideCoeffBankIn;
985 type O = WideCoeffBankOut;
986 type Kernel = wide_coeff_bank_kernel;
987}
988
989#[kernel]
991#[doc(hidden)]
992pub fn wide_coeff_bank_kernel(
993 _cr: ClockReset,
994 i: WideCoeffBankIn,
995 q: WideCoeffBankQ,
996) -> (WideCoeffBankOut, WideCoeffBankD) {
997 let output = WideCoeffBankOut {
998 coefficients: [
999 q.cell0.coefficient,
1000 q.cell1.coefficient,
1001 q.cell2.coefficient,
1002 q.cell3.coefficient,
1003 ],
1004 };
1005 let mut d = WideCoeffBankD::dont_care();
1006 d.cell0 = WideCoeffCellIn {
1007 x: i.windows[0],
1008 y: i.y,
1009 };
1010 d.cell1 = WideCoeffCellIn {
1011 x: i.windows[1],
1012 y: i.y,
1013 };
1014 d.cell2 = WideCoeffCellIn {
1015 x: i.windows[2],
1016 y: i.y,
1017 };
1018 d.cell3 = WideCoeffCellIn {
1019 x: i.windows[3],
1020 y: i.y,
1021 };
1022 (output, d)
1023}
1024
1025#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1026#[doc(hidden)]
1027pub struct WideCoeffBankPairIn {
1028 windows: [[b16; 16]; 8],
1029 y: [b16; 16],
1030}
1031
1032#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1033#[doc(hidden)]
1034pub struct WideCoeffBankPairOut {
1035 coefficients: [b36; 8],
1036}
1037
1038#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
1039#[doc(hidden)]
1040pub struct WideCoeffBankPair {
1041 bank0: WideCoeffBank,
1042 bank1: WideCoeffBank,
1043}
1044
1045impl SynchronousIO for WideCoeffBankPair {
1046 type I = WideCoeffBankPairIn;
1047 type O = WideCoeffBankPairOut;
1048 type Kernel = wide_coeff_bank_pair_kernel;
1049}
1050
1051#[kernel]
1053#[doc(hidden)]
1054pub fn wide_coeff_bank_pair_kernel(
1055 _cr: ClockReset,
1056 i: WideCoeffBankPairIn,
1057 q: WideCoeffBankPairQ,
1058) -> (WideCoeffBankPairOut, WideCoeffBankPairD) {
1059 let output = WideCoeffBankPairOut {
1060 coefficients: [
1061 q.bank0.coefficients[0],
1062 q.bank0.coefficients[1],
1063 q.bank0.coefficients[2],
1064 q.bank0.coefficients[3],
1065 q.bank1.coefficients[0],
1066 q.bank1.coefficients[1],
1067 q.bank1.coefficients[2],
1068 q.bank1.coefficients[3],
1069 ],
1070 };
1071 let mut d = WideCoeffBankPairD::dont_care();
1072 d.bank0 = WideCoeffBankIn {
1073 windows: [i.windows[0], i.windows[1], i.windows[2], i.windows[3]],
1074 y: i.y,
1075 };
1076 d.bank1 = WideCoeffBankIn {
1077 windows: [i.windows[4], i.windows[5], i.windows[6], i.windows[7]],
1078 y: i.y,
1079 };
1080 (output, d)
1081}
1082
1083#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1084#[doc(hidden)]
1085pub struct WideNormalizeState {
1086 coefficients: [b36; 32],
1087 limbs: [b16; 32],
1088 carry: b21,
1089}
1090
1091#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1092#[doc(hidden)]
1093pub struct WideNormalizePairIn {
1094 state: WideNormalizeState,
1095 start: b7,
1096}
1097
1098#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1099#[doc(hidden)]
1100pub struct WideNormalizePairOut {
1101 state: WideNormalizeState,
1102}
1103
1104#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
1105#[doc(hidden)]
1106pub struct WideNormalizePair {
1107 state: DFF<WideNormalizeState>,
1108}
1109
1110impl Default for WideNormalizePair {
1111 fn default() -> Self {
1112 Self {
1113 state: DFF::new(WideNormalizeState {
1114 coefficients: [b36(0); 32],
1115 limbs: [b16(0); 32],
1116 carry: b21(0),
1117 }),
1118 }
1119 }
1120}
1121
1122impl SynchronousIO for WideNormalizePair {
1123 type I = WideNormalizePairIn;
1124 type O = WideNormalizePairOut;
1125 type Kernel = wide_normalize_pair_kernel;
1126}
1127
1128#[kernel]
1130#[doc(hidden)]
1131pub fn wide_normalize_pair_kernel(
1132 _cr: ClockReset,
1133 i: WideNormalizePairIn,
1134 q: WideNormalizePairQ,
1135) -> (WideNormalizePairOut, WideNormalizePairD) {
1136 let mut next = i.state;
1137 let first = i.state.coefficients[i.start].resize::<37>() + i.state.carry.resize::<37>();
1138 let second_index = i.start + b7(1);
1139 let second = i.state.coefficients[second_index].resize::<37>() + (first >> 16);
1140 next.limbs[i.start] = first.resize::<16>();
1141 next.limbs[second_index] = second.resize::<16>();
1142 next.carry = (second >> 16).resize::<21>();
1143 (
1144 WideNormalizePairOut { state: q.state },
1145 WideNormalizePairD { state: next },
1146 )
1147}
1148
1149#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1150#[doc(hidden)]
1151pub struct WideNormalizeGroupIn {
1152 state: WideNormalizeState,
1153 start: b7,
1154}
1155
1156#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1157#[doc(hidden)]
1158pub struct WideNormalizeGroupOut {
1159 state: WideNormalizeState,
1160}
1161
1162#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
1163#[doc(hidden)]
1164pub struct WideNormalizeGroup {
1165 pair0: WideNormalizePair,
1166 pair1: WideNormalizePair,
1167 pair2: WideNormalizePair,
1168 pair3: WideNormalizePair,
1169}
1170
1171impl SynchronousIO for WideNormalizeGroup {
1172 type I = WideNormalizeGroupIn;
1173 type O = WideNormalizeGroupOut;
1174 type Kernel = wide_normalize_group_kernel;
1175}
1176
1177#[kernel]
1179#[doc(hidden)]
1180pub fn wide_normalize_group_kernel(
1181 _cr: ClockReset,
1182 i: WideNormalizeGroupIn,
1183 q: WideNormalizeGroupQ,
1184) -> (WideNormalizeGroupOut, WideNormalizeGroupD) {
1185 let mut d = WideNormalizeGroupD::dont_care();
1186 d.pair0 = WideNormalizePairIn {
1187 state: i.state,
1188 start: i.start,
1189 };
1190 d.pair1 = WideNormalizePairIn {
1191 state: q.pair0.state,
1192 start: i.start + b7(2),
1193 };
1194 d.pair2 = WideNormalizePairIn {
1195 state: q.pair1.state,
1196 start: i.start + b7(4),
1197 };
1198 d.pair3 = WideNormalizePairIn {
1199 state: q.pair2.state,
1200 start: i.start + b7(6),
1201 };
1202 (
1203 WideNormalizeGroupOut {
1204 state: q.pair3.state,
1205 },
1206 d,
1207 )
1208}
1209
1210#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1212pub struct Radix16X33 {
1213 pub limbs: [b16; 33],
1215}
1216
1217#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1219pub struct ScalarWideMulIn {
1220 pub valid: bool,
1222 pub x: ScalarWords,
1224 pub y: ScalarWords,
1226 pub addend: ScalarWords,
1228 pub tag: b16,
1230}
1231
1232#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1234pub struct ScalarWideMulOut {
1235 pub valid: bool,
1237 pub product: Radix16X33,
1239 pub addend: ScalarWords,
1241 pub tag: b16,
1243}
1244
1245#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
1247pub struct ScalarWideMulPipe {
1248 banks0: WideCoeffBankPair,
1249 banks1: WideCoeffBankPair,
1250 banks2: WideCoeffBankPair,
1251 banks3: WideCoeffBankPair,
1252 normalize0: WideNormalizeGroup,
1253 normalize1: WideNormalizeGroup,
1254 normalize2: WideNormalizeGroup,
1255 normalize3: WideNormalizeGroup,
1256 valid: DFF<[bool; 22]>,
1257 tags: DFF<[b16; 22]>,
1258 addends: DFF<[ScalarWords; 22]>,
1259}
1260
1261impl Default for ScalarWideMulPipe {
1262 fn default() -> Self {
1263 let zero = ScalarWords { words: [b64(0); 4] };
1264 Self {
1265 banks0: WideCoeffBankPair::default(),
1266 banks1: WideCoeffBankPair::default(),
1267 banks2: WideCoeffBankPair::default(),
1268 banks3: WideCoeffBankPair::default(),
1269 normalize0: WideNormalizeGroup::default(),
1270 normalize1: WideNormalizeGroup::default(),
1271 normalize2: WideNormalizeGroup::default(),
1272 normalize3: WideNormalizeGroup::default(),
1273 valid: DFF::new([false; 22]),
1274 tags: DFF::new([b16(0); 22]),
1275 addends: DFF::new([zero; 22]),
1276 }
1277 }
1278}
1279
1280impl SynchronousIO for ScalarWideMulPipe {
1281 type I = ScalarWideMulIn;
1282 type O = ScalarWideMulOut;
1283 type Kernel = scalar_wide_mul_kernel;
1284}
1285
1286#[kernel]
1287fn scalar_radix16(value: ScalarWords) -> [b16; 16] {
1288 let mut limbs = [b16(0); 16];
1289 for word in 0..4 {
1290 for part in 0..4 {
1291 limbs[word * 4 + part] = (value.words[word] >> ((part * 16) as u128)).resize::<16>();
1292 }
1293 }
1294 limbs
1295}
1296
1297#[kernel]
1299pub fn scalar_wide_mul_kernel(
1300 _cr: ClockReset,
1301 i: ScalarWideMulIn,
1302 q: ScalarWideMulPipeQ,
1303) -> (ScalarWideMulOut, ScalarWideMulPipeD) {
1304 let x = scalar_radix16(i.x);
1305 let y = scalar_radix16(i.y);
1306 let bank_coefficients = [
1307 q.banks0.coefficients,
1308 q.banks1.coefficients,
1309 q.banks2.coefficients,
1310 q.banks3.coefficients,
1311 ];
1312 let mut coefficients = [b36(0); 32];
1313 let mut windows = [[[b16(0); 16]; 8]; 4];
1314 for bank_pair in 0..4 {
1315 for slot in 0..8 {
1316 let coefficient = bank_pair * 8 + slot;
1317 coefficients[coefficient] = bank_coefficients[bank_pair][slot];
1318 for x_limb in 0..16 {
1319 for y_limb in 0..16 {
1320 if coefficient == x_limb + y_limb {
1321 windows[bank_pair][slot][y_limb] = x[x_limb];
1322 }
1323 }
1324 }
1325 }
1326 }
1327 let initial = WideNormalizeState {
1328 coefficients,
1329 limbs: [b16(0); 32],
1330 carry: b21(0),
1331 };
1332 let normalized = q.normalize3.state;
1333 let product = Radix16X33 {
1334 limbs: [
1335 normalized.limbs[0],
1336 normalized.limbs[1],
1337 normalized.limbs[2],
1338 normalized.limbs[3],
1339 normalized.limbs[4],
1340 normalized.limbs[5],
1341 normalized.limbs[6],
1342 normalized.limbs[7],
1343 normalized.limbs[8],
1344 normalized.limbs[9],
1345 normalized.limbs[10],
1346 normalized.limbs[11],
1347 normalized.limbs[12],
1348 normalized.limbs[13],
1349 normalized.limbs[14],
1350 normalized.limbs[15],
1351 normalized.limbs[16],
1352 normalized.limbs[17],
1353 normalized.limbs[18],
1354 normalized.limbs[19],
1355 normalized.limbs[20],
1356 normalized.limbs[21],
1357 normalized.limbs[22],
1358 normalized.limbs[23],
1359 normalized.limbs[24],
1360 normalized.limbs[25],
1361 normalized.limbs[26],
1362 normalized.limbs[27],
1363 normalized.limbs[28],
1364 normalized.limbs[29],
1365 normalized.limbs[30],
1366 normalized.limbs[31],
1367 normalized.carry.resize::<16>(),
1368 ],
1369 };
1370 let output = ScalarWideMulOut {
1371 valid: q.valid[21],
1372 product,
1373 addend: q.addends[21],
1374 tag: q.tags[21],
1375 };
1376 let mut d = ScalarWideMulPipeD::dont_care();
1377 d.banks0 = WideCoeffBankPairIn {
1378 windows: windows[0],
1379 y,
1380 };
1381 d.banks1 = WideCoeffBankPairIn {
1382 windows: windows[1],
1383 y,
1384 };
1385 d.banks2 = WideCoeffBankPairIn {
1386 windows: windows[2],
1387 y,
1388 };
1389 d.banks3 = WideCoeffBankPairIn {
1390 windows: windows[3],
1391 y,
1392 };
1393 d.normalize0 = WideNormalizeGroupIn {
1394 state: initial,
1395 start: b7(0),
1396 };
1397 d.normalize1 = WideNormalizeGroupIn {
1398 state: q.normalize0.state,
1399 start: b7(8),
1400 };
1401 d.normalize2 = WideNormalizeGroupIn {
1402 state: q.normalize1.state,
1403 start: b7(16),
1404 };
1405 d.normalize3 = WideNormalizeGroupIn {
1406 state: q.normalize2.state,
1407 start: b7(24),
1408 };
1409 d.valid[0] = i.valid;
1410 d.tags[0] = i.tag;
1411 d.addends[0] = i.addend;
1412 for stage in 1..22 {
1413 d.valid[stage] = q.valid[stage - 1];
1414 d.tags[stage] = q.tags[stage - 1];
1415 d.addends[stage] = q.addends[stage - 1];
1416 }
1417 (output, d)
1418}
1419
1420#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1421#[doc(hidden)]
1422pub struct MulAddStage {
1423 valid: bool,
1424 wide: Words512,
1425 tag: b16,
1426}
1427
1428#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1430pub struct ScalarMulAddIn {
1431 pub valid: bool,
1433 pub a: ScalarWords,
1435 pub b: ScalarWords,
1437 pub c: ScalarWords,
1439 pub tag: b16,
1441}
1442
1443#[derive(Copy, Clone, Debug, PartialEq, Digital)]
1445pub struct ScalarMulAddOut {
1446 pub valid: bool,
1448 pub scalar: ScalarWords,
1450 pub tag: b16,
1452}
1453
1454#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
1456pub struct ScalarMulAddPipe {
1457 multiplier: ScalarWideMulPipe,
1458 add: DFF<MulAddStage>,
1459 reducer: ScalarReducePipe,
1460}
1461
1462#[cfg(test)]
1463#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
1464struct DualReducerHarness {
1465 first: ScalarReducePipe,
1466 second: ScalarReducePipe,
1467}
1468
1469#[cfg(test)]
1470impl SynchronousIO for DualReducerHarness {
1471 type I = ScalarReducePipeIn;
1472 type O = ScalarReducePipeOut;
1473 type Kernel = dual_reducer_harness_kernel;
1474}
1475
1476#[cfg(test)]
1477#[kernel]
1478fn dual_reducer_harness_kernel(
1479 _cr: ClockReset,
1480 i: ScalarReducePipeIn,
1481 q: DualReducerHarnessQ,
1482) -> (ScalarReducePipeOut, DualReducerHarnessD) {
1483 let mut d = DualReducerHarnessD::dont_care();
1484 d.first = ScalarReducePipeIn {
1485 valid: false,
1486 wide: Words512 { words: [b64(0); 8] },
1487 tag: b16(0),
1488 };
1489 d.second = i;
1490 (q.second, d)
1491}
1492
1493impl Default for ScalarMulAddPipe {
1494 fn default() -> Self {
1495 Self {
1496 multiplier: ScalarWideMulPipe::default(),
1497 add: DFF::new(MulAddStage {
1498 valid: false,
1499 wide: Words512::zero(),
1500 tag: b16(0),
1501 }),
1502 reducer: ScalarReducePipe::default(),
1503 }
1504 }
1505}
1506
1507impl SynchronousIO for ScalarMulAddPipe {
1508 type I = ScalarMulAddIn;
1509 type O = ScalarMulAddOut;
1510 type Kernel = scalar_muladd_kernel;
1511}
1512
1513#[kernel]
1515pub fn scalar_product_addend_kernel(product: Radix16X33, addend: ScalarWords) -> Words512 {
1516 let addend_low = scalar_radix16(addend);
1517 let mut addend = [b16(0); 32];
1518 for index in 0..16 {
1519 addend[index] = addend_low[index];
1520 }
1521 let mut limbs = [b16(0); 32];
1522 let mut carry = b17(0);
1523 for index in 0..32 {
1524 let sum = product.limbs[index].resize::<17>() + addend[index].resize::<17>() + carry;
1525 limbs[index] = sum.resize::<16>();
1526 carry = sum >> 16;
1527 }
1528 let mut words = [b64(0); 8];
1529 for word in 0..8 {
1530 for part in 0..4 {
1531 words[word] |= limbs[word * 4 + part].resize::<64>() << ((part * 16) as u128);
1532 }
1533 }
1534 Words512 { words }
1535}
1536
1537#[kernel]
1539pub fn scalar_muladd_kernel(
1540 _cr: ClockReset,
1541 i: ScalarMulAddIn,
1542 q: ScalarMulAddPipeQ,
1543) -> (ScalarMulAddOut, ScalarMulAddPipeD) {
1544 let mut d = ScalarMulAddPipeD::dont_care();
1545 d.multiplier = ScalarWideMulIn {
1546 valid: i.valid,
1547 x: i.a,
1548 y: i.b,
1549 addend: i.c,
1550 tag: i.tag,
1551 };
1552 d.add = MulAddStage {
1553 valid: q.multiplier.valid,
1554 wide: scalar_product_addend_kernel(q.multiplier.product, q.multiplier.addend),
1555 tag: q.multiplier.tag,
1556 };
1557 d.reducer = ScalarReducePipeIn {
1558 valid: q.add.valid,
1559 wide: q.add.wide,
1560 tag: q.add.tag,
1561 };
1562 (
1563 ScalarMulAddOut {
1564 valid: q.reducer.valid,
1565 scalar: q.reducer.scalar,
1566 tag: q.reducer.tag,
1567 },
1568 d,
1569 )
1570}
1571
1572#[cfg(test)]
1573mod tests {
1574 use super::*;
1575 use curve25519_dalek::scalar::Scalar;
1576 use num_bigint::BigUint;
1577 use std::iter;
1578
1579 fn to_x17(value: &BigUint) -> Radix16X17 {
1580 let mask = BigUint::from(0xffffu32);
1581 let mut limbs = [b16(0); 17];
1582 for (index, limb) in limbs.iter_mut().enumerate() {
1583 let digits = ((value >> (index * 16)) & &mask).to_u32_digits();
1584 *limb = b16(digits.first().copied().unwrap_or(0) as u128);
1585 }
1586 Radix16X17 { limbs }
1587 }
1588
1589 fn from_x25(value: Radix16X25) -> BigUint {
1590 value
1591 .limbs
1592 .iter()
1593 .enumerate()
1594 .fold(BigUint::from(0u8), |sum, (index, limb)| {
1595 sum + (BigUint::from(limb.raw()) << (index * 16))
1596 })
1597 }
1598
1599 fn digest_words(bytes: [u8; 64]) -> Words512 {
1600 let mut value = Words512::zero();
1601 for (index, chunk) in bytes.chunks_exact(8).enumerate() {
1602 value.words[index] = b64(u64::from_le_bytes(chunk.try_into().unwrap()) as u128);
1603 }
1604 value
1605 }
1606
1607 fn scalar_bytes(value: ScalarWords) -> [u8; 32] {
1608 let mut bytes = [0u8; 32];
1609 for (index, word) in value.words.iter().enumerate() {
1610 bytes[index * 8..index * 8 + 8].copy_from_slice(&(word.raw() as u64).to_le_bytes());
1611 }
1612 bytes
1613 }
1614
1615 fn scalar_words(bytes: [u8; 32]) -> ScalarWords {
1616 let mut words = [b64(0); 4];
1617 for (index, chunk) in bytes.chunks_exact(8).enumerate() {
1618 words[index] = b64(u64::from_le_bytes(chunk.try_into().unwrap()) as u128);
1619 }
1620 ScalarWords { words }
1621 }
1622
1623 fn from_x33(value: Radix16X33) -> BigUint {
1624 value
1625 .limbs
1626 .iter()
1627 .enumerate()
1628 .fold(BigUint::from(0u8), |sum, (index, limb)| {
1629 sum + (BigUint::from(limb.raw()) << (index * 16))
1630 })
1631 }
1632
1633 #[test]
1634 fn constant_multiplier_accepts_one_value_per_cycle() {
1635 let q = BigUint::parse_bytes(b"14def9dea2f79cd65812631a5cf5d3ed", 16).unwrap();
1636 let values: Vec<_> = (0u64..40)
1637 .map(|index| {
1638 (BigUint::from(0x9e3779b97f4a7c15u64) * BigUint::from(index + 1)
1639 + (BigUint::from(index * 13 + 5) << 244))
1640 % (BigUint::from(1u8) << 272)
1641 })
1642 .collect();
1643 let idle = ConstQMulIn {
1644 valid: false,
1645 x: Radix16X17 {
1646 limbs: [b16(0); 17],
1647 },
1648 tag: b16(0),
1649 };
1650 let inputs = values.iter().enumerate().map(|(index, value)| ConstQMulIn {
1651 valid: true,
1652 x: to_x17(value),
1653 tag: b16(index as u128),
1654 });
1655 let outputs: Vec<_> = ConstQMulPipe::default()
1656 .run(
1657 iter::once(idle)
1658 .chain(inputs)
1659 .chain(iter::repeat_n(idle, 24))
1660 .with_reset(1)
1661 .clock_pos_edge(100),
1662 )
1663 .synchronous_sample()
1664 .map(|sample| sample.output)
1665 .filter(|output| output.valid)
1666 .collect();
1667 assert_eq!(outputs.len(), values.len());
1668 for (index, (output, input)) in outputs.into_iter().zip(values).enumerate() {
1669 assert_eq!(output.tag, b16(index as u128));
1670 assert_eq!(from_x25(output.product), input * &q);
1671 }
1672 }
1673
1674 #[test]
1675 fn reducer_matches_dalek_for_back_to_back_digests() {
1676 std::thread::Builder::new()
1677 .name("scalar-reducer-sim".into())
1678 .stack_size(64 * 1024 * 1024)
1679 .spawn(reducer_matches_dalek_body)
1680 .unwrap()
1681 .join()
1682 .unwrap();
1683 }
1684
1685 fn reducer_matches_dalek_body() {
1686 let digests: Vec<_> = (0u8..32)
1687 .map(|case| {
1688 let mut bytes = [0u8; 64];
1689 for (index, byte) in bytes.iter_mut().enumerate() {
1690 *byte = case
1691 .wrapping_mul(37)
1692 .wrapping_add(index as u8)
1693 .rotate_left((index % 7) as u32);
1694 }
1695 bytes
1696 })
1697 .collect();
1698 let idle = ScalarReducePipeIn {
1699 valid: false,
1700 wide: Words512::zero(),
1701 tag: b16(0),
1702 };
1703 let inputs = digests
1704 .iter()
1705 .enumerate()
1706 .map(|(index, digest)| ScalarReducePipeIn {
1707 valid: true,
1708 wide: digest_words(*digest),
1709 tag: b16(index as u128),
1710 });
1711 let outputs: Vec<_> = ScalarReducePipe::default()
1712 .run(
1713 iter::once(idle)
1714 .chain(inputs)
1715 .chain(iter::repeat_n(idle, 70))
1716 .with_reset(1)
1717 .clock_pos_edge(100),
1718 )
1719 .synchronous_sample()
1720 .map(|sample| sample.output)
1721 .filter(|output| output.valid)
1722 .collect();
1723 assert_eq!(outputs.len(), digests.len());
1724 for (index, (output, digest)) in outputs.into_iter().zip(digests).enumerate() {
1725 assert_eq!(output.tag, b16(index as u128));
1726 assert_eq!(
1727 scalar_bytes(output.scalar),
1728 Scalar::from_bytes_mod_order_wide(&digest).to_bytes()
1729 );
1730 }
1731 }
1732
1733 #[test]
1734 fn wide_multiplier_and_muladd_match_dalek() {
1735 std::thread::Builder::new()
1736 .name("scalar-muladd-sim".into())
1737 .stack_size(128 * 1024 * 1024)
1738 .spawn(wide_multiplier_and_muladd_body)
1739 .unwrap()
1740 .join()
1741 .unwrap();
1742 }
1743
1744 fn wide_multiplier_and_muladd_body() {
1745 let cases: Vec<_> = (0u8..24)
1746 .map(|case| {
1747 let mut a_bytes = [0u8; 64];
1748 let mut b_bytes = [0u8; 64];
1749 let mut c_bytes = [0u8; 64];
1750 for index in 0..64 {
1751 a_bytes[index] = case.wrapping_mul(17).wrapping_add(index as u8);
1752 b_bytes[index] = case.wrapping_mul(29).wrapping_add((index * 3) as u8);
1753 c_bytes[index] = case.wrapping_mul(43).wrapping_add((index * 5) as u8);
1754 }
1755 (
1756 Scalar::from_bytes_mod_order_wide(&a_bytes),
1757 Scalar::from_bytes_mod_order_wide(&b_bytes),
1758 Scalar::from_bytes_mod_order_wide(&c_bytes),
1759 )
1760 })
1761 .collect();
1762 let zero = ScalarWords { words: [b64(0); 4] };
1763 let wide_idle = ScalarWideMulIn {
1764 valid: false,
1765 x: zero,
1766 y: zero,
1767 addend: zero,
1768 tag: b16(0),
1769 };
1770 let wide_inputs = cases
1771 .iter()
1772 .enumerate()
1773 .map(|(index, (a, b, c))| ScalarWideMulIn {
1774 valid: true,
1775 x: scalar_words(a.to_bytes()),
1776 y: scalar_words(b.to_bytes()),
1777 addend: scalar_words(c.to_bytes()),
1778 tag: b16(index as u128),
1779 });
1780 let wide_outputs: Vec<_> = ScalarWideMulPipe::default()
1781 .run(
1782 iter::once(wide_idle)
1783 .chain(wide_inputs)
1784 .chain(iter::repeat_n(wide_idle, 30))
1785 .with_reset(1)
1786 .clock_pos_edge(100),
1787 )
1788 .synchronous_sample()
1789 .map(|sample| sample.output)
1790 .filter(|output| output.valid)
1791 .collect();
1792 assert_eq!(wide_outputs.len(), cases.len());
1793 for (index, (output, (a, b, c))) in wide_outputs.into_iter().zip(cases.iter()).enumerate() {
1794 assert_eq!(output.tag, b16(index as u128));
1795 assert_eq!(output.addend, scalar_words(c.to_bytes()));
1796 assert_eq!(
1797 from_x33(output.product),
1798 BigUint::from_bytes_le(&a.to_bytes()) * BigUint::from_bytes_le(&b.to_bytes())
1799 );
1800 }
1801
1802 let muladd_idle = ScalarMulAddIn {
1803 valid: false,
1804 a: zero,
1805 b: zero,
1806 c: zero,
1807 tag: b16(0),
1808 };
1809 let muladd_inputs = cases
1810 .iter()
1811 .enumerate()
1812 .map(|(index, (a, b, c))| ScalarMulAddIn {
1813 valid: true,
1814 a: scalar_words(a.to_bytes()),
1815 b: scalar_words(b.to_bytes()),
1816 c: scalar_words(c.to_bytes()),
1817 tag: b16(index as u128),
1818 });
1819 let outputs: Vec<_> = ScalarMulAddPipe::default()
1820 .run(
1821 iter::once(muladd_idle)
1822 .chain(muladd_inputs)
1823 .chain(iter::repeat_n(muladd_idle, 100))
1824 .with_reset(1)
1825 .clock_pos_edge(100),
1826 )
1827 .synchronous_sample()
1828 .map(|sample| sample.output)
1829 .filter(|output| output.valid)
1830 .collect();
1831 assert_eq!(outputs.len(), cases.len());
1832 for (index, (output, (a, b, c))) in outputs.into_iter().zip(cases).enumerate() {
1833 assert_eq!(output.tag, b16(index as u128));
1834 assert_eq!(scalar_bytes(output.scalar), (a * b + c).to_bytes());
1835 }
1836 }
1837
1838 #[test]
1839 fn muladd_preserves_a_single_sparse_valid() {
1840 std::thread::Builder::new()
1841 .name("scalar-muladd-sparse".into())
1842 .stack_size(128 * 1024 * 1024)
1843 .spawn(muladd_sparse_body)
1844 .unwrap()
1845 .join()
1846 .unwrap();
1847 }
1848
1849 fn muladd_sparse_body() {
1850 let zero = ScalarWords { words: [b64(0); 4] };
1851 let idle = ScalarMulAddIn {
1852 valid: false,
1853 a: zero,
1854 b: zero,
1855 c: zero,
1856 tag: b16(0),
1857 };
1858 let request = ScalarMulAddIn {
1859 valid: true,
1860 a: ScalarWords {
1861 words: [b64(3), b64(0), b64(0), b64(0)],
1862 },
1863 b: ScalarWords {
1864 words: [b64(5), b64(0), b64(0), b64(0)],
1865 },
1866 c: ScalarWords {
1867 words: [b64(7), b64(0), b64(0), b64(0)],
1868 },
1869 tag: b16(0x5151),
1870 };
1871 let outputs: Vec<_> = ScalarMulAddPipe::default()
1872 .run(
1873 iter::once(idle)
1874 .chain(iter::once(request))
1875 .chain(iter::repeat_n(idle, 120))
1876 .with_reset(1)
1877 .clock_pos_edge(100),
1878 )
1879 .synchronous_sample()
1880 .map(|sample| sample.output)
1881 .filter(|output| output.valid)
1882 .collect();
1883 assert_eq!(outputs.len(), 1);
1884 assert_eq!(outputs[0].tag, b16(0x5151));
1885 assert_eq!(outputs[0].scalar.words[0], b64(22));
1886 }
1887
1888 #[test]
1889 fn second_reducer_instance_preserves_sparse_valid() {
1890 std::thread::Builder::new()
1891 .name("dual-reducer-sparse".into())
1892 .stack_size(128 * 1024 * 1024)
1893 .spawn(|| {
1894 let idle = ScalarReducePipeIn {
1895 valid: false,
1896 wide: Words512 { words: [b64(0); 8] },
1897 tag: b16(0),
1898 };
1899 let request = ScalarReducePipeIn {
1900 valid: true,
1901 wide: Words512 {
1902 words: [
1903 b64(22),
1904 b64(0),
1905 b64(0),
1906 b64(0),
1907 b64(0),
1908 b64(0),
1909 b64(0),
1910 b64(0),
1911 ],
1912 },
1913 tag: b16(0x5252),
1914 };
1915 let outputs: Vec<_> = DualReducerHarness::default()
1916 .run(
1917 iter::once(idle)
1918 .chain(iter::once(request))
1919 .chain(iter::repeat_n(idle, 100))
1920 .with_reset(1)
1921 .clock_pos_edge(100),
1922 )
1923 .synchronous_sample()
1924 .map(|sample| sample.output)
1925 .filter(|output| output.valid)
1926 .collect();
1927 assert_eq!(outputs.len(), 1);
1928 assert_eq!(outputs[0].tag, b16(0x5252));
1929 assert_eq!(outputs[0].scalar.words[0], b64(22));
1930 })
1931 .unwrap()
1932 .join()
1933 .unwrap();
1934 }
1935
1936 #[test]
1937 fn compiles_to_rhdl_netlist() -> miette::Result<()> {
1938 ConstQMulPipe::default()
1939 .descriptor(ScopedName::top())?
1940 .hdl()?;
1941 ScalarReducePipe::default()
1942 .descriptor(ScopedName::top())?
1943 .hdl()?;
1944 Ok(())
1945 }
1946}